From 6ad8c83b3daff529e57c24f4b220b99aa95237cc Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 4 Mar 2020 08:48:07 -0500 Subject: [PATCH 001/190] processing code drop --- src/climate.c | 2 +- src/consts.h | 3 +- src/culvert.c | 2 +- src/dwflow.c | 10 ++++++- src/dynwave.c | 13 +++++--- src/enums.h | 37 ++++++++++------------- src/flowrout.c | 6 +++- src/funcs.h | 14 +++++---- src/gage.c | 2 +- src/globals.h | 3 +- src/infil.c | 2 +- src/infil.h | 2 +- src/keywords.c | 9 +++--- src/keywords.h | 5 ---- src/kinwave.c | 6 +++- src/lid.c | 31 ++++++++++--------- src/lid.h | 2 +- src/lidproc.c | 11 ++++++- src/link.c | 36 ++++++++++++----------- src/main.c | 2 +- src/massbal.c | 2 +- src/node.c | 13 ++++---- src/objects.h | 5 ++-- src/output.c | 6 +++- src/project.c | 54 ++++++++++++++++------------------ src/rain.c | 6 +++- src/rdii.c | 7 +++-- src/report.c | 13 ++++++-- src/roadway.c | 2 +- src/routing.c | 2 +- src/runoff.c | 10 ++++++- src/stats.c | 32 ++++++++++---------- src/statsrpt.c | 80 +++++++++++++++++++++++++++----------------------- src/subcatch.c | 2 +- src/surfqual.c | 10 +++++-- src/swmm5.c | 18 +++++------- src/text.h | 19 +++++------- src/xsect.c | 2 +- 38 files changed, 267 insertions(+), 214 deletions(-) diff --git a/src/climate.c b/src/climate.c index a44c5faae..f1b3cbfb2 100644 --- a/src/climate.c +++ b/src/climate.c @@ -8,7 +8,7 @@ // 03/19/15 (Build 5.1.008) // 08/05/15 (Build 5.1.010) // 08/01/16 (Build 5.1.011) -// 05/10/18 (Build 5.1.013) +// 11/27/17 (Build 5.1.013) // Author: L. Rossman // // Climate related functions. diff --git a/src/consts.h b/src/consts.h index 789eddea2..4421dd1eb 100644 --- a/src/consts.h +++ b/src/consts.h @@ -6,6 +6,7 @@ // Date: 03/20/14 (Build 5.1.001) // 08/01/16 (Build 5.1.011) // 05/10/18 (Build 5.1.013) +// 03/01/20 (Build 5.1.014) // Author: L. Rossman // // Various Constants @@ -15,7 +16,7 @@ // General Constants //------------------ -#define VERSION 51013 +#define VERSION 51014 #define MAGICNUMBER 516114522 #define EOFMARK 0x1A // Use 0x04 for UNIX systems #define MAXTITLE 3 // Max. # title lines diff --git a/src/culvert.c b/src/culvert.c index 37c7c8d56..fc3fe5502 100644 --- a/src/culvert.c +++ b/src/culvert.c @@ -4,7 +4,7 @@ // Project: EPA SWMM5 // Version: 5.1 // Date: 03/20/14 (Build 5.1.001) -// 05/10/18 (Build 5.1.013) +// 11/27/17 (Build 5.1.013) // Author: L. Rossman // // Culvert equations for SWMM5 diff --git a/src/dwflow.c b/src/dwflow.c index 420a6898d..de096ad38 100644 --- a/src/dwflow.c +++ b/src/dwflow.c @@ -7,6 +7,7 @@ // 03/19/15 (Build 5.1.008) // 03/14/17 (Build 5.1.012) // 05/10/18 (Build 5.1.013) +// 03/01/20 (Build 5.1.014) // Author: L. Rossman (EPA) // M. Tryby (EPA) // R. Dickinson (CDM) @@ -23,6 +24,11 @@ // Build 5.1.013: // - Preissmann slot surcharge option implemented. // - Changed sign of uniform loss rate term (dq6) in flow updating equation. +// +// Build 5.1.014: +// - Conduit evap. and seepage loss initialized to 0 in dwflow_findConduitFlow. +// - Most current flow (qLast) used instead of previous time period flow +// (qOld) in call to link_getLossRate. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -96,6 +102,8 @@ void dwflow_findConduitFlow(int j, int steps, double omega, double dt) barrels = Conduit[k].barrels; qOld = Link[j].oldFlow / barrels; qLast = Conduit[k].q1; + Conduit[k].evapLossRate = 0.0; //(5.1.014) + Conduit[k].seepLossRate = 0.0; //(5.1.014) // --- get most current heads at upstream and downstream ends of conduit n1 = Link[j].node1; @@ -226,7 +234,7 @@ void dwflow_findConduitFlow(int j, int steps, double omega, double dt) } // --- 6. term for evap and seepage losses per unit length - dq6 = link_getLossRate(j, qOld, dt) * 2.5 * dt * v / link_getLength(j); + dq6 = link_getLossRate(j, qLast) * 2.5 * dt * v / link_getLength(j); //(5.1.014) // --- combine terms to find new conduit flow denom = 1.0 + dq1 + dq5; diff --git a/src/dynwave.c b/src/dynwave.c index 40b6a71a6..294adc873 100644 --- a/src/dynwave.c +++ b/src/dynwave.c @@ -45,6 +45,10 @@ // - Storage nodes allowed to pressurize if their surcharge depth > 0. // - Minimum flow needed to compute a Courant time step modified. // +// Build 5.1.014: +// - updateNodeFlows() modified to subtract conduit evap. and seepage losses +// from downstream node inflow instead of upstream node outflow. +// //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -541,18 +545,19 @@ void updateNodeFlows(int i) k = Link[i].subIndex; uniformLossRate = Conduit[k].evapLossRate + Conduit[k].seepLossRate; barrels = Conduit[k].barrels; + uniformLossRate *= barrels; //(5.1.014) } // --- update total inflow & outflow at upstream/downstream nodes if ( q >= 0.0 ) { - Node[n1].outflow += q + uniformLossRate; - Node[n2].inflow += q; + Node[n1].outflow += q; //(5.1.014) + Node[n2].inflow += q - uniformLossRate; //(5.1.014) } else { - Node[n1].inflow -= q; - Node[n2].outflow -= q - uniformLossRate; + Node[n1].inflow -= q + uniformLossRate; //(5.1.014) + Node[n2].outflow -= q; //(5.1.014) } // --- add surf. area contributions to upstream/downstream nodes diff --git a/src/enums.h b/src/enums.h index e76f27519..9c5119394 100644 --- a/src/enums.h +++ b/src/enums.h @@ -9,7 +9,7 @@ // 03/19/15 (Build 5.1.008) // 08/05/15 (Build 5.1.010) // 08/01/16 (Build 5.1.011) -// 05/10/18 (Build 5.1.013) +// 11/27/17 (Build 5.1.013) // Author: L. Rossman // // Enumerated variables @@ -34,7 +34,7 @@ // - s_EVENT added to InputSectionType enumeration. // // Build 5.1.013: -// - SURCHARGE_METHOD and RULE_STEP options added. +// - CROWN_CUTOFF and RULE_STEP options added. // - WEIR_CURVE added as a curve type. // //----------------------------------------------------------------------------- @@ -362,11 +362,6 @@ enum CompatibilityType { PARTIAL_DAMPING, // partial damping FULL_DAMPING}; // full damping -//// Added to release 5.1.013. //// //(5.1.013) - enum SurchargeMethodType { - EXTRAN, // original EXTRAN method - SLOT}; // Preissmann slot method - enum InflowType { EXTERNAL_INFLOW, // user-supplied external inflow DRY_WEATHER_INFLOW, // user-supplied dry weather inflow @@ -456,20 +451,20 @@ enum CompatibilityType { s_ADJUST, s_EVENT}; enum InputOptionType { - FLOW_UNITS, INFIL_MODEL, ROUTE_MODEL, - START_DATE, START_TIME, END_DATE, - END_TIME, REPORT_START_DATE, REPORT_START_TIME, - SWEEP_START, SWEEP_END, START_DRY_DAYS, - WET_STEP, DRY_STEP, ROUTE_STEP, RULE_STEP, //(5.1.013) - REPORT_STEP, ALLOW_PONDING, INERT_DAMPING, - SLOPE_WEIGHTING, VARIABLE_STEP, NORMAL_FLOW_LTD, - LENGTHENING_STEP, MIN_SURFAREA, COMPATIBILITY, - SKIP_STEADY_STATE, TEMPDIR, IGNORE_RAINFALL, - FORCE_MAIN_EQN, LINK_OFFSETS, MIN_SLOPE, - IGNORE_SNOWMELT, IGNORE_GWATER, IGNORE_ROUTING, - IGNORE_QUALITY, MAX_TRIALS, HEAD_TOL, - SYS_FLOW_TOL, LAT_FLOW_TOL, IGNORE_RDII, - MIN_ROUTE_STEP, NUM_THREADS, SURCHARGE_METHOD}; //(5.1.013) + FLOW_UNITS, INFIL_MODEL, ROUTE_MODEL, + START_DATE, START_TIME, END_DATE, + END_TIME, REPORT_START_DATE, REPORT_START_TIME, + SWEEP_START, SWEEP_END, START_DRY_DAYS, + WET_STEP, DRY_STEP, ROUTE_STEP, RULE_STEP, //(5.1.013) + REPORT_STEP, ALLOW_PONDING, INERT_DAMPING, + SLOPE_WEIGHTING, VARIABLE_STEP, NORMAL_FLOW_LTD, + LENGTHENING_STEP, MIN_SURFAREA, COMPATIBILITY, + SKIP_STEADY_STATE, TEMPDIR, IGNORE_RAINFALL, + FORCE_MAIN_EQN, LINK_OFFSETS, MIN_SLOPE, + IGNORE_SNOWMELT, IGNORE_GWATER, IGNORE_ROUTING, + IGNORE_QUALITY, MAX_TRIALS, HEAD_TOL, + SYS_FLOW_TOL, LAT_FLOW_TOL, IGNORE_RDII, + MIN_ROUTE_STEP, NUM_THREADS, CROWN_CUTOFF}; //(5.1.013) enum NoYesType { NO, diff --git a/src/flowrout.c b/src/flowrout.c index 7b2503d40..b50b12787 100644 --- a/src/flowrout.c +++ b/src/flowrout.c @@ -7,6 +7,7 @@ // 09/15/14 (Build 5.1.007) // 03/19/15 (Build 5.1.008) // 03/14/17 (Build 5.1.012) +// 03/01/20 (Build 5.1.014) // Author: L. Rossman (EPA) // M. Tryby (EPA) // @@ -25,6 +26,9 @@ // - Overflow computed in updateStorageState() must be non-negative. // - Terminal storage nodes now updated corectly. // +// Build 5.1.014: +// - Arguments to function link_getLossRate changed. +// //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -766,7 +770,7 @@ int steadyflow_execute(int j, double* qin, double* qout, double tStep) else { // --- adjust flow for evap and infil losses - q -= link_getLossRate(j, q, tStep); + q -= link_getLossRate(j, q); //(5.1.014) // --- flow can't exceed full flow if ( q > Link[j].qFull ) diff --git a/src/funcs.h b/src/funcs.h index 620c03f7f..bdc2a4fe0 100644 --- a/src/funcs.h +++ b/src/funcs.h @@ -8,6 +8,7 @@ // 04/02/15 (Build 5.1.008) // 08/05/15 (Build 5.1.010) // 05/10/18 (Build 5.1.013) +// 03/01/20 (Build 5.1.014) // Author: L. Rossman (EPA) // M. Tryby (EPA) // @@ -27,6 +28,9 @@ // Build 5.1.013: // - Additional arguments added to function stats_updateSubcatchStats. // +// Build 5.1.014: +// - Arguments to link_getLossRate function changed. +// //----------------------------------------------------------------------------- void project_open(char *f1, char *f2, char *f3); void project_close(void); @@ -333,9 +337,7 @@ void node_initState(int node); void node_initInflow(int node, double tStep); void node_setOldHydState(int node); void node_setOldQualState(int node); - void node_setOutletDepth(int node, double yNorm, double yCrit, double z); -void node_setDividerCutoff(int node, int link); double node_getSurfArea(int node, double depth); double node_getDepth(int node, double volume); @@ -355,10 +357,10 @@ int inflow_readExtInflow(char* tok[], int ntoks); int inflow_readDwfInflow(char* tok[], int ntoks); int inflow_readDwfPattern(char* tok[], int ntoks); int inflow_setExtInflow(int j, int param, int type, - int tSeries, int basePat, double cf, - double baseline, double sf); + int tSeries, int basePat, double cf, + double baseline, double sf); int inflow_validate(int param, int type, int tSeries, - int basePat, double *cf); + int basePat, double *cf); void inflow_initDwfInflow(TDwfInflow* inflow); void inflow_initDwfPattern(int pattern); @@ -412,7 +414,7 @@ double link_getYnorm(int link, double q); double link_getVelocity(int link, double q, double y); double link_getFroude(int link, double v, double y); double link_getPower(int link); -double link_getLossRate(int link, double q, double tStep); +double link_getLossRate(int link, double q); //(5.1.014) char link_getFullState(double a1, double a2, double aFull); void link_getResults(int link, double wt, float x[]); diff --git a/src/gage.c b/src/gage.c index b8b0e65d0..d7852e579 100644 --- a/src/gage.c +++ b/src/gage.c @@ -5,7 +5,7 @@ // Version: 5.1 // Date: 03/20/10 (Build 5.1.001) // 09/15/14 (Build 5.1.007) -// 05/10/18 (Build 5.1.013) +// 11/27/17 (Build 5.1.013) // Author: L. Rossman // // Rain gage functions. diff --git a/src/globals.h b/src/globals.h index 1d7c9207e..d63abe76a 100644 --- a/src/globals.h +++ b/src/globals.h @@ -9,7 +9,7 @@ // 03/19/15 (Build 5.1.008) // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) -// 05/10/18 (Build 5.1.013) +// 11/27/17 (Build 5.1.013) // Author: L. Rossman // // Global Variables @@ -74,7 +74,6 @@ EXTERN int RouteModel, // Flow routing method ForceMainEqn, // Flow equation for force mains LinkOffsets, // Link offset convention - SurchargeMethod, // EXTRAN or SLOT method //(5.1.013) AllowPonding, // Allow water to pond at nodes InertDamping, // Degree of inertial damping NormalFlowLtd, // Normal flow limited diff --git a/src/infil.c b/src/infil.c index 19bc2a253..f7ae88ad2 100644 --- a/src/infil.c +++ b/src/infil.c @@ -8,7 +8,7 @@ // 03/19/15 (Build 5.1.008) // 08/05/15 (Build 5.1.010) // 08/01/16 (Build 5.1.011) -// 05/10/17 (Build 5.1.013) +// 11/27/17 (Build 5.1.013) // Author: L. Rossman // // Infiltration functions. diff --git a/src/infil.h b/src/infil.h index cf208e234..61adc28d3 100644 --- a/src/infil.h +++ b/src/infil.h @@ -6,7 +6,7 @@ // Date: 03/20/14 (Build 5.1.001) // 09/15/14 (Build 5.1.007) // 08/05/15 (Build 5.1.010) -// 05/10/18 (Build 5.1.013) +// 11/27/17 (Build 5.1.013) // Author: L. Rossman (US EPA) // // Public interface for infiltration functions. diff --git a/src/keywords.c b/src/keywords.c index 7ae5b2d5a..e6abd812d 100644 --- a/src/keywords.c +++ b/src/keywords.c @@ -9,7 +9,7 @@ // 03/19/15 (Build 5.1.008) // 08/05/15 (Build 5.1.010) // 08/01/16 (Build 5.1.011) -// 05/10/18 (Build 5.1.013) +// 11/27/17 (Build 5.1.013) // Author: L. Rossman // // Exportable keyword dictionary @@ -35,7 +35,7 @@ // - New section keyword for [EVENTS] added. // // Build 5.1.013: -// - New option keywords w_SURCHARGE_METHOD, w_RULE_STEP, w_AVERAGES +// - New option keywords w_CROWN_CUTOFF, w_RULE_STEP, w_AVERAGES // and w_WEIR added. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -80,7 +80,7 @@ char* OptionWords[] = { w_FLOW_UNITS, w_INFIL_MODEL, w_SWEEP_END, w_START_DRY_DAYS, w_WET_STEP, w_DRY_STEP, w_ROUTE_STEP, w_RULE_STEP, //(5.1.013) - w_REPORT_STEP, + w_REPORT_STEP, w_ALLOW_PONDING, w_INERT_DAMPING, w_SLOPE_WEIGHTING, w_VARIABLE_STEP, w_NORMAL_FLOW_LTD, w_LENGTHENING_STEP, @@ -93,7 +93,7 @@ char* OptionWords[] = { w_FLOW_UNITS, w_INFIL_MODEL, w_MAX_TRIALS, w_HEAD_TOL, w_SYS_FLOW_TOL, w_LAT_FLOW_TOL, w_IGNORE_RDII, w_MIN_ROUTE_STEP, - w_NUM_THREADS, w_SURCHARGE_METHOD, //(5.1.013) + w_NUM_THREADS, w_CROWN_CUTOFF, //(5.1.013) NULL }; char* OrificeTypeWords[] = { w_SIDE, w_BOTTOM, NULL}; char* OutfallTypeWords[] = { w_FREE, w_NORMAL, w_FIXED, w_TIDAL, @@ -142,7 +142,6 @@ char* SectWords[] = { ws_TITLE, ws_OPTION, ws_ADJUST, ws_EVENT, NULL}; char* SnowmeltWords[] = { w_PLOWABLE, w_IMPERV, w_PERV, w_REMOVAL, NULL}; -char* SurchargeWords[] = { w_EXTRAN, w_SLOT, NULL}; //(5.1.013) char* TempKeyWords[] = { w_TIMESERIES, w_FILE, w_WINDSPEED, w_SNOWMELT, w_ADC, NULL}; char* TransectKeyWords[] = { w_NC, w_X1, w_GR, NULL}; diff --git a/src/keywords.h b/src/keywords.h index 3724a0c89..0caee2280 100644 --- a/src/keywords.h +++ b/src/keywords.h @@ -5,16 +5,12 @@ // Version: 5.1 // Date: 03/19/14 (Build 5.1.000) // 03/19/15 (Build 5.1.008) -// 05/10/18 (Build 5.1.013) // Author: L. Rossman // // Exportable keyword dictionary // // Build 5.1.008: // - Keyword arrays listed in alphabetical order. -// -// Build 5.1.013: -// - New keyword array defined for surcharge method. //----------------------------------------------------------------------------- extern char* BuildupTypeWords[]; @@ -55,7 +51,6 @@ extern char* RouteModelWords[]; extern char* RuleKeyWords[]; extern char* SectWords[]; extern char* SnowmeltWords[]; -extern char* SurchargeWords[]; //(5.1.013) extern char* TempKeyWords[]; extern char* TransectKeyWords[]; extern char* TreatTypeWords[]; diff --git a/src/kinwave.c b/src/kinwave.c index d13662b68..c0fdf6fce 100644 --- a/src/kinwave.c +++ b/src/kinwave.c @@ -5,6 +5,7 @@ // Version: 5.1 // Date: 03/20/14 (Build 5.1.001) // 03/19/15 (Build 5.1.008) +// 03/01/20 (Build 5.1.014) // Author: L. Rossman (EPA) // M. Tryby (EPA) // @@ -13,6 +14,9 @@ // Build 5.1.008: // - Conduit inflow passed to function that computes conduit losses. // +// Build 5.1.014: +// - Arguments to function link_getLossRate changed. +// //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -98,7 +102,7 @@ int kinwave_execute(int j, double* qinflow, double* qoutflow, double tStep) qin = (*qinflow) / Conduit[k].barrels / Qfull; // --- compute evaporation and infiltration loss rate - q3 = link_getLossRate(j, qin*Qfull, tStep) / Qfull; + q3 = link_getLossRate(j, qin*Qfull) / Qfull; //(5.1.014) // --- normalize previous areas a1 = Conduit[k].a1 / Afull; diff --git a/src/lid.c b/src/lid.c index f204cc5fb..4dcf02a1d 100644 --- a/src/lid.c +++ b/src/lid.c @@ -12,6 +12,7 @@ // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) // 05/10/18 (Build 5.1.013) +// 03/01/20 (Build 5.1.014) // Author: L. Rossman (US EPA) // // This module handles all data processing involving LID (Low Impact @@ -72,6 +73,10 @@ // control curve for underdrain flow. // - Support added for unclogging permeable pavement at fixed intervals. // - Support added for pollutant removal in underdrain flow. +// +// Build 5.1.014: +// - Fixed bug in creating LidProcs when there are no subcatchments. +// - Fixed bug in adding underdrain pollutant loads to mass balances. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -248,17 +253,19 @@ void lid_create(int lidCount, int subcatchCount) //... create LID groups GroupCount = subcatchCount; - if ( GroupCount == 0 ) return; - LidGroups = (TLidGroup *) calloc(GroupCount, sizeof(TLidGroup)); - if ( LidGroups == NULL ) + if ( GroupCount > 0 ) { - ErrorCode = ERR_MEMORY; - return; + LidGroups = (TLidGroup *) calloc(GroupCount, sizeof(TLidGroup)); + if ( LidGroups == NULL ) + { + ErrorCode = ERR_MEMORY; + return; + } } - + //... initialize LID groups for (j = 0; j < GroupCount; j++) LidGroups[j] = NULL; - + //... create LID objects if ( LidCount == 0 ) return; LidProcs = (TLidProc *) calloc(LidCount, sizeof(TLidProc)); @@ -1465,16 +1472,12 @@ void lid_addDrainLoads(int j, double c[], double tStep) { lidUnit = lidList->lidUnit; - //... skip LID unit if it sends its drain flow onto - // its subcatchment's pervious area - if (lidUnit->toPerv) continue; - //... see if unit's drain flow becomes external runoff isRunoffLoad = (lidUnit->drainNode >= 0 || lidUnit->drainSubcatch == j); - //... for each pollutant - for (p = 0; p < Nobjects[POLLUT]; p++) + //... for each pollutant not routed back on to subcatchment surface + if (!lidUnit->toPerv) for (p = 0; p < Nobjects[POLLUT]; p++) { //... get mass load flowing through the drain w = lidUnit->newDrainFlow * c[p] * tStep * LperFT3 * Pollut[p].mcf; @@ -1485,7 +1488,7 @@ void lid_addDrainLoads(int j, double c[], double tStep) //... update system mass balance totals massbal_updateLoadingTotals(BMP_REMOVAL_LOAD, p, r*w); if (isRunoffLoad) - massbal_updateLoadingTotals(RUNOFF_LOAD, p, w*(1.0-r)); + massbal_updateLoadingTotals(RUNOFF_LOAD, p, w*(1.0 - r)); } // process next LID unit in the group diff --git a/src/lid.h b/src/lid.h index 93ee2b779..6dcf829a2 100644 --- a/src/lid.h +++ b/src/lid.h @@ -7,7 +7,7 @@ // 03/19/15 (Build 5.1.008) // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) -// 05/10/18 (Build 5.1.013) +// 11/27/17 (Build 5.1.013) // Author: L. Rossman (US EPA) // // Public interface for LID functions. diff --git a/src/lidproc.c b/src/lidproc.c index 1c7d8d216..54b3f9ca9 100644 --- a/src/lidproc.c +++ b/src/lidproc.c @@ -12,6 +12,7 @@ // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) // 05/10/18 (Build 5.1.013) +// 03/01/20 (Build 5.1.014) // Author: L. Rossman (US EPA) // // This module computes the hydrologic performance of an LID (Low Impact @@ -59,6 +60,10 @@ // to control underdrain flow. // - Support added for regenerating pavement permeability at fixed intervals. // +// Build 5.1.014: +// - Fixed failure to initialize all LID layer moisture volumes to 0 before +// computing LID unit performance in lidproc_getOutflow. +// //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -247,7 +252,11 @@ double lidproc_getOutflow(TLidUnit* lidUnit, TLidProc* lidProc, double inflow, x[STOR] = theLidUnit->storageDepth; x[PAVE] = theLidUnit->paveDepth; - //... initialize layer flux rates and moisture limits + //... initialize layer moisture volumes, flux rates and moisture limits + SurfaceVolume = 0.0; + PaveVolume = 0.0; + SoilVolume = 0.0; + StorageVolume = 0.0; SurfaceInflow = inflow; SurfaceInfil = 0.0; SurfaceEvap = 0.0; diff --git a/src/link.c b/src/link.c index c2e618291..3abc2f5f8 100644 --- a/src/link.c +++ b/src/link.c @@ -10,6 +10,7 @@ // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) // 05/10/18 (Build 5.1.013) +// 03/01/20 (Build 5.1.014) // Author: L. Rossman (EPA) // M. Tryby (EPA) // @@ -45,6 +46,10 @@ // - Support added for head-dependent weir coefficient curves. // - Adjustment of regulator link crest offset to match downstream node invert // now only done for Dynamic Wave flow routing. +// +// Build 5.1.014: +// - Conduit evap. and seepage losses initialized to 0 in conduit_initState() +// and not allowed to exceed current flow rate in conduit_getLossRate(). //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -97,7 +102,7 @@ static double conduit_getLength(int j); static double conduit_getLengthFactor(int j, int k, double roughness); static double conduit_getSlope(int j); static double conduit_getInflow(int j); -static double conduit_getLossRate(int j, double q, double tstep); +static double conduit_getLossRate(int j, double q); //(5.1.014) static int pump_readParams(int j, int k, char* tok[], int ntoks); static void pump_validate(int j, int k); @@ -865,7 +870,7 @@ double link_getPower(int j) //============================================================================= -double link_getLossRate(int j, double q, double tStep) +double link_getLossRate(int j, double q) //(5.1.014) // // Input: j = link index // q = flow rate (ft3/sec) @@ -875,7 +880,7 @@ double link_getLossRate(int j, double q, double tStep) // evaporation and seepage. // { - if ( Link[j].type == CONDUIT ) return conduit_getLossRate(j, q, tStep); + if ( Link[j].type == CONDUIT ) return conduit_getLossRate(j, q); //(5.1.014) else return 0.0; } @@ -1277,6 +1282,8 @@ void conduit_initState(int j, int k) { Link[j].newDepth = link_getYnorm(j, Link[j].q0 / Conduit[k].barrels); Link[j].oldDepth = Link[j].newDepth; + Conduit[k].evapLossRate = 0.0; //(5.1.014) + Conduit[k].seepLossRate = 0.0; //(5.1.014) } //============================================================================= @@ -1295,12 +1302,12 @@ double conduit_getInflow(int j) //============================================================================= -//// This function was modified for relese 5.1.013. //// //(5.1.013) +//// This function was modified for relese 5.1.014. //// //(5.1.014) -double conduit_getLossRate(int j, double q, double tStep) +double conduit_getLossRate(int j, double q) // // Input: j = link index -// tStep = time step (sec) +// q = current link flow rate (cfs) // Output: returns rate of evaporation & seepage losses (ft3/sec) // Purpose: computes volumetric rate of water evaporation & seepage // from a conduit (per barrel). @@ -1310,7 +1317,6 @@ double conduit_getLossRate(int j, double q, double tStep) double depth = 0.5 * (Link[j].oldDepth + Link[j].newDepth); double length; double topWidth; - double maxLossRate; double evapLossRate = 0.0, seepLossRate = 0.0, totalLossRate = 0.0; @@ -1342,17 +1348,13 @@ double conduit_getLossRate(int j, double q, double tStep) // --- compute total loss rate totalLossRate = evapLossRate + seepLossRate; - // --- total loss rate cannot exceed current volume - if ( totalLossRate > 0.0 ) + // --- total loss rate cannot exceed flow rate + q = ABS(q); + if (totalLossRate > q) { - maxLossRate = 0.5 * (Link[j].oldVolume + Link[j].newVolume) / tStep; - maxLossRate = MIN(maxLossRate, fabs(q)); - if ( totalLossRate > maxLossRate ) - { - evapLossRate = evapLossRate * maxLossRate / totalLossRate; - seepLossRate = seepLossRate * maxLossRate / totalLossRate; - totalLossRate = maxLossRate; - } + evapLossRate = evapLossRate * q / totalLossRate; + seepLossRate = seepLossRate * q / totalLossRate; + totalLossRate = q; } } diff --git a/src/main.c b/src/main.c index b4af1f112..763b367bc 100644 --- a/src/main.c +++ b/src/main.c @@ -3,7 +3,7 @@ // // Project: EPA SWMM5 // Version: 5.1 -// Date: 05/10/2018 +// Date: 11/27/2017 // Author: L. Rossman // Main stub for the command line version of EPA SWMM 5.1 diff --git a/src/massbal.c b/src/massbal.c index c5a02903d..6b6abee18 100644 --- a/src/massbal.c +++ b/src/massbal.c @@ -9,7 +9,7 @@ // 08/05/15 (Build 5.1.010) // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) -// 05/10/18 (Build 5.1.013) +// 11/27/17 (Build 5.1.013) // Author: L. Rossman (EPA) // M. Tryby (EPA) // diff --git a/src/node.c b/src/node.c index 2917b5749..4312b2d01 100644 --- a/src/node.c +++ b/src/node.c @@ -8,6 +8,7 @@ // 04/02/15 (Build 5.1.008) // 08/05/15 (Build 5.1.010) // 05/10/18 (Build 5.1.013) +// 03/01/20 (Build 5.1.014) // Author: L. Rossman // // Conveyance system node functions. @@ -27,6 +28,9 @@ // Build 5.1.013: // - A surcharge depth can now be applied to storage nodes. // - A negative inflow is now assigned to an Outfall node with backflow. +// +// Build 5.1.014: +// - Fixed bug in storage_losses() that affected storage exfiltration. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -916,9 +920,6 @@ double storage_getLosses(int j, double tStep) double lossRatio; TExfil* exfil; - // --- if node has some stored volume - if ( Node[j].newVolume > FUDGE ) - { // --- get node's evap. rate (ft/s) & exfiltration object k = Node[j].subIndex; evapRate = Evap.rate * Storage[k].fEvap; @@ -932,7 +933,8 @@ double storage_getLosses(int j, double tStep) area = storage_getSurfArea(j, depth); // --- compute evap rate over this area (cfs) - evapRate = area * evapRate; + if (Node[j].newVolume > FUDGE) + evapRate = area * evapRate; // --- find exfiltration rate (cfs) through bottom and side banks if ( exfil != NULL ) @@ -949,8 +951,7 @@ double storage_getLosses(int j, double tStep) exfilRate *= lossRatio; } } - } - + // --- save evap & infil losses at the node Storage[Node[j].subIndex].evapLoss = evapRate * tStep; Storage[Node[j].subIndex].exfilLoss = exfilRate * tStep; diff --git a/src/objects.h b/src/objects.h index 9fd738067..0c652853b 100644 --- a/src/objects.h +++ b/src/objects.h @@ -8,7 +8,7 @@ // 03/19/15 (Build 5.1.008) // 08/05/15 (Build 5.1.010) // 08/01/16 (Build 5.1.011) -// 05/10/18 (Build 5.1.013) +// 11/27/17 (Build 5.1.013) // // Author: L. Rossman (EPA) // M. Tryby (EPA) @@ -50,7 +50,7 @@ // - Added definition of a hydraulic event time period (TEvent). // // Build 5.1.013: -// - New member 'averages' added to the TRptFlags structure. +// - Member averages was added to the TRptFlags structure. // - Adjustment patterns added to TSubcatch structure. // - Members impervRunoff and pervRunoff added to TSubcatchStats structure. // - Member cdCurve (weir coeff. curve) added to TWeir structure. @@ -945,6 +945,7 @@ typedef struct double maxDepth; DateTime maxDepthDate; double maxRptDepth; + double maxSurDepth; //(5.1.013) double volFlooded; double timeFlooded; double timeSurcharged; diff --git a/src/output.c b/src/output.c index 186a3979d..47cd9df93 100644 --- a/src/output.c +++ b/src/output.c @@ -7,6 +7,7 @@ // 03/19/15 (Build 5.1.008) // 08/05/15 (Build 5.1.010) // 05/10/18 (Build 5.1.013) +// 03/01/20 (Build 5.1.014) // Author: L. Rossman (EPA) // // Binary output file access functions. @@ -24,6 +25,9 @@ // - Support added for saving average node & link routing results to // binary file in each reporting period. // +// Build 5.1.014: +// - Incorrect loop limit fixed in function output_saveAvgResults. +// //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -936,7 +940,7 @@ void output_saveAvgResults(FILE* file) } // --- add each link's volume to total system storage - for (i = 0; i < Nobjects[NODE]; i++) + for (i = 0; i < Nobjects[LINK]; i++) //(5.1.014) { SysResults[SYS_STORAGE] += (REAL4)(Link[i].newVolume * UCF(VOLUME)); } diff --git a/src/project.c b/src/project.c index 0fae207d5..dc3ecd46c 100644 --- a/src/project.c +++ b/src/project.c @@ -10,7 +10,7 @@ // 04/30/15 (Build 5.1.009) // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) -// 05/10/18 (Build 5.1.013) +// 11/27/17 (Build 5.1.013) // Author: L. Rossman // // Project management functions. @@ -52,8 +52,7 @@ // - omp_get_num_threads function protected against lack of compiler // support for OpenMP. // - Rain gage validation now performed after subcatchment validation. -// - More robust parsing of MinSurfarea option provided. -// - Support added for new RuleStep analysis option. +// - Support added for CrownCutoff and RuleStep analysis options. // //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -260,13 +259,9 @@ void project_validate() if ( RouteModel == DW ) dynwave_validate(); // --- adjust number of parallel threads to be used //(5.1.013) -#pragma omp parallel //(5.1.008) -{ - if ( NumThreads == 0 ) NumThreads = omp_get_num_threads(); //(5.1.008) - else NumThreads = MIN(NumThreads, omp_get_num_threads()); //(5.1.008) -} - if ( Nobjects[LINK] < 4 * NumThreads ) NumThreads = 1; //(5.1.008) - + if ( NumThreads == 0 ) NumThreads = omp_get_num_threads(); // + else NumThreads = MIN(NumThreads, omp_get_num_threads()); // + if ( Nobjects[LINK] < 4 * NumThreads ) NumThreads = 1; // } //============================================================================= @@ -532,7 +527,7 @@ int project_readOption(char* s1, char* s2) case WET_STEP: case DRY_STEP: case REPORT_STEP: - case RULE_STEP: //(5.1.013) + case RULE_STEP: //(5.1.013) if ( !datetime_strToTime(s2, &aTime) ) { return error_setInpError(ERR_DATETIME, s2); @@ -541,11 +536,11 @@ int project_readOption(char* s1, char* s2) h += 24*(int)aTime; s = s + 60*m + 3600*h; - // --- RuleStep allowed to be 0 while other time steps must be > 0 //(5.1.013) - if (k == RULE_STEP) // - { // - if (s < 0) return error_setInpError(ERR_NUMBER, s2); // - } // + // --- RuleStep allowed to be 0 while other time steps must be > 0 //(5.1.013) + if (k == RULE_STEP) // + { // + if (s < 0) return error_setInpError(ERR_NUMBER, s2); // + } // else if ( s <= 0 ) return error_setInpError(ERR_NUMBER, s2); // switch ( k ) @@ -553,7 +548,7 @@ int project_readOption(char* s1, char* s2) case WET_STEP: WetStep = s; break; case DRY_STEP: DryStep = s; break; case REPORT_STEP: ReportStep = s; break; - case RULE_STEP: RuleStep = s; break; //(5.1.013) + case RULE_STEP: RuleStep = s; break; //(5.1.013) } break; @@ -670,10 +665,7 @@ int project_readOption(char* s1, char* s2) // --- minimum surface area (ft2 or sq. meters) associated with nodes // under dynamic wave flow routing case MIN_SURFAREA: - if (!getDouble(s2, &MinSurfArea)) //(5.1.013) - return error_setInpError(ERR_NUMBER, s2); //(5.1.013) - if (MinSurfArea < 0.0) //(5.1.013) - return error_setInpError(ERR_NUMBER, s2); //(5.1.013) + MinSurfArea = atof(s2); break; // --- minimum conduit slope (%) @@ -718,12 +710,17 @@ int project_readOption(char* s1, char* s2) LatFlowTol /= 100.0; break; - // --- method used for surcharging in dynamic wave flow routing //(5.1.013) - case SURCHARGE_METHOD: - m = findmatch(s2, SurchargeWords); - if (m < 0) return error_setInpError(ERR_KEYWORD, s2); - SurchargeMethod = m; - break; + // --- fraction of full pipe depth used to compute a lower //(5.1.013) + // limit on surface area for higher flow depths // + case CROWN_CUTOFF: // + if (!getDouble(s2, &CrownCutoff)) // + { // + return error_setInpError(ERR_NUMBER, s2); // + } // + CrownCutoff /= 100.0; // + if (CrownCutoff < 0.96) CrownCutoff = 0.96; // + if (CrownCutoff > 1.0) CrownCutoff = 1.0; // + break; // case TEMPDIR: // Temporary Directory sstrncpy(TempDir, s2, MAXFNAME); @@ -808,8 +805,6 @@ void setDefaults() FlowUnits = CFS; // CFS flow units InfilModel = HORTON; // Horton infiltration method RouteModel = KW; // Kin. wave flow routing method - SurchargeMethod = EXTRAN; // Use EXTRAN method for surcharging //(5.1.013) - CrownCutoff = 0.96; //(5.1.013) AllowPonding = FALSE; // No ponding at nodes InertDamping = SOME; // Partial inertial damping NormalFlowLtd = BOTH; // Default normal flow limitation @@ -837,6 +832,7 @@ void setDefaults() HeadTol = 0.0; // Force use of default head tolerance SysFlowTol = 0.05; // System flow tolerance for steady state LatFlowTol = 0.05; // Lateral flow tolerance for steady state + CrownCutoff = 0.96; // Fractional pipe crown cutoff //(5.1.013) NumThreads = 0; // Number of parallel threads to use NumEvents = 0; // Number of detailed routing events diff --git a/src/rain.c b/src/rain.c index 03af41187..873031e74 100644 --- a/src/rain.c +++ b/src/rain.c @@ -7,6 +7,7 @@ // 08/05/15 (Build 5.1.010) // 08/22/16 (Build 5.1.011) // 05/10/18 (Build 5.1.013) +// 03/01/20 (Build 5.1.014) // Author: L. Rossman // // Places rainfall data from external files into a SWMM rainfall @@ -48,6 +49,9 @@ // // Release 5.1.013: // - Variable x properly initialized with float value in readNwsOnlineValue(). +// +// Release 5.1.014: +// - Fixed indexing bug in rainFileConflict() function. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -286,7 +290,7 @@ int rainFileConflict(int i) int j; char* staID = Gage[i].staID; char* fname = Gage[i].fname; - for (j = 1; j < i; j++) + for (j = 0; j < i; j++) { if ( strcomp(Gage[j].staID, staID) && !strcomp(Gage[j].fname, fname) ) { diff --git a/src/rdii.c b/src/rdii.c index 6da1d7a23..d94962489 100644 --- a/src/rdii.c +++ b/src/rdii.c @@ -7,6 +7,7 @@ // 04/04/14 (Build 5.1.003) // 04/14/14 (Build 5.1.004) // 09/15/14 (Build 5.1.007) +// 03/01/20 (Build 5.1.014) // Author: L. Rossman (EPA) // R. Dickinson (CDM) // @@ -19,6 +20,8 @@ // - Ignore RDII option implemented. // - Rainfall climate adjustment implemented. // +// Build 5.1.014: +// - Fixes bug related to isUsed property of a unit hydrograph's rain gage. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -246,6 +249,7 @@ int rdii_readUnitHydParams(char* tok[], int ntoks) g = project_findObject(GAGE, tok[1]); if ( g < 0 ) return error_setInpError(ERR_NAME, tok[1]); UnitHyd[j].rainGage = g; + Gage[g].isUsed = TRUE; return 0; } else if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); @@ -1050,14 +1054,11 @@ void initGageData() g = UnitHyd[i].rainGage; if ( g >= 0 ) { - Gage[g].isUsed = TRUE; - // --- if UH's gage uses same time series as a previous gage, // then assign the latter gage to the UH if ( Gage[g].coGage >= 0 ) { UnitHyd[i].rainGage = Gage[g].coGage; - Gage[Gage[g].coGage].isUsed = TRUE; } } } diff --git a/src/report.c b/src/report.c index a78adb232..7a0ced556 100644 --- a/src/report.c +++ b/src/report.c @@ -10,6 +10,7 @@ // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) // 05/10/18 (Build 5.1.013) +// 03/01/20 (Build 5.1.014) // Author: L. Rossman (EPA) // // Report writing functions. @@ -40,6 +41,8 @@ // - Name of surcharge method reported in report_writeOptions(). // - Missing format specifier added to fprintf() in report_writeErrorCode. // +// Build 5.1.014: +// - Fixed bug in confusing keywords with ID names in report_readOptions(). //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -150,8 +153,12 @@ int report_readOptions(char* tok[], int ntoks) default: return error_setInpError(ERR_KEYWORD, tok[1]); } - k = (char)findmatch(tok[1], NoneAllWords); - if ( k < 0 ) + + if (strcomp(tok[1], w_NONE)) + k = NONE; + else if (strcomp(tok[1], w_ALL)) + k = ALL; + else { k = SOME; for (t = 1; t < ntoks; t++) @@ -805,7 +812,7 @@ void report_writeFlowError(TRoutingTotals *totals) //============================================================================= -void report_writeQualError(TRoutingTotals QualTotals[]) +void report_writeQualError(TRoutingTotals* QualTotals) // // Input: totals = accumulated quality routing totals for each pollutant // Output: none diff --git a/src/roadway.c b/src/roadway.c index 4530f4fa3..02ec4e604 100644 --- a/src/roadway.c +++ b/src/roadway.c @@ -24,7 +24,7 @@ #include #include "headers.h" -enum RoadSurface {PAVED = 1, GRAVEL = 2}; +enum RoadSurface {PAVED = 1, GRAVEL}; //----------------------------------------------------------------------------- // Constants diff --git a/src/routing.c b/src/routing.c index 9882746c4..1c359e89a 100644 --- a/src/routing.c +++ b/src/routing.c @@ -784,7 +784,7 @@ void removeConduitLosses() for ( i = 0; i < Nobjects[LINK]; i++ ) { - if (Link[i].type == CONDUIT) + if (Link[i].type == CONDUIT) { // --- retrieve number of barrels k = Link[i].subIndex; diff --git a/src/runoff.c b/src/runoff.c index bad45a44a..a7a6f5d83 100644 --- a/src/runoff.c +++ b/src/runoff.c @@ -8,6 +8,7 @@ // 03/19/15 (Build 5.1.008) // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) +// 03/01/20 (Build 5.1.014) // Author: L. Rossman // M. Tryby // @@ -29,6 +30,9 @@ // // Build 5.1.012: // - Runoff wet time step no longer kept aligned with reporting times. +// +// Build 5.1.014: +// - Fixed street sweeping bug. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -205,7 +209,11 @@ void runoff_execute() // --- see if street sweeping can occur on current date day = datetime_dayOfYear(currentDate); - if ( day >= SweepStart && day <= SweepEnd ) canSweep = TRUE; + if ( SweepStart <= SweepEnd ) + { + if ( day >= SweepStart && day <= SweepEnd ) canSweep = TRUE; + } + else if ( day <= SweepEnd || day >= SweepStart ) canSweep = TRUE; else canSweep = FALSE; // --- get runoff time step (in seconds) diff --git a/src/stats.c b/src/stats.c index 7d6cb8b90..52538e079 100644 --- a/src/stats.c +++ b/src/stats.c @@ -8,7 +8,7 @@ // 03/19/15 (Build 5.1.008) // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) -// 05/10/18 (Build 5.1.013) +// 11/27/17 (Build 5.1.013) // Author: L. Rossman (EPA) // R. Dickinson (CDM) // @@ -32,10 +32,11 @@ // - Check for full conduit flow now accounts for number of barrels. // // Build 5.1.013: -// - Include omp.h protected against lack of compiler support for OpenMP. +// - No need to include omp.h. // - Statistics on impervious and pervious runoff totals added. // - Storage nodes with a non-zero surcharge depth (e.g. enclosed tanks) // can now be classified as being surcharged. +// - Node surcharge statistics modified to accommodate variable crown elev. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -44,9 +45,6 @@ #include #include "headers.h" #include "swmm5.h" -#if defined(_OPENMP) //(5.1.013) -#include -#endif //----------------------------------------------------------------------------- // Shared variables @@ -148,6 +146,7 @@ int stats_open() Subcatch[j].groundwater->stats.evap = 0.0; Subcatch[j].groundwater->stats.maxFlow = 0.0; } +//// } // --- allocate memory for node & link stats @@ -169,6 +168,7 @@ int stats_open() NodeStats[j].maxDepth = 0.0; NodeStats[j].maxDepthDate = StartDateTime; NodeStats[j].maxRptDepth = 0.0; + NodeStats[j].maxSurDepth = 0.0; //(5.1.013) NodeStats[j].volFlooded = 0.0; NodeStats[j].timeFlooded = 0.0; NodeStats[j].timeSurcharged = 0.0; @@ -495,7 +495,7 @@ void stats_updateNodeStats(int j, double tStep, DateTime aDate) int k, p; double newVolume = Node[j].newVolume; double newDepth = Node[j].newDepth; - double yCrown = Node[j].crownElev - Node[j].invertElev; + double yCrown = Node[j].crownElev - Node[j].invertElev; //(5.1.013) int canPond = (AllowPonding && Node[j].pondedArea > 0.0); // --- update depth statistics @@ -518,14 +518,16 @@ void stats_updateNodeStats(int j, double tStep, DateTime aDate) (newVolume - Node[j].fullVolume)); } - // --- for dynamic wave routing, classify a node as //(5.1.013) - // surcharged if its water level exceeds its crown elev. - if (RouteModel == DW) //(5.1.013) - { - if ((Node[j].type != STORAGE || Node[j].surDepth > 0.0) && //(5.1.013) - newDepth + Node[j].invertElev + FUDGE >= Node[j].crownElev) - { - NodeStats[j].timeSurcharged += tStep; + // --- for dynamic wave routing, classify a node as surcharged //(5.1.013) + // if its water level exceeds its crown elev. // + if (RouteModel == DW) // + { // + if ((Node[j].type != STORAGE || Node[j].surDepth > 0.0) && // + newDepth + FUDGE >= yCrown) // + { // + NodeStats[j].timeSurcharged += tStep; // + NodeStats[j].maxSurDepth = MAX(NodeStats[j].maxSurDepth, // + yCrown); // } } } @@ -562,7 +564,7 @@ void stats_updateNodeStats(int j, double tStep, DateTime aDate) for (p=0; p 0.0) + if ( Link[j].type == PUMP && Link[j].qFull > 0.0) { fprintf(Frpt.file, " "); fprintf(Frpt.file, " %6.2f", @@ -742,29 +746,31 @@ void writeLinkFlows() } // --- stop printing for dummy conduits - if (Link[j].xsect.type == DUMMY) continue; + if ( Link[j].xsect.type == DUMMY ) continue; // --- stop printing for outlet links (since they don't have xsections) - if (Link[j].type == OUTLET) continue; + if ( Link[j].type == OUTLET ) continue; // --- print max velocity & max/full flow for conduits - if (Link[j].type == CONDUIT) + if ( Link[j].type == CONDUIT ) { v = LinkStats[j].maxVeloc*UCF(LENGTH); - if (v > 50.0) fprintf(Frpt.file, " >50.00"); + if ( v > 50.0 ) fprintf(Frpt.file, " >50.00"); else fprintf(Frpt.file, " %7.2f", v); fprintf(Frpt.file, " %6.2f", LinkStats[j].maxFlow / Link[j].qFull / - (double)Conduit[k].barrels); + (double)Conduit[k].barrels); } else fprintf(Frpt.file, " "); // --- print max/full depth fullDepth = Link[j].xsect.yFull; - if (Link[j].type == ORIFICE && - Orifice[k].type == BOTTOM_ORIFICE) fullDepth = 0.0; - if (fullDepth > 0.0) + if ( Link[j].type == ORIFICE && + Orifice[k].type == BOTTOM_ORIFICE ) fullDepth = 0.0; + if ( fullDepth > 0.0 ) { fprintf(Frpt.file, " %6.2f", LinkStats[j].maxDepth / fullDepth); + aMax = xsect_getAofY(&Link[j].xsect, LinkStats[j].maxDepth); //(5.1.013) + fprintf(Frpt.file, " %6.2f", aMax / Link[j].xsect.aFull); // } else fprintf(Frpt.file, " "); } @@ -829,7 +835,7 @@ void writeLinkSurcharge() for ( j = 0; j < Nobjects[LINK]; j++ ) { if ( Link[j].type != CONDUIT || - Link[j].xsect.type == DUMMY ) continue; + Link[j].xsect.type == DUMMY ) continue; t[0] = LinkStats[j].timeSurcharged / 3600.0; t[1] = LinkStats[j].timeFullUpstream / 3600.0; t[2] = LinkStats[j].timeFullDnstream / 3600.0; diff --git a/src/subcatch.c b/src/subcatch.c index 175d5d41c..2383a522d 100644 --- a/src/subcatch.c +++ b/src/subcatch.c @@ -10,7 +10,7 @@ // 08/05/15 (Build 5.1.010) // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) -// 05/10/18 (Build 5.1.013) +// 11/27/17 (Build 5.1.013) // Author: L. Rossman // // Subcatchment runoff functions. diff --git a/src/surfqual.c b/src/surfqual.c index 359cef56d..244fab2f0 100644 --- a/src/surfqual.c +++ b/src/surfqual.c @@ -4,6 +4,7 @@ // Project: EPA SWMM5 // Version: 5.1 // Date: 03/19/15 (Build 5.1.008) +// 03/01/20 (Build 5.1.014) // Author: L. Rossman // // Subcatchment water quality functions. @@ -13,6 +14,8 @@ // subcatch.c. // - Support for separate accounting of LID drain flows included. // +// Build 5.1.014: +// - Fixed bug in computing effective BMP removal by LIDs. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -253,11 +256,12 @@ void surfqual_getWashoff(int j, double runoff, double tStep) if ( vOut1 > 0.0 ) cOut = OutflowLoad[p] / vOut1; // --- assign any difference between pre- and post-LID - // loads (with LID return flow included) to BMP removal + // subcatchment outflow loads to BMP removal if ( Subcatch[j].lidArea > 0.0 ) { - massLoad = cOut * (vOut1 - vOut2 - VlidReturn) * Pollut[p].mcf; - massbal_updateLoadingTotals(BMP_REMOVAL_LOAD, p, massLoad); + massLoad = cOut * (vOut1 - vOut2) * Pollut[p].mcf; + if (massLoad > 0.0) + massbal_updateLoadingTotals(BMP_REMOVAL_LOAD, p, massLoad); } // --- update subcatchment's cumulative runoff load in lbs (or kg) diff --git a/src/swmm5.c b/src/swmm5.c index 2917cb5e3..8eefa25cd 100644 --- a/src/swmm5.c +++ b/src/swmm5.c @@ -7,7 +7,7 @@ // 03/19/15 (Build 5.1.008) // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) -// 05/10/18 (Build 5.1.013) +// 11/27/17 (Build 5.1.013) // Author: L. Rossman // // This is the main module of the computational engine for Version 5 of @@ -55,7 +55,7 @@ #undef EXH // indicates if exception handling included #ifdef WINDOWS #ifdef _MSC_VER - #define EXH + #define EXH #endif #endif @@ -211,7 +211,7 @@ int DLLEXPORT swmm_run(char* f1, char* f2, char* f3) // --- close the system swmm_close(); - return error_getCode(ErrorCode); + return error_getCode(ErrorCode); //(5.1.011) } //============================================================================= @@ -225,10 +225,8 @@ int DLLEXPORT swmm_open(char* f1, char* f2, char* f3) // Purpose: opens a SWMM project. // { -// --- to be safe, reset the state of the floating point unit //(5.1.013) -#ifdef WINDOWS //(5.1.013) - _fpreset(); -#endif + +// --- call to _fpreset() removed //(5.1.013) #ifdef EXH // --- begin exception handling here @@ -497,8 +495,7 @@ void execRouting() // --- route flows & pollutants through drainage system // (while updating NewRoutingTime) if ( DoRouting ) routing_execute(RouteModel, routingStep); - else - NewRoutingTime = nextRoutingTime; + else NewRoutingTime = nextRoutingTime; } #ifdef EXH @@ -632,6 +629,7 @@ int DLLEXPORT swmm_getVersion(void) // y = minor version number, and zzz = build number. // // NOTE: Each New Release should be updated in consts.h +// THIS FUNCTION WILL EVENTUALLY BE DEPRECATED { return VERSION; } @@ -877,7 +875,7 @@ int xfilter(int xc, char* module, double elapsedTime, long step) rc = EXCEPTION_CONTINUE_EXECUTION; break; default: - sprintf(msg, "\n Exception %d ", xc); + sprintf(msg, "\n Exception %d", xc); rc = EXCEPTION_EXECUTE_HANDLER; } hour = (long)(elapsedTime / 1000.0 / 3600.0); diff --git a/src/text.h b/src/text.h index 6f3100613..faa1c9cc3 100644 --- a/src/text.h +++ b/src/text.h @@ -14,7 +14,7 @@ // 08/05/15 (Build 5.1.010) // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) -// 05/10/18 (Build 5.1.013) +// 11/27/17 (Build 5.1.013) // Author: L. Rossman // // Text strings @@ -39,8 +39,8 @@ #define FMT14 "\n Cannot open output file " #define FMT15 "\n Cannot open temporary output file" #define FMT16 "\n ERROR %d detected. Execution halted." -#define FMT17 "at line %ld of input file:" //(5.1.013) -#define FMT18 "at line %ld of %s] section:" //(5.1.013) +#define FMT17 "at line %d of input file:" +#define FMT18 "at line %d of %s] section:" #define FMT19 "\n Maximum error count exceeded." #define FMT20 "\n\n Analysis begun on: %s" #define FMT20a " Analysis ended on: %s" @@ -56,10 +56,9 @@ #define WARN07 "WARNING 07: routing time step reduced to the wet weather time step" #define WARN08 "WARNING 08: elevation drop exceeds length for Conduit" #define WARN09 "WARNING 09: time series interval greater than recording interval for Rain Gage" -#define WARN10a \ -"WARNING 10: crest elevation is below downstream invert for regulator Link" //(5.1.013) -#define WARN10b \ -"WARNING 10: crest elevation raised to downstream invert for regulator Link" //(5.1.013) +//#define WARN10 "WARNING 10: crest elevation is below downstream invert for regulator Link" +#define WARN10 \ +"WARNING 10: crest elevation raised to downstream invert for regulator Link" #define WARN11 "WARNING 11: non-matching attributes in Control Rule" // Analysis Option Keywords @@ -105,7 +104,7 @@ #define w_IGNORE_RDII "IGNORE_RDII" #define w_MIN_ROUTE_STEP "MINIMUM_STEP" #define w_NUM_THREADS "THREADS" -#define w_SURCHARGE_METHOD "SURCHARGE_METHOD" //(5.1.013) +#define w_CROWN_CUTOFF "CROWN_CUTOFF" //(5.1.013) // Flow Units #define w_CFS "CFS" @@ -126,10 +125,6 @@ #define w_XKINWAVE "XKINWAVE" #define w_DYNWAVE "DYNWAVE" -// Surcharge Methods //(5.1.013) -#define w_EXTRAN "EXTRAN" -#define w_SLOT "SLOT" - // Infiltration Methods #define w_HORTON "HORTON" #define w_MOD_HORTON "MODIFIED_HORTON" diff --git a/src/xsect.c b/src/xsect.c index 9a4cc6dab..3eeff4ef2 100644 --- a/src/xsect.c +++ b/src/xsect.c @@ -5,7 +5,7 @@ // Version: 5.1 // Date: 03/20/14 (Build 5.1.001) // 03/14/17 (Build 5.1.012) -// 05/10/18 (Build 5.1.013) +// 11/27/17 (Build 5.1.013) // Author: L. Rossman (EPA) // M. Tryby (EPA) // From 01abb4007de63772307cba0fbbfc396ed1b9fb6f Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 4 Mar 2020 15:47:11 -0500 Subject: [PATCH 002/190] Work in progress Updates from v5.1.13 11/27/17 to v5.1.13 5/10/18 --- .gitignore | 5 +- Build.md | 32 + CMakeLists.txt | 86 ++ src/outfile/CMakeLists.txt | 61 + src/outfile/errormanager.c | 74 ++ src/outfile/errormanager.h | 27 + src/outfile/include/swmm_output.h | 65 + src/outfile/include/swmm_output_enums.h | 99 ++ src/outfile/include/swmm_output_export.h | 42 + src/outfile/messages.h | 31 + src/outfile/swmm_output.c | 1256 ++++++++++++++++++ src/run/CMakeLists.txt | 35 + src/{ => run}/main.c | 2 +- src/solver/CMakeLists.txt | 57 + src/{ => solver}/Roadmap.txt | 0 src/{ => solver}/climate.c | 2 +- src/{ => solver}/consts.h | 0 src/{ => solver}/controls.c | 0 src/{ => solver}/culvert.c | 2 +- src/{ => solver}/datetime.c | 0 src/{ => solver}/datetime.h | 0 src/{ => solver}/dwflow.c | 0 src/{ => solver}/dynwave.c | 0 src/{ => solver}/enums.h | 37 +- src/{ => solver}/error.c | 0 src/{ => solver}/error.h | 0 src/{ => solver}/exfil.c | 0 src/{ => solver}/exfil.h | 0 src/{ => solver}/findroot.c | 0 src/{ => solver}/findroot.h | 0 src/{ => solver}/flowrout.c | 0 src/{ => solver}/forcmain.c | 0 src/{ => solver}/funcs.h | 0 src/{ => solver}/gage.c | 0 src/{ => solver}/globals.h | 3 +- src/{ => solver}/gwater.c | 0 src/{ => solver}/hash.c | 0 src/{ => solver}/hash.h | 0 src/{ => solver}/headers.h | 0 src/{ => solver}/hotstart.c | 0 src/{ => solver}/iface.c | 0 src/{ => solver/include}/swmm5.h | 0 src/{ => solver}/infil.c | 2 +- src/{ => solver}/infil.h | 2 +- src/{ => solver}/inflow.c | 0 src/{ => solver}/input.c | 0 src/{ => solver}/inputrpt.c | 0 src/{ => solver}/keywords.c | 9 +- src/{ => solver}/keywords.h | 5 + src/{ => solver}/kinwave.c | 0 src/{ => solver}/landuse.c | 0 src/{ => solver}/lid.c | 0 src/{ => solver}/lid.h | 2 +- src/{ => solver}/lidproc.c | 0 src/{ => solver}/link.c | 0 src/{ => solver}/macros.h | 0 src/{ => solver}/massbal.c | 2 +- src/{ => solver}/mathexpr.c | 0 src/{ => solver}/mathexpr.h | 0 src/{ => solver}/mempool.c | 0 src/{ => solver}/mempool.h | 0 src/{ => solver}/node.c | 0 src/{ => solver}/objects.h | 5 +- src/{ => solver}/odesolve.c | 0 src/{ => solver}/odesolve.h | 0 src/{ => solver}/output.c | 0 src/{ => solver}/project.c | 54 +- src/{ => solver}/qualrout.c | 0 src/{ => solver}/rain.c | 0 src/{ => solver}/rdii.c | 0 src/{ => solver}/report.c | 0 src/{ => solver}/roadway.c | 0 src/{ => solver}/routing.c | 0 src/{ => solver}/runoff.c | 0 src/{ => solver}/shape.c | 0 src/{ => solver}/snow.c | 0 src/{ => solver}/stats.c | 32 +- src/{ => solver}/statsrpt.c | 80 +- src/{ => solver}/subcatch.c | 2 +- src/{ => solver}/surfqual.c | 0 src/{ => solver}/swmm5.c | 18 +- src/{ => solver}/swmm5.def | 0 src/{ => solver}/table.c | 0 src/{ => solver}/text.h | 27 +- src/{ => solver}/toposort.c | 0 src/{ => solver}/transect.c | 0 src/{ => solver}/treatmnt.c | 0 src/{ => solver}/xsect.c | 2 +- src/{ => solver}/xsect.dat | 0 tests/CMakeLists.txt | 29 + tests/Unit_Testing.md | 35 + tests/outfile/CMakeLists.txt | 22 + tests/outfile/data/Example1.out | Bin 0 -> 44018 bytes tests/outfile/test_output.cpp | 344 +++++ tools/.gitignore | 23 - tools/Reg_Testing.md | 50 + tools/app-config.cmd | 58 + tools/before-nrtest.cmd | 116 ++ tools/make.cmd | 108 ++ tools/nrtest-swmm/nrtest_swmm/__init__.py | 112 -- tools/nrtest-swmm/setup.py | 44 - tools/requirements-win.txt | 22 + tools/run-nrtests.cmd | 114 ++ tools/swmm-output/src/outputapi.c | 1015 -------------- tools/swmm-output/src/outputapi.h | 190 --- tools/swmm-output/test/main.c | 58 - tools/swmm-reader/main.py | 165 --- tools/swmm-reader/setup.py | 29 - tools/swmm-reader/swmm_reader/__init__.py | 43 - tools/swmm-reader/swmm_reader/outputapi.py | 1069 --------------- tools/swmm-reader/swmm_reader/swmm_reader.py | 105 -- 111 files changed, 2918 insertions(+), 2991 deletions(-) create mode 100644 Build.md create mode 100644 CMakeLists.txt create mode 100644 src/outfile/CMakeLists.txt create mode 100644 src/outfile/errormanager.c create mode 100644 src/outfile/errormanager.h create mode 100644 src/outfile/include/swmm_output.h create mode 100644 src/outfile/include/swmm_output_enums.h create mode 100644 src/outfile/include/swmm_output_export.h create mode 100644 src/outfile/messages.h create mode 100644 src/outfile/swmm_output.c create mode 100644 src/run/CMakeLists.txt rename src/{ => run}/main.c (99%) create mode 100644 src/solver/CMakeLists.txt rename src/{ => solver}/Roadmap.txt (100%) rename src/{ => solver}/climate.c (99%) rename src/{ => solver}/consts.h (100%) rename src/{ => solver}/controls.c (100%) rename src/{ => solver}/culvert.c (99%) rename src/{ => solver}/datetime.c (100%) rename src/{ => solver}/datetime.h (100%) rename src/{ => solver}/dwflow.c (100%) rename src/{ => solver}/dynwave.c (100%) rename src/{ => solver}/enums.h (94%) rename src/{ => solver}/error.c (100%) rename src/{ => solver}/error.h (100%) rename src/{ => solver}/exfil.c (100%) rename src/{ => solver}/exfil.h (100%) rename src/{ => solver}/findroot.c (100%) rename src/{ => solver}/findroot.h (100%) rename src/{ => solver}/flowrout.c (100%) rename src/{ => solver}/forcmain.c (100%) rename src/{ => solver}/funcs.h (100%) rename src/{ => solver}/gage.c (100%) rename src/{ => solver}/globals.h (98%) rename src/{ => solver}/gwater.c (100%) rename src/{ => solver}/hash.c (100%) rename src/{ => solver}/hash.h (100%) rename src/{ => solver}/headers.h (100%) rename src/{ => solver}/hotstart.c (100%) rename src/{ => solver}/iface.c (100%) rename src/{ => solver/include}/swmm5.h (100%) rename src/{ => solver}/infil.c (99%) rename src/{ => solver}/infil.h (99%) rename src/{ => solver}/inflow.c (100%) rename src/{ => solver}/input.c (100%) rename src/{ => solver}/inputrpt.c (100%) rename src/{ => solver}/keywords.c (96%) rename src/{ => solver}/keywords.h (90%) rename src/{ => solver}/kinwave.c (100%) rename src/{ => solver}/landuse.c (100%) rename src/{ => solver}/lid.c (100%) rename src/{ => solver}/lid.h (99%) rename src/{ => solver}/lidproc.c (100%) rename src/{ => solver}/link.c (100%) rename src/{ => solver}/macros.h (100%) rename src/{ => solver}/massbal.c (99%) rename src/{ => solver}/mathexpr.c (100%) rename src/{ => solver}/mathexpr.h (100%) rename src/{ => solver}/mempool.c (100%) rename src/{ => solver}/mempool.h (100%) rename src/{ => solver}/node.c (100%) rename src/{ => solver}/objects.h (99%) rename src/{ => solver}/odesolve.c (100%) rename src/{ => solver}/odesolve.h (100%) rename src/{ => solver}/output.c (100%) rename src/{ => solver}/project.c (96%) rename src/{ => solver}/qualrout.c (100%) rename src/{ => solver}/rain.c (100%) rename src/{ => solver}/rdii.c (100%) rename src/{ => solver}/report.c (100%) rename src/{ => solver}/roadway.c (100%) rename src/{ => solver}/routing.c (100%) rename src/{ => solver}/runoff.c (100%) rename src/{ => solver}/shape.c (100%) rename src/{ => solver}/snow.c (100%) rename src/{ => solver}/stats.c (96%) rename src/{ => solver}/statsrpt.c (93%) rename src/{ => solver}/subcatch.c (99%) rename src/{ => solver}/surfqual.c (100%) rename src/{ => solver}/swmm5.c (98%) rename src/{ => solver}/swmm5.def (100%) rename src/{ => solver}/table.c (100%) rename src/{ => solver}/text.h (95%) rename src/{ => solver}/toposort.c (100%) rename src/{ => solver}/transect.c (100%) rename src/{ => solver}/treatmnt.c (100%) rename src/{ => solver}/xsect.c (99%) rename src/{ => solver}/xsect.dat (100%) create mode 100644 tests/CMakeLists.txt create mode 100644 tests/Unit_Testing.md create mode 100644 tests/outfile/CMakeLists.txt create mode 100644 tests/outfile/data/Example1.out create mode 100644 tests/outfile/test_output.cpp delete mode 100644 tools/.gitignore create mode 100644 tools/Reg_Testing.md create mode 100644 tools/app-config.cmd create mode 100644 tools/before-nrtest.cmd create mode 100644 tools/make.cmd delete mode 100644 tools/nrtest-swmm/nrtest_swmm/__init__.py delete mode 100644 tools/nrtest-swmm/setup.py create mode 100644 tools/requirements-win.txt create mode 100644 tools/run-nrtests.cmd delete mode 100644 tools/swmm-output/src/outputapi.c delete mode 100644 tools/swmm-output/src/outputapi.h delete mode 100644 tools/swmm-output/test/main.c delete mode 100644 tools/swmm-reader/main.py delete mode 100644 tools/swmm-reader/setup.py delete mode 100644 tools/swmm-reader/swmm_reader/__init__.py delete mode 100644 tools/swmm-reader/swmm_reader/outputapi.py delete mode 100644 tools/swmm-reader/swmm_reader/swmm_reader.py diff --git a/.gitignore b/.gitignore index 9f6b91d60..a441c732a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ # Eclipse Stuff .metadata/ -.settings/ \ No newline at end of file +.settings/ + +build/ +nrtests/ diff --git a/Build.md b/Build.md new file mode 100644 index 000000000..b9352e45d --- /dev/null +++ b/Build.md @@ -0,0 +1,32 @@ + + + +## Building SWMM Locally on Windows + + +### Dependencies + +Before the project can be built the required dependencies must be installed. + +**Summary of Build Dependencies: Windows** + + - Build + - Build Tools for Visual Studio 2017 + - CMake 3.13 + + +### Build + +SWMM can be built with one simple command. +``` +\> cd swmm +\swmm>tools\make.cmd +``` diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..bc9b5f36a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,86 @@ +# +# CMakeLists.txt - CMake configuration file for swmm-solver +# +# Created: July 11, 2019 +# Modified: Nov 25, 2019 +# +# Author: Michael E. Tryby +# US EPA ORD/CESER +# + + +cmake_minimum_required (VERSION 3.13) + +if("${CMAKE_BINARY_DIR}" STREQUAL "${CMAKE_SOURCE_DIR}") + message(FATAL_ERROR "In-source builds are disabled.") +endif() + + +project(swmm-solver + VERSION 5.1.14 + LANGUAGES C CXX + ) + +# Append local dir to module search path +list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) + +# Sets the position independent code property for all targets +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +# Sets default install prefix when cmakecache is initialized for first time +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/install CACHE PATH "..." FORCE) +endif() + + +# Define install locations (will be prepended by install prefix) +set(TOOL_DIST "bin") +set(INCLUDE_DIST "include") +set(LIBRARY_DIST "lib") +set(CONFIG_DIST "cmake") + + +option(BUILD_TESTS "Build component tests (requires Boost)" OFF) + + +# Add project subdirectories +add_subdirectory(src/outfile) +add_subdirectory(src/solver) +add_subdirectory(src/run) + +if(BUILD_TESTS) + enable_testing() + add_subdirectory(tests) +endif() + + +# Create target import scripts so other cmake projects can use swmm libraries +install( + EXPORT + swmm5Targets + DESTINATION + "${CONFIG_DIST}" + FILE + swmm5-config.cmake + ) + +install( + EXPORT + swmm-outputTargets + DESTINATION + "${CONFIG_DIST}" + FILE + swmm-output-config.cmake + ) + +# Create install rules for vcruntime.dll, msvcp.dll, vcomp.dll etc. +set(CMAKE_INSTALL_OPENMP_LIBRARIES TRUE) +include(InstallRequiredSystemLibraries) + + +# Configure CPack driven installer package +set(CPACK_GENERATOR "ZIP") +set(CPACK_PACKAGE_VENDOR "US_EPA") +set(CPACK_ARCHIVE_FILE_NAME "swmm") + +include(CPack) diff --git a/src/outfile/CMakeLists.txt b/src/outfile/CMakeLists.txt new file mode 100644 index 000000000..5c5d2ed96 --- /dev/null +++ b/src/outfile/CMakeLists.txt @@ -0,0 +1,61 @@ +# +# CMakeLists.txt - CMake configuration file for swmm/outfile +# +# Created: July 11, 2019 +# +# Author: Michael E. Tryby +# US EPA ORD/CESER +# + +# configure file groups +set(SWMM_OUT_PUBLIC_HEADERS + include/swmm_output.h + include/swmm_output_enums.h + include/swmm_output_export.h + ) + + +# the binary output file API +add_library(swmm-output + SHARED + swmm_output.c + errormanager.c + ) + +target_include_directories(swmm-output + PUBLIC + $ + $ + ) + +include(GenerateExportHeader) +generate_export_header(swmm-output + BASE_NAME swmm_output + EXPORT_MACRO_NAME EXPORT_OUT_API + EXPORT_FILE_NAME swmm_output_export.h + STATIC_DEFINE SHARED_EXPORTS_BUILT_AS_STATIC + ) + +file(COPY ${CMAKE_CURRENT_BINARY_DIR}/swmm_output_export.h + DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/include + ) + + +install(TARGETS swmm-output EXPORT swmm-outputTargets + RUNTIME DESTINATION "${TOOL_DIST}" + LIBRARY DESTINATION "${TOOL_DIST}" + ARCHIVE DESTINATION "${LIBRARY_DIST}" + FRAMEWORK DESTINATION "${TOOL_DIST}" + ) + +install(FILES ${SWMM_OUT_PUBLIC_HEADERS} DESTINATION "${INCLUDE_DIST}") + + +# copy epanet-output to build tree for testing +if(BUILD_TESTS) + add_custom_command(TARGET swmm-output POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + $ + ${CMAKE_BINARY_DIR}/bin/$/$ + ) +endif() diff --git a/src/outfile/errormanager.c b/src/outfile/errormanager.c new file mode 100644 index 000000000..6d70e0094 --- /dev/null +++ b/src/outfile/errormanager.c @@ -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 +#include +#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; +} diff --git a/src/outfile/errormanager.h b/src/outfile/errormanager.h new file mode 100644 index 000000000..cfa97ba70 --- /dev/null +++ b/src/outfile/errormanager.h @@ -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_ */ diff --git a/src/outfile/include/swmm_output.h b/src/outfile/include/swmm_output.h new file mode 100644 index 000000000..e9e028069 --- /dev/null +++ b/src/outfile/include/swmm_output.h @@ -0,0 +1,65 @@ +/* + * swmm_output.c - SWMM Output API + * + * Author: Colleen Barr + * US EPA - ORD/NHEERL + * + * Modified by: Michael E. Tryby, + * Bryant McDonnell + * + */ + +#ifndef SWMM_OUTPUT_H_ +#define SWMM_OUTPUT_H_ + +#define MAXFILENAME 259 // Max characters in file path +#define MAXELENAME 31 // Max characters in element name + +// This is an opaque pointer to struct. Do not access variables. +typedef void *SMO_Handle; + + +#include "swmm_output_enums.h" +#include "swmm_output_export.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int EXPORT_OUT_API SMO_init(SMO_Handle *p_handle); +int EXPORT_OUT_API SMO_close(SMO_Handle *p_handle); +int EXPORT_OUT_API SMO_open(SMO_Handle p_handle, const char *path); +int EXPORT_OUT_API SMO_getVersion(SMO_Handle p_handle, int *version); +int EXPORT_OUT_API SMO_getProjectSize(SMO_Handle p_handle, int **elementCount, int *length); + +int EXPORT_OUT_API SMO_getUnits(SMO_Handle p_handle, int **unitFlag, int *length); +int EXPORT_OUT_API SMO_getFlowUnits(SMO_Handle p_handle, int *unitFlag); +int EXPORT_OUT_API SMO_getPollutantUnits(SMO_Handle p_handle, int **unitFlag, int *length); +int EXPORT_OUT_API SMO_getStartDate(SMO_Handle p_handle, double *date); +int EXPORT_OUT_API SMO_getTimes(SMO_Handle p_handle, SMO_time code, int *time); +int EXPORT_OUT_API SMO_getElementName(SMO_Handle p_handle, SMO_elementType type, int elementIndex, char **elementName, int *size); + +int EXPORT_OUT_API SMO_getSubcatchSeries(SMO_Handle p_handle, int subcatchIndex, SMO_subcatchAttribute attr, int startPeriod, int endPeriod, float **outValueArray, int *length); +int EXPORT_OUT_API SMO_getNodeSeries(SMO_Handle p_handle, int nodeIndex, SMO_nodeAttribute attr, int startPeriod, int endPeriod, float **outValueArray, int *length); +int EXPORT_OUT_API SMO_getLinkSeries(SMO_Handle p_handle, int linkIndex, SMO_linkAttribute attr, int startPeriod, int endPeriod, float **outValueArray, int *length); +int EXPORT_OUT_API SMO_getSystemSeries(SMO_Handle p_handle, SMO_systemAttribute attr, int startPeriod, int endPeriod, float **outValueArray, int *length); + +int EXPORT_OUT_API SMO_getSubcatchAttribute(SMO_Handle p_handle, int timeIndex, SMO_subcatchAttribute attr, float **outValueArray, int *length); +int EXPORT_OUT_API SMO_getNodeAttribute(SMO_Handle p_handle, int timeIndex, SMO_nodeAttribute attr, float **outValueArray, int *length); +int EXPORT_OUT_API SMO_getLinkAttribute(SMO_Handle p_handle, int timeIndex, SMO_linkAttribute attr, float **outValueArray, int *length); +int EXPORT_OUT_API SMO_getSystemAttribute(SMO_Handle p_handle, int timeIndex, SMO_systemAttribute attr, float **outValueArray, int *length); + +int EXPORT_OUT_API SMO_getSubcatchResult(SMO_Handle p_handle, int timeIndex, int subcatchIndex, float **outValueArray, int *length); +int EXPORT_OUT_API SMO_getNodeResult(SMO_Handle p_handle, int timeIndex, int nodeIndex, float **outValueArray, int *length); +int EXPORT_OUT_API SMO_getLinkResult(SMO_Handle p_handle, int timeIndex, int linkIndex, float **outValueArray, int *length); +int EXPORT_OUT_API SMO_getSystemResult(SMO_Handle p_handle, int timeIndex, int dummyIndex, float **outValueArray, int *length); + +void EXPORT_OUT_API SMO_free(void **array); +void EXPORT_OUT_API SMO_clearError(SMO_Handle p_handle_in); +int EXPORT_OUT_API SMO_checkError(SMO_Handle p_handle_in, char **msg_buffer); + +#ifdef __cplusplus +} +#endif + +#endif /* SWMM_OUTPUT_H_ */ diff --git a/src/outfile/include/swmm_output_enums.h b/src/outfile/include/swmm_output_enums.h new file mode 100644 index 000000000..e4034c86a --- /dev/null +++ b/src/outfile/include/swmm_output_enums.h @@ -0,0 +1,99 @@ +/* + * swmm_output_enums.h + * + * Created on: October 18, 2019 + * + * Author: Michael E. Tryby + * US EPA - ORD/CESER + */ + + + #ifndef SWMM_OUTPUT_ENUMS_H_ + #define SWMM_OUTPUT_ENUMS_H_ + + +typedef enum { + SMO_US, + SMO_SI +} SMO_unitSystem; + +typedef enum { + SMO_CFS, + SMO_GPM, + SMO_MGD, + SMO_CMS, + SMO_LPS, + SMO_MLD +} SMO_flowUnits; + +typedef enum { + SMO_MG, + SMO_UG, + SMO_COUNT, + SMO_NONE +} SMO_concUnits; + +typedef enum { + SMO_subcatch, + SMO_node, + SMO_link, + SMO_sys, + SMO_pollut +} SMO_elementType; + +typedef enum { + SMO_reportStep, + SMO_numPeriods +} SMO_time; + +typedef enum { + SMO_rainfall_subcatch, // (in/hr or mm/hr), + SMO_snow_depth_subcatch, // (in or mm), + SMO_evap_loss, // (in/hr or mm/hr), + SMO_infil_loss, // (in/hr or mm/hr), + SMO_runoff_rate, // (flow units), + SMO_gwoutflow_rate, // (flow units), + SMO_gwtable_elev, // (ft or m), + SMO_soil_moisture, // unsaturated zone moisture content (-), + SMO_pollutant_conc_subcatch // first pollutant +} SMO_subcatchAttribute; + +typedef enum { + SMO_invert_depth, // (ft or m), + SMO_hydraulic_head, // (ft or m), + SMO_stored_ponded_volume, // (ft3 or m3), + SMO_lateral_inflow, // (flow units), + SMO_total_inflow, // lateral + upstream (flow units), + SMO_flooding_losses, // (flow units), + SMO_pollutant_conc_node // first pollutant, +} SMO_nodeAttribute; + +typedef enum { + SMO_flow_rate_link, // (flow units), + SMO_flow_depth, // (ft or m), + SMO_flow_velocity, // (ft/s or m/s), + SMO_flow_volume, // (ft3 or m3), + SMO_capacity, // (fraction of conduit filled), + SMO_pollutant_conc_link // first pollutant, +} SMO_linkAttribute; + +typedef enum { + SMO_air_temp, // (deg. F or deg. C), + SMO_rainfall_system, // (in/hr or mm/hr), + SMO_snow_depth_system, // (in or mm), + SMO_evap_infil_loss, // (in/hr or mm/hr), + SMO_runoff_flow, // (flow units), + SMO_dry_weather_inflow, // (flow units), + SMO_groundwater_inflow, // (flow units), + SMO_RDII_inflow, // (flow units), + SMO_direct_inflow, // user defined (flow units), + SMO_total_lateral_inflow, // (sum of variables 4 to 8) //(flow units), + SMO_flood_losses, // (flow units), + SMO_outfall_flows, // (flow units), + SMO_volume_stored, // (ft3 or m3), + SMO_evap_rate // (in/day or mm/day), + //p_evap_rate // (in/day or mm/day) +} SMO_systemAttribute; + + +#endif /* SWMM_OUTPUT_ENUMS_H_ */ diff --git a/src/outfile/include/swmm_output_export.h b/src/outfile/include/swmm_output_export.h new file mode 100644 index 000000000..3396ce6b4 --- /dev/null +++ b/src/outfile/include/swmm_output_export.h @@ -0,0 +1,42 @@ + +#ifndef EXPORT_OUT_API_H +#define EXPORT_OUT_API_H + +#ifdef SHARED_EXPORTS_BUILT_AS_STATIC +# define EXPORT_OUT_API +# define SWMM_OUTPUT_NO_EXPORT +#else +# ifndef EXPORT_OUT_API +# ifdef swmm_output_EXPORTS + /* We are building this library */ +# define EXPORT_OUT_API __declspec(dllexport) +# else + /* We are using this library */ +# define EXPORT_OUT_API __declspec(dllimport) +# endif +# endif + +# ifndef SWMM_OUTPUT_NO_EXPORT +# define SWMM_OUTPUT_NO_EXPORT +# endif +#endif + +#ifndef SWMM_OUTPUT_DEPRECATED +# define SWMM_OUTPUT_DEPRECATED __declspec(deprecated) +#endif + +#ifndef SWMM_OUTPUT_DEPRECATED_EXPORT +# define SWMM_OUTPUT_DEPRECATED_EXPORT EXPORT_OUT_API SWMM_OUTPUT_DEPRECATED +#endif + +#ifndef SWMM_OUTPUT_DEPRECATED_NO_EXPORT +# define SWMM_OUTPUT_DEPRECATED_NO_EXPORT SWMM_OUTPUT_NO_EXPORT SWMM_OUTPUT_DEPRECATED +#endif + +#if 0 /* DEFINE_NO_DEPRECATED */ +# ifndef SWMM_OUTPUT_NO_DEPRECATED +# define SWMM_OUTPUT_NO_DEPRECATED +# endif +#endif + +#endif /* EXPORT_OUT_API_H */ diff --git a/src/outfile/messages.h b/src/outfile/messages.h new file mode 100644 index 000000000..9fa691df3 --- /dev/null +++ b/src/outfile/messages.h @@ -0,0 +1,31 @@ +/* + * messages.h - SWMM + * + * Created on: Oct 20, 2017 + * + * Author: Michael E. Tryby + * US EPA - ORD/NRMRL + */ + +#ifndef SRC_MESSAGES_H_ +#define SRC_MESSAGES_H_ + +#define MAXMSG 56 + +/*------------------- Error Messages --------------------*/ +#define WARN10 "Warning: model run issued warnings" + +#define ERR411 "Error 411: memory allocation failure" + +#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 ERR424 "Input Error 424: no memory allocated for results" + +#define ERR434 "File Error 434: unable to open binary output file" +#define ERR435 "File Error 435: invalid file - not created by SWMM" +#define ERR436 "File Error 436: invalid file - contains no results" + +#define ERR440 "ERROR 440: an unspecified error has occurred" + +#endif /* SRC_MESSAGES_H_ */ diff --git a/src/outfile/swmm_output.c b/src/outfile/swmm_output.c new file mode 100644 index 000000000..dae5dc8fb --- /dev/null +++ b/src/outfile/swmm_output.c @@ -0,0 +1,1256 @@ +/* + * swmm_output.c - SWMM Output API + * + * Author: Colleen Barr + * US EPA - ORD/NHEERL + * + * Modified by: Michael E. Tryby, + * Bryant McDonnell + * + */ + + +#include +#include +#include + +#include "errormanager.h" + +#include "messages.h" +#include "swmm_output.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 RECORDSIZE 4 // Memory alignment 4 byte word size for both int and real +#define DATESIZE 8 // Dates are stored as 8 byte word size + +#define NELEMENTTYPES 5 // Number of element types + +#define MEMCHECK(x) (((x) == NULL) ? 414 : 0) + +struct IDentry { + char* IDname; + int length; +}; +typedef struct IDentry idEntry; + +//----------------------------------------------------------------------------- +// Shared variables +//----------------------------------------------------------------------------- + +typedef struct { + char name[MAXFILENAME + 1]; // file path/name + FILE* file; // FILE structure pointer + + struct IDentry* elementNames; // array of pointers to element names + + long Nperiods; // number of reporting periods + int FlowUnits; // flow units code + + int Nsubcatch; // number of subcatchments + int Nnodes; // number of drainage system nodes + int Nlinks; // number of drainage system links + int Npolluts; // number of pollutants tracked + + int SubcatchVars; // number of subcatch reporting variables + int NodeVars; // number of node reporting variables + int LinkVars; // number of link reporting variables + int SysVars; // number of system reporting variables + + double StartDate; // start date of simulation + int ReportStep; // reporting time step (seconds) + + F_OFF IDPos; // file position where object ID names start + F_OFF ObjPropPos; // file position where object properties start + F_OFF ResultsPos; // file position where results start + F_OFF BytesPerPeriod; // bytes used for results in each period + + error_handle_t* error_handle; +} data_t; + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +void errorLookup(int errcode, char *errmsg, int length); +int validateFile(data_t *p_data); +void initElementNames(data_t *p_data); + +double getTimeValue(data_t *p_data, int timeIndex); +float getSubcatchValue(data_t *p_data, int timeIndex, int subcatchIndex, SMO_subcatchAttribute attr); +float getNodeValue(data_t *p_data, int timeIndex, int nodeIndex, SMO_nodeAttribute attr); +float getLinkValue(data_t *p_data, int timeIndex, int linkIndex, SMO_linkAttribute attr); +float getSystemValue(data_t *p_data, int timeIndex, SMO_systemAttribute attr); + +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 EXPORT_OUT_API SMO_init(SMO_Handle *p_handle) +// Purpose: Initialized pointer for the opaque SMO_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 *priv_data; + + // Allocate memory for private data + priv_data = (data_t *)calloc(1, sizeof(data_t)); + + if (priv_data != NULL) { + priv_data->error_handle = new_errormanager(&errorLookup); + *p_handle = priv_data; + } else + errorcode = -1; + + // TODO: Need to handle errors during initialization better. + return errorcode; +} + +int EXPORT_OUT_API SMO_close(SMO_Handle* p_handle) +// +// Purpose: Clean up after and close Output API +// +{ + data_t *p_data; + int i, n, errorcode = 0; + + p_data = (data_t *)*p_handle; + + if (p_data == NULL) + errorcode = -1; + + else { + if (p_data->elementNames != NULL) { + n = p_data->Nsubcatch + p_data->Nnodes + p_data->Nlinks + + p_data->Npolluts; + + for (i = 0; i < n; i++) + free(p_data->elementNames[i].IDname); + + free(p_data->elementNames); + } + + dst_errormanager(p_data->error_handle); + + if (p_data->file != NULL) + fclose(p_data->file); + + free(p_data); + + *p_handle = NULL; + } + + return errorcode; +} + +int EXPORT_OUT_API SMO_open(SMO_Handle p_handle, const char *path) +// +// Purpose: Open the output binary file and read the header. +// +{ + int err, errorcode = 0; + F_OFF offset; + + data_t *p_data; + + p_data = (data_t *)p_handle; + + if (p_data == NULL) + return -1; + else { + strncpy(p_data->name, path, MAXFILENAME); + + // Attempt to open binary output file for reading only + if ((_fopen(&(p_data->file), path, "rb")) != 0) + errorcode = 434; + + // --- validate the output file + else if ((err = validateFile(p_data)) != 0) + errorcode = err; + + // If a warning is encountered read file header + if (errorcode < 400) { + // --- otherwise read additional parameters from start of file + fseek(p_data->file, 3 * RECORDSIZE, SEEK_SET); + fread(&(p_data->Nsubcatch), RECORDSIZE, 1, p_data->file); + fread(&(p_data->Nnodes), RECORDSIZE, 1, p_data->file); + fread(&(p_data->Nlinks), RECORDSIZE, 1, p_data->file); + fread(&(p_data->Npolluts), RECORDSIZE, 1, p_data->file); + + // Compute offset for saved subcatch/node/link input values + offset = + (p_data->Nsubcatch + 2) * RECORDSIZE // Subcatchment area + + (3 * p_data->Nnodes + 4) * + RECORDSIZE // Node type, invert & max depth + + (5 * p_data->Nlinks + 6) * + RECORDSIZE; // Link type, z1, z2, max depth & length + offset += p_data->ObjPropPos; + + // Read number & codes of computed variables + _fseek(p_data->file, offset, SEEK_SET); + fread(&(p_data->SubcatchVars), RECORDSIZE, 1, + p_data->file); // # Subcatch variables + + _fseek(p_data->file, p_data->SubcatchVars * RECORDSIZE, SEEK_CUR); + fread(&(p_data->NodeVars), RECORDSIZE, 1, + p_data->file); // # Node variables + + _fseek(p_data->file, p_data->NodeVars * RECORDSIZE, SEEK_CUR); + fread(&(p_data->LinkVars), RECORDSIZE, 1, + p_data->file); // # Link variables + + _fseek(p_data->file, p_data->LinkVars * RECORDSIZE, SEEK_CUR); + fread(&(p_data->SysVars), RECORDSIZE, 1, + p_data->file); // # System variables + + // --- read data just before start of output results + offset = p_data->ResultsPos - 3 * RECORDSIZE; + _fseek(p_data->file, offset, SEEK_SET); + fread(&(p_data->StartDate), DATESIZE, 1, p_data->file); + fread(&(p_data->ReportStep), RECORDSIZE, 1, p_data->file); + + // --- compute number of bytes of results values used per time + // period + p_data->BytesPerPeriod = + DATESIZE + + (p_data->Nsubcatch * p_data->SubcatchVars + + p_data->Nnodes * p_data->NodeVars + + p_data->Nlinks * p_data->LinkVars + p_data->SysVars) * + RECORDSIZE; + } + } + // If error close the binary file + if (errorcode > 400) { + set_error(p_data->error_handle, errorcode); + SMO_close(&p_handle); + } + + return errorcode; +} + +int EXPORT_OUT_API SMO_getVersion(SMO_Handle p_handle, int *version) +// +// Input: p_handle = pointer to SMO_Handle struct +// Output: version SWMM version +// Returns: error code +// +// Purpose: Returns SWMM version that wrote binary file +// +{ + 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 * RECORDSIZE, SEEK_SET); + if (fread(version, RECORDSIZE, 1, p_data->file) != 1) + errorcode = 436; + } + + return set_error(p_data->error_handle, errorcode); +} + +int EXPORT_OUT_API SMO_getProjectSize(SMO_Handle p_handle, int **elementCount, int *length) +// +// Purpose: Returns project size. +// +{ + int errorcode = 0; + int *temp; + data_t *p_data; + + p_data = (data_t*)p_handle; + + *elementCount = NULL; + *length = NELEMENTTYPES; + + if (p_data == NULL) + errorcode = -1; + else if (MEMCHECK(temp = newIntArray(*length))) + errorcode = 414; + else { + temp[0] = p_data->Nsubcatch; + temp[1] = p_data->Nnodes; + temp[2] = p_data->Nlinks; + temp[3] = 1; // NSystems + temp[4] = p_data->Npolluts; + + *elementCount = temp; + } + + return set_error(p_data->error_handle, errorcode); +} + + +int EXPORT_OUT_API SMO_getUnits(SMO_Handle p_handle, int **unitFlag, int *length) +// +// Purpose: Returns unit flags for unit_system, flow, and pollutants. +// +{ + int errorcode = 0; + int* temp; + F_OFF offset; + data_t* p_data; + + p_data = (data_t*)p_handle; + + *unitFlag = NULL; + if (p_data->Npolluts > 0) + *length = 2 + p_data->Npolluts; + else + *length = 3; + + p_data = (data_t*)p_handle; + + if (p_data == NULL) + errorcode = -1; + else if (MEMCHECK(temp = newIntArray(*length))) + errorcode = 414; + else { + // Set flow units flag + fseek(p_data->file, 2*RECORDSIZE, SEEK_SET); + fread(&temp[1], RECORDSIZE, 1, p_data->file); + + // Set unit system based on flow flag + if (temp[1] < SMO_CMS) + temp[0] = SMO_US; + else + temp[0] = SMO_SI; + + // Set conc units flag + if (p_data->Npolluts == 0) + temp[2] = SMO_NONE; + else { + offset = p_data->ObjPropPos - (p_data->Npolluts * RECORDSIZE); + _fseek(p_data->file, offset, SEEK_SET); + fread(&temp[2], RECORDSIZE, p_data->Npolluts, p_data->file); + } + *unitFlag = temp; + } + + return set_error(p_data->error_handle, errorcode); +} + +int EXPORT_OUT_API SMO_getFlowUnits(SMO_Handle p_handle, int *unitFlag) +// +// Purpose: Returns unit flag for flow. +// +// Returns: +// 0: CFS (cubic feet per second) +// 1: GPM (gallons per minute) +// 2: MGD (million gallons per day) +// 3: CMS (cubic meters per second) +// 4: LPS (liters per second) +// 5: MLD (million liters per day) +// +{ + int errorcode = 0; + data_t* p_data; + + *unitFlag = -1; + + p_data = (data_t*)p_handle; + + if (p_data == NULL) + return -1; + else { + fseek(p_data->file, 2 * RECORDSIZE, SEEK_SET); + fread(unitFlag, RECORDSIZE, 1, p_data->file); + } + + return set_error(p_data->error_handle, errorcode); +} + +int EXPORT_OUT_API SMO_getPollutantUnits(SMO_Handle p_handle, int **unitFlag, int *length) +// +// Purpose: +// Return integer flag representing the units that the given pollutant is +// measured in. Concentration units are located after the pollutant ID +// names and before the object properties start, and are stored for each +// pollutant. They're stored as 4-byte integers with the following codes: +// 0: mg/L +// 1: ug/L +// 2: count/L +// +// Args: +// pollutantIndex: valid values are 0 to Npolluts-1 +{ + int errorcode = 0; + int *temp; + F_OFF offset; + data_t *p_data; + + p_data = (data_t *)p_handle; + + if (p_data == NULL) + errorcode = -1; + else if (MEMCHECK(temp = newIntArray(p_data->Npolluts))) + errorcode = 414; + else { + offset = p_data->ObjPropPos - (p_data->Npolluts * RECORDSIZE); + _fseek(p_data->file, offset, SEEK_SET); + fread(temp, RECORDSIZE, p_data->Npolluts, p_data->file); + + *unitFlag = temp; + *length = p_data->Npolluts; + } + + return set_error(p_data->error_handle, errorcode); +} + +int EXPORT_OUT_API SMO_getStartDate(SMO_Handle p_handle, double *date) +// +// Purpose: Returns start date. +// +{ + int errorcode = 0; + data_t *p_data; + + *date = -1.0; + + p_data = (data_t *)p_handle; + + if (p_data == NULL) + errorcode = -1; + else + *date = p_data->StartDate; + + return set_error(p_data->error_handle, errorcode); +} + +int EXPORT_OUT_API SMO_getTimes(SMO_Handle p_handle, SMO_time code, int *time) +// +// Purpose: Returns step size and number of periods. +// +{ + int errorcode = 0; + data_t *p_data; + + *time = -1; + + p_data = (data_t *)p_handle; + + if (p_data == NULL) + errorcode = -1; + else { + switch (code) { + case SMO_reportStep: + *time = p_data->ReportStep; + break; + case SMO_numPeriods: + *time = p_data->Nperiods; + break; + default: + errorcode = 421; + } + } + + return set_error(p_data->error_handle, errorcode); +} + +int EXPORT_OUT_API SMO_getElementName(SMO_Handle p_handle, SMO_elementType type, + int index, char **name, int *length) +// +// Purpose: Given an element index returns the element name. +// +{ + int idx = -1, errorcode = 0; + data_t *p_data; + + p_data = (data_t *)p_handle; + + if (p_data == NULL) + errorcode = 410; + else if (p_data->file == NULL) + errorcode = 411; + else { + // Initialize the name array if necessary + if (p_data->elementNames == NULL) + initElementNames(p_data); + + switch (type) { + case SMO_subcatch: + if (index < 0 || index >= p_data->Nsubcatch) + errorcode = 423; + else + idx = index; + break; + + case SMO_node: + if (index < 0 || index >= p_data->Nnodes) + errorcode = 423; + else + idx = p_data->Nsubcatch + index; + break; + + case SMO_link: + if (index < 0 || index >= p_data->Nlinks) + errorcode = 423; + else + idx = p_data->Nsubcatch + p_data->Nnodes + index; + break; + + case SMO_pollut: + if (index < 0 || index >= p_data->Npolluts) + errorcode = 423; + else + idx = p_data->Nsubcatch + p_data->Nnodes + p_data->Nlinks + + index; + break; + + default: + errorcode = 421; + } + + if (!errorcode) { + *length = p_data->elementNames[idx].length; + *name = newCharArray(*length + 1); + // Writes IDname and an additional null character to name + strncpy(*name, p_data->elementNames[idx].IDname, + (*length + 1) * sizeof(char)); + } + } + + return set_error(p_data->error_handle, errorcode); +} + +int EXPORT_OUT_API SMO_getSubcatchSeries(SMO_Handle p_handle, int subcatchIndex, + SMO_subcatchAttribute attr, int startPeriod, int endPeriod, + float **outValueArray, int *length) +// +// Purpose: Get time series results for particular attribute. Specify series +// start and length using timeIndex and length respectively. +// +{ + int k, len, errorcode = 0; + float *temp; + data_t *p_data; + + p_data = (data_t *)p_handle; + + if (p_data == NULL) + errorcode = -1; + else if (subcatchIndex < 0 || subcatchIndex > p_data->Nsubcatch) + errorcode = 420; + else if (startPeriod < 0 || startPeriod >= p_data->Nperiods || + endPeriod <= startPeriod) + errorcode = 422; + // Check memory for outValues + else if + MEMCHECK(temp = newFloatArray(len = endPeriod - startPeriod)) + errorcode = 411; + else { + // loop over and build time series + for (k = 0; k < len; k++) + temp[k] = + getSubcatchValue(p_data, startPeriod + k, subcatchIndex, attr); + + *outValueArray = temp; + *length = len; + } + + return set_error(p_data->error_handle, errorcode); +} + +int EXPORT_OUT_API SMO_getNodeSeries(SMO_Handle p_handle, int nodeIndex, + SMO_nodeAttribute attr, int startPeriod, int endPeriod, + float **outValueArray, int *length) +// +// Purpose: Get time series results for particular attribute. Specify series +// start and length using timeIndex and length respectively. +// +{ + int k, len, errorcode = 0; + float *temp; + data_t *p_data; + + p_data = (data_t *)p_handle; + + if (p_data == NULL) + errorcode = -1; + else if (nodeIndex < 0 || nodeIndex > p_data->Nnodes) + errorcode = 420; + else if (startPeriod < 0 || startPeriod >= p_data->Nperiods || + endPeriod <= startPeriod) + errorcode = 422; + // Check memory for outValues + else if + MEMCHECK(temp = newFloatArray(len = endPeriod - startPeriod)) + errorcode = 411; + else { + // loop over and build time series + for (k = 0; k < len; k++) + temp[k] = getNodeValue(p_data, startPeriod + k, nodeIndex, attr); + + *outValueArray = temp; + *length = len; + } + + return set_error(p_data->error_handle, errorcode); +} + +int EXPORT_OUT_API SMO_getLinkSeries(SMO_Handle p_handle, int linkIndex, + SMO_linkAttribute attr, int startPeriod, int endPeriod, + float **outValueArray, int *length) +// +// Purpose: Get time series results for particular attribute. Specify series +// start and length using timeIndex and length respectively. +// +{ + int k, len, errorcode = 0; + float *temp; + data_t *p_data; + + p_data = (data_t *)p_handle; + + if (p_data == NULL) + errorcode = -1; + else if (linkIndex < 0 || linkIndex > p_data->Nlinks) + errorcode = 420; + else if (startPeriod < 0 || startPeriod >= p_data->Nperiods || + endPeriod <= startPeriod) + errorcode = 422; + // Check memory for outValues + else if + MEMCHECK(temp = newFloatArray(len = endPeriod - startPeriod)) + errorcode = 411; + else { + // loop over and build time series + for (k = 0; k < len; k++) + temp[k] = getLinkValue(p_data, startPeriod + k, linkIndex, attr); + + *outValueArray = temp; + *length = len; + } + + return set_error(p_data->error_handle, errorcode); +} + +int EXPORT_OUT_API SMO_getSystemSeries(SMO_Handle p_handle, SMO_systemAttribute attr, + int startPeriod, int endPeriod, float **outValueArray, int *length) +// +// Purpose: Get time series results for particular attribute. Specify series +// start and length using timeIndex and length respectively. +// +{ + int k, len, errorcode = 0; + float *temp; + data_t *p_data; + + p_data = (data_t *)p_handle; + + if (p_data == NULL) + errorcode = -1; + else if (startPeriod < 0 || startPeriod >= p_data->Nperiods || + endPeriod <= startPeriod) + errorcode = 422; + // Check memory for outValues + else if + MEMCHECK(temp = newFloatArray(len = endPeriod - startPeriod)) + errorcode = 411; + else { + // loop over and build time series + for (k = 0; k < length; k++) + temp[k] = getSystemValue(p_data, startPeriod + k, attr); + + *outValueArray = temp; + *length = len; + } + + return set_error(p_data->error_handle, errorcode); +} + +int EXPORT_OUT_API SMO_getSubcatchAttribute(SMO_Handle p_handle, int periodIndex, + SMO_subcatchAttribute attr, float **outValueArray, int *length) +// +// Purpose: For all subcatchments at given time, get a particular attribute. +// +{ + int k, errorcode = 0; + float *temp; + data_t *p_data; + + p_data = (data_t *)p_handle; + + if (p_data == NULL) + errorcode = -1; + else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) + errorcode = 422; + // Check memory for outValues + else if + MEMCHECK(temp = newFloatArray(p_data->Nsubcatch)) errorcode = 411; + else { + // loop over and pull result + for (k = 0; k < p_data->Nsubcatch; k++) + temp[k] = getSubcatchValue(p_data, periodIndex, k, attr); + + *outValueArray = temp; + *length = p_data->Nsubcatch; + } + + return set_error(p_data->error_handle, errorcode); +} + +int EXPORT_OUT_API SMO_getNodeAttribute(SMO_Handle p_handle, int periodIndex, + SMO_nodeAttribute attr, float **outValueArray, int *length) +// +// Purpose: For all nodes at given time, get a particular attribute. +// +{ + int k, errorcode = 0; + float *temp; + data_t *p_data; + + p_data = (data_t *)p_handle; + + if (p_data == NULL) + errorcode = -1; + else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) + errorcode = 422; + // Check memory for outValues + else if + MEMCHECK(temp = newFloatArray(p_data->Nnodes)) errorcode = 411; + else { + // loop over and pull result + for (k = 0; k < p_data->Nnodes; k++) + temp[k] = getNodeValue(p_data, periodIndex, k, attr); + + *outValueArray = temp; + *length = p_data->Nnodes; + } + + return set_error(p_data->error_handle, errorcode); +} + +int EXPORT_OUT_API SMO_getLinkAttribute(SMO_Handle p_handle, int periodIndex, + SMO_linkAttribute attr, float **outValueArray, int *length) +// +// Purpose: For all links at given time, get a particular attribute. +// +{ + int k, errorcode = 0; + float *temp; + data_t *p_data; + + p_data = (data_t *)p_handle; + + if (p_data == NULL) + errorcode = -1; + else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) + errorcode = 422; + // Check memory for outValues + else if + MEMCHECK(temp = newFloatArray(p_data->Nlinks)) errorcode = 411; + else { + // loop over and pull result + for (k = 0; k < p_data->Nlinks; k++) + temp[k] = getLinkValue(p_data, periodIndex, k, attr); + + *outValueArray = temp; + *length = p_data->Nlinks; + } + + return set_error(p_data->error_handle, errorcode); +} + +int EXPORT_OUT_API SMO_getSystemAttribute(SMO_Handle p_handle, int periodIndex, + SMO_systemAttribute attr, float **outValueArray, int *length) +// +// Purpose: For the system at given time, get a particular attribute. +// +{ + int errorcode = 0; + float temp; + data_t *p_data; + + p_data = (data_t *)p_handle; + + if (p_data == NULL) + errorcode = -1; + else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) + errorcode = 422; + else { + // don't need to loop since there's only one system + temp = getSystemValue(p_data, periodIndex, attr); + + *outValueArray = &temp; + *length = 1; + } + + return set_error(p_data->error_handle, errorcode); +} + +int EXPORT_OUT_API SMO_getSubcatchResult(SMO_Handle p_handle, int periodIndex, + int subcatchIndex, float **outValueArray, int *arrayLength) +// +// Purpose: For a subcatchment at given time, get all attributes. +// +{ + int errorcode = 0; + float *temp; + F_OFF offset; + data_t *p_data; + + p_data = (data_t *)p_handle; + + if (p_data == NULL) + errorcode = -1; + else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) + errorcode = 422; + else if (subcatchIndex < 0 || subcatchIndex > p_data->Nsubcatch) + errorcode = 423; + else if + MEMCHECK(temp = newFloatArray(p_data->SubcatchVars)) errorcode = 411; + else { + // --- compute offset into output file + offset = p_data->ResultsPos + (periodIndex)*p_data->BytesPerPeriod + + 2 * RECORDSIZE; + // add offset for subcatchment + offset += (subcatchIndex * p_data->SubcatchVars) * RECORDSIZE; + + _fseek(p_data->file, offset, SEEK_SET); + fread(temp, RECORDSIZE, p_data->SubcatchVars, p_data->file); + + *outValueArray = temp; + *arrayLength = p_data->SubcatchVars; + } + + return set_error(p_data->error_handle, errorcode); +} + +int EXPORT_OUT_API SMO_getNodeResult(SMO_Handle p_handle, int periodIndex, + int nodeIndex, float **outValueArray, int *arrayLength) +// +// Purpose: For a node at given time, get all attributes. +// +{ + int errorcode = 0; + float *temp; + F_OFF offset; + data_t *p_data; + + p_data = (data_t *)p_handle; + + if (p_data == NULL) + errorcode = -1; + else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) + errorcode = 422; + else if (nodeIndex < 0 || nodeIndex > p_data->Nnodes) + errorcode = 423; + else if + MEMCHECK(temp = newFloatArray(p_data->NodeVars)) errorcode = 411; + else { + // calculate byte offset to start time for series + offset = p_data->ResultsPos + (periodIndex)*p_data->BytesPerPeriod + + 2 * RECORDSIZE; + // add offset for subcatchment and node + offset += (p_data->Nsubcatch * p_data->SubcatchVars + + nodeIndex * p_data->NodeVars) * + RECORDSIZE; + + _fseek(p_data->file, offset, SEEK_SET); + fread(temp, RECORDSIZE, p_data->NodeVars, p_data->file); + + *outValueArray = temp; + *arrayLength = p_data->NodeVars; + } + + return set_error(p_data->error_handle, errorcode); +} + +int EXPORT_OUT_API SMO_getLinkResult(SMO_Handle p_handle, int periodIndex, + int linkIndex, float **outValueArray, int *arrayLength) +// +// Purpose: For a link at given time, get all attributes. +// +{ + int errorcode = 0; + float *temp; + F_OFF offset; + data_t *p_data; + + p_data = (data_t *)p_handle; + + if (p_data == NULL) + errorcode = -1; + else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) + errorcode = 422; + else if (linkIndex < 0 || linkIndex > p_data->Nlinks) + errorcode = 423; + else if + MEMCHECK(temp = newFloatArray(p_data->LinkVars)) errorcode = 411; + else { + // calculate byte offset to start time for series + offset = p_data->ResultsPos + (periodIndex)*p_data->BytesPerPeriod + + 2 * RECORDSIZE; + // add offset for subcatchment and node and link + offset += + (p_data->Nsubcatch * p_data->SubcatchVars + + p_data->Nnodes * p_data->NodeVars + linkIndex * p_data->LinkVars) * + RECORDSIZE; + + _fseek(p_data->file, offset, SEEK_SET); + fread(temp, RECORDSIZE, p_data->LinkVars, p_data->file); + + *outValueArray = temp; + *arrayLength = p_data->LinkVars; + } + + return set_error(p_data->error_handle, errorcode); +} + +int EXPORT_OUT_API SMO_getSystemResult(SMO_Handle p_handle, int periodIndex, + int dummyIndex, float **outValueArray, int *arrayLength) +// +// Purpose: For the system at given time, get all attributes. +// +{ + int errorcode = 0; + float *temp; + F_OFF offset; + data_t *p_data; + + p_data = (data_t *)p_handle; + + if (p_data == NULL) + errorcode = -1; + else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) + errorcode = 422; + else if + MEMCHECK(temp = newFloatArray(p_data->SysVars)) errorcode = 411; + { + // calculate byte offset to start time for series + offset = p_data->ResultsPos + (periodIndex)*p_data->BytesPerPeriod + + 2 * RECORDSIZE; + // add offset for subcatchment and node and link (system starts after + // the last link) + offset += (p_data->Nsubcatch * p_data->SubcatchVars + + p_data->Nnodes * p_data->NodeVars + + p_data->Nlinks * p_data->LinkVars) * + RECORDSIZE; + + _fseek(p_data->file, offset, SEEK_SET); + fread(temp, RECORDSIZE, p_data->SysVars, p_data->file); + + *outValueArray = temp; + *arrayLength = p_data->SysVars; + } + + return set_error(p_data->error_handle, errorcode); +} + +void EXPORT_OUT_API SMO_free(void **array) +// +// Purpose: Frees memory allocated by API calls +// +{ + if (array != NULL) { + free(*array); + *array = NULL; + } +} + +void EXPORT_OUT_API SMO_clearError(SMO_Handle p_handle) { + data_t *p_data; + + p_data = (data_t *)p_handle; + clear_error(p_data->error_handle); +} + +int EXPORT_OUT_API SMO_checkError(SMO_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 421: + msg = ERR421; + break; + case 422: + msg = ERR422; + break; + case 423: + msg = ERR423; + break; + case 424: + msg = ERR424; + break; + case 434: + msg = ERR434; + break; + case 435: + msg = ERR435; + break; + case 436: + msg = ERR436; + break; + default: + msg = ERR440; + } + + strncpy(dest_msg, msg, MAXMSG); +} + +// Local functions: +int validateFile(data_t *p_data) { + INT4 magic1, magic2, errcode; + int errorcode = 0; + + // --- fast forward to end and read epilogue + _fseek(p_data->file, -6 * RECORDSIZE, SEEK_END); + fread(&(p_data->IDPos), RECORDSIZE, 1, p_data->file); + fread(&(p_data->ObjPropPos), RECORDSIZE, 1, p_data->file); + fread(&(p_data->ResultsPos), RECORDSIZE, 1, p_data->file); + fread(&(p_data->Nperiods), RECORDSIZE, 1, p_data->file); + fread(&errcode, RECORDSIZE, 1, p_data->file); + fread(&magic2, RECORDSIZE, 1, p_data->file); + + // --- rewind and read magic number from beginning of the file + _fseek(p_data->file, 0L, SEEK_SET); + fread(&magic1, RECORDSIZE, 1, p_data->file); + + // Is this a valid SWMM binary output file? + if (magic1 != magic2) + errorcode = 435; + // Does the binary file contain results? + else if (p_data->Nperiods <= 0) + errorcode = 436; + // Were there problems with the model run? + else if (errcode != 0) + errorcode = 10; + + return errorcode; +} + +void initElementNames(data_t *p_data) { + int j, numNames; + + numNames = + p_data->Nsubcatch + p_data->Nnodes + p_data->Nlinks + p_data->Npolluts; + + // allocate memory for array of idEntries + p_data->elementNames = (idEntry*)calloc(numNames, sizeof(idEntry)); + + // Position the file to the start of the ID entries + _fseek(p_data->file, p_data->IDPos, SEEK_SET); + + for (j = 0; j < numNames; j++) { + fread(&(p_data->elementNames[j].length), RECORDSIZE, 1, p_data->file); + + p_data->elementNames[j].IDname = + (char*)calloc(p_data->elementNames[j].length + 1, sizeof(char)); + + fread(p_data->elementNames[j].IDname, sizeof(char), + p_data->elementNames[j].length, p_data->file); + } +} + +double getTimeValue(data_t *p_data, int timeIndex) { + + F_OFF offset; + double value; + + // --- compute offset into output file + offset = p_data->ResultsPos + timeIndex * p_data->BytesPerPeriod; + + // --- re-position the file and read the result + _fseek(p_data->file, offset, SEEK_SET); + fread(&value, RECORDSIZE * 2, 1, p_data->file); + + return value; +} + +float getSubcatchValue(data_t *p_data, int timeIndex, int subcatchIndex, + SMO_subcatchAttribute attr) { + + F_OFF offset; + float value; + + // --- compute offset into output file + offset = p_data->ResultsPos + timeIndex * p_data->BytesPerPeriod + + 2 * RECORDSIZE; + // offset for subcatch + offset += RECORDSIZE * (subcatchIndex * p_data->SubcatchVars + attr); + + // --- re-position the file and read the result + _fseek(p_data->file, offset, SEEK_SET); + fread(&value, RECORDSIZE, 1, p_data->file); + + return value; +} + +float getNodeValue(data_t *p_data, int timeIndex, int nodeIndex, + SMO_nodeAttribute attr) { + + F_OFF offset; + float value; + + // --- compute offset into output file + offset = p_data->ResultsPos + timeIndex * p_data->BytesPerPeriod + + 2 * RECORDSIZE; + // offset for node + offset += RECORDSIZE * (p_data->Nsubcatch * p_data->SubcatchVars + + nodeIndex * p_data->NodeVars + attr); + + // --- re-position the file and read the result + _fseek(p_data->file, offset, SEEK_SET); + fread(&value, RECORDSIZE, 1, p_data->file); + + return value; +} + +float getLinkValue(data_t *p_data, int timeIndex, int linkIndex, + SMO_linkAttribute attr) { + + F_OFF offset; + float value; + + // --- compute offset into output file + offset = p_data->ResultsPos + timeIndex * p_data->BytesPerPeriod + + 2 * RECORDSIZE; + // offset for link + offset += RECORDSIZE * (p_data->Nsubcatch * p_data->SubcatchVars + + p_data->Nnodes * p_data->NodeVars + + linkIndex * p_data->LinkVars + attr); + + // --- re-position the file and read the result + _fseek(p_data->file, offset, SEEK_SET); + fread(&value, RECORDSIZE, 1, p_data->file); + + return value; +} + +float getSystemValue(data_t *p_data, int timeIndex, SMO_systemAttribute attr) { + + F_OFF offset; + float value; + + // --- compute offset into output file + offset = p_data->ResultsPos + timeIndex * p_data->BytesPerPeriod + + 2 * RECORDSIZE; + // offset for system + offset += RECORDSIZE * (p_data->Nsubcatch * p_data->SubcatchVars + + p_data->Nnodes * p_data->NodeVars + + p_data->Nlinks * p_data->LinkVars + attr); + + // --- re-position the file and read the result + _fseek(p_data->file, offset, SEEK_SET); + fread(&value, RECORDSIZE, 1, p_data->file); + + return value; +} + +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 _MSC_VER + 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 _MSC_VER +#define FSEEK64 _fseeki64 +#else +#define FSEEK64 fseeko +#endif + + return FSEEK64(stream, offset, whence); +} + +F_OFF _ftell(FILE *stream) +// +// Purpose: Selects platform ftell() for large file support +// +{ +#ifdef _MSC_VER +#define FTELL64 _ftelli64 +#else +#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)); +} diff --git a/src/run/CMakeLists.txt b/src/run/CMakeLists.txt new file mode 100644 index 000000000..a0937db10 --- /dev/null +++ b/src/run/CMakeLists.txt @@ -0,0 +1,35 @@ +# +# CMakeLists.txt - CMake configuration file for swmm-solver/run +# +# Created: July 11, 2019 +# +# Modified by: Michael E. Tryby +# US EPA ORD/NRMRL +# + + +# Creates the EPANET command line executable +add_executable(runswmm + main.c + ) +target_include_directories(runswmm + PUBLIC + include + ) +target_link_libraries(runswmm + LINK_PUBLIC + swmm5 + ) + + +install(TARGETS runswmm + DESTINATION "${TOOL_DIST}" + ) + + +# copy runswmm to build tree for testing +add_custom_command(TARGET runswmm POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + $ + ${CMAKE_BINARY_DIR}/bin/$/$ + ) diff --git a/src/main.c b/src/run/main.c similarity index 99% rename from src/main.c rename to src/run/main.c index 763b367bc..b4af1f112 100644 --- a/src/main.c +++ b/src/run/main.c @@ -3,7 +3,7 @@ // // Project: EPA SWMM5 // Version: 5.1 -// Date: 11/27/2017 +// Date: 05/10/2018 // Author: L. Rossman // Main stub for the command line version of EPA SWMM 5.1 diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt new file mode 100644 index 000000000..e9453d7e5 --- /dev/null +++ b/src/solver/CMakeLists.txt @@ -0,0 +1,57 @@ +# +# CMakeLists.txt - CMake configuration file for swmm-solver/library +# +# Created: July 11, 2019 +# Modified: Nov 25, 2019 +# +# Author: Michael E. Tryby +# US EPA ORD/CESER +# + + +find_package(OpenMP) + + +# configure file groups +set(SWMM_PUBLIC_HEADERS + include/swmm5.h + ) + +file(GLOB + SWMM_SOURCES + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.c *.h + ) + +add_library(swmm5 + SHARED + ${SWMM_SOURCES} + ) + +target_link_libraries(swmm5 + PUBLIC + $<$>>:m> + $<$:OpenMP::OpenMP_C> + ) + +target_include_directories(swmm5 + PUBLIC + $ + $ + ) + +install(TARGETS swmm5 EXPORT swmm5Targets + RUNTIME DESTINATION "${TOOL_DIST}" + LIBRARY DESTINATION "${TOOL_DIST}" + ARCHIVE DESTINATION "${LIBRARY_DIST}" + FRAMEWORK DESTINATION "${TOOL_DIST}" + ) + +install(FILES ${SWMM_PUBLIC_HEADERS} DESTINATION "${INCLUDE_DIST}") + + +# copy swmm5 to build tree for testing +add_custom_command(TARGET swmm5 POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + $ + ${CMAKE_BINARY_DIR}/bin/$/$ + ) diff --git a/src/Roadmap.txt b/src/solver/Roadmap.txt similarity index 100% rename from src/Roadmap.txt rename to src/solver/Roadmap.txt diff --git a/src/climate.c b/src/solver/climate.c similarity index 99% rename from src/climate.c rename to src/solver/climate.c index f1b3cbfb2..a44c5faae 100644 --- a/src/climate.c +++ b/src/solver/climate.c @@ -8,7 +8,7 @@ // 03/19/15 (Build 5.1.008) // 08/05/15 (Build 5.1.010) // 08/01/16 (Build 5.1.011) -// 11/27/17 (Build 5.1.013) +// 05/10/18 (Build 5.1.013) // Author: L. Rossman // // Climate related functions. diff --git a/src/consts.h b/src/solver/consts.h similarity index 100% rename from src/consts.h rename to src/solver/consts.h diff --git a/src/controls.c b/src/solver/controls.c similarity index 100% rename from src/controls.c rename to src/solver/controls.c diff --git a/src/culvert.c b/src/solver/culvert.c similarity index 99% rename from src/culvert.c rename to src/solver/culvert.c index fc3fe5502..37c7c8d56 100644 --- a/src/culvert.c +++ b/src/solver/culvert.c @@ -4,7 +4,7 @@ // Project: EPA SWMM5 // Version: 5.1 // Date: 03/20/14 (Build 5.1.001) -// 11/27/17 (Build 5.1.013) +// 05/10/18 (Build 5.1.013) // Author: L. Rossman // // Culvert equations for SWMM5 diff --git a/src/datetime.c b/src/solver/datetime.c similarity index 100% rename from src/datetime.c rename to src/solver/datetime.c diff --git a/src/datetime.h b/src/solver/datetime.h similarity index 100% rename from src/datetime.h rename to src/solver/datetime.h diff --git a/src/dwflow.c b/src/solver/dwflow.c similarity index 100% rename from src/dwflow.c rename to src/solver/dwflow.c diff --git a/src/dynwave.c b/src/solver/dynwave.c similarity index 100% rename from src/dynwave.c rename to src/solver/dynwave.c diff --git a/src/enums.h b/src/solver/enums.h similarity index 94% rename from src/enums.h rename to src/solver/enums.h index 9c5119394..e76f27519 100644 --- a/src/enums.h +++ b/src/solver/enums.h @@ -9,7 +9,7 @@ // 03/19/15 (Build 5.1.008) // 08/05/15 (Build 5.1.010) // 08/01/16 (Build 5.1.011) -// 11/27/17 (Build 5.1.013) +// 05/10/18 (Build 5.1.013) // Author: L. Rossman // // Enumerated variables @@ -34,7 +34,7 @@ // - s_EVENT added to InputSectionType enumeration. // // Build 5.1.013: -// - CROWN_CUTOFF and RULE_STEP options added. +// - SURCHARGE_METHOD and RULE_STEP options added. // - WEIR_CURVE added as a curve type. // //----------------------------------------------------------------------------- @@ -362,6 +362,11 @@ enum CompatibilityType { PARTIAL_DAMPING, // partial damping FULL_DAMPING}; // full damping +//// Added to release 5.1.013. //// //(5.1.013) + enum SurchargeMethodType { + EXTRAN, // original EXTRAN method + SLOT}; // Preissmann slot method + enum InflowType { EXTERNAL_INFLOW, // user-supplied external inflow DRY_WEATHER_INFLOW, // user-supplied dry weather inflow @@ -451,20 +456,20 @@ enum CompatibilityType { s_ADJUST, s_EVENT}; enum InputOptionType { - FLOW_UNITS, INFIL_MODEL, ROUTE_MODEL, - START_DATE, START_TIME, END_DATE, - END_TIME, REPORT_START_DATE, REPORT_START_TIME, - SWEEP_START, SWEEP_END, START_DRY_DAYS, - WET_STEP, DRY_STEP, ROUTE_STEP, RULE_STEP, //(5.1.013) - REPORT_STEP, ALLOW_PONDING, INERT_DAMPING, - SLOPE_WEIGHTING, VARIABLE_STEP, NORMAL_FLOW_LTD, - LENGTHENING_STEP, MIN_SURFAREA, COMPATIBILITY, - SKIP_STEADY_STATE, TEMPDIR, IGNORE_RAINFALL, - FORCE_MAIN_EQN, LINK_OFFSETS, MIN_SLOPE, - IGNORE_SNOWMELT, IGNORE_GWATER, IGNORE_ROUTING, - IGNORE_QUALITY, MAX_TRIALS, HEAD_TOL, - SYS_FLOW_TOL, LAT_FLOW_TOL, IGNORE_RDII, - MIN_ROUTE_STEP, NUM_THREADS, CROWN_CUTOFF}; //(5.1.013) + FLOW_UNITS, INFIL_MODEL, ROUTE_MODEL, + START_DATE, START_TIME, END_DATE, + END_TIME, REPORT_START_DATE, REPORT_START_TIME, + SWEEP_START, SWEEP_END, START_DRY_DAYS, + WET_STEP, DRY_STEP, ROUTE_STEP, RULE_STEP, //(5.1.013) + REPORT_STEP, ALLOW_PONDING, INERT_DAMPING, + SLOPE_WEIGHTING, VARIABLE_STEP, NORMAL_FLOW_LTD, + LENGTHENING_STEP, MIN_SURFAREA, COMPATIBILITY, + SKIP_STEADY_STATE, TEMPDIR, IGNORE_RAINFALL, + FORCE_MAIN_EQN, LINK_OFFSETS, MIN_SLOPE, + IGNORE_SNOWMELT, IGNORE_GWATER, IGNORE_ROUTING, + IGNORE_QUALITY, MAX_TRIALS, HEAD_TOL, + SYS_FLOW_TOL, LAT_FLOW_TOL, IGNORE_RDII, + MIN_ROUTE_STEP, NUM_THREADS, SURCHARGE_METHOD}; //(5.1.013) enum NoYesType { NO, diff --git a/src/error.c b/src/solver/error.c similarity index 100% rename from src/error.c rename to src/solver/error.c diff --git a/src/error.h b/src/solver/error.h similarity index 100% rename from src/error.h rename to src/solver/error.h diff --git a/src/exfil.c b/src/solver/exfil.c similarity index 100% rename from src/exfil.c rename to src/solver/exfil.c diff --git a/src/exfil.h b/src/solver/exfil.h similarity index 100% rename from src/exfil.h rename to src/solver/exfil.h diff --git a/src/findroot.c b/src/solver/findroot.c similarity index 100% rename from src/findroot.c rename to src/solver/findroot.c diff --git a/src/findroot.h b/src/solver/findroot.h similarity index 100% rename from src/findroot.h rename to src/solver/findroot.h diff --git a/src/flowrout.c b/src/solver/flowrout.c similarity index 100% rename from src/flowrout.c rename to src/solver/flowrout.c diff --git a/src/forcmain.c b/src/solver/forcmain.c similarity index 100% rename from src/forcmain.c rename to src/solver/forcmain.c diff --git a/src/funcs.h b/src/solver/funcs.h similarity index 100% rename from src/funcs.h rename to src/solver/funcs.h diff --git a/src/gage.c b/src/solver/gage.c similarity index 100% rename from src/gage.c rename to src/solver/gage.c diff --git a/src/globals.h b/src/solver/globals.h similarity index 98% rename from src/globals.h rename to src/solver/globals.h index d63abe76a..1d7c9207e 100644 --- a/src/globals.h +++ b/src/solver/globals.h @@ -9,7 +9,7 @@ // 03/19/15 (Build 5.1.008) // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) -// 11/27/17 (Build 5.1.013) +// 05/10/18 (Build 5.1.013) // Author: L. Rossman // // Global Variables @@ -74,6 +74,7 @@ EXTERN int RouteModel, // Flow routing method ForceMainEqn, // Flow equation for force mains LinkOffsets, // Link offset convention + SurchargeMethod, // EXTRAN or SLOT method //(5.1.013) AllowPonding, // Allow water to pond at nodes InertDamping, // Degree of inertial damping NormalFlowLtd, // Normal flow limited diff --git a/src/gwater.c b/src/solver/gwater.c similarity index 100% rename from src/gwater.c rename to src/solver/gwater.c diff --git a/src/hash.c b/src/solver/hash.c similarity index 100% rename from src/hash.c rename to src/solver/hash.c diff --git a/src/hash.h b/src/solver/hash.h similarity index 100% rename from src/hash.h rename to src/solver/hash.h diff --git a/src/headers.h b/src/solver/headers.h similarity index 100% rename from src/headers.h rename to src/solver/headers.h diff --git a/src/hotstart.c b/src/solver/hotstart.c similarity index 100% rename from src/hotstart.c rename to src/solver/hotstart.c diff --git a/src/iface.c b/src/solver/iface.c similarity index 100% rename from src/iface.c rename to src/solver/iface.c diff --git a/src/swmm5.h b/src/solver/include/swmm5.h similarity index 100% rename from src/swmm5.h rename to src/solver/include/swmm5.h diff --git a/src/infil.c b/src/solver/infil.c similarity index 99% rename from src/infil.c rename to src/solver/infil.c index f7ae88ad2..19bc2a253 100644 --- a/src/infil.c +++ b/src/solver/infil.c @@ -8,7 +8,7 @@ // 03/19/15 (Build 5.1.008) // 08/05/15 (Build 5.1.010) // 08/01/16 (Build 5.1.011) -// 11/27/17 (Build 5.1.013) +// 05/10/17 (Build 5.1.013) // Author: L. Rossman // // Infiltration functions. diff --git a/src/infil.h b/src/solver/infil.h similarity index 99% rename from src/infil.h rename to src/solver/infil.h index 61adc28d3..cf208e234 100644 --- a/src/infil.h +++ b/src/solver/infil.h @@ -6,7 +6,7 @@ // Date: 03/20/14 (Build 5.1.001) // 09/15/14 (Build 5.1.007) // 08/05/15 (Build 5.1.010) -// 11/27/17 (Build 5.1.013) +// 05/10/18 (Build 5.1.013) // Author: L. Rossman (US EPA) // // Public interface for infiltration functions. diff --git a/src/inflow.c b/src/solver/inflow.c similarity index 100% rename from src/inflow.c rename to src/solver/inflow.c diff --git a/src/input.c b/src/solver/input.c similarity index 100% rename from src/input.c rename to src/solver/input.c diff --git a/src/inputrpt.c b/src/solver/inputrpt.c similarity index 100% rename from src/inputrpt.c rename to src/solver/inputrpt.c diff --git a/src/keywords.c b/src/solver/keywords.c similarity index 96% rename from src/keywords.c rename to src/solver/keywords.c index e6abd812d..7ae5b2d5a 100644 --- a/src/keywords.c +++ b/src/solver/keywords.c @@ -9,7 +9,7 @@ // 03/19/15 (Build 5.1.008) // 08/05/15 (Build 5.1.010) // 08/01/16 (Build 5.1.011) -// 11/27/17 (Build 5.1.013) +// 05/10/18 (Build 5.1.013) // Author: L. Rossman // // Exportable keyword dictionary @@ -35,7 +35,7 @@ // - New section keyword for [EVENTS] added. // // Build 5.1.013: -// - New option keywords w_CROWN_CUTOFF, w_RULE_STEP, w_AVERAGES +// - New option keywords w_SURCHARGE_METHOD, w_RULE_STEP, w_AVERAGES // and w_WEIR added. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -80,7 +80,7 @@ char* OptionWords[] = { w_FLOW_UNITS, w_INFIL_MODEL, w_SWEEP_END, w_START_DRY_DAYS, w_WET_STEP, w_DRY_STEP, w_ROUTE_STEP, w_RULE_STEP, //(5.1.013) - w_REPORT_STEP, + w_REPORT_STEP, w_ALLOW_PONDING, w_INERT_DAMPING, w_SLOPE_WEIGHTING, w_VARIABLE_STEP, w_NORMAL_FLOW_LTD, w_LENGTHENING_STEP, @@ -93,7 +93,7 @@ char* OptionWords[] = { w_FLOW_UNITS, w_INFIL_MODEL, w_MAX_TRIALS, w_HEAD_TOL, w_SYS_FLOW_TOL, w_LAT_FLOW_TOL, w_IGNORE_RDII, w_MIN_ROUTE_STEP, - w_NUM_THREADS, w_CROWN_CUTOFF, //(5.1.013) + w_NUM_THREADS, w_SURCHARGE_METHOD, //(5.1.013) NULL }; char* OrificeTypeWords[] = { w_SIDE, w_BOTTOM, NULL}; char* OutfallTypeWords[] = { w_FREE, w_NORMAL, w_FIXED, w_TIDAL, @@ -142,6 +142,7 @@ char* SectWords[] = { ws_TITLE, ws_OPTION, ws_ADJUST, ws_EVENT, NULL}; char* SnowmeltWords[] = { w_PLOWABLE, w_IMPERV, w_PERV, w_REMOVAL, NULL}; +char* SurchargeWords[] = { w_EXTRAN, w_SLOT, NULL}; //(5.1.013) char* TempKeyWords[] = { w_TIMESERIES, w_FILE, w_WINDSPEED, w_SNOWMELT, w_ADC, NULL}; char* TransectKeyWords[] = { w_NC, w_X1, w_GR, NULL}; diff --git a/src/keywords.h b/src/solver/keywords.h similarity index 90% rename from src/keywords.h rename to src/solver/keywords.h index 0caee2280..3724a0c89 100644 --- a/src/keywords.h +++ b/src/solver/keywords.h @@ -5,12 +5,16 @@ // Version: 5.1 // Date: 03/19/14 (Build 5.1.000) // 03/19/15 (Build 5.1.008) +// 05/10/18 (Build 5.1.013) // Author: L. Rossman // // Exportable keyword dictionary // // Build 5.1.008: // - Keyword arrays listed in alphabetical order. +// +// Build 5.1.013: +// - New keyword array defined for surcharge method. //----------------------------------------------------------------------------- extern char* BuildupTypeWords[]; @@ -51,6 +55,7 @@ extern char* RouteModelWords[]; extern char* RuleKeyWords[]; extern char* SectWords[]; extern char* SnowmeltWords[]; +extern char* SurchargeWords[]; //(5.1.013) extern char* TempKeyWords[]; extern char* TransectKeyWords[]; extern char* TreatTypeWords[]; diff --git a/src/kinwave.c b/src/solver/kinwave.c similarity index 100% rename from src/kinwave.c rename to src/solver/kinwave.c diff --git a/src/landuse.c b/src/solver/landuse.c similarity index 100% rename from src/landuse.c rename to src/solver/landuse.c diff --git a/src/lid.c b/src/solver/lid.c similarity index 100% rename from src/lid.c rename to src/solver/lid.c diff --git a/src/lid.h b/src/solver/lid.h similarity index 99% rename from src/lid.h rename to src/solver/lid.h index 6dcf829a2..93ee2b779 100644 --- a/src/lid.h +++ b/src/solver/lid.h @@ -7,7 +7,7 @@ // 03/19/15 (Build 5.1.008) // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) -// 11/27/17 (Build 5.1.013) +// 05/10/18 (Build 5.1.013) // Author: L. Rossman (US EPA) // // Public interface for LID functions. diff --git a/src/lidproc.c b/src/solver/lidproc.c similarity index 100% rename from src/lidproc.c rename to src/solver/lidproc.c diff --git a/src/link.c b/src/solver/link.c similarity index 100% rename from src/link.c rename to src/solver/link.c diff --git a/src/macros.h b/src/solver/macros.h similarity index 100% rename from src/macros.h rename to src/solver/macros.h diff --git a/src/massbal.c b/src/solver/massbal.c similarity index 99% rename from src/massbal.c rename to src/solver/massbal.c index 6b6abee18..c5a02903d 100644 --- a/src/massbal.c +++ b/src/solver/massbal.c @@ -9,7 +9,7 @@ // 08/05/15 (Build 5.1.010) // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) -// 11/27/17 (Build 5.1.013) +// 05/10/18 (Build 5.1.013) // Author: L. Rossman (EPA) // M. Tryby (EPA) // diff --git a/src/mathexpr.c b/src/solver/mathexpr.c similarity index 100% rename from src/mathexpr.c rename to src/solver/mathexpr.c diff --git a/src/mathexpr.h b/src/solver/mathexpr.h similarity index 100% rename from src/mathexpr.h rename to src/solver/mathexpr.h diff --git a/src/mempool.c b/src/solver/mempool.c similarity index 100% rename from src/mempool.c rename to src/solver/mempool.c diff --git a/src/mempool.h b/src/solver/mempool.h similarity index 100% rename from src/mempool.h rename to src/solver/mempool.h diff --git a/src/node.c b/src/solver/node.c similarity index 100% rename from src/node.c rename to src/solver/node.c diff --git a/src/objects.h b/src/solver/objects.h similarity index 99% rename from src/objects.h rename to src/solver/objects.h index 0c652853b..9fd738067 100644 --- a/src/objects.h +++ b/src/solver/objects.h @@ -8,7 +8,7 @@ // 03/19/15 (Build 5.1.008) // 08/05/15 (Build 5.1.010) // 08/01/16 (Build 5.1.011) -// 11/27/17 (Build 5.1.013) +// 05/10/18 (Build 5.1.013) // // Author: L. Rossman (EPA) // M. Tryby (EPA) @@ -50,7 +50,7 @@ // - Added definition of a hydraulic event time period (TEvent). // // Build 5.1.013: -// - Member averages was added to the TRptFlags structure. +// - New member 'averages' added to the TRptFlags structure. // - Adjustment patterns added to TSubcatch structure. // - Members impervRunoff and pervRunoff added to TSubcatchStats structure. // - Member cdCurve (weir coeff. curve) added to TWeir structure. @@ -945,7 +945,6 @@ typedef struct double maxDepth; DateTime maxDepthDate; double maxRptDepth; - double maxSurDepth; //(5.1.013) double volFlooded; double timeFlooded; double timeSurcharged; diff --git a/src/odesolve.c b/src/solver/odesolve.c similarity index 100% rename from src/odesolve.c rename to src/solver/odesolve.c diff --git a/src/odesolve.h b/src/solver/odesolve.h similarity index 100% rename from src/odesolve.h rename to src/solver/odesolve.h diff --git a/src/output.c b/src/solver/output.c similarity index 100% rename from src/output.c rename to src/solver/output.c diff --git a/src/project.c b/src/solver/project.c similarity index 96% rename from src/project.c rename to src/solver/project.c index dc3ecd46c..0fae207d5 100644 --- a/src/project.c +++ b/src/solver/project.c @@ -10,7 +10,7 @@ // 04/30/15 (Build 5.1.009) // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) -// 11/27/17 (Build 5.1.013) +// 05/10/18 (Build 5.1.013) // Author: L. Rossman // // Project management functions. @@ -52,7 +52,8 @@ // - omp_get_num_threads function protected against lack of compiler // support for OpenMP. // - Rain gage validation now performed after subcatchment validation. -// - Support added for CrownCutoff and RuleStep analysis options. +// - More robust parsing of MinSurfarea option provided. +// - Support added for new RuleStep analysis option. // //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -259,9 +260,13 @@ void project_validate() if ( RouteModel == DW ) dynwave_validate(); // --- adjust number of parallel threads to be used //(5.1.013) - if ( NumThreads == 0 ) NumThreads = omp_get_num_threads(); // - else NumThreads = MIN(NumThreads, omp_get_num_threads()); // - if ( Nobjects[LINK] < 4 * NumThreads ) NumThreads = 1; // +#pragma omp parallel //(5.1.008) +{ + if ( NumThreads == 0 ) NumThreads = omp_get_num_threads(); //(5.1.008) + else NumThreads = MIN(NumThreads, omp_get_num_threads()); //(5.1.008) +} + if ( Nobjects[LINK] < 4 * NumThreads ) NumThreads = 1; //(5.1.008) + } //============================================================================= @@ -527,7 +532,7 @@ int project_readOption(char* s1, char* s2) case WET_STEP: case DRY_STEP: case REPORT_STEP: - case RULE_STEP: //(5.1.013) + case RULE_STEP: //(5.1.013) if ( !datetime_strToTime(s2, &aTime) ) { return error_setInpError(ERR_DATETIME, s2); @@ -536,11 +541,11 @@ int project_readOption(char* s1, char* s2) h += 24*(int)aTime; s = s + 60*m + 3600*h; - // --- RuleStep allowed to be 0 while other time steps must be > 0 //(5.1.013) - if (k == RULE_STEP) // - { // - if (s < 0) return error_setInpError(ERR_NUMBER, s2); // - } // + // --- RuleStep allowed to be 0 while other time steps must be > 0 //(5.1.013) + if (k == RULE_STEP) // + { // + if (s < 0) return error_setInpError(ERR_NUMBER, s2); // + } // else if ( s <= 0 ) return error_setInpError(ERR_NUMBER, s2); // switch ( k ) @@ -548,7 +553,7 @@ int project_readOption(char* s1, char* s2) case WET_STEP: WetStep = s; break; case DRY_STEP: DryStep = s; break; case REPORT_STEP: ReportStep = s; break; - case RULE_STEP: RuleStep = s; break; //(5.1.013) + case RULE_STEP: RuleStep = s; break; //(5.1.013) } break; @@ -665,7 +670,10 @@ int project_readOption(char* s1, char* s2) // --- minimum surface area (ft2 or sq. meters) associated with nodes // under dynamic wave flow routing case MIN_SURFAREA: - MinSurfArea = atof(s2); + if (!getDouble(s2, &MinSurfArea)) //(5.1.013) + return error_setInpError(ERR_NUMBER, s2); //(5.1.013) + if (MinSurfArea < 0.0) //(5.1.013) + return error_setInpError(ERR_NUMBER, s2); //(5.1.013) break; // --- minimum conduit slope (%) @@ -710,17 +718,12 @@ int project_readOption(char* s1, char* s2) LatFlowTol /= 100.0; break; - // --- fraction of full pipe depth used to compute a lower //(5.1.013) - // limit on surface area for higher flow depths // - case CROWN_CUTOFF: // - if (!getDouble(s2, &CrownCutoff)) // - { // - return error_setInpError(ERR_NUMBER, s2); // - } // - CrownCutoff /= 100.0; // - if (CrownCutoff < 0.96) CrownCutoff = 0.96; // - if (CrownCutoff > 1.0) CrownCutoff = 1.0; // - break; // + // --- method used for surcharging in dynamic wave flow routing //(5.1.013) + case SURCHARGE_METHOD: + m = findmatch(s2, SurchargeWords); + if (m < 0) return error_setInpError(ERR_KEYWORD, s2); + SurchargeMethod = m; + break; case TEMPDIR: // Temporary Directory sstrncpy(TempDir, s2, MAXFNAME); @@ -805,6 +808,8 @@ void setDefaults() FlowUnits = CFS; // CFS flow units InfilModel = HORTON; // Horton infiltration method RouteModel = KW; // Kin. wave flow routing method + SurchargeMethod = EXTRAN; // Use EXTRAN method for surcharging //(5.1.013) + CrownCutoff = 0.96; //(5.1.013) AllowPonding = FALSE; // No ponding at nodes InertDamping = SOME; // Partial inertial damping NormalFlowLtd = BOTH; // Default normal flow limitation @@ -832,7 +837,6 @@ void setDefaults() HeadTol = 0.0; // Force use of default head tolerance SysFlowTol = 0.05; // System flow tolerance for steady state LatFlowTol = 0.05; // Lateral flow tolerance for steady state - CrownCutoff = 0.96; // Fractional pipe crown cutoff //(5.1.013) NumThreads = 0; // Number of parallel threads to use NumEvents = 0; // Number of detailed routing events diff --git a/src/qualrout.c b/src/solver/qualrout.c similarity index 100% rename from src/qualrout.c rename to src/solver/qualrout.c diff --git a/src/rain.c b/src/solver/rain.c similarity index 100% rename from src/rain.c rename to src/solver/rain.c diff --git a/src/rdii.c b/src/solver/rdii.c similarity index 100% rename from src/rdii.c rename to src/solver/rdii.c diff --git a/src/report.c b/src/solver/report.c similarity index 100% rename from src/report.c rename to src/solver/report.c diff --git a/src/roadway.c b/src/solver/roadway.c similarity index 100% rename from src/roadway.c rename to src/solver/roadway.c diff --git a/src/routing.c b/src/solver/routing.c similarity index 100% rename from src/routing.c rename to src/solver/routing.c diff --git a/src/runoff.c b/src/solver/runoff.c similarity index 100% rename from src/runoff.c rename to src/solver/runoff.c diff --git a/src/shape.c b/src/solver/shape.c similarity index 100% rename from src/shape.c rename to src/solver/shape.c diff --git a/src/snow.c b/src/solver/snow.c similarity index 100% rename from src/snow.c rename to src/solver/snow.c diff --git a/src/stats.c b/src/solver/stats.c similarity index 96% rename from src/stats.c rename to src/solver/stats.c index 52538e079..7d6cb8b90 100644 --- a/src/stats.c +++ b/src/solver/stats.c @@ -8,7 +8,7 @@ // 03/19/15 (Build 5.1.008) // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) -// 11/27/17 (Build 5.1.013) +// 05/10/18 (Build 5.1.013) // Author: L. Rossman (EPA) // R. Dickinson (CDM) // @@ -32,11 +32,10 @@ // - Check for full conduit flow now accounts for number of barrels. // // Build 5.1.013: -// - No need to include omp.h. +// - Include omp.h protected against lack of compiler support for OpenMP. // - Statistics on impervious and pervious runoff totals added. // - Storage nodes with a non-zero surcharge depth (e.g. enclosed tanks) // can now be classified as being surcharged. -// - Node surcharge statistics modified to accommodate variable crown elev. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -45,6 +44,9 @@ #include #include "headers.h" #include "swmm5.h" +#if defined(_OPENMP) //(5.1.013) +#include +#endif //----------------------------------------------------------------------------- // Shared variables @@ -146,7 +148,6 @@ int stats_open() Subcatch[j].groundwater->stats.evap = 0.0; Subcatch[j].groundwater->stats.maxFlow = 0.0; } -//// } // --- allocate memory for node & link stats @@ -168,7 +169,6 @@ int stats_open() NodeStats[j].maxDepth = 0.0; NodeStats[j].maxDepthDate = StartDateTime; NodeStats[j].maxRptDepth = 0.0; - NodeStats[j].maxSurDepth = 0.0; //(5.1.013) NodeStats[j].volFlooded = 0.0; NodeStats[j].timeFlooded = 0.0; NodeStats[j].timeSurcharged = 0.0; @@ -495,7 +495,7 @@ void stats_updateNodeStats(int j, double tStep, DateTime aDate) int k, p; double newVolume = Node[j].newVolume; double newDepth = Node[j].newDepth; - double yCrown = Node[j].crownElev - Node[j].invertElev; //(5.1.013) + double yCrown = Node[j].crownElev - Node[j].invertElev; int canPond = (AllowPonding && Node[j].pondedArea > 0.0); // --- update depth statistics @@ -518,16 +518,14 @@ void stats_updateNodeStats(int j, double tStep, DateTime aDate) (newVolume - Node[j].fullVolume)); } - // --- for dynamic wave routing, classify a node as surcharged //(5.1.013) - // if its water level exceeds its crown elev. // - if (RouteModel == DW) // - { // - if ((Node[j].type != STORAGE || Node[j].surDepth > 0.0) && // - newDepth + FUDGE >= yCrown) // - { // - NodeStats[j].timeSurcharged += tStep; // - NodeStats[j].maxSurDepth = MAX(NodeStats[j].maxSurDepth, // - yCrown); // + // --- for dynamic wave routing, classify a node as //(5.1.013) + // surcharged if its water level exceeds its crown elev. + if (RouteModel == DW) //(5.1.013) + { + if ((Node[j].type != STORAGE || Node[j].surDepth > 0.0) && //(5.1.013) + newDepth + Node[j].invertElev + FUDGE >= Node[j].crownElev) + { + NodeStats[j].timeSurcharged += tStep; } } } @@ -564,7 +562,7 @@ void stats_updateNodeStats(int j, double tStep, DateTime aDate) for (p=0; p 0.0) + if (Link[j].type == PUMP && Link[j].qFull > 0.0) { fprintf(Frpt.file, " "); fprintf(Frpt.file, " %6.2f", @@ -746,31 +742,29 @@ void writeLinkFlows() } // --- stop printing for dummy conduits - if ( Link[j].xsect.type == DUMMY ) continue; + if (Link[j].xsect.type == DUMMY) continue; // --- stop printing for outlet links (since they don't have xsections) - if ( Link[j].type == OUTLET ) continue; + if (Link[j].type == OUTLET) continue; // --- print max velocity & max/full flow for conduits - if ( Link[j].type == CONDUIT ) + if (Link[j].type == CONDUIT) { v = LinkStats[j].maxVeloc*UCF(LENGTH); - if ( v > 50.0 ) fprintf(Frpt.file, " >50.00"); + if (v > 50.0) fprintf(Frpt.file, " >50.00"); else fprintf(Frpt.file, " %7.2f", v); fprintf(Frpt.file, " %6.2f", LinkStats[j].maxFlow / Link[j].qFull / - (double)Conduit[k].barrels); + (double)Conduit[k].barrels); } else fprintf(Frpt.file, " "); // --- print max/full depth fullDepth = Link[j].xsect.yFull; - if ( Link[j].type == ORIFICE && - Orifice[k].type == BOTTOM_ORIFICE ) fullDepth = 0.0; - if ( fullDepth > 0.0 ) + if (Link[j].type == ORIFICE && + Orifice[k].type == BOTTOM_ORIFICE) fullDepth = 0.0; + if (fullDepth > 0.0) { fprintf(Frpt.file, " %6.2f", LinkStats[j].maxDepth / fullDepth); - aMax = xsect_getAofY(&Link[j].xsect, LinkStats[j].maxDepth); //(5.1.013) - fprintf(Frpt.file, " %6.2f", aMax / Link[j].xsect.aFull); // } else fprintf(Frpt.file, " "); } @@ -835,7 +829,7 @@ void writeLinkSurcharge() for ( j = 0; j < Nobjects[LINK]; j++ ) { if ( Link[j].type != CONDUIT || - Link[j].xsect.type == DUMMY ) continue; + Link[j].xsect.type == DUMMY ) continue; t[0] = LinkStats[j].timeSurcharged / 3600.0; t[1] = LinkStats[j].timeFullUpstream / 3600.0; t[2] = LinkStats[j].timeFullDnstream / 3600.0; diff --git a/src/subcatch.c b/src/solver/subcatch.c similarity index 99% rename from src/subcatch.c rename to src/solver/subcatch.c index 2383a522d..175d5d41c 100644 --- a/src/subcatch.c +++ b/src/solver/subcatch.c @@ -10,7 +10,7 @@ // 08/05/15 (Build 5.1.010) // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) -// 11/27/17 (Build 5.1.013) +// 05/10/18 (Build 5.1.013) // Author: L. Rossman // // Subcatchment runoff functions. diff --git a/src/surfqual.c b/src/solver/surfqual.c similarity index 100% rename from src/surfqual.c rename to src/solver/surfqual.c diff --git a/src/swmm5.c b/src/solver/swmm5.c similarity index 98% rename from src/swmm5.c rename to src/solver/swmm5.c index 8eefa25cd..2917cb5e3 100644 --- a/src/swmm5.c +++ b/src/solver/swmm5.c @@ -7,7 +7,7 @@ // 03/19/15 (Build 5.1.008) // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) -// 11/27/17 (Build 5.1.013) +// 05/10/18 (Build 5.1.013) // Author: L. Rossman // // This is the main module of the computational engine for Version 5 of @@ -55,7 +55,7 @@ #undef EXH // indicates if exception handling included #ifdef WINDOWS #ifdef _MSC_VER - #define EXH + #define EXH #endif #endif @@ -211,7 +211,7 @@ int DLLEXPORT swmm_run(char* f1, char* f2, char* f3) // --- close the system swmm_close(); - return error_getCode(ErrorCode); //(5.1.011) + return error_getCode(ErrorCode); } //============================================================================= @@ -225,8 +225,10 @@ int DLLEXPORT swmm_open(char* f1, char* f2, char* f3) // Purpose: opens a SWMM project. // { - -// --- call to _fpreset() removed //(5.1.013) +// --- to be safe, reset the state of the floating point unit //(5.1.013) +#ifdef WINDOWS //(5.1.013) + _fpreset(); +#endif #ifdef EXH // --- begin exception handling here @@ -495,7 +497,8 @@ void execRouting() // --- route flows & pollutants through drainage system // (while updating NewRoutingTime) if ( DoRouting ) routing_execute(RouteModel, routingStep); - else NewRoutingTime = nextRoutingTime; + else + NewRoutingTime = nextRoutingTime; } #ifdef EXH @@ -629,7 +632,6 @@ int DLLEXPORT swmm_getVersion(void) // y = minor version number, and zzz = build number. // // NOTE: Each New Release should be updated in consts.h -// THIS FUNCTION WILL EVENTUALLY BE DEPRECATED { return VERSION; } @@ -875,7 +877,7 @@ int xfilter(int xc, char* module, double elapsedTime, long step) rc = EXCEPTION_CONTINUE_EXECUTION; break; default: - sprintf(msg, "\n Exception %d", xc); + sprintf(msg, "\n Exception %d ", xc); rc = EXCEPTION_EXECUTE_HANDLER; } hour = (long)(elapsedTime / 1000.0 / 3600.0); diff --git a/src/swmm5.def b/src/solver/swmm5.def similarity index 100% rename from src/swmm5.def rename to src/solver/swmm5.def diff --git a/src/table.c b/src/solver/table.c similarity index 100% rename from src/table.c rename to src/solver/table.c diff --git a/src/text.h b/src/solver/text.h similarity index 95% rename from src/text.h rename to src/solver/text.h index faa1c9cc3..53cc0817a 100644 --- a/src/text.h +++ b/src/solver/text.h @@ -14,7 +14,7 @@ // 08/05/15 (Build 5.1.010) // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) -// 11/27/17 (Build 5.1.013) +// 05/10/18 (Build 5.1.013) // Author: L. Rossman // // Text strings @@ -29,7 +29,7 @@ #define FMT06 "\n o Retrieving project data" #define FMT07 "\n o Writing output report" #define FMT08 \ - "\n EPA STORM WATER MANAGEMENT MODEL - VERSION 5.1 (Build 5.1.013)" //(5.1.013) + "\n EPA STORM WATER MANAGEMENT MODEL - VERSION 5.1 (Build 5.1.014)" //(5.1.014) #define FMT09 \ "\n --------------------------------------------------------------" #define FMT10 "\n" @@ -39,15 +39,15 @@ #define FMT14 "\n Cannot open output file " #define FMT15 "\n Cannot open temporary output file" #define FMT16 "\n ERROR %d detected. Execution halted." -#define FMT17 "at line %d of input file:" -#define FMT18 "at line %d of %s] section:" +#define FMT17 "at line %ld of input file:" //(5.1.013) +#define FMT18 "at line %ld of %s] section:" //(5.1.013) #define FMT19 "\n Maximum error count exceeded." #define FMT20 "\n\n Analysis begun on: %s" #define FMT20a " Analysis ended on: %s" #define FMT21 " Total elapsed time: " // Warning messages -#define WARN01 "WARNING 01: wet weather time step reduced to recording interval for Rain Gage" +#define WARN01 "WARNING 01: wet weather time step reduced to recording interval for Rain Gage" #define WARN02 "WARNING 02: maximum depth increased for Node" #define WARN03 "WARNING 03: negative offset ignored for Link" #define WARN04 "WARNING 04: minimum elevation drop used for Conduit" @@ -56,9 +56,10 @@ #define WARN07 "WARNING 07: routing time step reduced to the wet weather time step" #define WARN08 "WARNING 08: elevation drop exceeds length for Conduit" #define WARN09 "WARNING 09: time series interval greater than recording interval for Rain Gage" -//#define WARN10 "WARNING 10: crest elevation is below downstream invert for regulator Link" -#define WARN10 \ -"WARNING 10: crest elevation raised to downstream invert for regulator Link" +#define WARN10a \ +"WARNING 10: crest elevation is below downstream invert for regulator Link" //(5.1.013) +#define WARN10b \ +"WARNING 10: crest elevation raised to downstream invert for regulator Link" //(5.1.013) #define WARN11 "WARNING 11: non-matching attributes in Control Rule" // Analysis Option Keywords @@ -104,7 +105,7 @@ #define w_IGNORE_RDII "IGNORE_RDII" #define w_MIN_ROUTE_STEP "MINIMUM_STEP" #define w_NUM_THREADS "THREADS" -#define w_CROWN_CUTOFF "CROWN_CUTOFF" //(5.1.013) +#define w_SURCHARGE_METHOD "SURCHARGE_METHOD" //(5.1.013) // Flow Units #define w_CFS "CFS" @@ -125,6 +126,10 @@ #define w_XKINWAVE "XKINWAVE" #define w_DYNWAVE "DYNWAVE" +// Surcharge Methods //(5.1.013) +#define w_EXTRAN "EXTRAN" +#define w_SLOT "SLOT" + // Infiltration Methods #define w_HORTON "HORTON" #define w_MOD_HORTON "MODIFIED_HORTON" @@ -234,7 +239,7 @@ #define w_CUSTOM "CUSTOM" #define w_FORCE_MAIN "FORCE_MAIN" #define w_H_W "H-W" -#define w_D_W "D-W" +#define w_D_W "D-W" // Link Offset Options #define w_ELEVATION "ELEVATION" @@ -251,7 +256,7 @@ // Flow Volume Units #define w_MGAL "10^6 gal" #define w_MLTRS "10^6 ltr" -#define w_GAL "gal" +#define w_GAL "gal" #define w_LTR "ltr" // Ponded Depth Units diff --git a/src/toposort.c b/src/solver/toposort.c similarity index 100% rename from src/toposort.c rename to src/solver/toposort.c diff --git a/src/transect.c b/src/solver/transect.c similarity index 100% rename from src/transect.c rename to src/solver/transect.c diff --git a/src/treatmnt.c b/src/solver/treatmnt.c similarity index 100% rename from src/treatmnt.c rename to src/solver/treatmnt.c diff --git a/src/xsect.c b/src/solver/xsect.c similarity index 99% rename from src/xsect.c rename to src/solver/xsect.c index 3eeff4ef2..9a4cc6dab 100644 --- a/src/xsect.c +++ b/src/solver/xsect.c @@ -5,7 +5,7 @@ // Version: 5.1 // Date: 03/20/14 (Build 5.1.001) // 03/14/17 (Build 5.1.012) -// 11/27/17 (Build 5.1.013) +// 05/10/18 (Build 5.1.013) // Author: L. Rossman (EPA) // M. Tryby (EPA) // diff --git a/src/xsect.dat b/src/solver/xsect.dat similarity index 100% rename from src/xsect.dat rename to src/solver/xsect.dat diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 000000000..4e5a06dbf --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,29 @@ +# +# CMakeLists.txt - CMake configuration file for epanet/tests +# + + +#Prep ourselves for compiling with boost +if(WIN32) + set(Boost_USE_STATIC_LIBS ON) +else() + set(Boost_USE_STATIC_LIBS OFF) + add_definitions(-DBOOST_ALL_DYN_LINK) +endif() + +find_package(Boost COMPONENTS unit_test_framework system thread filesystem) +include_directories (${Boost_INCLUDE_DIRS}) + + +add_subdirectory(outfile) + + +# Setting up tests to run from build tree +set(TEST_BIN_DIRECTORY "${CMAKE_BINARY_DIR}/bin/$") + + +# ctest doesn't like tests added in subdirectories so adding them here +add_test(NAME test_output + COMMAND "${TEST_BIN_DIRECTORY}/test_output" + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/outfile/data + ) diff --git a/tests/Unit_Testing.md b/tests/Unit_Testing.md new file mode 100644 index 000000000..65869798d --- /dev/null +++ b/tests/Unit_Testing.md @@ -0,0 +1,35 @@ + + + +## Unit Testing SWMM locally on Windows + +### Dependencies + +Before the project can be built and tested the required dependencies must be installed. + +**Summary of Build Dependencies: Windows** + + - Build + - Build Tools for Visual Studio 2017 + - CMake 3.13 + + - Unit Test + - Boost 1.67.0 (installed in default location "C:\\local") + + + +### Build and Unit Test + +SWMM can be built and unit tests run with one simple command. +``` +\> cd swmm +\swmm>tools\make.cmd /t +``` diff --git a/tests/outfile/CMakeLists.txt b/tests/outfile/CMakeLists.txt new file mode 100644 index 000000000..4ad2f47bb --- /dev/null +++ b/tests/outfile/CMakeLists.txt @@ -0,0 +1,22 @@ +# +# CMakeLists.txt - CMake configuration file for tests/outfile +# +# Created: July 11, 2019 +# +# Author: Michael E. Tryby +# US EPA ORD/CESER +# + +add_executable(test_output + test_output.cpp + ) +target_include_directories(test_output + PUBLIC ../../outfile/include + ) +target_link_libraries(test_output + ${Boost_LIBRARIES} + swmm-output + ) + +set_target_properties(test_output + PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) diff --git a/tests/outfile/data/Example1.out b/tests/outfile/data/Example1.out new file mode 100644 index 0000000000000000000000000000000000000000..78b15a8b5c3f6075f1415dad14fd66f6a3bfa11d GIT binary patch literal 44018 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

QkarH%qg5b>V+S?DT(I*gJO=Xg*tRd! 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 literal 0 HcmV?d00001 diff --git a/tests/outfile/test_output.cpp b/tests/outfile/test_output.cpp new file mode 100644 index 000000000..43bc73ad3 --- /dev/null +++ b/tests/outfile/test_output.cpp @@ -0,0 +1,344 @@ +/* + * test_output.cpp + * + * Created: 11/2/2017 + * Author: Michael E. Tryby + * US EPA - ORD/NRMRL + * + * Unit testing for SWMM outputapi using Boost Test. + */ + +// NOTE: Travis installs libboost test version 1.5.4 +//#define BOOST_TEST_DYN_LINK + +#define BOOST_TEST_MODULE "output" +#include + +#include +#include +#include +#include + +#include "swmm_output.h" + +// NOTE: Reference data for the unit tests is currently tied to SWMM 5.1.7 +#define DATA_PATH "./Example1.out" + +using namespace std; + +// Checks for minimum number of correct decimal digits +boost::test_tools::predicate_result check_cdd_float(std::vector& test, + std::vector& ref, long cdd_tol){ + + float tmp, min_cdd = 10.0; + + // TODO: What if the vectors aren't the same length? + + std::vector::iterator test_it; + std::vector::iterator ref_it; + + for (test_it = test.begin(), ref_it = ref.begin(); + (test_it < test.end()) && (ref_it < ref.end()); + ++test_it, ++ref_it) + { + if (*test_it != *ref_it) { + // Compute log absolute error + tmp = abs(*test_it - *ref_it); + if (tmp < 1.0e-7f) + tmp = 1.0e-7f; + + else if (tmp > 2.0f) + tmp = 1.0f; + + tmp = -log10(tmp); + if (tmp < 0.0f) + tmp = 0.0f; + + if (tmp < min_cdd) + min_cdd = tmp; + } + } + return floor(min_cdd) >= cdd_tol; +} + +boost::test_tools::predicate_result check_string(std::string test, + std::string ref) { + + if (ref.compare(test) == 0) + return true; + else + return false; +} + +BOOST_AUTO_TEST_SUITE(test_output_auto) + +BOOST_AUTO_TEST_CASE(InitTest) { + SMO_Handle p_handle = NULL; + + int error = SMO_init(&p_handle); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(p_handle != NULL); + + SMO_close(&p_handle); +} + +BOOST_AUTO_TEST_CASE(CloseTest) { + SMO_Handle p_handle = NULL; + SMO_init(&p_handle); + + int error = SMO_close(&p_handle); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(p_handle == NULL); +} + +BOOST_AUTO_TEST_CASE(InitOpenCloseTest) { + std::string path = std::string(DATA_PATH); + SMO_Handle p_handle = NULL; + SMO_init(&p_handle); + + int error = SMO_open(p_handle, path.c_str()); + BOOST_REQUIRE(error == 0); + + SMO_close(&p_handle); +} + +BOOST_AUTO_TEST_SUITE_END() + +struct Fixture { + Fixture() { + std::string path = std::string(DATA_PATH); + + error = SMO_init(&p_handle); + SMO_clearError(p_handle); + error = SMO_open(p_handle, path.c_str()); + + array = NULL; + array_dim = 0; + } + ~Fixture() { + SMO_free((void**)&array); + error = SMO_close(&p_handle); + } + + std::string path; + int error; + SMO_Handle p_handle; + + float* array; + int array_dim; +}; + +BOOST_AUTO_TEST_SUITE(test_output_fixture) + +BOOST_FIXTURE_TEST_CASE(test_getVersion, Fixture) { + int version; + + error = SMO_getVersion(p_handle, &version); + BOOST_REQUIRE(error == 0); + + BOOST_CHECK_EQUAL(51000, version); +} + +BOOST_FIXTURE_TEST_CASE(test_getProjectSize, Fixture) { + int* i_array = NULL; + + error = SMO_getProjectSize(p_handle, &i_array, &array_dim); + BOOST_REQUIRE(error == 0); + + std::vector test; + test.assign(i_array, i_array + array_dim); + + // subcatchs, nodes, links, pollutants + const int ref_dim = 5; + int ref_array[ref_dim] = {8, 14, 13, 1, 2}; + + std::vector ref; + ref.assign(ref_array, ref_array + ref_dim); + + BOOST_CHECK_EQUAL_COLLECTIONS(ref.begin(), ref.end(), test.begin(), + test.end()); + + SMO_free((void**)&i_array); +} + +BOOST_FIXTURE_TEST_CASE(test_getUnits, Fixture) { + int* i_array = NULL; + + error = SMO_getUnits(p_handle, &i_array, &array_dim); + BOOST_REQUIRE(error == 0); + + std::vector test; + test.assign(i_array, i_array + array_dim); + + // unit system, flow units, pollut units + const int ref_dim = 4; + const int ref_array[ref_dim] = {SMO_US, SMO_CFS, SMO_MG, SMO_UG}; + + std::vector ref; + ref.assign(ref_array, ref_array + ref_dim); + + BOOST_CHECK_EQUAL_COLLECTIONS(ref.begin(), ref.end(), test.begin(), + test.end()); + + SMO_free((void**)&i_array); +} + +BOOST_FIXTURE_TEST_CASE(test_getFlowUnits, Fixture) { + int units = -1; + + error = SMO_getFlowUnits(p_handle, &units); + BOOST_REQUIRE(error == 0); + BOOST_CHECK_EQUAL(0, units); +} + +BOOST_FIXTURE_TEST_CASE(test_getPollutantUnits, Fixture) { + int* i_array = NULL; + + error = SMO_getPollutantUnits(p_handle, &i_array, &array_dim); + BOOST_REQUIRE(error == 0); + + std::vector test; + test.assign(i_array, i_array + array_dim); + + const int ref_dim = 2; + int ref_array[ref_dim] = {0, 1}; + + std::vector ref; + ref.assign(ref_array, ref_array + ref_dim); + + BOOST_CHECK_EQUAL_COLLECTIONS(ref.begin(), ref.end(), test.begin(), + test.end()); + + SMO_free((void**)&i_array); + BOOST_CHECK(i_array == NULL); +} + +BOOST_FIXTURE_TEST_CASE(test_getStartDate, Fixture) { + double date = -1; + + error = SMO_getStartDate(p_handle, &date); + BOOST_REQUIRE(error == 0); + + BOOST_CHECK_EQUAL(35796., date); +} + +BOOST_FIXTURE_TEST_CASE(test_getTimes, Fixture) { + int time = -1; + + error = SMO_getTimes(p_handle, SMO_reportStep, &time); + BOOST_REQUIRE(error == 0); + + BOOST_CHECK_EQUAL(3600, time); + + error = SMO_getTimes(p_handle, SMO_numPeriods, &time); + BOOST_REQUIRE(error == 0); + + BOOST_CHECK_EQUAL(36, time); +} + +BOOST_FIXTURE_TEST_CASE(test_getElementName, Fixture) { + char* c_array = NULL; + int index = 1; + + error = SMO_getElementName(p_handle, SMO_node, index, &c_array, &array_dim); + BOOST_REQUIRE(error == 0); + + std::string test(c_array); + std::string ref("10"); + BOOST_CHECK(check_string(test, ref)); + + SMO_free((void**)&c_array); +} + +BOOST_FIXTURE_TEST_CASE(test_getSubcatchSeries, Fixture) { + error = SMO_getSubcatchSeries(p_handle, 1, SMO_runoff_rate, 0, 10, &array, + &array_dim); + BOOST_REQUIRE(error == 0); + + const int ref_dim = 10; + float ref_array[ref_dim] = { + 0.0f, 1.2438242f, 2.5639679f, 4.524055f, 2.5115132f, 0.69808137f, + 0.040894926f, 0.011605669f, 0.00509294f, 0.0027438672f}; + + std::vector ref_vec; + ref_vec.assign(ref_array, ref_array + 10); + + std::vector test_vec; + test_vec.assign(array, array + array_dim); + + BOOST_CHECK(check_cdd_float(test_vec, ref_vec, 3)); +} + +BOOST_FIXTURE_TEST_CASE(test_getSubcatchResult, Fixture) { + error = SMO_getSubcatchResult(p_handle, 1, 1, &array, &array_dim); + BOOST_REQUIRE(error == 0); + + const int ref_dim = 10; + float ref_array[ref_dim] = { + 0.5f, 0.0f, 0.0f, 0.125f, 1.2438242f, + 0.0f, 0.0f, 0.0f, 33.481991f, 6.6963983f}; + + std::vector ref_vec; + ref_vec.assign(ref_array, ref_array + ref_dim); + + std::vector test_vec; + test_vec.assign(array, array + array_dim); + + BOOST_CHECK(check_cdd_float(test_vec, ref_vec, 3)); +} + +BOOST_FIXTURE_TEST_CASE(test_getNodeResult, Fixture) { + error = SMO_getNodeResult(p_handle, 2, 2, &array, &array_dim); + BOOST_REQUIRE(error == 0); + + const int ref_dim = 8; + float ref_array[ref_dim] = { + 0.296234f, 995.296204f, 0.0f, 1.302650f, 1.302650f, 0.0f, + 15.361463f, 3.072293f}; + + std::vector ref_vec; + ref_vec.assign(ref_array, ref_array + ref_dim); + + std::vector test_vec; + test_vec.assign(array, array + array_dim); + + BOOST_CHECK(check_cdd_float(test_vec, ref_vec, 3)); +} + +BOOST_FIXTURE_TEST_CASE(test_getLinkResult, Fixture) { + error = SMO_getLinkResult(p_handle, 3, 3, &array, &array_dim); + BOOST_REQUIRE(error == 0); + + const int ref_dim = 7; + float ref_array[ref_dim] = { + 4.631762f, 1.0f, 5.8973422f, 314.15927f, 1.0f, 19.070757f, + 3.8141515f}; + + std::vector ref_vec; + ref_vec.assign(ref_array, ref_array + ref_dim); + + std::vector test_vec; + test_vec.assign(array, array + array_dim); + + BOOST_CHECK(check_cdd_float(test_vec, ref_vec, 3)); +} + +BOOST_FIXTURE_TEST_CASE(test_getSystemResult, Fixture) { + error = SMO_getSystemResult(p_handle, 4, 4, &array, &array_dim); + BOOST_REQUIRE(error == 0); + + const int ref_dim = 14; + float ref_array[ref_dim] = { + 70.0f, 0.1f, 0.0f, 0.19042271f, 14.172027f, 0.0f, 0.0f, 0.0f, + 0.0f, 14.172027f, 0.55517411f, 13.622702f, 2913.0793f, 0.0f}; + + std::vector ref_vec; + ref_vec.assign(ref_array, ref_array + ref_dim); + + std::vector test_vec; + test_vec.assign(array, array + array_dim); + + BOOST_CHECK(check_cdd_float(test_vec, ref_vec, 3)); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tools/.gitignore b/tools/.gitignore deleted file mode 100644 index 5a45dbdff..000000000 --- a/tools/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -# Python compiler files -*.py[c] - -# Python distribution and packaging -build/ -dist/ -temp/ -*.cfg -*.egg-info/ - - -# C compiler -*.o -*.dll -*.exe - -# Eclipse project files and directories -.metadata/ -.settings/ -Release/ -.project -.cproject -.pydevproject diff --git a/tools/Reg_Testing.md b/tools/Reg_Testing.md new file mode 100644 index 000000000..b6d31afec --- /dev/null +++ b/tools/Reg_Testing.md @@ -0,0 +1,50 @@ + + +## Regression Testing SWMM locally on Windows + + +### Dependencies + +Before the project can be built and tested the required dependencies must be installed. + +**Summary of Build Dependencies: Windows** + + - Build + - Build Tools for Visual Studio 2017 + - CMake 3.13 + + - Regression Test + - Python 3.6 64 bit + - curl + - git + - 7z + +Once Python is present, the following command installs the required packages for regression testing. +``` +\> cd swmm +\swmm>pip install -r tools\requirements-win.txt +``` + + +### Build + +EPANET can be built with one simple command. +``` +\swmm>tools\make.cmd +``` + + +### Regression Test + +This command runs regression tests for the local build and compares them to the latest benchmark. +``` +\swmm>tools\before-nrtest.cmd && tools\run-nrtest.cmd +``` diff --git a/tools/app-config.cmd b/tools/app-config.cmd new file mode 100644 index 000000000..09ce04daf --- /dev/null +++ b/tools/app-config.cmd @@ -0,0 +1,58 @@ +:: +:: app-config.cmd - Generates nrtest app configuration file for SUT executable +:: +:: Date Created: 10/16/2019 +:: Date Modified: 10/17/2019 +:: +:: Author: Michael E. Tryby +:: US EPA - ORD/CESER +:: +:: Requires: +:: git +:: +:: Environment Variables: +:: PROJECT +:: +:: Arguments: +:: 1 - absolute path to test executable (valid path seperator for nrtest is "/") +:: 2 - (platform) +:: 3 - (build identifier for SUT) +:: + +@echo off +setlocal + + +:: check requirements +where git > nul +if %ERRORLEVEL% NEQ 0 ( echo "ERROR: git not installed" & exit /B 1 ) + +:: check environment +if not defined PROJECT ( echo "ERROR: PROJECT must be defined" & exit /B 1 ) + + +:: swmm target created by the cmake build script +set TEST_CMD=run%PROJECT%.exe + +:: remove quotes from path and convert backward to forward slash +set ABS_BUILD_PATH=%~1 +set ABS_BUILD_PATH=%ABS_BUILD_PATH:\=/% + +if [%2]==[] ( set "PLATFORM=unknown" +) else ( set "PLATFORM=%~2" ) + +if [%3]==[] ( set "BUILD_ID=unknown" +) else ( set "BUILD_ID=%~3" ) + +:: determine version +for /F "tokens=1" %%v in ( 'git rev-parse --short HEAD' ) do ( set "VERSION=%%v" ) +if not defined VERSION ( echo "ERROR: VERSION could not be determined" & exit /B 1 ) + + +echo { +echo "name" : "%PROJECT%", +echo "version" : "%VERSION%", +echo "description" : "%PLATFORM% %BUILD_ID%", +echo "setup_script" : "", +echo "exe" : "%ABS_BUILD_PATH%/%TEST_CMD%" +echo } diff --git a/tools/before-nrtest.cmd b/tools/before-nrtest.cmd new file mode 100644 index 000000000..6a7a36428 --- /dev/null +++ b/tools/before-nrtest.cmd @@ -0,0 +1,116 @@ +:: +:: before-test.cmd - Stages test and benchmark files for nrtest +:: +:: Date Created: 10/16/2019 +:: Date Updated: +:: +:: Author: Michael E. Tryby +:: US EPA - ORD/CESER +:: +:: Dependencies: +:: curl +:: 7z +:: +:: Environment Variables: +:: PROJECT +:: BUILD_HOME - defaults to "build" +:: PLATFORM +:: +:: Arguments: +:: 1 - (RELEASE_TAG) release tag for benchmark version (defaults to latest tag) +:: +:: Note: +:: Tests and benchmark files are stored in the project-nrtests repo. +:: This script retrieves them using a stable URL associated with a GitHub +:: release, stages the files, and sets up the environment for nrtest to run. +:: + +::@echo off + +:: set global default +set "TEST_HOME=nrtests" + +:: determine project directory +set "CUR_DIR=%CD%" +set "SCRIPT_HOME=%~dp0" +cd %SCRIPT_HOME% +cd .. + +setlocal + + +:: check that dependencies are installed +where curl > nul +if %ERRORLEVEL% neq 0 ( echo "ERROR: curl not installed" & exit /B 1 ) +where 7z > nul +if %ERRORLEVEL% neq 0 ( echo "ERROR: 7zip not installed" & exit /B 1 ) + + +:: set URL to github repo with test files +set "NRTESTS_URL=https://github.com/SWMM-Project/%PROJECT%-nrtestsuite" + + +:: if release tag isn't provided latest tag will be retrieved +if [%1] == [] (set "RELEASE_TAG=" +) else (set "RELEASE_TAG=%~1") + + +:: check env variables and apply defaults +if not defined PROJECT ( echo "ERROR: PROJECT must be defined" & exit /B 1 ) +if not defined BUILD_HOME ( echo "ERROR: BUILD_HOME must be defined" & exit /B 1 ) +if not defined PLATFORM ( echo "ERROR: PLATFORM must be defined" & exit /B 1 ) + + +echo INFO: Staging files for regression testing + + +:: determine latest tag in the tests repo +if [%RELEASE_TAG%] == [] ( + for /F delims^=^"^ tokens^=2 %%g in ('curl --silent %NRTESTS_URL%/releases/latest') do ( + set "RELEASE_TAG=%%~nxg" + ) +) + +if defined RELEASE_TAG ( + set "TESTFILES_URL=%NRTESTS_URL%/archive/%RELEASE_TAG%.zip" + set "BENCHFILES_URL=%NRTESTS_URL%/releases/download/%RELEASE_TAG%/benchmark-%PLATFORM%.zip" +) else ( echo ERROR: tag %RELEASE_TAG% is invalid & exit /B 1 ) + + +:: create a clean directory for staging regression tests +if exist %TEST_HOME% ( + rmdir /s /q %TEST_HOME% +) +mkdir %TEST_HOME% +if %ERRORLEVEL% NEQ 0 ( echo "ERROR: unable to make %TEST_HOME% dir" & exit /B 1 ) +cd %TEST_HOME% +if %ERRORLEVEL% NEQ 0 ( echo "ERROR: unable to cd %TEST_HOME% dir" & exit /B 1 ) + + +:: retrieve nrtest cases and benchmark results for regression testing +curl -fsSL -o nrtestfiles.zip %TESTFILES_URL% +curl -fsSL -o benchmark.zip %BENCHFILES_URL% + + +:: extract tests, scripts, benchmarks, and manifest +7z x nrtestfiles.zip * > nul +7z x benchmark.zip -obenchmark\ > nul +7z e benchmark.zip -o. manifest.json -r > nul + + +:: set up symlinks for tests directory +mklink /D .\tests .\%PROJECT%-nrtestsuite-%RELEASE_TAG:~1%\public > nul + + +endlocal + + +:: determine REF_BUILD_ID from manifest file +for /F delims^=^"^ tokens^=4 %%d in ( 'findstr %PLATFORM% %TEST_HOME%\manifest.json' ) do ( + for /F "tokens=2" %%r in ( 'echo %%d' ) do ( set "REF_BUILD_ID=%%r" ) +) +if not defined REF_BUILD_ID ( echo "ERROR: REF_BUILD_ID could not be determined" & exit /B 1 ) + + +:: return to users current directory +cd %CUR_DIR% diff --git a/tools/make.cmd b/tools/make.cmd new file mode 100644 index 000000000..3fabcc683 --- /dev/null +++ b/tools/make.cmd @@ -0,0 +1,108 @@ +:: +:: make.cmd - builds project +:: +:: Date Created: 10/15/2019 +:: Date Updated: +:: +:: Author: Michael E. Tryby +:: US EPA - ORD/CESER +:: +:: Requires: +:: Build Tools for Visual Studio download: +:: https://visualstudio.microsoft.com/downloads/ +:: +:: CMake download: +:: https://cmake.org/download/ +:: +:: Optional Arguments: +:: /g ("GENERATOR") defaults to "Visual Studio 15 2017" +:: /t builds and runs unit tests (requires Boost) +:: + + +::echo off + + +:: set global defaults +set "PROJECT=swmm" +set "BUILD_HOME=build" +set "PLATFORM=win32" + +:: determine project directory +set "CUR_DIR=%CD%" +set "SCRIPT_HOME=%~dp0" +cd %SCRIPT_HOME% +cd .. + +:: check for requirements +where cmake > nul +if %ERRORLEVEL% NEQ 0 ( echo "ERROR: cmake not installed" & exit /B 1 ) + + +setlocal EnableDelayedExpansion + + +echo INFO: Building %PROJECT% ... + + +:: set local defaults +set "GENERATOR=Visual Studio 15 2017" +set "TESTING=0" + +:: process arguments +:loop +if NOT [%1]==[] ( + if "%1"=="/g" ( + set "GENERATOR=%~2" + shift + ) + if "%1"=="/t" ( + set "TESTING=1" + ) + shift + goto :loop +) + + +:: if generator has changed delete the build folder +if exist %BUILD_HOME% ( + for /F "tokens=*" %%f in ( 'findstr CMAKE_GENERATOR:INTERNAL %BUILD_HOME%\CmakeCache.txt' ) do ( + for /F "delims=:= tokens=3" %%m in ( 'echo %%f' ) do ( + set CACHE_GEN=%%m + if not "!CACHE_GEN!" == "!GENERATOR!" ( rmdir /s /q %BUILD_HOME% & mkdir %BUILD_HOME% ) + ) + ) +) else ( + mkdir %BUILD_HOME%^ + & if %ERRORLEVEL% NEQ 0 ( echo "ERROR: unable to make %BUILD_HOME% dir" & exit /B 1 ) +) + + +:: perform the build +cd %BUILD_HOME% +if %ERRORLEVEL% NEQ 0 ( echo "ERROR: unable to cd %BUILD_HOME% dir" & exit /B 1 ) + +if %TESTING% EQU 1 ( + cmake -G"%GENERATOR%" -DBUILD_TESTS=ON -DBOOST_ROOT=C:\local\boost_1_67_0 ..^ + && cmake --build . --config Debug^ + & echo. && ctest -C Debug --output-on-failure +) else ( + cmake -G"%GENERATOR%" -DBUILD_TESTS=OFF ..^ + && cmake --build . --config Release --target install +) + + +endlocal + + +:: determine platform from CmakeCache.txt file +for /F "tokens=*" %%f in ( 'findstr CMAKE_SHARED_LINKER_FLAGS:STRING %BUILD_HOME%\CmakeCache.txt' ) do ( + for /F "delims=: tokens=3" %%m in ( 'echo %%f' ) do ( + if "%%m" == "X86" ( set "PLATFORM=win32" ) else if "%%m" == "x64" ( set "PLATFORM=win64" ) + ) +) +if not defined PLATFORM ( echo "ERROR: PLATFORM could not be determined" & exit /B 1 ) + + +:: return to users current dir +:: cd %CUR_DIR% diff --git a/tools/nrtest-swmm/nrtest_swmm/__init__.py b/tools/nrtest-swmm/nrtest_swmm/__init__.py deleted file mode 100644 index dd8abd1c6..000000000 --- a/tools/nrtest-swmm/nrtest_swmm/__init__.py +++ /dev/null @@ -1,112 +0,0 @@ -# -*- coding: utf-8 -*- -''' -Numerical regression testing (nrtest) plugin for comparing SWMM binary results -files and SWMM text based report files. -''' - -# system imports -import itertools as it - -# third party imports -import header_detail_footer as hdf -import numpy as np - -# project imports -import swmm_reader as sr - - -__author__ = "Michael Tryby" -__copyright__ = "None" -__credits__ = "Colleen Barr, Maurizio Cingi, Mark Gray, David Hall, Bryant McDonnell" -__license__ = "CC0 1.0 Universal" - -__version__ = "0.2.0" -__date__ = "September 20, 2016" - -__maintainer__ = "Michael Tryby" -__email__ = "tryby.michael@epa.gov" -__status = "Development" - - -def swmm_allclose_compare(path_test, path_ref, rtol, atol): - ''' - Compares results in two SWMM 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 or raises an error - - Raises: - ValueError() - AssertionError() - ... - ''' - for (test, ref) in it.izip(sr.swmm_output_generator(path_test), - sr.swmm_output_generator(path_ref)): - - if test.size != ref.size: - raise ValueError('Inconsistent lengths') - - # Skip over results if they are equal - if (np.array_equal(test, ref)): - continue - - else: - np.testing.assert_allclose(test, ref, rtol, atol) - - return True - -#def swmm_better_compare(): -# ''' -# If for some reason you don't like numpy.testing.assert_allclose() add a -# better function here. Be sure to add the entry point to the setup file so -# nrtest can find it at runtime. -# ''' -# pass - -def swmm_report_compare(path_test, path_ref, rtol, atol): - ''' - Compares results in two report files ignoring contents of header and footer. - - 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 = 4 - FOOTER = 4 - - 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 diff --git a/tools/nrtest-swmm/setup.py b/tools/nrtest-swmm/setup.py deleted file mode 100644 index 91a2af8c7..000000000 --- a/tools/nrtest-swmm/setup.py +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: utf-8 -*- - -''' -Created on Aug 11, 2016 - -@author: mtryby -''' - -try: - from setuptools import setup -except ImportError: - from distutils.core import setup - - -entry_points = { - 'nrtest.compare': [ - 'swmm allclose = nrtest_swmm:swmm_allclose_compare', - 'swmm report = nrtest_swmm:swmm_report_compare', - # Add the entry point for new comparison functions here - ] -} - - -setup( - name='nrtest-swmm', - version='0.2.0', - description="SWMM extension for nrtest", - - author="Michael E. Tryby", - author_email='tryby.michael@epa.gov', - url='https://github.com/USEPA', - - packages=['nrtest_swmm',], - entry_points=entry_points, - include_package_data=True, - install_requires=[ - 'header_detail_footer>=2.3', - 'nrtest>=0.2.0', - 'numpy>=1.6.0', - 'swmm_reader>=0.2.0', - ], - zipsafe=True, - keywords='nrtest_swmm' -) diff --git a/tools/requirements-win.txt b/tools/requirements-win.txt new file mode 100644 index 000000000..0f18bb0ee --- /dev/null +++ b/tools/requirements-win.txt @@ -0,0 +1,22 @@ +# +# requirements-win.txt - Python requirements for running nrtest on Win32/Win64 +# +# Date Created: 10/17/2019 +# Date Updated: 11/26/2019 +# +# Author: Michael E. Tryby +# US EPA ORD/CESER +# +# Useful for configuring a python environment to run nrtests on swmm. +# +# usage: +# pip install -r tools/requirements-win.txt +# + +nrtest + +-f https://github.com/michaeltryby/swmm-python/releases/download/v0.3.0-dev3/swmm.output-0.4.0.dev3-cp36-cp36m-win_amd64.whl +swmm.output + +-f https://github.com/michaeltryby/swmm-python/releases/download/v0.3.0-dev3/nrtest_swmm-0.5.0-py3-none-any.whl +nrtest-swmm diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd new file mode 100644 index 000000000..fb02a11ae --- /dev/null +++ b/tools/run-nrtests.cmd @@ -0,0 +1,114 @@ +:: +:: run_nrtest.cmd - Runs numerical regression test +:: +:: Date Created: 10/16/2019 +:: Date Updated: +:: +:: Author: Michael E. Tryby +:: US EPA - ORD/CESER +:: +:: Dependencies: +:: python -m pip install -r requirements.txt +:: +:: Environment Variables: +:: PROJECT +:: BUILD_HOME - relative path +:: TEST_HOME - relative path +:: PLATFORM +:: REF_BUILD_ID +:: +:: Arguments: +:: 1 - (SUT_VERSION) - optional argument +:: 2 - (SUT_BUILD_ID) - optional argument +:: + +::@echo off +setlocal EnableDelayedExpansion + + +:: Check that required environment variables are set +if not defined PROJECT ( echo "ERROR: PROJECT must be defined" & exit /B 1 ) +if not defined BUILD_HOME ( echo "ERROR: BUILD_HOME must be defined" & exit /B 1 ) +if not defined TEST_HOME ( echo "ERROR: TEST_HOME must be defined" & exit /B 1 ) +if not defined PLATFORM ( echo "ERROR: PLATFORM must be defined" & exit /B 1 ) +if not defined REF_BUILD_ID ( echo "ERROR: REF_BUILD_ID must be defined" & exit /B 1 ) + + +:: determine project directory +set "CUR_DIR=%CD%" +set "SCRIPT_HOME=%~dp0" +cd %SCRIPT_HOME% +pushd .. +set PROJ_DIR=%CD% +popd + + +cd %PROJ_DIR%\%TEST_HOME% + +:: Process optional arguments +if [%1]==[] (set "SUT_VERSION=unknown" +) else ( set "SUT_VERSION=%~1" ) + +if [%2]==[] ( set "SUT_BUILD_ID=local" +) else ( set "SUT_BUILD_ID=%~2" ) + + +:: check if app config file exists +if not exist apps\%PROJECT%-%SUT_BUILD_ID%.json ( + mkdir apps + call %SCRIPT_HOME%\app-config.cmd %PROJ_DIR%\%BUILD_HOME%\bin\Release^ + %PLATFORM% %SUT_BUILD_ID% %SUT_VERSION% > apps\%PROJECT%-%SUT_BUILD_ID%.json +) + + +:: recursively build test list +set TESTS= +for /F "tokens=*" %%T in ('dir /b /s /a:d tests') do ( + set FULL_PATH=%%T + set TESTS=!TESTS! !FULL_PATH:*%TEST_HOME%\=! +) + + +:: determine location of python Scripts folder +for /F "tokens=*" %%G in ('where python.exe') do ( + set PYTHON_DIR=%%~dpG + goto break_loop_1 +) +:break_loop_1 +set "NRTEST_SCRIPT_PATH=%PYTHON_DIR%Scripts" + + +:: build nrtest execute command +set NRTEST_EXECUTE_CMD=python.exe %NRTEST_SCRIPT_PATH%\nrtest execute +set TEST_APP_PATH=apps\%PROJECT%-%SUT_BUILD_ID%.json +set TEST_OUTPUT_PATH=benchmark\%PROJECT%-%SUT_BUILD_ID% + +:: build nrtest compare command +set NRTEST_COMPARE_CMD=python.exe %NRTEST_SCRIPT_PATH%\nrtest compare +set REF_OUTPUT_PATH=benchmark\%PROJECT%-%REF_BUILD_ID% +set RTOL_VALUE=0.01 +set ATOL_VALUE=0.0 + +:: change current directory to test suite +::cd %TEST_HOME% + +:: if present clean test benchmark results +if exist %TEST_OUTPUT_PATH% ( + rmdir /s /q %TEST_OUTPUT_PATH% +) + +:: perform nrtest execute +echo INFO: Creating SUT %SUT_BUILD_ID% artifacts +set NRTEST_COMMAND=%NRTEST_EXECUTE_CMD% %TEST_APP_PATH% %TESTS% -o %TEST_OUTPUT_PATH% +:: if there is an error exit the script with error value 1 +%NRTEST_COMMAND% || exit /B 1 + +echo. + +:: perform nrtest compare +echo INFO: Comparing SUT artifacts to REF %REF_BUILD_ID% +set NRTEST_COMMAND=%NRTEST_COMPARE_CMD% %TEST_OUTPUT_PATH% %REF_OUTPUT_PATH% --rtol %RTOL_VALUE% --atol %ATOL_VALUE% -o benchmark\receipt.json +%NRTEST_COMMAND% + +:: Return user to their current dir +cd %CUR_DIR% diff --git a/tools/swmm-output/src/outputapi.c b/tools/swmm-output/src/outputapi.c deleted file mode 100644 index fe5f0372f..000000000 --- a/tools/swmm-output/src/outputapi.c +++ /dev/null @@ -1,1015 +0,0 @@ -/* -* outputAPI.c -* -* Author: Colleen Barr -* Modified by: Michael E. Tryby, -* Bryant McDonnell -* -* -*/ - -#include -#include -#include -#include "outputAPI.h" -//#include "datetime.h" - - -// NOTE: These depend on machine data model and may change when porting -#define F_OFF off_t // Must be a 8 byte / 64 bit integer for large file support -#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 RECORDSIZE 4 // Memory alignment 4 byte word size for both int and real -#define DATESIZE 8 // Dates are stored as 8 byte word size - -#define MEMCHECK(x) (((x) == NULL) ? 414 : 0 ) - -struct IDentry { - char* IDname; - int length; -}; -typedef struct IDentry idEntry; - -//----------------------------------------------------------------------------- -// Shared variables -//----------------------------------------------------------------------------- - -struct SMOutputAPI { - char name[MAXFILENAME + 1]; // file path/name - FILE* file; // FILE structure pointer - - struct IDentry *elementNames; // array of pointers to element names - - long Nperiods; // number of reporting periods - int FlowUnits; // flow units code - - int Nsubcatch; // number of subcatchments - int Nnodes; // number of drainage system nodes - int Nlinks; // number of drainage system links - int Npolluts; // number of pollutants tracked - - int SubcatchVars; // number of subcatch reporting variables - int NodeVars; // number of node reporting variables - int LinkVars; // number of link reporting variables - int SysVars; // number of system reporting variables - - double StartDate; // start date of simulation - int ReportStep; // reporting time step (seconds) - - F_OFF IDPos; // file position where object ID names start - F_OFF ObjPropPos; // file position where object properties start - F_OFF ResultsPos; // file position where results start - F_OFF BytesPerPeriod; // bytes used for results in each period -}; - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -int validateFile(SMOutputAPI* smoapi); -void initElementNames(SMOutputAPI* smoapi); - -double getTimeValue(SMOutputAPI* smoapi, long timeIndex); -float getSubcatchValue(SMOutputAPI* smoapi, long timeIndex, int subcatchIndex, SMO_subcatchAttribute attr); -float getNodeValue(SMOutputAPI* smoapi, long timeIndex, int nodeIndex, SMO_nodeAttribute attr); -float getLinkValue(SMOutputAPI* smoapi, long timeIndex, int linkIndex, SMO_linkAttribute attr); -float getSystemValue(SMOutputAPI* smoapi, long timeIndex, SMO_systemAttribute attr); - - -SMOutputAPI* DLLEXPORT SMO_init(void) -// -// Purpose: Returns an initialized pointer for the opaque SMOutputAPI -// structure. -// -{ - SMOutputAPI *smoapi = malloc(sizeof(struct SMOutputAPI)); - smoapi->elementNames = NULL; - - return smoapi; -} - -int DLLEXPORT SMO_open(SMOutputAPI* smoapi, const char* path) -// -// Purpose: Open the output binary file and read the header. -// -{ - int version, err, errorcode = 0; - F_OFF offset; - - if (smoapi == NULL) errorcode = 410; - else - { - strncpy(smoapi->name, path, MAXFILENAME); - - // --- open the output file - if ((smoapi->file = fopen(path, "rb")) == NULL) errorcode = 434; - // --- validate the output file - else if ((err = validateFile(smoapi)) != 0) errorcode = err; - - else { - // --- otherwise read additional parameters from start of file - fread(&version, RECORDSIZE, 1, smoapi->file); - fread(&(smoapi->FlowUnits), RECORDSIZE, 1, smoapi->file); - fread(&(smoapi->Nsubcatch), RECORDSIZE, 1, smoapi->file); - fread(&(smoapi->Nnodes), RECORDSIZE, 1, smoapi->file); - fread(&(smoapi->Nlinks), RECORDSIZE, 1, smoapi->file); - fread(&(smoapi->Npolluts), RECORDSIZE, 1, smoapi->file); - - // Skip over saved subcatch/node/link input values - offset = (smoapi->Nsubcatch + 2) * RECORDSIZE // Subcatchment area - + (3 * smoapi->Nnodes + 4) * RECORDSIZE // Node type, invert & max depth - + (5 * smoapi->Nlinks + 6) * RECORDSIZE; // Link type, z1, z2, max depth & length - offset += smoapi->ObjPropPos; - - // Read number & codes of computed variables - fseeko64(smoapi->file, offset, SEEK_SET); - fread(&(smoapi->SubcatchVars), RECORDSIZE, 1, smoapi->file); // # Subcatch variables - - fseeko64(smoapi->file, smoapi->SubcatchVars*RECORDSIZE, SEEK_CUR); - fread(&(smoapi->NodeVars), RECORDSIZE, 1, smoapi->file); // # Node variables - - fseeko64(smoapi->file, smoapi->NodeVars*RECORDSIZE, SEEK_CUR); - fread(&(smoapi->LinkVars), RECORDSIZE, 1, smoapi->file); // # Link variables - - fseeko64(smoapi->file, smoapi->LinkVars*RECORDSIZE, SEEK_CUR); - fread(&(smoapi->SysVars), RECORDSIZE, 1, smoapi->file); // # System variables - - // --- read data just before start of output results - offset = smoapi->ResultsPos - 3 * RECORDSIZE; - fseeko64(smoapi->file, offset, SEEK_SET); - fread(&(smoapi->StartDate), DATESIZE, 1, smoapi->file); - fread(&(smoapi->ReportStep), RECORDSIZE, 1, smoapi->file); - - // --- compute number of bytes of results values used per time period - smoapi->BytesPerPeriod = DATESIZE + - (smoapi->Nsubcatch*smoapi->SubcatchVars + - smoapi->Nnodes*smoapi->NodeVars + - smoapi->Nlinks*smoapi->LinkVars + - smoapi->SysVars)*RECORDSIZE; - } - } - - if (errorcode) - SMO_close(smoapi); - - return errorcode; - -} - -int DLLEXPORT SMO_getProjectSize(SMOutputAPI* smoapi, SMO_elementCount code, int* count) -// -// Purpose: Returns project size. -// -{ - int errorcode = 0; - - *count = -1; - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else - { - switch (code) - { - case subcatchCount: - *count = smoapi->Nsubcatch; - break; - - case nodeCount: - *count = smoapi->Nnodes; - break; - - case linkCount: - *count = smoapi->Nlinks; - break; - - case pollutantCount: - *count = smoapi->Npolluts; - break; - - default: - errorcode = 421; - } - } - - return errorcode; -} - -int DLLEXPORT SMO_getUnits(SMOutputAPI* smoapi, SMO_unit code, int* unitFlag) -// -// Purpose: Returns flow rate units. -// -// Returns: -// 0: CFS (cubic feet per second) -// 1: GPM (gallons per minute) -// 2: MGD (million gallons per day) -// 3: CMS (cubic meters per second) -// 4: LPS (liters per second) -// 5: MLD (million liters per day) -// -{ - int errorcode = 0; - - *unitFlag = -1; - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else - { - switch (code) - { - case flow_rate: *unitFlag = smoapi->FlowUnits; - break; -// case concentration: *unitFlag = ConcUnits; -// break; - default: errorcode = 421; - } - } - - return errorcode; -} - - -int DLLEXPORT SMO_getPollutantUnits(SMOutputAPI* smoapi, int pollutantIndex, int* unitFlag) -// -// Purpose: Return integer flag representing the units that the given pollutant is measured in. -// Concentration units are located after the pollutant ID names and before the object properties start, -// and are stored for each pollutant. They're stored as 4-byte integers with the following codes: -// 0: mg/L -// 1: ug/L -// 2: count/L -// -// pollutantIndex: valid values are 0 to Npolluts-1 -{ - int errorcode = 0; - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else if (pollutantIndex < 0 || pollutantIndex >= smoapi->Npolluts) errorcode = 423; - else - { - int offset = smoapi->ObjPropPos - (smoapi->Npolluts - pollutantIndex) * RECORDSIZE; - fseek(smoapi->file, offset, SEEK_SET); - fread(unitFlag, RECORDSIZE, 1, smoapi->file); - } - - return errorcode; -} - - -int DLLEXPORT SMO_getStartTime(SMOutputAPI* smoapi, double* time) -// -// Purpose: Returns start date. -// -{ - int errorcode = 0; - - *time = -1; - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else - *time = smoapi->StartDate; - - return errorcode; -} - - -int DLLEXPORT SMO_getTimes(SMOutputAPI* smoapi, SMO_time code, int* time) -// -// Purpose: Returns step size and number of periods. -// -{ - int errorcode = 0; - - *time = -1; - - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else - { - switch (code) - { - case reportStep: *time = smoapi->ReportStep; - break; - case numPeriods: *time = smoapi->Nperiods; - break; - default: errorcode = 421; - } - } - - return errorcode; -} - -int DLLEXPORT SMO_getElementName(SMOutputAPI* smoapi, SMO_elementType type, - int index, char* name, int length) -// -// Purpose: Given an element index returns the element name. -// -// Note: The caller is responsible for allocating memory for the char array -// name. The caller passes the length of the array allocated. The name may -// be truncated if an array of adequate length is not passed. -// -{ - int idx, errorcode = 0; - - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else - { - // Initialize the name array if necessary - if (smoapi->elementNames == NULL) initElementNames(smoapi); - - switch (type) - { - case subcatch: - if (index < 0 || index >= smoapi->Nsubcatch) - errorcode = 423; - else - idx = index; - break; - - case node: - if (index < 0 || index >= smoapi->Nnodes) - errorcode = 423; - else - idx = smoapi->Nsubcatch + index; - break; - - case link: - if (index < 0 || index >= smoapi->Nlinks) - errorcode = 423; - else - idx = smoapi->Nsubcatch + smoapi->Nnodes + index; - break; - - case sys: - if (index < 0 || index >= smoapi->Npolluts) - errorcode = 423; - else - idx = smoapi->Nsubcatch + smoapi->Nnodes + smoapi->Nlinks + index; - break; - - default: - errorcode = 421; - } - - if (!errorcode) - strncpy(name, smoapi->elementNames[idx].IDname, length); - } - - return errorcode; -} - - -float* DLLEXPORT SMO_newOutValueSeries(SMOutputAPI* smoapi, long startPeriod, - long endPeriod, long* length, int* errcode) -// -// Purpose: Allocates memory for outValue Series. -// -// Warning: Caller must free memory allocated by this function using SMO_free(). -// -{ - long size; - float* array; - - if (smoapi == NULL) *errcode = 410; - else if (smoapi->file == NULL) *errcode = 411; - else if (startPeriod < 0 || endPeriod >= smoapi->Nperiods || - endPeriod <= startPeriod) *errcode = 422; - else - { - - size = endPeriod - startPeriod; - if (size > smoapi->Nperiods) - size = smoapi->Nperiods; - - array = (float*)calloc(size, sizeof(float)); - *errcode = (MEMCHECK(array)); - - *length = size; - return array; - } - - return NULL; -} - - -float* DLLEXPORT SMO_newOutValueArray(SMOutputAPI* smoapi, SMO_apiFunction func, - SMO_elementType type, long* length, int* errcode) -// -// Purpose: Allocates memory for outValue Array. -// -// Warning: Caller must free memory allocated by this function using SMO_free(). -// -{ - long size; - float* array; - - if (smoapi == NULL) *errcode = 410; - else if (smoapi->file == NULL) *errcode = 411; - else - { - switch (func) - { - case getAttribute: - if (type == subcatch) - size = smoapi->Nsubcatch; - else if (type == node) - size = smoapi->Nnodes; - else if (type == link) - size = smoapi->Nlinks; - else // system - size = 1; - break; - - case getResult: - if (type == subcatch) - size = smoapi->SubcatchVars; - else if (type == node) - size = smoapi->NodeVars; - else if (type == link) - size = smoapi->LinkVars; - else // system - size = smoapi->SysVars; - break; - - default: *errcode = 421; - return NULL; - } - - // Allocate memory for outValues - array = (float*)calloc(size, sizeof(float)); - *errcode = (MEMCHECK(array)); - - *length = size; - return array; - } - - return NULL; -} - - -int DLLEXPORT SMO_getSubcatchSeries(SMOutputAPI* smoapi, int subcatchIndex, - SMO_subcatchAttribute attr, long startPeriod, long length, float* outValueSeries) -// -// Purpose: Get time series results for particular attribute. Specify series -// start and length using timeIndex and length respectively. -// -{ - int errorcode = 0; - - long k; - - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else if (subcatchIndex < 0 || subcatchIndex > smoapi->Nsubcatch) errorcode = 420; - else if (startPeriod < 0 || startPeriod >= smoapi->Nperiods || - length > smoapi->Nperiods) errorcode = 422; - else if (outValueSeries == NULL) errorcode = 424; - else - { - // loop over and build time series - for (k = 0; k < length; k++) - outValueSeries[k] = getSubcatchValue(smoapi, startPeriod + k, - subcatchIndex, attr); - } - - return errorcode; -} - - -int DLLEXPORT SMO_getNodeSeries(SMOutputAPI* smoapi, int nodeIndex, SMO_nodeAttribute attr, - long startPeriod, long length, float* outValueSeries) -// -// Purpose: Get time series results for particular attribute. Specify series -// start and length using timeIndex and length respectively. -// -{ - int errorcode = 0; - - long k; - - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else if (nodeIndex < 0 || nodeIndex > smoapi->Nnodes) errorcode = 420; - else if (startPeriod < 0 || startPeriod >= smoapi->Nperiods || - length > smoapi->Nperiods) errorcode = 422; - else if (outValueSeries == NULL) errorcode = 424; - else - { - // loop over and build time series - for (k = 0; k < length; k++) - outValueSeries[k] = getNodeValue(smoapi, startPeriod + k, - nodeIndex, attr); - } - - return errorcode; -} - - -int DLLEXPORT SMO_getLinkSeries(SMOutputAPI* smoapi, int linkIndex, SMO_linkAttribute attr, - long startPeriod, long length, float* outValueSeries) -// -// Purpose: Get time series results for particular attribute. Specify series -// start and length using timeIndex and length respectively. -// -{ - int errorcode = 0; - - long k; - - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else if (linkIndex < 0 || linkIndex > smoapi->Nlinks) errorcode = 420; - else if (startPeriod < 0 || startPeriod >= smoapi->Nperiods || - length > smoapi->Nperiods) errorcode = 422; - else if (outValueSeries == NULL) errorcode = 424; - else - { - // loop over and build time series - for (k = 0; k < length; k++) - outValueSeries[k] = getLinkValue(smoapi, startPeriod + k, linkIndex, attr); - } - - return errorcode; -} - - - -int DLLEXPORT SMO_getSystemSeries(SMOutputAPI* smoapi, SMO_systemAttribute attr, - long startPeriod, long length, float *outValueSeries) -// -// Purpose: Get time series results for particular attribute. Specify series -// start and length using timeIndex and length respectively. -// -{ - int errorcode = 0; - - long k; - - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else if (startPeriod < 0 || startPeriod >= smoapi->Nperiods || - length > smoapi->Nperiods) errorcode = 422; - else if (outValueSeries == NULL) errorcode = 424; - else - { - // loop over and build time series - for (k = 0; k < length; k++) - outValueSeries[k] = getSystemValue(smoapi, startPeriod + k, attr); - } - - return errorcode; -} - -int DLLEXPORT SMO_getSubcatchAttribute(SMOutputAPI* smoapi, long periodIndex, - SMO_subcatchAttribute attr, float* outValueArray) -// -// Purpose: For all subcatchments at given time, get a particular attribute. -// -{ - int errorcode = 0; - - long k; - - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else if (periodIndex < 0 || periodIndex >= smoapi->Nperiods) errorcode = 422; - else if (outValueArray == NULL) errorcode = 424; - else - { - // loop over and pull result - for (k = 0; k < smoapi->Nsubcatch; k++) - outValueArray[k] = getSubcatchValue(smoapi, periodIndex, k, attr); - } - - return errorcode; -} - - - -int DLLEXPORT SMO_getNodeAttribute(SMOutputAPI* smoapi, long periodIndex, - SMO_nodeAttribute attr, float* outValueArray) -// -// Purpose: For all nodes at given time, get a particular attribute. -// -{ - int errorcode = 0; - - long k; - - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else if (periodIndex < 0 || periodIndex >= smoapi->Nperiods) errorcode = 422; - else if (outValueArray == NULL) errorcode = 424; - else - { - // loop over and pull result - for (k = 0; k < smoapi->Nnodes; k++) - outValueArray[k] = getNodeValue(smoapi, periodIndex, k, attr); - } - - return errorcode; -} - -int DLLEXPORT SMO_getLinkAttribute(SMOutputAPI* smoapi, long periodIndex, - SMO_linkAttribute attr, float* outValueArray) -// -// Purpose: For all links at given time, get a particular attribute. -// -{ - int errorcode = 0; - - long k; - - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else if (periodIndex < 0 || periodIndex >= smoapi->Nperiods) errorcode = 422; - else if (outValueArray == NULL) errorcode = 424; - else - { - // loop over and pull result - for (k = 0; k < smoapi->Nlinks; k++) - outValueArray[k] = getLinkValue(smoapi, periodIndex, k, attr); - } - - return errorcode; -} - - -int DLLEXPORT SMO_getSystemAttribute(SMOutputAPI* smoapi, long periodIndex, - SMO_systemAttribute attr, float* outValueArray) -// -// Purpose: For the system at given time, get a particular attribute. -// -{ - int errorcode = 0; - - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else if (periodIndex < 0 || periodIndex >= smoapi->Nperiods) errorcode = 422; - else if (outValueArray == NULL) errorcode = 424; - else - { - // don't need to loop since there's only one system - outValueArray[0] = getSystemValue(smoapi, periodIndex, attr); - } - - return errorcode; -} - -int DLLEXPORT SMO_getSubcatchResult(SMOutputAPI* smoapi, long periodIndex, int subcatchIndex, - float* outValueArray, int* arrayLength) -// -// Purpose: For a subcatchment at given time, get all attributes. -// -{ - int errorcode = 0; - - F_OFF offset; - - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else if (periodIndex < 0 || periodIndex >= smoapi->Nperiods) errorcode = 422; - else if (subcatchIndex < 0 || subcatchIndex > smoapi->Nsubcatch) errorcode = 423; - else if (outValueArray == NULL) errorcode = 424; - else - { - // --- compute offset into output file - offset = smoapi->ResultsPos + (periodIndex)*smoapi->BytesPerPeriod + 2 * RECORDSIZE; - // add offset for subcatchment - offset += (subcatchIndex*smoapi->SubcatchVars)*RECORDSIZE; - - fseeko64(smoapi->file, offset, SEEK_SET); - fread(outValueArray, RECORDSIZE, smoapi->SubcatchVars, smoapi->file); - *arrayLength = smoapi->SubcatchVars; - } - - return errorcode; -} - - -int DLLEXPORT SMO_getNodeResult(SMOutputAPI* smoapi, long periodIndex, int nodeIndex, - float* outValueArray, int* arrayLength) -// -// Purpose: For a node at given time, get all attributes. -// -{ - int errorcode = 0; - - F_OFF offset; - - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else if (periodIndex < 0 || periodIndex >= smoapi->Nperiods) errorcode = 422; - else if (nodeIndex < 0 || nodeIndex > smoapi->Nnodes) errorcode = 423; - else if (outValueArray == NULL) errorcode = 424; - else - { - // calculate byte offset to start time for series - offset = smoapi->ResultsPos + (periodIndex)*smoapi->BytesPerPeriod + 2 * RECORDSIZE; - // add offset for subcatchment and node - offset += (smoapi->Nsubcatch*smoapi->SubcatchVars + nodeIndex*smoapi->NodeVars)*RECORDSIZE; - - fseeko64(smoapi->file, offset, SEEK_SET); - fread(outValueArray, RECORDSIZE, smoapi->NodeVars, smoapi->file); - *arrayLength = smoapi->NodeVars; - } - - return errorcode; -} - - -int DLLEXPORT SMO_getLinkResult(SMOutputAPI* smoapi, long periodIndex, int linkIndex, - float* outValueArray, int* arrayLength) -// -// Purpose: For a link at given time, get all attributes. -// -{ - int errorcode = 0; - - F_OFF offset; - - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else if (periodIndex < 0 || periodIndex >= smoapi->Nperiods) errorcode = 422; - else if (linkIndex < 0 || linkIndex > smoapi->Nlinks) errorcode = 423; - else if (outValueArray == NULL) errorcode = 424; - else - { - // calculate byte offset to start time for series - offset = smoapi->ResultsPos + (periodIndex)*smoapi->BytesPerPeriod + 2 * RECORDSIZE; - // add offset for subcatchment and node and link - offset += (smoapi->Nsubcatch*smoapi->SubcatchVars - + smoapi->Nnodes*smoapi->NodeVars + linkIndex*smoapi->LinkVars)*RECORDSIZE; - - fseeko64(smoapi->file, offset, SEEK_SET); - fread(outValueArray, RECORDSIZE, smoapi->LinkVars, smoapi->file); - *arrayLength = smoapi->LinkVars; - } - - return errorcode; -} - -int DLLEXPORT SMO_getSystemResult(SMOutputAPI* smoapi, long periodIndex, int dummyIndex, - float* outValueArray, int* arrayLength) -// -// Purpose: For the system at given time, get all attributes. -// -{ - int errorcode = 0; - - F_OFF offset; - - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else if (periodIndex < 0 || periodIndex >= smoapi->Nperiods) errorcode = 422; - else if (outValueArray == NULL) errorcode = 424; - { - // calculate byte offset to start time for series - offset = smoapi->ResultsPos + (periodIndex)*smoapi->BytesPerPeriod + 2 * RECORDSIZE; - // add offset for subcatchment and node and link (system starts after the last link) - offset += (smoapi->Nsubcatch*smoapi->SubcatchVars + smoapi->Nnodes*smoapi->NodeVars - + smoapi->Nlinks*smoapi->LinkVars)*RECORDSIZE; - - fseeko64(smoapi->file, offset, SEEK_SET); - fread(outValueArray, RECORDSIZE, smoapi->SysVars, smoapi->file); - *arrayLength = smoapi->SysVars; - } - - return errorcode; -} - -void DLLEXPORT SMO_free(float *array) -// -// Purpose: frees memory allocated using SMO_newOutValueSeries() or -// SMO_newOutValueArray(). -// -{ - if (array != NULL) - free(array); -} - - -int DLLEXPORT SMO_close(SMOutputAPI* smoapi) -// -// Purpose: Clean up after and close Output API -// -{ - int i, n, errorcode = 0; - - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else - { - if (smoapi->elementNames != NULL) - { - n = smoapi->Nsubcatch + smoapi->Nnodes + smoapi->Nlinks + smoapi->Npolluts; - - for(i = 0; i < n; i++) - free(smoapi->elementNames[i].IDname); - } - - fclose(smoapi->file); - free(smoapi); - smoapi = NULL; - } - - return errorcode; -} - -void DLLEXPORT SMO_errMessage(int errcode, char* errmsg, int n) -// -// Purpose: takes error code returns error message -// -// ERR410 "Error 410: SMO_init has not been called" -// ERR411 "Error 411: SMO_open has not been called" -// ERR414 "Error 414: memory allocation failure" -// -// ERR421 "Input Error 421: invalid parameter code" -// ERR422 "Input Error 422: reporting period index out of range" -// ERR423 "Input Error 423: element index out of range" -// ERR424 "Input Error 424: no memory allocated for results" -// -// ERR434 "File Error 434: unable to open binary output file" -// ERR435 "File Error 435: invalid file - not created by SWMM" -// ERR436 "File Error 436: invalid file - contains no results" -// ERR437 "File Error 437: invalid file - model run issued warnings" -// -// ERR440 "ERROR 440: an unspecified error has occurred" -{ - switch (errcode) - { - case 410: - strncpy(errmsg, ERR410, n); - break; - case 411: - strncpy(errmsg, ERR411, n); - break; - case 414: - strncpy(errmsg, ERR414, n); - break; - case 421: - strncpy(errmsg, ERR421, n); - break; - case 422: - strncpy(errmsg, ERR422, n); - break; - case 423: - strncpy(errmsg, ERR423, n); - break; - case 424: - strncpy(errmsg, ERR424, n); - break; - case 434: - strncpy(errmsg, ERR434, n); - break; - case 435: - strncpy(errmsg, ERR435, n); - break; - case 436: - strncpy(errmsg, ERR436, n); - break; - case 437: - strncpy(errmsg, ERR437, n); - break; - default: - strncpy(errmsg, ERR440, n); - } -} - - -// Local functions: -int validateFile(SMOutputAPI* smoapi) -{ - INT4 magic1, magic2, errcode; - int errorcode = 0; - - // --- fast forward to end and read epilogue - fseeko64(smoapi->file, -6 * RECORDSIZE, SEEK_END); - fread(&(smoapi->IDPos), RECORDSIZE, 1, smoapi->file); - fread(&(smoapi->ObjPropPos), RECORDSIZE, 1, smoapi->file); - fread(&(smoapi->ResultsPos), RECORDSIZE, 1, smoapi->file); - fread(&(smoapi->Nperiods), RECORDSIZE, 1, smoapi->file); - fread(&errcode, RECORDSIZE, 1, smoapi->file); - fread(&magic2, RECORDSIZE, 1, smoapi->file); - - // --- rewind and read magic number from beginning of the file - fseeko(smoapi->file, 0L, SEEK_SET); - fread(&magic1, RECORDSIZE, 1, smoapi->file); - - // Is this a valid SWMM binary output file? - if (magic1 != magic2) errorcode = 435; - // Does the binary file contain results? - else if (smoapi->Nperiods <= 0) errorcode = 436; - // Were there problems with the model run? - else if (errcode != 0) errorcode = 437; - - return errorcode; -} - -void initElementNames(SMOutputAPI* smoapi) -{ - int j, numNames; - - numNames = smoapi->Nsubcatch + smoapi->Nnodes + smoapi->Nlinks + smoapi->Npolluts; - - // allocate memory for array of idEntries - smoapi->elementNames = (idEntry*)calloc(numNames, sizeof(idEntry)); - - // Position the file to the start of the ID entries - fseeko64(smoapi->file, smoapi->IDPos, SEEK_SET); - - for(j=0;jelementNames[j].length), RECORDSIZE, 1, smoapi->file); - smoapi->elementNames[j].IDname = calloc(smoapi->elementNames[j].length + 1, sizeof(char)); - fread(smoapi->elementNames[j].IDname, sizeof(char), smoapi->elementNames[j].length, smoapi->file); - } -} - -double getTimeValue(SMOutputAPI* smoapi, long timeIndex) -{ - F_OFF offset; - double value; - - // --- compute offset into output file - offset = smoapi->ResultsPos + timeIndex*smoapi->BytesPerPeriod; - - // --- re-position the file and read the result - fseeko64(smoapi->file, offset, SEEK_SET); - fread(&value, RECORDSIZE * 2, 1, smoapi->file); - - return value; -} - -float getSubcatchValue(SMOutputAPI* smoapi, long timeIndex, int subcatchIndex, - SMO_subcatchAttribute attr) -{ - F_OFF offset; - float value; - - // --- compute offset into output file - offset = smoapi->ResultsPos + timeIndex*smoapi->BytesPerPeriod + 2*RECORDSIZE; - // offset for subcatch - offset += RECORDSIZE*(subcatchIndex*smoapi->SubcatchVars + attr); - - // --- re-position the file and read the result - fseeko64(smoapi->file, offset, SEEK_SET); - fread(&value, RECORDSIZE, 1, smoapi->file); - - return value; -} - -float getNodeValue(SMOutputAPI* smoapi, long timeIndex, int nodeIndex, - SMO_nodeAttribute attr) -{ - F_OFF offset; - float value; - - // --- compute offset into output file - offset = smoapi->ResultsPos + timeIndex*smoapi->BytesPerPeriod + 2*RECORDSIZE; - // offset for node - offset += RECORDSIZE*(smoapi->Nsubcatch*smoapi->SubcatchVars + nodeIndex*smoapi->NodeVars + attr); - - // --- re-position the file and read the result - fseeko64(smoapi->file, offset, SEEK_SET); - fread(&value, RECORDSIZE, 1, smoapi->file); - - return value; -} - -float getLinkValue(SMOutputAPI* smoapi, long timeIndex, int linkIndex, - SMO_linkAttribute attr) -{ - F_OFF offset; - float value; - - // --- compute offset into output file - offset = smoapi->ResultsPos + timeIndex*smoapi->BytesPerPeriod + 2*RECORDSIZE; - // offset for link - offset += RECORDSIZE*(smoapi->Nsubcatch*smoapi->SubcatchVars + smoapi->Nnodes*smoapi->NodeVars + - linkIndex*smoapi->LinkVars + attr); - - // --- re-position the file and read the result - fseeko64(smoapi->file, offset, SEEK_SET); - fread(&value, RECORDSIZE, 1, smoapi->file); - - return value; -} - -float getSystemValue(SMOutputAPI* smoapi, long timeIndex, - SMO_systemAttribute attr) -{ - F_OFF offset; - float value; - - // --- compute offset into output file - offset = smoapi->ResultsPos + timeIndex*smoapi->BytesPerPeriod + 2*RECORDSIZE; - // offset for system - offset += RECORDSIZE*(smoapi->Nsubcatch*smoapi->SubcatchVars + smoapi->Nnodes*smoapi->NodeVars + - smoapi->Nlinks*smoapi->LinkVars + attr); - - // --- re-position the file and read the result - fseeko64(smoapi->file, offset, SEEK_SET); - fread(&value, RECORDSIZE, 1, smoapi->file); - - return value; -} diff --git a/tools/swmm-output/src/outputapi.h b/tools/swmm-output/src/outputapi.h deleted file mode 100644 index 2428fa0df..000000000 --- a/tools/swmm-output/src/outputapi.h +++ /dev/null @@ -1,190 +0,0 @@ -/* -* outputAPI.h -* -* Author: Colleen Barr -* Modified by: Michael Tryby, -* Bryant McDonnell -* -* -*/ - -#ifndef OUTPUTAPI_H_ -#define OUTPUTAPI_H_ - -#define MAXFILENAME 259 // Max characters in file path -#define MAXELENAME 31 // Max characters in element name - -/*------------------- Error Messages --------------------*/ -#define ERR410 "Error 410: SMO_init() has not been called" -#define ERR411 "Error 411: SMO_open() has not been called" -#define ERR414 "Error 414: memory allocation failure" - -#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 ERR424 "Input Error 424: no memory allocated for results" - -#define ERR434 "File Error 434: unable to open binary output file" -#define ERR435 "File Error 435: invalid file - not created by SWMM" -#define ERR436 "File Error 436: invalid file - contains no results" -#define ERR437 "File Error 437: invalid file - model run issued warnings" - -#define ERR440 "ERROR 440: an unspecified error has occurred" - - -typedef struct SMOutputAPI SMOutputAPI; // opaque pointer - -typedef enum { - subcatchCount, - nodeCount, - linkCount, - pollutantCount - -} SMO_elementCount; - -typedef enum { - flow_rate, - concentration - -} SMO_unit; - -typedef enum { - //getSeries, - getAttribute, - getResult - -} SMO_apiFunction; - -typedef enum { - subcatch, - node, - link, - sys - -} SMO_elementType; - -typedef enum { -// reportStart, - reportStep, - numPeriods - -} SMO_time; - -typedef enum { - rainfall_subcatch, // (in/hr or mm/hr), - snow_depth_subcatch, // (in or mm), - evap_loss, // (in/hr or mm/hr), - infil_loss, // (in/hr or mm/hr), - runoff_rate, // (flow units), - gwoutflow_rate, // (flow units), - gwtable_elev, // (ft or m), - soil_moisture, // unsaturated zone moisture content (-), - pollutant_conc_subcatch // first pollutant - -} SMO_subcatchAttribute; - -typedef enum { - invert_depth, // (ft or m), - hydraulic_head, // (ft or m), - stored_ponded_volume, // (ft3 or m3), - lateral_inflow, // (flow units), - total_inflow, // lateral + upstream (flow units), - flooding_losses, // (flow units), - pollutant_conc_node // first pollutant, - -} SMO_nodeAttribute; - -typedef enum { - flow_rate_link, // (flow units), - flow_depth, // (ft or m), - flow_velocity, // (ft/s or m/s), - flow_volume, // (ft3 or m3), - capacity, // (fraction of conduit filled), - pollutant_conc_link // first pollutant, - -} SMO_linkAttribute; - -typedef enum { - air_temp, // (deg. F or deg. C), - rainfall_system, // (in/hr or mm/hr), - snow_depth_system, // (in or mm), - evap_infil_loss, // (in/hr or mm/hr), - runoff_flow, // (flow units), - dry_weather_inflow, // (flow units), - groundwater_inflow, // (flow units), - RDII_inflow, // (flow units), - direct_inflow, // user defined (flow units), - total_lateral_inflow, // (sum of variables 4 to 8) //(flow units), - flood_losses, // (flow units), - outfall_flows, // (flow units), - volume_stored, // (ft3 or m3), - evap_rate // (in/day or mm/day), - //p_evap_rate // (in/day or mm/day) -} SMO_systemAttribute; - - -#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 - -SMOutputAPI* DLLEXPORT SMO_init(void); -int DLLEXPORT SMO_open(SMOutputAPI* smoapi, const char* path); - -int DLLEXPORT SMO_getProjectSize(SMOutputAPI* smoapi, SMO_elementCount code, - int* count); - -int DLLEXPORT SMO_getUnits(SMOutputAPI* smoapi, SMO_unit code, int* unitFlag); -int DLLEXPORT SMO_getStartTime(SMOutputAPI* smoapi, double* time); -int DLLEXPORT SMO_getTimes(SMOutputAPI* smoapi, SMO_time code, int* time); - -int DLLEXPORT SMO_getElementName(SMOutputAPI* smoapi, SMO_elementType type, - int elementIndex, char* elementName, int length); - -float* DLLEXPORT SMO_newOutValueSeries(SMOutputAPI* smoapi, long seriesStart, - long seriesLength, long* length, int* errcode); -float* DLLEXPORT SMO_newOutValueArray(SMOutputAPI* smoapi, SMO_apiFunction func, - SMO_elementType type, long* length, int* errcode); - -int DLLEXPORT SMO_getSubcatchSeries(SMOutputAPI* smoapi, int subcatchIndex, - SMO_subcatchAttribute attr, long timeIndex, long length, float* outValueSeries); -int DLLEXPORT SMO_getNodeSeries(SMOutputAPI* smoapi, int nodeIndex, SMO_nodeAttribute attr, - long timeIndex, long length, float* outValueSeries); -int DLLEXPORT SMO_getLinkSeries(SMOutputAPI* smoapi, int linkIndex, SMO_linkAttribute attr, - long timeIndex, long length, float* outValueSeries); -int DLLEXPORT SMO_getSystemSeries(SMOutputAPI* smoapi, SMO_systemAttribute attr, - long timeIndex, long length, float *outValueSeries); - -int DLLEXPORT SMO_getSubcatchAttribute(SMOutputAPI* smoapi, long timeIndex, - SMO_subcatchAttribute attr, float* outValueArray); -int DLLEXPORT SMO_getNodeAttribute(SMOutputAPI* smoapi, long timeIndex, - SMO_nodeAttribute attr, float* outValueArray); -int DLLEXPORT SMO_getLinkAttribute(SMOutputAPI* smoapi, long timeIndex, - SMO_linkAttribute attr, float* outValueArray); -int DLLEXPORT SMO_getSystemAttribute(SMOutputAPI* smoapi, long timeIndex, - SMO_systemAttribute attr, float* outValueArray); - -int DLLEXPORT SMO_getSubcatchResult(SMOutputAPI* smoapi, long timeIndex, - int subcatchIndex, float* outValueArray, int* arrayLength); -int DLLEXPORT SMO_getNodeResult(SMOutputAPI* smoapi, long timeIndex, - int nodeIndex, float* outValueArray, int* arrayLength); -int DLLEXPORT SMO_getLinkResult(SMOutputAPI* smoapi, long timeIndex, - int linkIndex, float* outValueArray, int* arrayLength); -int DLLEXPORT SMO_getSystemResult(SMOutputAPI* smoapi, long timeIndex, - int dummyIndex, float* outValueArray, int* arrayLength); - -void DLLEXPORT SMO_free(float *array); - -int DLLEXPORT SMO_close(SMOutputAPI* smoapi); -void DLLEXPORT SMO_errMessage(int errcode, char* errmsg, int n); - -#endif /* OUTPUTAPI_H_ */ diff --git a/tools/swmm-output/test/main.c b/tools/swmm-output/test/main.c deleted file mode 100644 index 6584c237b..000000000 --- a/tools/swmm-output/test/main.c +++ /dev/null @@ -1,58 +0,0 @@ -/* -* main.c -* -* Author: Colleen Barr -* Modified by: Michael E. Tryby -* -*/ - -#include -#include -#include -#include "..\src\outputAPI.h" - -int testGetSubcatchResult(char* path) -{ - int error = 0; - long length; - float* array; - - SMOutputAPI* smoapi = SMO_init(); - error = SMO_open(smoapi, path); - - array = SMO_newOutValueArray(smoapi, getResult, subcatch, &length, &error); - error = SMO_getSubcatchResult(smoapi, 1, 0, array); - - if (!error) - { - for (int i = 0; i < length; i++) - printf("%f\n", array[i]); - } - printf("\n"); - - SMO_free(array); - error = SMO_close(smoapi); - - return error; -} - -int main(int argc, char* argv[]) -{ - int length, error = 0; - char path[MAXFILENAME] = "C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-testsuite\\Benchmarks\\v517\\Example1\\Example1.out"; - char name[MAXELENAME]; - SMOutputAPI* smoapi = SMO_init(); - - error = SMO_open(smoapi, path); - - length = MAXELENAME; - SMO_getElementName(smoapi, subcatch, 0, name, &length); - - length = MAXELENAME; - SMO_getElementName(smoapi, sys, 0, name, &length); - - testGetSubcatchResult(path); - - return error; - -} diff --git a/tools/swmm-reader/main.py b/tools/swmm-reader/main.py deleted file mode 100644 index 25571bc89..000000000 --- a/tools/swmm-reader/main.py +++ /dev/null @@ -1,165 +0,0 @@ -# -*- coding: utf-8 -*- -''' -Provides entry point main. Useful for development and testing purposes. -''' - -import cStringIO -import itertools as it -import time - -import header_detail_footer as hdf -import numpy as np - -import swmm_reader as sr - - -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 = sr.swmm_output_generator(path_test) - ref_reader = sr.swmm_output_generator(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, comp_args[0], comp_args[1]) - 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 - -def array_zero(test, ref): - if not test.any() and not ref.any(): - return True - return False - -def report_compare(path_test, path_ref, (comp_args)): - ''' - Compares results in two report files ignoring contents of header and footer. - ''' - with open(path_test ,'r') as ftest, open(path_ref, 'r') as fref: - for (test_line, ref_line) in it.izip(hdf.parse(ftest, 4, 4)[1], hdf.parse(fref, 4, 4)[1]): - if test_line != ref_line: - return False - - return True - - -import logging -from os import listdir -from os.path import exists, isfile, isdir, join - -from nrtest.testsuite import TestSuite -from nrtest.compare import compare_testsuite, validate_testsuite -from nrtest.execute import execute_testsuite - -def nrtest_compare(path_test, path_ref, rtol, atol): - - 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, rtol, atol) - 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): - - - for p in test_path + [app_path]: - if not exists(p): - logging.error('Could not find path: "%s"' % p) - - test_dirs = [p for p in test_path if isdir(p)] - test_files = [p for p in test_path if isfile(p)] - test_files += [join(d, p) for d in test_dirs for p in listdir(d) - 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 = "C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-testsuite\\apps\\swmm-5111_x64.json" -# test_path = "C:\\Users\mtryby\\Workspace\\GitRepo\\Local\\swmm-testsuite\\tests\\update_5111\\events_example.json" -# output_path = "C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-testsuite\\benchmarks\\v5111_x64" -# nrtest_execute(app_path, [test_path], output_path) - -# test_path = "C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-testsuite\\benchmarks\\v5111_x64" -# ref_path = "C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-testsuite\\benchmarks\\v5111_x86" -# nrtest_compare(test_path, ref_path, 0.001, 0.0) - - -# path_test = "C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-testsuite\\benchmarks\\v5111\\Example_4\\Example4.out" -# path_ref = "C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-testsuite\\benchmarks\\v5110\\Example_4\\Example4.out" -# result_compare(path_test, path_ref, (0.001, 0.0)) - - path_test = "C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-testsuite\\benchmarks\\v5111_bf\\lid_cat\\lid_cat.out" - path_ref = "C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-testsuite\\benchmarks\\v5110\\lid_cat\\lid_cat.out" - print(result_compare(path_test, path_ref, (1.0, 0.0))) - - \ No newline at end of file diff --git a/tools/swmm-reader/setup.py b/tools/swmm-reader/setup.py deleted file mode 100644 index 2995909da..000000000 --- a/tools/swmm-reader/setup.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- - -try: - from setuptools import setup -except ImportError: - from distutils.core import setup - - -setup( - name = "swmm-reader", - version = "0.2.0", - description = "Tools for reading SWMM binary results file", - - author="Michael E. Tryby", - author_email='tryby.michael@epa.gov', - url='https://github.com/USEPA', - - packages = ['swmm_reader'], - - install_requires = ['numpy', 'enum34'], - - package_data = { - 'swmm_reader':['*.dll'] - }, - include_package_data = True, - - zip_safe = False, - keywords='swmm_reader' -) diff --git a/tools/swmm-reader/swmm_reader/__init__.py b/tools/swmm-reader/swmm_reader/__init__.py deleted file mode 100644 index a85c0a0e1..000000000 --- a/tools/swmm-reader/swmm_reader/__init__.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- -''' -The function swmm_output_generator is used to iterate over a swmm binary file. -''' - -# project import -from .swmm_reader import * - - -__author__ = "Michael Tryby" -__copyright = "None" -__credits__ = "Colleen Barr, Maurizio Cingi, Mark Gray, David Hall, Bryant McDonnell" -__license__ = "CC0 1.0 Universal" - -__version__ = "0.2.0" -__maintainer__ = "Michael Tryby" -__email__= "tryby.michael@epa.gov" -__status = "Development" - - -def swmm_output_generator(path_ref): - ''' - swmm_output_generator is designed to iterate over a swmm binary file and - yield element results. It is useful for comparing contents of binary files - for numerical regression testing. - - The generator yields a numpy array containing the SWMM element result. - - Arguments: - path_ref - path to result file - - Raises: - SWMM_OutputReaderError() - ... - ''' - with SWMM_OutputReader(path_ref) as sor: - - for period_index in range(0, sor.report_periods()): - for element_type in ElementType: - for element_index in range(0, sor.element_count(element_type)): - - yield sor.element_result(element_type, period_index, element_index) - \ No newline at end of file diff --git a/tools/swmm-reader/swmm_reader/outputapi.py b/tools/swmm-reader/swmm_reader/outputapi.py deleted file mode 100644 index cbcf4df49..000000000 --- a/tools/swmm-reader/swmm_reader/outputapi.py +++ /dev/null @@ -1,1069 +0,0 @@ -# -*- coding: utf-8 -*- - -'''Wrapper for outputapi.h - -Generated with: -C:\Users\mtryby\dev\Anaconda2\Scripts\ctypesgen.py -a -l libswmm-output -o outputapi.py .\src\outputapi.h - -Do not modify this file ... - -THIS FILE HAS BEEN MODIFIED! See WindowsLibraryLoader.genplatformpaths() starting on line 560 -''' - -__docformat__ = 'restructuredtext' - -# Begin preamble - -import ctypes, os, sys -from ctypes import * - -_int_types = (c_int16, c_int32) -if hasattr(ctypes, 'c_int64'): - # Some builds of ctypes apparently do not have c_int64 - # defined; it's a pretty good bet that these builds do not - # have 64-bit pointers. - _int_types += (c_int64,) -for t in _int_types: - if sizeof(t) == sizeof(c_size_t): - c_ptrdiff_t = t -del t -del _int_types - -class c_void(Structure): - # c_void_p is a buggy return type, converting to int, so - # POINTER(None) == c_void_p is actually written as - # POINTER(c_void), so it can be treated as a real pointer. - _fields_ = [('dummy', c_int)] - -def POINTER(obj): - p = ctypes.POINTER(obj) - - # Convert None to a real NULL pointer to work around bugs - # in how ctypes handles None on 64-bit platforms - if not isinstance(p.from_param, classmethod): - def from_param(cls, x): - if x is None: - return cls() - else: - return x - p.from_param = classmethod(from_param) - - return p - -class UserString: - def __init__(self, seq): - if isinstance(seq, basestring): - self.data = seq - elif isinstance(seq, UserString): - self.data = seq.data[:] - else: - self.data = str(seq) - def __str__(self): return str(self.data) - def __repr__(self): return repr(self.data) - def __int__(self): return int(self.data) - def __long__(self): return long(self.data) - def __float__(self): return float(self.data) - def __complex__(self): return complex(self.data) - def __hash__(self): return hash(self.data) - - def __cmp__(self, string): - if isinstance(string, UserString): - return cmp(self.data, string.data) - else: - return cmp(self.data, string) - def __contains__(self, char): - return char in self.data - - def __len__(self): return len(self.data) - def __getitem__(self, index): return self.__class__(self.data[index]) - def __getslice__(self, start, end): - start = max(start, 0); end = max(end, 0) - return self.__class__(self.data[start:end]) - - def __add__(self, other): - if isinstance(other, UserString): - return self.__class__(self.data + other.data) - elif isinstance(other, basestring): - return self.__class__(self.data + other) - else: - return self.__class__(self.data + str(other)) - def __radd__(self, other): - if isinstance(other, basestring): - return self.__class__(other + self.data) - else: - return self.__class__(str(other) + self.data) - def __mul__(self, n): - return self.__class__(self.data*n) - __rmul__ = __mul__ - def __mod__(self, args): - return self.__class__(self.data % args) - - # the following methods are defined in alphabetical order: - def capitalize(self): return self.__class__(self.data.capitalize()) - def center(self, width, *args): - return self.__class__(self.data.center(width, *args)) - def count(self, sub, start=0, end=sys.maxint): - return self.data.count(sub, start, end) - def decode(self, encoding=None, errors=None): # XXX improve this? - if encoding: - if errors: - return self.__class__(self.data.decode(encoding, errors)) - else: - return self.__class__(self.data.decode(encoding)) - else: - return self.__class__(self.data.decode()) - def encode(self, encoding=None, errors=None): # XXX improve this? - if encoding: - if errors: - return self.__class__(self.data.encode(encoding, errors)) - else: - return self.__class__(self.data.encode(encoding)) - else: - return self.__class__(self.data.encode()) - def endswith(self, suffix, start=0, end=sys.maxint): - return self.data.endswith(suffix, start, end) - def expandtabs(self, tabsize=8): - return self.__class__(self.data.expandtabs(tabsize)) - def find(self, sub, start=0, end=sys.maxint): - return self.data.find(sub, start, end) - def index(self, sub, start=0, end=sys.maxint): - return self.data.index(sub, start, end) - def isalpha(self): return self.data.isalpha() - def isalnum(self): return self.data.isalnum() - def isdecimal(self): return self.data.isdecimal() - def isdigit(self): return self.data.isdigit() - def islower(self): return self.data.islower() - def isnumeric(self): return self.data.isnumeric() - def isspace(self): return self.data.isspace() - def istitle(self): return self.data.istitle() - def isupper(self): return self.data.isupper() - def join(self, seq): return self.data.join(seq) - def ljust(self, width, *args): - return self.__class__(self.data.ljust(width, *args)) - def lower(self): return self.__class__(self.data.lower()) - def lstrip(self, chars=None): return self.__class__(self.data.lstrip(chars)) - def partition(self, sep): - return self.data.partition(sep) - def replace(self, old, new, maxsplit=-1): - return self.__class__(self.data.replace(old, new, maxsplit)) - def rfind(self, sub, start=0, end=sys.maxint): - return self.data.rfind(sub, start, end) - def rindex(self, sub, start=0, end=sys.maxint): - return self.data.rindex(sub, start, end) - def rjust(self, width, *args): - return self.__class__(self.data.rjust(width, *args)) - def rpartition(self, sep): - return self.data.rpartition(sep) - def rstrip(self, chars=None): return self.__class__(self.data.rstrip(chars)) - def split(self, sep=None, maxsplit=-1): - return self.data.split(sep, maxsplit) - def rsplit(self, sep=None, maxsplit=-1): - return self.data.rsplit(sep, maxsplit) - def splitlines(self, keepends=0): return self.data.splitlines(keepends) - def startswith(self, prefix, start=0, end=sys.maxint): - return self.data.startswith(prefix, start, end) - def strip(self, chars=None): return self.__class__(self.data.strip(chars)) - def swapcase(self): return self.__class__(self.data.swapcase()) - def title(self): return self.__class__(self.data.title()) - def translate(self, *args): - return self.__class__(self.data.translate(*args)) - def upper(self): return self.__class__(self.data.upper()) - def zfill(self, width): return self.__class__(self.data.zfill(width)) - -class MutableString(UserString): - """mutable string objects - - Python strings are immutable objects. This has the advantage, that - strings may be used as dictionary keys. If this property isn't needed - and you insist on changing string values in place instead, you may cheat - and use MutableString. - - But the purpose of this class is an educational one: to prevent - people from inventing their own mutable string class derived - from UserString and than forget thereby to remove (override) the - __hash__ method inherited from UserString. This would lead to - errors that would be very hard to track down. - - A faster and better solution is to rewrite your program using lists.""" - def __init__(self, string=""): - self.data = string - def __hash__(self): - raise TypeError("unhashable type (it is mutable)") - def __setitem__(self, index, sub): - if index < 0: - index += len(self.data) - if index < 0 or index >= len(self.data): raise IndexError - self.data = self.data[:index] + sub + self.data[index+1:] - def __delitem__(self, index): - if index < 0: - index += len(self.data) - if index < 0 or index >= len(self.data): raise IndexError - self.data = self.data[:index] + self.data[index+1:] - def __setslice__(self, start, end, sub): - start = max(start, 0); end = max(end, 0) - if isinstance(sub, UserString): - self.data = self.data[:start]+sub.data+self.data[end:] - elif isinstance(sub, basestring): - self.data = self.data[:start]+sub+self.data[end:] - else: - self.data = self.data[:start]+str(sub)+self.data[end:] - def __delslice__(self, start, end): - start = max(start, 0); end = max(end, 0) - self.data = self.data[:start] + self.data[end:] - def immutable(self): - return UserString(self.data) - def __iadd__(self, other): - if isinstance(other, UserString): - self.data += other.data - elif isinstance(other, basestring): - self.data += other - else: - self.data += str(other) - return self - def __imul__(self, n): - self.data *= n - return self - -class String(MutableString, Union): - - _fields_ = [('raw', POINTER(c_char)), - ('data', c_char_p)] - - def __init__(self, obj=""): - if isinstance(obj, (str, unicode, UserString)): - self.data = str(obj) - else: - self.raw = obj - - def __len__(self): - return self.data and len(self.data) or 0 - - def from_param(cls, obj): - # Convert None or 0 - if obj is None or obj == 0: - return cls(POINTER(c_char)()) - - # Convert from String - elif isinstance(obj, String): - return obj - - # Convert from str - elif isinstance(obj, str): - return cls(obj) - - # Convert from c_char_p - elif isinstance(obj, c_char_p): - return obj - - # Convert from POINTER(c_char) - elif isinstance(obj, POINTER(c_char)): - return obj - - # Convert from raw pointer - elif isinstance(obj, int): - return cls(cast(obj, POINTER(c_char))) - - # Convert from object - else: - return String.from_param(obj._as_parameter_) - from_param = classmethod(from_param) - -def ReturnString(obj, func=None, arguments=None): - return String.from_param(obj) - -# As of ctypes 1.0, ctypes does not support custom error-checking -# functions on callbacks, nor does it support custom datatypes on -# callbacks, so we must ensure that all callbacks return -# primitive datatypes. -# -# Non-primitive return values wrapped with UNCHECKED won't be -# typechecked, and will be converted to c_void_p. -def UNCHECKED(type): - if (hasattr(type, "_type_") and isinstance(type._type_, str) - and type._type_ != "P"): - return type - else: - return c_void_p - -# ctypes doesn't have direct support for variadic functions, so we have to write -# our own wrapper class -class _variadic_function(object): - def __init__(self,func,restype,argtypes): - self.func=func - self.func.restype=restype - self.argtypes=argtypes - def _as_parameter_(self): - # So we can pass this variadic function as a function pointer - return self.func - def __call__(self,*args): - fixed_args=[] - i=0 - for argtype in self.argtypes: - # Typecheck what we can - fixed_args.append(argtype.from_param(args[i])) - i+=1 - return self.func(*fixed_args+list(args[i:])) - -# End preamble - -_libs = {} -_libdirs = [] - -# Begin loader - -# ---------------------------------------------------------------------------- -# Copyright (c) 2008 David James -# Copyright (c) 2006-2008 Alex Holkner -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in -# the documentation and/or other materials provided with the -# distribution. -# * Neither the name of pyglet nor the names of its -# contributors may be used to endorse or promote products -# derived from this software without specific prior written -# permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. -# ---------------------------------------------------------------------------- - -import os.path, re, sys, glob -import ctypes -import ctypes.util - -def _environ_path(name): - if name in os.environ: - return os.environ[name].split(":") - else: - return [] - -class LibraryLoader(object): - def __init__(self): - self.other_dirs=[] - - def load_library(self,libname): - """Given the name of a library, load it.""" - paths = self.getpaths(libname) - - for path in paths: - if os.path.exists(path): - return self.load(path) - - raise ImportError("%s not found." % libname) - - def load(self,path): - """Given a path to a library, load it.""" - try: - # Darwin requires dlopen to be called with mode RTLD_GLOBAL instead - # of the default RTLD_LOCAL. Without this, you end up with - # libraries not being loadable, resulting in "Symbol not found" - # errors - if sys.platform == 'darwin': - return ctypes.CDLL(path, ctypes.RTLD_GLOBAL) - else: - return ctypes.cdll.LoadLibrary(path) - except OSError,e: - raise ImportError(e) - - def getpaths(self,libname): - """Return a list of paths where the library might be found.""" - if os.path.isabs(libname): - yield libname - - else: - for path in self.getplatformpaths(libname): - yield path - - path = ctypes.util.find_library(libname) - if path: yield path - - def getplatformpaths(self, libname): - return [] - -# Darwin (Mac OS X) - -class DarwinLibraryLoader(LibraryLoader): - name_formats = ["lib%s.dylib", "lib%s.so", "lib%s.bundle", "%s.dylib", - "%s.so", "%s.bundle", "%s"] - - def getplatformpaths(self,libname): - if os.path.pathsep in libname: - names = [libname] - else: - names = [format % libname for format in self.name_formats] - - for dir in self.getdirs(libname): - for name in names: - yield os.path.join(dir,name) - - def getdirs(self,libname): - '''Implements the dylib search as specified in Apple documentation: - - http://developer.apple.com/documentation/DeveloperTools/Conceptual/ - DynamicLibraries/Articles/DynamicLibraryUsageGuidelines.html - - Before commencing the standard search, the method first checks - the bundle's ``Frameworks`` directory if the application is running - within a bundle (OS X .app). - ''' - - dyld_fallback_library_path = _environ_path("DYLD_FALLBACK_LIBRARY_PATH") - if not dyld_fallback_library_path: - dyld_fallback_library_path = [os.path.expanduser('~/lib'), - '/usr/local/lib', '/usr/lib'] - - dirs = [] - - if '/' in libname: - dirs.extend(_environ_path("DYLD_LIBRARY_PATH")) - else: - dirs.extend(_environ_path("LD_LIBRARY_PATH")) - dirs.extend(_environ_path("DYLD_LIBRARY_PATH")) - - dirs.extend(self.other_dirs) - dirs.append(".") - - if hasattr(sys, 'frozen') and sys.frozen == 'macosx_app': - dirs.append(os.path.join( - os.environ['RESOURCEPATH'], - '..', - 'Frameworks')) - - dirs.extend(dyld_fallback_library_path) - - return dirs - -# Posix - -class PosixLibraryLoader(LibraryLoader): - _ld_so_cache = None - - def _create_ld_so_cache(self): - # Recreate search path followed by ld.so. This is going to be - # slow to build, and incorrect (ld.so uses ld.so.cache, which may - # not be up-to-date). Used only as fallback for distros without - # /sbin/ldconfig. - # - # We assume the DT_RPATH and DT_RUNPATH binary sections are omitted. - - directories = [] - for name in ("LD_LIBRARY_PATH", - "SHLIB_PATH", # HPUX - "LIBPATH", # OS/2, AIX - "LIBRARY_PATH", # BE/OS - ): - if name in os.environ: - directories.extend(os.environ[name].split(os.pathsep)) - directories.extend(self.other_dirs) - directories.append(".") - - try: directories.extend([dir.strip() for dir in open('/etc/ld.so.conf')]) - except IOError: pass - - directories.extend(['/lib', '/usr/lib', '/lib64', '/usr/lib64']) - - cache = {} - lib_re = re.compile(r'lib(.*)\.s[ol]') - ext_re = re.compile(r'\.s[ol]$') - for dir in directories: - try: - for path in glob.glob("%s/*.s[ol]*" % dir): - file = os.path.basename(path) - - # Index by filename - if file not in cache: - cache[file] = path - - # Index by library name - match = lib_re.match(file) - if match: - library = match.group(1) - if library not in cache: - cache[library] = path - except OSError: - pass - - self._ld_so_cache = cache - - def getplatformpaths(self, libname): - if self._ld_so_cache is None: - self._create_ld_so_cache() - - result = self._ld_so_cache.get(libname) - if result: yield result - - path = ctypes.util.find_library(libname) - if path: yield os.path.join("/lib",path) - -# Windows - -class _WindowsLibrary(object): - def __init__(self, path): - self.cdll = ctypes.cdll.LoadLibrary(path) - self.windll = ctypes.windll.LoadLibrary(path) - - def __getattr__(self, name): - try: return getattr(self.cdll,name) - except AttributeError: - try: return getattr(self.windll,name) - except AttributeError: - raise - -class WindowsLibraryLoader(LibraryLoader): - name_formats = ["%s.dll", "lib%s.dll", "%slib.dll"] - - def load_library(self, libname): - try: - result = LibraryLoader.load_library(self, libname) - except ImportError: - result = None - if os.path.sep not in libname: - for name in self.name_formats: - try: - result = getattr(ctypes.cdll, name % libname) - if result: - break - except WindowsError: - result = None - if result is None: - try: - result = getattr(ctypes.cdll, libname) - except WindowsError: - result = None - if result is None: - raise ImportError("%s not found." % libname) - return result - - def load(self, path): - return _WindowsLibrary(path) - -############################################################################### -###### THIS NEEDS TO BE HAND CODED OR LIBRARY LOAD WILL BREAK ON WINDOWS ###### - def getplatformpaths(self, libname): - if os.path.sep not in libname: - for name in self.name_formats: - # search the directory where this file is executing - dll_in_package_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), name % libname) - if os.path.exists(dll_in_package_dir): - yield dll_in_package_dir - # search in directories passed as arguments to wrapper generator - for dir in self.other_dirs: - dll_in_other_dirs = os.path.join(dir, name % libname) - if os.path.exists(dll_in_other_dirs): - yield dll_in_other_dirs - # searches current working directory - dll_in_current_dir = os.path.abspath(name % libname) - if os.path.exists(dll_in_current_dir): - yield dll_in_current_dir - # searches system path - path = ctypes.util.find_library(name % libname) - if path: - yield path -############################################################################### -############################################################################### - -# Platform switching - -# If your value of sys.platform does not appear in this dict, please contact -# the Ctypesgen maintainers. - -loaderclass = { - "darwin": DarwinLibraryLoader, - "cygwin": WindowsLibraryLoader, - "win32": WindowsLibraryLoader -} - -loader = loaderclass.get(sys.platform, PosixLibraryLoader)() - -def add_library_search_dirs(other_dirs): - loader.other_dirs = other_dirs - -load_library = loader.load_library - -del loaderclass - -# End loader - -add_library_search_dirs([]) - -# Begin libraries - -_libs["libswmm-output"] = load_library("libswmm-output") - -# 1 libraries -# End libraries - -# No modules - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 33 -class struct_SMOutputAPI(Structure): - pass - -SMOutputAPI = struct_SMOutputAPI # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 33 - -enum_anon_1 = c_int # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 41 - -subcatchCount = 0 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 41 - -nodeCount = (subcatchCount + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 41 - -linkCount = (nodeCount + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 41 - -pollutantCount = (linkCount + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 41 - -SMO_elementCount = enum_anon_1 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 41 - -enum_anon_2 = c_int # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 47 - -flow_rate = 0 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 47 - -concentration = (flow_rate + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 47 - -SMO_unit = enum_anon_2 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 47 - -enum_anon_3 = c_int # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 54 - -getAttribute = 0 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 54 - -getResult = (getAttribute + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 54 - -SMO_apiFunction = enum_anon_3 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 54 - -enum_anon_4 = c_int # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 62 - -subcatch = 0 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 62 - -node = (subcatch + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 62 - -link = (node + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 62 - -_sys = (link + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 62 - -SMO_elementType = enum_anon_4 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 62 - -enum_anon_5 = c_int # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 69 - -reportStep = 0 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 69 - -numPeriods = (reportStep + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 69 - -SMO_time = enum_anon_5 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 69 - -enum_anon_6 = c_int # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 82 - -rainfall_subcatch = 0 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 82 - -snow_depth_subcatch = (rainfall_subcatch + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 82 - -evap_loss = (snow_depth_subcatch + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 82 - -infil_loss = (evap_loss + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 82 - -runoff_rate = (infil_loss + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 82 - -gwoutflow_rate = (runoff_rate + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 82 - -gwtable_elev = (gwoutflow_rate + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 82 - -soil_moisture = (gwtable_elev + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 82 - -pollutant_conc_subcatch = (soil_moisture + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 82 - -SMO_subcatchAttribute = enum_anon_6 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 82 - -enum_anon_7 = c_int # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 93 - -invert_depth = 0 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 93 - -hydraulic_head = (invert_depth + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 93 - -stored_ponded_volume = (hydraulic_head + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 93 - -lateral_inflow = (stored_ponded_volume + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 93 - -total_inflow = (lateral_inflow + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 93 - -flooding_losses = (total_inflow + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 93 - -pollutant_conc_node = (flooding_losses + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 93 - -SMO_nodeAttribute = enum_anon_7 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 93 - -enum_anon_8 = c_int # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 103 - -flow_rate_link = 0 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 103 - -flow_depth = (flow_rate_link + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 103 - -flow_velocity = (flow_depth + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 103 - -flow_volume = (flow_velocity + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 103 - -capacity = (flow_volume + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 103 - -pollutant_conc_link = (capacity + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 103 - -SMO_linkAttribute = enum_anon_8 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 103 - -enum_anon_9 = c_int # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -air_temp = 0 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -rainfall_system = (air_temp + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -snow_depth_system = (rainfall_system + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -evap_infil_loss = (snow_depth_system + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -runoff_flow = (evap_infil_loss + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -dry_weather_inflow = (runoff_flow + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -groundwater_inflow = (dry_weather_inflow + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -RDII_inflow = (groundwater_inflow + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -direct_inflow = (RDII_inflow + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -total_lateral_inflow = (direct_inflow + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -flood_losses = (total_lateral_inflow + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -outfall_flows = (flood_losses + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -volume_stored = (outfall_flows + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -evap_rate = (volume_stored + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -SMO_systemAttribute = enum_anon_9 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 138 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_init'): - continue - SMO_init = _lib.SMO_init - SMO_init.argtypes = [] - SMO_init.restype = POINTER(SMOutputAPI) - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 139 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_open'): - continue - SMO_open = _lib.SMO_open - SMO_open.argtypes = [POINTER(SMOutputAPI), String] - SMO_open.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 141 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getProjectSize'): - continue - SMO_getProjectSize = _lib.SMO_getProjectSize - SMO_getProjectSize.argtypes = [POINTER(SMOutputAPI), SMO_elementCount, POINTER(c_int)] - SMO_getProjectSize.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 143 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getUnits'): - continue - SMO_getUnits = _lib.SMO_getUnits - SMO_getUnits.argtypes = [POINTER(SMOutputAPI), SMO_unit, POINTER(c_int)] - SMO_getUnits.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 144 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getStartTime'): - continue - SMO_getStartTime = _lib.SMO_getStartTime - SMO_getStartTime.argtypes = [POINTER(SMOutputAPI), POINTER(c_double)] - SMO_getStartTime.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 145 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getTimes'): - continue - SMO_getTimes = _lib.SMO_getTimes - SMO_getTimes.argtypes = [POINTER(SMOutputAPI), SMO_time, POINTER(c_int)] - SMO_getTimes.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 147 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getElementName'): - continue - SMO_getElementName = _lib.SMO_getElementName - SMO_getElementName.argtypes = [POINTER(SMOutputAPI), SMO_elementType, c_int, String, c_int] - SMO_getElementName.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 150 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_newOutValueSeries'): - continue - SMO_newOutValueSeries = _lib.SMO_newOutValueSeries - SMO_newOutValueSeries.argtypes = [POINTER(SMOutputAPI), c_long, c_long, POINTER(c_long), POINTER(c_int)] - SMO_newOutValueSeries.restype = POINTER(c_float) - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 152 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_newOutValueArray'): - continue - SMO_newOutValueArray = _lib.SMO_newOutValueArray - SMO_newOutValueArray.argtypes = [POINTER(SMOutputAPI), SMO_apiFunction, SMO_elementType, POINTER(c_long), POINTER(c_int)] - SMO_newOutValueArray.restype = POINTER(c_float) - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 155 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getSubcatchSeries'): - continue - SMO_getSubcatchSeries = _lib.SMO_getSubcatchSeries - SMO_getSubcatchSeries.argtypes = [POINTER(SMOutputAPI), c_int, SMO_subcatchAttribute, c_long, c_long, POINTER(c_float)] - SMO_getSubcatchSeries.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 157 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getNodeSeries'): - continue - SMO_getNodeSeries = _lib.SMO_getNodeSeries - SMO_getNodeSeries.argtypes = [POINTER(SMOutputAPI), c_int, SMO_nodeAttribute, c_long, c_long, POINTER(c_float)] - SMO_getNodeSeries.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 159 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getLinkSeries'): - continue - SMO_getLinkSeries = _lib.SMO_getLinkSeries - SMO_getLinkSeries.argtypes = [POINTER(SMOutputAPI), c_int, SMO_linkAttribute, c_long, c_long, POINTER(c_float)] - SMO_getLinkSeries.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 161 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getSystemSeries'): - continue - SMO_getSystemSeries = _lib.SMO_getSystemSeries - SMO_getSystemSeries.argtypes = [POINTER(SMOutputAPI), SMO_systemAttribute, c_long, c_long, POINTER(c_float)] - SMO_getSystemSeries.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 164 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getSubcatchAttribute'): - continue - SMO_getSubcatchAttribute = _lib.SMO_getSubcatchAttribute - SMO_getSubcatchAttribute.argtypes = [POINTER(SMOutputAPI), c_long, SMO_subcatchAttribute, POINTER(c_float)] - SMO_getSubcatchAttribute.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 166 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getNodeAttribute'): - continue - SMO_getNodeAttribute = _lib.SMO_getNodeAttribute - SMO_getNodeAttribute.argtypes = [POINTER(SMOutputAPI), c_long, SMO_nodeAttribute, POINTER(c_float)] - SMO_getNodeAttribute.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 168 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getLinkAttribute'): - continue - SMO_getLinkAttribute = _lib.SMO_getLinkAttribute - SMO_getLinkAttribute.argtypes = [POINTER(SMOutputAPI), c_long, SMO_linkAttribute, POINTER(c_float)] - SMO_getLinkAttribute.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 170 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getSystemAttribute'): - continue - SMO_getSystemAttribute = _lib.SMO_getSystemAttribute - SMO_getSystemAttribute.argtypes = [POINTER(SMOutputAPI), c_long, SMO_systemAttribute, POINTER(c_float)] - SMO_getSystemAttribute.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 173 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getSubcatchResult'): - continue - SMO_getSubcatchResult = _lib.SMO_getSubcatchResult - SMO_getSubcatchResult.argtypes = [POINTER(SMOutputAPI), c_long, c_int, POINTER(c_float), POINTER(c_int)] - SMO_getSubcatchResult.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 175 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getNodeResult'): - continue - SMO_getNodeResult = _lib.SMO_getNodeResult - SMO_getNodeResult.argtypes = [POINTER(SMOutputAPI), c_long, c_int, POINTER(c_float), POINTER(c_int)] - SMO_getNodeResult.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 177 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getLinkResult'): - continue - SMO_getLinkResult = _lib.SMO_getLinkResult - SMO_getLinkResult.argtypes = [POINTER(SMOutputAPI), c_long, c_int, POINTER(c_float), POINTER(c_int)] - SMO_getLinkResult.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 179 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getSystemResult'): - continue - SMO_getSystemResult = _lib.SMO_getSystemResult - SMO_getSystemResult.argtypes = [POINTER(SMOutputAPI), c_long, c_int, POINTER(c_float), POINTER(c_int)] - SMO_getSystemResult.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 182 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_free'): - continue - SMO_free = _lib.SMO_free - SMO_free.argtypes = [POINTER(c_float)] - SMO_free.restype = None - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 184 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_close'): - continue - SMO_close = _lib.SMO_close - SMO_close.argtypes = [POINTER(SMOutputAPI)] - SMO_close.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 185 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_errMessage'): - continue - SMO_errMessage = _lib.SMO_errMessage - SMO_errMessage.argtypes = [c_int, String, c_int] - SMO_errMessage.restype = None - break - -__const = c_int # : 5 - -# : 8 -try: - CTYPESGEN = 1 -except: - pass - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 13 -try: - MAXFILENAME = 259 -except: - pass - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 14 -try: - MAXELENAME = 31 -except: - pass - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 17 -try: - ERR410 = 'Error 410: SMO_init() has not been called' -except: - pass - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 18 -try: - ERR411 = 'Error 411: SMO_open() has not been called' -except: - pass - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 19 -try: - ERR414 = 'Error 414: memory allocation failure' -except: - pass - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 21 -try: - ERR421 = 'Input Error 421: invalid parameter code' -except: - pass - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 22 -try: - ERR422 = 'Input Error 422: reporting period index out of range' -except: - pass - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 23 -try: - ERR423 = 'Input Error 423: element index out of range' -except: - pass - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 24 -try: - ERR424 = 'Input Error 424: no memory allocated for results' -except: - pass - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 26 -try: - ERR434 = 'File Error 434: unable to open binary output file' -except: - pass - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 27 -try: - ERR435 = 'File Error 435: invalid file - not created by SWMM' -except: - pass - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 28 -try: - ERR436 = 'File Error 436: invalid file - contains no results' -except: - pass - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 29 -try: - ERR437 = 'File Error 437: invalid file - model run issued warnings' -except: - pass - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 31 -try: - ERR440 = 'ERROR 440: an unspecified error has occurred' -except: - pass - -SMOutputAPI = struct_SMOutputAPI # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 33 - -# No inserted files - diff --git a/tools/swmm-reader/swmm_reader/swmm_reader.py b/tools/swmm-reader/swmm_reader/swmm_reader.py deleted file mode 100644 index 65579a02d..000000000 --- a/tools/swmm-reader/swmm_reader/swmm_reader.py +++ /dev/null @@ -1,105 +0,0 @@ -# -*- coding: utf-8 -*- -''' -The module swmm_reader provides classes used to implement the swmm output -generator. -''' - -# system imports -import ctypes - -# third party imports -import enum -import numpy as np - -# project import -import outputapi - - -__author__ = "Michael Tryby" -__copyright = "None" -__credits__ = "Colleen Barr, Maurizio Cingi, Mark Gray, David Hall, Bryant McDonnell" -__license__ = "CC0 1.0 Universal" - -__version__ = "0.2.0" -__maintainer__ = "Michael Tryby" -__email__= "tryby.michael@epa.gov" -__status = "Development" - - -_err_max_char = 80 - -class ElementType(enum.Enum): - subcatch = outputapi.subcatch - node = outputapi.node - link = outputapi.link - system = outputapi._sys - -class SWMM_OutputReaderError(Exception): - ''' - Custom exception class for SWMM errors. - ''' - def __init__(self, error_code, error_message): - self.warning = False - self.args = (error_code,) - self.message = error_message - - def __str__(self): - return self.message - -class SWMM_OutputReader(): - ''' - Provides minimal functionality needed to implement the SWMM output generator. - ''' - def __init__(self, filename): - self.filepath = filename - self.ptr_api = ctypes.c_void_p - self.ptr_resultbuff = ctypes.c_void_p - self.bufflength = ctypes.c_long() - self.getElementResult = {ElementType.subcatch: outputapi.SMO_getSubcatchResult, - ElementType.node: outputapi.SMO_getNodeResult, - ElementType.link: outputapi.SMO_getLinkResult, - ElementType.system: outputapi.SMO_getSystemResult} - - def __enter__(self): - self.ptr_api = outputapi.SMO_init() - self._error_check(outputapi.SMO_open(self.ptr_api, ctypes.c_char_p(self.filepath.encode()))) - - # max system result is vector with 15 elements so should be adequate for result buffer - # TODO: What about when there are more than six pollutants defined? - error = ctypes.c_long() - self.ptr_resultbuff = outputapi.SMO_newOutValueArray(self.ptr_api, ctypes.c_int(outputapi.getResult), - ctypes.c_int(ElementType.system.value), - ctypes.byref(self.bufflength), ctypes.byref(error)) - self._error_check(error.value) - return self - - def __exit__(self, type, value, traceback): - outputapi.SMO_free(self.ptr_resultbuff) - self._error_check(outputapi.SMO_close(self.ptr_api)) - - def _error_message(self, code): - error_code = ctypes.c_int(code) - error_message = outputapi.String(ctypes.create_string_buffer(_err_max_char)) - outputapi.SMO_errMessage(error_code, error_message, _err_max_char) - return error_message.data - - def _error_check(self, err): - if err != 0: - raise SWMM_OutputReaderError(err, self._error_message(err)) - - def report_periods(self): - num_periods = ctypes.c_int() - self._error_check(outputapi.SMO_getTimes(self.ptr_api, outputapi.numPeriods, ctypes.byref(num_periods))) - return num_periods.value - - def element_count(self, element_type): - count = ctypes.c_int() - self._error_check(outputapi.SMO_getProjectSize(self.ptr_api, ctypes.c_int(element_type.value), ctypes.byref(count))) - return count.value - - def element_result(self, element_type, time_index, element_index): - time_idx = ctypes.c_long(time_index) - element_idx = ctypes.c_int(element_index) - count = ctypes.c_int() - self._error_check(self.getElementResult[element_type](self.ptr_api, time_idx, element_idx, self.ptr_resultbuff, ctypes.byref(count))) - return np.fromiter((self.ptr_resultbuff[i] for i in range(count.value)), np.float, count.value) From 21cf316f0f97f82bdcbf9bd93405059632f3e195 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 4 Mar 2020 16:06:13 -0500 Subject: [PATCH 003/190] Updating gage.c --- src/solver/gage.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solver/gage.c b/src/solver/gage.c index d7852e579..b8b0e65d0 100644 --- a/src/solver/gage.c +++ b/src/solver/gage.c @@ -5,7 +5,7 @@ // Version: 5.1 // Date: 03/20/10 (Build 5.1.001) // 09/15/14 (Build 5.1.007) -// 11/27/17 (Build 5.1.013) +// 05/10/18 (Build 5.1.013) // Author: L. Rossman // // Rain gage functions. From bb51eeebf1acb47f2e9ba4b587d45d108212c314 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 5 Mar 2020 17:24:54 -0500 Subject: [PATCH 004/190] Update CMakeLists.txt Updates compiler and linker settings to match results generated with VS2017_DLL project file distributed with swmm release --- src/solver/CMakeLists.txt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index e9453d7e5..0201aa474 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -27,6 +27,22 @@ add_library(swmm5 ${SWMM_SOURCES} ) +target_compile_options(swmm5 +PUBLIC + "$<$:" + "$<$:/GL>" + "$<$:/fp:fast>" + "$<$:/Zi>" + ">" +) + +target_link_options(swmm5 + PUBLIC + "$<$:" + "$<$:/LTCG:incremental>" + ">" + ) + target_link_libraries(swmm5 PUBLIC $<$>>:m> From 00788d9c8fbd16d7d6cbb97ab840c2747946a991 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 5 Mar 2020 17:28:01 -0500 Subject: [PATCH 005/190] Update CMakeLists.txt --- src/solver/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index 0201aa474..66d939447 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -1,8 +1,8 @@ # # CMakeLists.txt - CMake configuration file for swmm-solver/library # -# Created: July 11, 2019 -# Modified: Nov 25, 2019 +# Created: Jul 11, 2019 +# Updated: Mar 5, 2020 # # Author: Michael E. Tryby # US EPA ORD/CESER From cb6cc3d9d5e7f1f75632a610c7c5dc3ae8cdefd1 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 6 Mar 2020 14:49:16 -0500 Subject: [PATCH 006/190] Work in progress Reconciles differences --- src/solver/roadway.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/solver/roadway.c b/src/solver/roadway.c index 02ec4e604..0237ea96d 100644 --- a/src/solver/roadway.c +++ b/src/solver/roadway.c @@ -24,7 +24,10 @@ #include #include "headers.h" -enum RoadSurface {PAVED = 1, GRAVEL}; +enum RoadSurface { + PAVED = 1, + GRAVEL = 2 +}; //----------------------------------------------------------------------------- // Constants @@ -39,7 +42,7 @@ enum RoadSurface {PAVED = 1, GRAVEL}; static const int N_Cr_Low_Paved = 4; static const double Cr_Low_Paved[4][2] = { {0.0, 2.85}, {0.2, 2.95}, {0.7, 3.03}, {4.0, 3.05}}; - + static const int N_Cr_Low_Gravel = 8; static const double Cr_Low_Gravel[8][2] = { {0.0, 2.5}, {0.5, 2.7}, {1.0, 2.8}, {1.5, 2.9}, {2.0, 2.98}, @@ -187,4 +190,3 @@ double getY(double x, const double table[][2], const int n) } return table[n-1][1]; } - From a74e2622bc5dfa02c0c6e19d0527c1adba454354 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 6 Mar 2020 14:50:12 -0500 Subject: [PATCH 007/190] Work in progress Moves def file to bindings folder Adds build option for def file --- CMakeLists.txt | 5 +++-- {src/solver => bindings}/swmm5.def | 0 src/solver/CMakeLists.txt | 36 +++++++++++++++++++++--------- 3 files changed, 29 insertions(+), 12 deletions(-) rename {src/solver => bindings}/swmm5.def (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index bc9b5f36a..5918cfe51 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,7 +40,8 @@ set(LIBRARY_DIST "lib") set(CONFIG_DIST "cmake") -option(BUILD_TESTS "Build component tests (requires Boost)" OFF) +option(BUILD_TESTS "Builds component tests (requires Boost)" OFF) +option(BUILD_DEF "Builds library with def file interface" OFF) # Add project subdirectories @@ -72,7 +73,7 @@ install( FILE swmm-output-config.cmake ) - + # Create install rules for vcruntime.dll, msvcp.dll, vcomp.dll etc. set(CMAKE_INSTALL_OPENMP_LIBRARIES TRUE) include(InstallRequiredSystemLibraries) diff --git a/src/solver/swmm5.def b/bindings/swmm5.def similarity index 100% rename from src/solver/swmm5.def rename to bindings/swmm5.def diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index 66d939447..f109b5f49 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -22,19 +22,35 @@ file(GLOB RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.c *.h ) -add_library(swmm5 - SHARED - ${SWMM_SOURCES} +if(BUILD_DEF) + # Build library with def file interface for backward compatibility + set_source_files_properties(${PROJECT_SOURCE_DIR}/bindings/swmm5.def + PROPERTIES_HEADER_FILE_ONLY TRUE ) + add_library(swmm5 + SHARED + ${SWMM_SOURCES} + ${PROJECT_SOURCE_DIR/bindings/swmm5.def} + ) + +else() + + add_library(swmm5 + SHARED + ${SWMM_SOURCES} + ) + +endif() + target_compile_options(swmm5 -PUBLIC - "$<$:" - "$<$:/GL>" - "$<$:/fp:fast>" - "$<$:/Zi>" - ">" -) + PUBLIC + "$<$:" + "$<$:/GL>" + "$<$:/fp:fast>" + "$<$:/Zi>" + ">" + ) target_link_options(swmm5 PUBLIC From af37e4da0c337ccf9fb234de8032dbdca755a839 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 6 Mar 2020 14:51:53 -0500 Subject: [PATCH 008/190] Work in progress Changes default abs tol from 0.0 to 1.E0-6 --- tools/run-nrtests.cmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd index fb02a11ae..c51d96c80 100644 --- a/tools/run-nrtests.cmd +++ b/tools/run-nrtests.cmd @@ -87,7 +87,7 @@ set TEST_OUTPUT_PATH=benchmark\%PROJECT%-%SUT_BUILD_ID% set NRTEST_COMPARE_CMD=python.exe %NRTEST_SCRIPT_PATH%\nrtest compare set REF_OUTPUT_PATH=benchmark\%PROJECT%-%REF_BUILD_ID% set RTOL_VALUE=0.01 -set ATOL_VALUE=0.0 +set ATOL_VALUE=1.E-6 :: change current directory to test suite ::cd %TEST_HOME% From 87281a817e100f332199f3b5ebfefa5aa619753e Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 10 Mar 2020 10:59:17 -0400 Subject: [PATCH 009/190] Delete swmm_output_export.h --- src/outfile/include/swmm_output_export.h | 42 ------------------------ 1 file changed, 42 deletions(-) delete mode 100644 src/outfile/include/swmm_output_export.h diff --git a/src/outfile/include/swmm_output_export.h b/src/outfile/include/swmm_output_export.h deleted file mode 100644 index 3396ce6b4..000000000 --- a/src/outfile/include/swmm_output_export.h +++ /dev/null @@ -1,42 +0,0 @@ - -#ifndef EXPORT_OUT_API_H -#define EXPORT_OUT_API_H - -#ifdef SHARED_EXPORTS_BUILT_AS_STATIC -# define EXPORT_OUT_API -# define SWMM_OUTPUT_NO_EXPORT -#else -# ifndef EXPORT_OUT_API -# ifdef swmm_output_EXPORTS - /* We are building this library */ -# define EXPORT_OUT_API __declspec(dllexport) -# else - /* We are using this library */ -# define EXPORT_OUT_API __declspec(dllimport) -# endif -# endif - -# ifndef SWMM_OUTPUT_NO_EXPORT -# define SWMM_OUTPUT_NO_EXPORT -# endif -#endif - -#ifndef SWMM_OUTPUT_DEPRECATED -# define SWMM_OUTPUT_DEPRECATED __declspec(deprecated) -#endif - -#ifndef SWMM_OUTPUT_DEPRECATED_EXPORT -# define SWMM_OUTPUT_DEPRECATED_EXPORT EXPORT_OUT_API SWMM_OUTPUT_DEPRECATED -#endif - -#ifndef SWMM_OUTPUT_DEPRECATED_NO_EXPORT -# define SWMM_OUTPUT_DEPRECATED_NO_EXPORT SWMM_OUTPUT_NO_EXPORT SWMM_OUTPUT_DEPRECATED -#endif - -#if 0 /* DEFINE_NO_DEPRECATED */ -# ifndef SWMM_OUTPUT_NO_DEPRECATED -# define SWMM_OUTPUT_NO_DEPRECATED -# endif -#endif - -#endif /* EXPORT_OUT_API_H */ From d591081170f5cc2b11815dc5194b58f4ce28b58c Mon Sep 17 00:00:00 2001 From: calebbuahin <7217571+cbuahin@users.noreply.github.com> Date: Thu, 14 Jan 2021 03:55:29 -0500 Subject: [PATCH 010/190] Fixed shared variable declaration error --- src/solver/include/swmm5.h | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/solver/include/swmm5.h b/src/solver/include/swmm5.h index f2eaf9872..e1547243c 100644 --- a/src/solver/include/swmm5.h +++ b/src/solver/include/swmm5.h @@ -26,11 +26,30 @@ // --- define DLLEXPORT +//#ifndef DLLEXPORT #ifdef WINDOWS - #define DLLEXPORT __declspec(dllexport) __stdcall + #ifdef __MINGW32__ + // Seems to be more wrapper friendly + #define DLLEXPORT __declspec(dllexport) __cdecl + #else + #define DLLEXPORT __declspec(dllexport) __stdcall + #endif #else - #define DLLEXPORT + #define DLLEXPORT #endif +//#endif + + +//----------------------------------------------------------------------------- +// Shared variables +//----------------------------------------------------------------------------- +static int IsOpenFlag; // TRUE if a project has been opened +static int IsStartedFlag; // TRUE if a simulation has been started +static int SaveResultsFlag; // TRUE if output to be saved to binary file + +int swmm_IsOpenFlag(void); +int swmm_IsStartedFlag(void); + // --- use "C" linkage for C++ programs From 4e52d666f2eada609598cdf25cf7eefdf9b56e59 Mon Sep 17 00:00:00 2001 From: calebbuahin <7217571+cbuahin@users.noreply.github.com> Date: Thu, 14 Jan 2021 04:00:41 -0500 Subject: [PATCH 011/190] Fixed conflicting types --- src/solver/include/swmm5.h | 81 ++++++++++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 12 deletions(-) diff --git a/src/solver/include/swmm5.h b/src/solver/include/swmm5.h index e1547243c..0aa59c62f 100644 --- a/src/solver/include/swmm5.h +++ b/src/solver/include/swmm5.h @@ -57,18 +57,75 @@ int swmm_IsStartedFlag(void); extern "C" { #endif -int DLLEXPORT swmm_run(char* f1, char* f2, char* f3); -int DLLEXPORT swmm_open(char* f1, char* f2, char* f3); -int DLLEXPORT swmm_start(int saveFlag); -int DLLEXPORT swmm_step(double* elapsedTime); -int DLLEXPORT swmm_end(void); -int DLLEXPORT swmm_report(void); -int DLLEXPORT swmm_getMassBalErr(float* runoffErr, float* flowErr, - float* qualErr); -int DLLEXPORT swmm_close(void); -int DLLEXPORT swmm_getVersion(void); -int DLLEXPORT swmm_getError(char* errMsg, int msgLen); -int DLLEXPORT swmm_getWarnings(void); +/** + @brief Opens SWMM input file, reads in network data, runs, and closes + @param f1 pointer to name of input file (must exist) + @param f2 pointer to name of report file (to be created) + @param f3 pointer to name of binary output file (to be created) + @return error code +*/ +int DLLEXPORT swmm_run(const char *f1, const char *f2, const char *f3); + +/** + @brief Opens SWMM input file & reads in network data + @param f1 pointer to name of input file (must exist) + @param f2 pointer to name of report file (to be created) + @param f3 pointer to name of binary output file (to be created) + @return error code +*/ +int DLLEXPORT swmm_open(const char *f1, const char *f2, const char *f3); + +/** + @brief Start SWMM simulation + @param saveFlag TRUE or FALSE to save timeseries to report file + @return error code +*/ +int DLLEXPORT swmm_start(int saveFlag); + +/** + @brief Step SWMM simulation forward + @param[out] elapsedTime elapsed simulation time [milliseconds] + @return error code +*/ +int DLLEXPORT swmm_step(double* elapsedTime); + +/** + @brief End SWMM simulation + @return error code +*/ +int DLLEXPORT swmm_end(void); + +/** + @brief Write text report file + @return error code +*/ +int DLLEXPORT swmm_report(void); + +/** + @brief Get routing errors + @param[out] runoffErr Runoff routing error + @param[out] flowErr Flow routing error + @param[out] qualErr Quality routing error + @return error code +*/ +int DLLEXPORT swmm_getMassBalErr(float* runoffErr, float* flowErr, float* qualErr); + +/** + @brief Frees all memory and files used by SWMM + @return Error code +*/ +int DLLEXPORT swmm_close(void); + +/** + @brief Get Legacy SWMM version number + @return Version +*/ +int DLLEXPORT swmm_getVersion(void); + + +int DLLEXPORT swmm_getError(char* errMsg, int msgLen); + +int DLLEXPORT swmm_getWarnings(void); #ifdef __cplusplus } // matches the linkage specification from above */ From 5741b01393aa30ec17c3dc08308c356529a2aba2 Mon Sep 17 00:00:00 2001 From: calebbuahin <7217571+cbuahin@users.noreply.github.com> Date: Thu, 14 Jan 2021 04:09:52 -0500 Subject: [PATCH 012/190] Updated ci-tools submodule --- ci-tools | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci-tools b/ci-tools index a3d6d55bb..6a636baad 160000 --- a/ci-tools +++ b/ci-tools @@ -1 +1 @@ -Subproject commit a3d6d55bbb11dc0e2a3c78ddfa0092f64b813959 +Subproject commit 6a636baadb70fcb546ce7f32ae41419e53df9606 From 8fb3e28d6487ee71d00eb1886c976b29afc2c573 Mon Sep 17 00:00:00 2001 From: calebbuahin <7217571+cbuahin@users.noreply.github.com> Date: Thu, 14 Jan 2021 09:42:14 -0500 Subject: [PATCH 013/190] Changed ci-tools to master --- ci-tools | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci-tools b/ci-tools index 6a636baad..98e969009 160000 --- a/ci-tools +++ b/ci-tools @@ -1 +1 @@ -Subproject commit 6a636baadb70fcb546ce7f32ae41419e53df9606 +Subproject commit 98e969009b092d2a27550ba4f44f0eca1661a970 From 2038d4e89960b25dbb9dec5aad52ac4d43a2f661 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 21 Jan 2021 14:04:42 -0500 Subject: [PATCH 014/190] Adding lib relocation for Linux --- CMakeLists.txt | 2 +- src/solver/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e771219de..8064489b5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,7 +58,7 @@ endif() # Pass var up the project tree -if(APPLE) +if(NOT MSVC) set(EXTERN_LIB_PATH ${EXTERN_LIB_PATH} PARENT_SCOPE) endif() diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index 5108d1a61..d7376de64 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -104,7 +104,7 @@ install( # TODO: Figure out why this doesn't work for package target # When building on MacOS relocate libomp to install package -if(APPLE) +if(NOT MSVC) get_filename_component(EXTERN_LIB_PATH ${OpenMP_libomp_LIBRARY} REALPATH) install( From 0351a2a41165107fd02c4ce21ed5a94fc9d5fefd Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 21 Jan 2021 14:55:15 -0500 Subject: [PATCH 015/190] Updating ci-tools --- ci-tools | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci-tools b/ci-tools index 0eb92bbbe..98e969009 160000 --- a/ci-tools +++ b/ci-tools @@ -1 +1 @@ -Subproject commit 0eb92bbbe905b745f9933dd0773179bca6176930 +Subproject commit 98e969009b092d2a27550ba4f44f0eca1661a970 From 6607497018ed66836913d084b98fb1b5e63327b7 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 22 Jan 2021 15:42:14 -0500 Subject: [PATCH 016/190] Refactoring wheel build --- CMakeLists.txt | 6 +++++- src/outfile/CMakeLists.txt | 10 ++++++++++ src/solver/CMakeLists.txt | 39 +++++++++++++++++++++++++++++++------- 3 files changed, 47 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8064489b5..74f1e39f0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,10 @@ # US EPA ORD/CESER # +################################################################################ +################## CMAKELISTS FOR SWMM-SOLVER PROJECT #################### +################################################################################ + cmake_minimum_required (VERSION 3.13) @@ -57,7 +61,7 @@ if(BUILD_TESTS) endif() -# Pass var up the project tree +# Pass var up the project tree used for library reloacation if(NOT MSVC) set(EXTERN_LIB_PATH ${EXTERN_LIB_PATH} PARENT_SCOPE) endif() diff --git a/src/outfile/CMakeLists.txt b/src/outfile/CMakeLists.txt index 914eadadc..5a7996a16 100644 --- a/src/outfile/CMakeLists.txt +++ b/src/outfile/CMakeLists.txt @@ -41,6 +41,16 @@ file(COPY ${CMAKE_CURRENT_BINARY_DIR}/swmm_output_export.h ) +set_target_properties(swmm-output + PROPERTIES + MACOSX_RPATH TRUE + SKIP_BUILD_RPATH FALSE + BUILD_WITH_INSTALL_RPATH FALSE + INSTALL_RPATH "${PACKAGE_RPATH}" + INSTALL_RPATH_USE_LINK_PATH TRUE +) + + install(TARGETS swmm-output EXPORT swmm-outputTargets diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index d7376de64..717539487 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -8,6 +8,10 @@ # US EPA ORD/CESER # +################################################################################ +################### CMAKELISTS FOR SWMM-SOLVER TARGET #################### +################################################################################ + find_package(OpenMP REQUIRED) @@ -26,7 +30,7 @@ file(GLOB ) if(BUILD_DEF) - # Build library with def file interface for backward compatibility + # Builds library with def file interface for backward compatibility set_source_files_properties(${PROJECT_SOURCE_DIR}/bindings/swmm5.def PROPERTIES_HEADER_FILE_ONLY TRUE ) @@ -39,7 +43,7 @@ if(BUILD_DEF) ) else() - + # Performs standard library build add_library(swmm5 SHARED ${SWMM_SOURCES} @@ -48,6 +52,7 @@ else() endif() +# Sets MSVC compiler flags target_compile_options(swmm5 PUBLIC "$<$:" @@ -78,6 +83,17 @@ target_include_directories(swmm5 ${CMAKE_CURRENT_SOURCE_DIR}/.. ) + +set_target_properties(swmm5 + PROPERTIES + MACOSX_RPATH TRUE + SKIP_BUILD_RPATH FALSE + BUILD_WITH_INSTALL_RPATH FALSE + #INSTALL_RPATH "${PACKAGE_RPATH}" + INSTALL_RPATH_USE_LINK_PATH TRUE +) + + install(TARGETS swmm5 EXPORT swmm5Targets RUNTIME DESTINATION "${TOOL_DIST}" LIBRARY DESTINATION "${LIBRARY_DIST}" @@ -102,19 +118,28 @@ install( ) -# TODO: Figure out why this doesn't work for package target -# When building on MacOS relocate libomp to install package +# TODO: Figure out why relocation doesn't work for package target + +# When building on Linux/MacOS relocate OpenMP library to install for wheel build if(NOT MSVC) - get_filename_component(EXTERN_LIB_PATH ${OpenMP_libomp_LIBRARY} REALPATH) + if (APPLE) + set(OMP_LIB_NAME ${OpenMP_libomp_LIBRARY}) + elseif (UNIX AND NOT APPLE) + set(OMP_LIB_NAME ${OpenMP_gomp_LIBRARY}) + endif() + + get_filename_component(EXTERN_LIB_PATH ${OMP_LIB_NAME} REALPATH) install( FILES ${EXTERN_LIB_PATH} DESTINATION - ${CMAKE_INSTALL_PREFIX}/extern + ${CMAKE_INSTALL_PREFIX}/lib + PERMISSIONS + OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE ) - # Pass the var up the project tree + # Pass the var up the project tree set(EXTERN_LIB_PATH ${EXTERN_LIB_PATH} PARENT_SCOPE) endif() From 7311ef87b155cc66e28b1736f20120dadb6b2199 Mon Sep 17 00:00:00 2001 From: calebbuahin <7217571+cbuahin@users.noreply.github.com> Date: Sun, 24 Jan 2021 17:16:59 -0500 Subject: [PATCH 017/190] Added string.h header for strncmp function --- src/run/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/run/main.c b/src/run/main.c index b4af1f112..e0eb84036 100644 --- a/src/run/main.c +++ b/src/run/main.c @@ -11,6 +11,7 @@ #include #include +#include #include "swmm5.h" int main(int argc, char *argv[]) From f762f779f2aaee5fdc14eefa4a6ac6c77e779678 Mon Sep 17 00:00:00 2001 From: calebbuahin <7217571+cbuahin@users.noreply.github.com> Date: Tue, 26 Jan 2021 04:50:09 -0500 Subject: [PATCH 018/190] Trigger rebuild --- src/run/timer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/run/timer.c b/src/run/timer.c index ea15e23a4..59224314b 100644 --- a/src/run/timer.c +++ b/src/run/timer.c @@ -26,7 +26,7 @@ long current_time_millis(void) if (sec < 0) sec = tv.tv_sec; - return (tv.tv_sec - sec)*1000 + tv.tv_usec/1000; + return (tv.tv_sec - sec)*1000 + tv.tv_usec/1000; #endif } From a3503b5ab24f7668937a29d7d45ee940c3440ed1 Mon Sep 17 00:00:00 2001 From: calebbuahin <7217571+cbuahin@users.noreply.github.com> Date: Tue, 26 Jan 2021 10:53:38 -0500 Subject: [PATCH 019/190] Reverted to previous version of aenum --- ci-tools | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci-tools b/ci-tools index 98e969009..04a65bd49 160000 --- a/ci-tools +++ b/ci-tools @@ -1 +1 @@ -Subproject commit 98e969009b092d2a27550ba4f44f0eca1661a970 +Subproject commit 04a65bd4965fbf27bc5e8c8289c9519630cca422 From a060fa54607fc855c6ef7b0332645639b5a35427 Mon Sep 17 00:00:00 2001 From: calebbuahin <7217571+cbuahin@users.noreply.github.com> Date: Tue, 26 Jan 2021 11:04:27 -0500 Subject: [PATCH 020/190] Trigger rebuild --- ci-tools | 2 +- src/run/timer.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ci-tools b/ci-tools index 04a65bd49..4e02d7583 160000 --- a/ci-tools +++ b/ci-tools @@ -1 +1 @@ -Subproject commit 04a65bd4965fbf27bc5e8c8289c9519630cca422 +Subproject commit 4e02d758355933ff9f280537ebdd1da67bd74a61 diff --git a/src/run/timer.h b/src/run/timer.h index 49e576f4b..2e89c5ccb 100644 --- a/src/run/timer.h +++ b/src/run/timer.h @@ -9,7 +9,7 @@ */ #ifndef TIMER_H -#define TIMER_H +#define TIMER_H // System includes From 5c8bf78ceefc7f8bb4cef60c6097034d98f2daef Mon Sep 17 00:00:00 2001 From: cbuahin Date: Tue, 16 Feb 2021 13:51:03 -0500 Subject: [PATCH 021/190] Pointed ci-tools to dev --- ci-tools | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci-tools b/ci-tools index 4e02d7583..6a636baad 160000 --- a/ci-tools +++ b/ci-tools @@ -1 +1 @@ -Subproject commit 4e02d758355933ff9f280537ebdd1da67bd74a61 +Subproject commit 6a636baadb70fcb546ce7f32ae41419e53df9606 From b94bd2b47eead9f45a88291e4d94ca457704c1d1 Mon Sep 17 00:00:00 2001 From: cbuahin Date: Tue, 16 Feb 2021 14:06:19 -0500 Subject: [PATCH 022/190] Reverted ci-tools --- ci-tools | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci-tools b/ci-tools index 6a636baad..8f4e856b5 160000 --- a/ci-tools +++ b/ci-tools @@ -1 +1 @@ -Subproject commit 6a636baadb70fcb546ce7f32ae41419e53df9606 +Subproject commit 8f4e856b5eb6e17bfaca0f7d316bb2aa30787bbb From 2dc88550fc97def4e330e75e6298773da01a0055 Mon Sep 17 00:00:00 2001 From: cbuahin Date: Tue, 16 Feb 2021 14:08:57 -0500 Subject: [PATCH 023/190] Switched ci-tools to master --- ci-tools | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci-tools b/ci-tools index 8f4e856b5..13cbde7a5 160000 --- a/ci-tools +++ b/ci-tools @@ -1 +1 @@ -Subproject commit 8f4e856b5eb6e17bfaca0f7d316bb2aa30787bbb +Subproject commit 13cbde7a523c4265a0d4127612bc77dfab7aa96f From 4b2bf5d61c03d6a68342ec07c94de4adfef3e164 Mon Sep 17 00:00:00 2001 From: calebbuahin <7217571+cbuahin@users.noreply.github.com> Date: Fri, 19 Feb 2021 19:17:15 -0500 Subject: [PATCH 024/190] Addressed PR comments --- ci-tools | 2 +- src/outfile/CMakeLists.txt | 3 - src/outfile/include/swmm_output_enums.h | 1 - src/run/CMakeLists.txt | 3 - src/run/main.c | 161 +++++++++++++++--------- src/solver/CMakeLists.txt | 6 +- src/solver/{ => bindings}/swmm5.def | 0 src/solver/consts.h | 2 +- 8 files changed, 105 insertions(+), 73 deletions(-) rename src/solver/{ => bindings}/swmm5.def (100%) diff --git a/ci-tools b/ci-tools index 13cbde7a5..4e02d7583 160000 --- a/ci-tools +++ b/ci-tools @@ -1 +1 @@ -Subproject commit 13cbde7a523c4265a0d4127612bc77dfab7aa96f +Subproject commit 4e02d758355933ff9f280537ebdd1da67bd74a61 diff --git a/src/outfile/CMakeLists.txt b/src/outfile/CMakeLists.txt index 02d47a042..c50d93b0e 100644 --- a/src/outfile/CMakeLists.txt +++ b/src/outfile/CMakeLists.txt @@ -7,9 +7,6 @@ # Author: Michael E. Tryby # US EPA ORD/CESER # -# Modified by: Caleb Buahin -# Xylem Inc. -# # # configure file groups diff --git a/src/outfile/include/swmm_output_enums.h b/src/outfile/include/swmm_output_enums.h index 37893b54a..23c53c78e 100644 --- a/src/outfile/include/swmm_output_enums.h +++ b/src/outfile/include/swmm_output_enums.h @@ -7,7 +7,6 @@ * Author: Michael E. Tryby * US EPA - ORD/CESER * - * Modified by: Caleb Buahin */ diff --git a/src/run/CMakeLists.txt b/src/run/CMakeLists.txt index 2a8831936..28325ba82 100644 --- a/src/run/CMakeLists.txt +++ b/src/run/CMakeLists.txt @@ -7,9 +7,6 @@ # Modified by: Michael E. Tryby # US EPA ORD/NRMRL # -# Caleb Buahin -# Xylem Inc. -# # Location of libs runswmm uses at runtime on MacOS/Linux diff --git a/src/run/main.c b/src/run/main.c index e0eb84036..91f304e24 100644 --- a/src/run/main.c +++ b/src/run/main.c @@ -12,7 +12,57 @@ #include #include #include + + +// Private project includes +#include "timer.h" + +// Public project includes #include "swmm5.h" +#include "toolkit.h" + + +#define BAR_LEN 50l +#define MSG_LEN 84 + +static long Start; + + +void write_console(char *msg) +{ + fprintf(stdout, "%s", msg); + fflush(stdout); +} + +void progress_bar(double *ratio) +{ + char bar[BAR_LEN + 1] = {'\0'}; + + // Create progress bar + long prog_len = lround(*ratio * BAR_LEN); + memset(bar, ' ', (size_t)BAR_LEN); + if (prog_len < BAR_LEN) + memset(bar, '>', (size_t)(prog_len + 1)); + memset(bar, '=', (size_t)prog_len); + + // Compute percent complete + double pct = *ratio * 100.0; + + // Compute time remaining using naive approach + long t_r = 0; + if (*ratio > 0.0) + t_r = lround((1.0 - *ratio) * (current_time_millis() - Start) / *ratio); + + // Format and print progress + char msg[MSG_LEN + 1] = {'\0'}; + char tmp[TIMER_LEN + 1] = {'\0'}; + + write_console("\r"); + snprintf(msg, (size_t)(MSG_LEN + 1), "... Running [%50s] %5.1f%% [%8s]", bar, + pct, format_time(tmp, t_r)); + write_console(msg); +} + int main(int argc, char *argv[]) // @@ -26,79 +76,70 @@ int main(int argc, char *argv[]) // f3 = name of binary output file if saved (or blank if not saved). // { - char *inputFile; - char *reportFile; - char *binaryFile; - char *arg1; - char blank[] = ""; - int version, vMajor, vMinor, vRelease; - char errMsg[128]; - int msgLen = 127; - time_t start; - double runTime; - - version = swmm_getVersion(); - vMajor = version / 10000; - vMinor = (version - 10000 * vMajor) / 1000; - vRelease = (version - 10000 * vMajor - 1000 * vMinor); - start = time(0); - - // --- check for proper number of command line arguments - if (argc == 1) - { - printf("\nNot Enough Arguments (See Help --help)\n\n"); + // --- check for proper number of command line arguments + if (argc == 4) { + // --- extract file names from command line arguments + char *inputFile = argv[1]; + char *reportFile = argv[2]; + + char *binaryFile = ""; + if (argc > 3) + binaryFile = argv[3]; + + Start = current_time_millis(); + // --- run SWMM + swmm_run_cb(inputFile, reportFile, binaryFile, &progress_bar); + + long stop = current_time_millis(); + char time[TIMER_LEN + 1] = {'\0'}; + + printf("\n\n... EPA-SWMM completed in %s", format_time(time, stop - Start)); + + char errMsg[128]; + int msgLen = 127; + + if ( swmm_getError(errMsg, msgLen) > 0 ) + printf(" with errors.\n"); + + else if ( swmm_getWarnings() > 0 ) + printf(" with warnings.\n"); + + else + printf(" successfully.\n"); } - else if (argc == 2) - { + + else if (argc == 2) { // --- extract first argument - arg1 = argv[1]; + char *arg1 = argv[1]; - if (strcmp(arg1, "--help") == 0 || strcmp(arg1, "-h") == 0) - { + if (strcmp(arg1, "--help") == 0 || strcmp(arg1, "-h") == 0) { // Help - printf("\n\nSTORMWATER MANAGEMENT MODEL (SWMM5) HELP\n\n"); - printf("COMMANDS:\n"); + printf("\n\nEPA Stormwater Management Model (SWMM5) Help\n\n"); + printf("Commands:\n"); printf("\t--help (-h) Help Docs\n"); printf("\t--version (-v) Build Version\n"); - printf("\nRUNNING A SIMULATION:\n"); + printf("\nUsage:\n"); printf("\t swmm5 \n\n"); } - else if (strcmp(arg1, "--version") == 0 || strcmp(arg1, "-v") == 0) - { + else if (strcmp(arg1, "--version") == 0 || strcmp(arg1, "-v") == 0) { + int version = swmm_getVersion(); + int vMajor = version / 10000; + int vMinor = (version - 10000 * vMajor) / 1000; + int vRelease = (version - 10000 * vMajor - 1000 * vMinor); + // Output version number - printf("\n%d.%d.%0d\n\n", vMajor, vMinor, vRelease); + printf("\nVersion:\n"); + printf("\tEPA-SWMM %d.%d.%0d\n\n", vMajor, vMinor, vRelease); } - else - { - printf("\nUnknown Argument (See Help --help)\n\n"); + else { + printf("\nError:\n"); + printf("\tUnknown Argument (See Help --help)\n\n"); } } - else - { - // --- extract file names from command line arguments - inputFile = argv[1]; - reportFile = argv[2]; - if (argc > 3) binaryFile = argv[3]; - else binaryFile = blank; - printf("\n... EPA-SWMM %d.%d (Build %d.%d.%0d)\n", vMajor, vMinor, - vMajor, vMinor, vRelease); - - // --- run SWMM - swmm_run(inputFile, reportFile, binaryFile); - - // Display closing status on console - runTime = difftime(time(0), start); - printf("\n\n... EPA-SWMM completed in %.2f seconds.", runTime); - if ( swmm_getError(errMsg, msgLen) > 0 ) printf(" There are errors.\n"); - else if ( swmm_getWarnings() > 0 ) printf(" There are warnings.\n"); - else printf("\n"); + else { + printf("\nUsage:\n"); + printf("\trunswmm \n\n"); } -// --- Use the code below if you need to keep the console window visible -/* - printf(" Press Enter to continue..."); - getchar(); -*/ - return 0; } \ No newline at end of file diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index 1aba56a86..42fa979b9 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -7,9 +7,7 @@ # Author: Michael E. Tryby # US EPA ORD/CESER # -# Caleb Buahin -# Xylem Inc. -# + find_package(OpenMP REQUIRED) @@ -30,7 +28,7 @@ file(GLOB if(BUILD_DEF) # Build library with def file interface for backward compatibility - set_source_files_properties(${PROJECT_SOURCE_DIR}/swmm5.def + set_source_files_properties(${PROJECT_SOURCE_DIR}/bindings/swmm5.def PROPERTIES_HEADER_FILE_ONLY TRUE ) diff --git a/src/solver/swmm5.def b/src/solver/bindings/swmm5.def similarity index 100% rename from src/solver/swmm5.def rename to src/solver/bindings/swmm5.def diff --git a/src/solver/consts.h b/src/solver/consts.h index 7beb1a430..ad72d126b 100644 --- a/src/solver/consts.h +++ b/src/solver/consts.h @@ -20,7 +20,7 @@ #define VERSION 51014 // Eventually will be deprecated. #define SEMVERSION_MAJOR "5" // Major Semantic Version #define SEMVERSION_MINOR "1" // Minor Semantic Version -#define SEMVERSION_PATCH "014.dev7" // Patch Semantic Version +#define SEMVERSION_PATCH "014" // Patch Semantic Version #define SEMVERSION_LEN 20 // Version String Len #define MAGICNUMBER 516114522 From ece7bcaa0b687ad5f03eef3c8fc7fa0b4ea4cb50 Mon Sep 17 00:00:00 2001 From: calebbuahin <7217571+cbuahin@users.noreply.github.com> Date: Fri, 19 Feb 2021 19:29:17 -0500 Subject: [PATCH 025/190] Deprecate old build --- Build.md | 33 --------- src/outfile/include/swmm_output.h | 1 - tests/CMakeLists.txt | 4 +- tests/Unit_Testing.md | 35 --------- tools/Reg_Testing.md | 50 ------------- tools/app-config.cmd | 58 --------------- tools/before-nrtest.cmd | 116 ------------------------------ tools/make.cmd | 108 ---------------------------- tools/requirements-win.txt | 22 ------ tools/run-nrtests.cmd | 114 ----------------------------- 10 files changed, 1 insertion(+), 540 deletions(-) delete mode 100644 Build.md delete mode 100644 tests/Unit_Testing.md delete mode 100644 tools/Reg_Testing.md delete mode 100644 tools/app-config.cmd delete mode 100644 tools/before-nrtest.cmd delete mode 100644 tools/make.cmd delete mode 100644 tools/requirements-win.txt delete mode 100644 tools/run-nrtests.cmd diff --git a/Build.md b/Build.md deleted file mode 100644 index cd677408b..000000000 --- a/Build.md +++ /dev/null @@ -1,33 +0,0 @@ - - -## Building SWMM Locally on Windows - - -### Dependencies - -Before the project can be built the required dependencies must be installed. - -** Summary of Build Dependencies: Windows - - - Build - - Build Tools for Visual Studio 2017 - - CMake 3.13 - - -### Build - -SWMM can be built with one simple command. -``` -\> cd swmm -\swmm>tools\make.cmd -``` diff --git a/src/outfile/include/swmm_output.h b/src/outfile/include/swmm_output.h index ded3f0528..3c57ad29e 100644 --- a/src/outfile/include/swmm_output.h +++ b/src/outfile/include/swmm_output.h @@ -6,7 +6,6 @@ * * Modified by: Michael E. Tryby, * Bryant McDonnell - * Caleb Buahin * */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c84b8ac29..17f17d546 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -7,9 +7,7 @@ # Author: Michael E. Tryby # US EPA ORD/CESER # -# Modified by: Caleb Buahin -# Xylem Inc. -# + #Prep ourselves for compiling with boost diff --git a/tests/Unit_Testing.md b/tests/Unit_Testing.md deleted file mode 100644 index 65869798d..000000000 --- a/tests/Unit_Testing.md +++ /dev/null @@ -1,35 +0,0 @@ - - - -## Unit Testing SWMM locally on Windows - -### Dependencies - -Before the project can be built and tested the required dependencies must be installed. - -**Summary of Build Dependencies: Windows** - - - Build - - Build Tools for Visual Studio 2017 - - CMake 3.13 - - - Unit Test - - Boost 1.67.0 (installed in default location "C:\\local") - - - -### Build and Unit Test - -SWMM can be built and unit tests run with one simple command. -``` -\> cd swmm -\swmm>tools\make.cmd /t -``` diff --git a/tools/Reg_Testing.md b/tools/Reg_Testing.md deleted file mode 100644 index b6d31afec..000000000 --- a/tools/Reg_Testing.md +++ /dev/null @@ -1,50 +0,0 @@ - - -## Regression Testing SWMM locally on Windows - - -### Dependencies - -Before the project can be built and tested the required dependencies must be installed. - -**Summary of Build Dependencies: Windows** - - - Build - - Build Tools for Visual Studio 2017 - - CMake 3.13 - - - Regression Test - - Python 3.6 64 bit - - curl - - git - - 7z - -Once Python is present, the following command installs the required packages for regression testing. -``` -\> cd swmm -\swmm>pip install -r tools\requirements-win.txt -``` - - -### Build - -EPANET can be built with one simple command. -``` -\swmm>tools\make.cmd -``` - - -### Regression Test - -This command runs regression tests for the local build and compares them to the latest benchmark. -``` -\swmm>tools\before-nrtest.cmd && tools\run-nrtest.cmd -``` diff --git a/tools/app-config.cmd b/tools/app-config.cmd deleted file mode 100644 index 09ce04daf..000000000 --- a/tools/app-config.cmd +++ /dev/null @@ -1,58 +0,0 @@ -:: -:: app-config.cmd - Generates nrtest app configuration file for SUT executable -:: -:: Date Created: 10/16/2019 -:: Date Modified: 10/17/2019 -:: -:: Author: Michael E. Tryby -:: US EPA - ORD/CESER -:: -:: Requires: -:: git -:: -:: Environment Variables: -:: PROJECT -:: -:: Arguments: -:: 1 - absolute path to test executable (valid path seperator for nrtest is "/") -:: 2 - (platform) -:: 3 - (build identifier for SUT) -:: - -@echo off -setlocal - - -:: check requirements -where git > nul -if %ERRORLEVEL% NEQ 0 ( echo "ERROR: git not installed" & exit /B 1 ) - -:: check environment -if not defined PROJECT ( echo "ERROR: PROJECT must be defined" & exit /B 1 ) - - -:: swmm target created by the cmake build script -set TEST_CMD=run%PROJECT%.exe - -:: remove quotes from path and convert backward to forward slash -set ABS_BUILD_PATH=%~1 -set ABS_BUILD_PATH=%ABS_BUILD_PATH:\=/% - -if [%2]==[] ( set "PLATFORM=unknown" -) else ( set "PLATFORM=%~2" ) - -if [%3]==[] ( set "BUILD_ID=unknown" -) else ( set "BUILD_ID=%~3" ) - -:: determine version -for /F "tokens=1" %%v in ( 'git rev-parse --short HEAD' ) do ( set "VERSION=%%v" ) -if not defined VERSION ( echo "ERROR: VERSION could not be determined" & exit /B 1 ) - - -echo { -echo "name" : "%PROJECT%", -echo "version" : "%VERSION%", -echo "description" : "%PLATFORM% %BUILD_ID%", -echo "setup_script" : "", -echo "exe" : "%ABS_BUILD_PATH%/%TEST_CMD%" -echo } diff --git a/tools/before-nrtest.cmd b/tools/before-nrtest.cmd deleted file mode 100644 index 6a7a36428..000000000 --- a/tools/before-nrtest.cmd +++ /dev/null @@ -1,116 +0,0 @@ -:: -:: before-test.cmd - Stages test and benchmark files for nrtest -:: -:: Date Created: 10/16/2019 -:: Date Updated: -:: -:: Author: Michael E. Tryby -:: US EPA - ORD/CESER -:: -:: Dependencies: -:: curl -:: 7z -:: -:: Environment Variables: -:: PROJECT -:: BUILD_HOME - defaults to "build" -:: PLATFORM -:: -:: Arguments: -:: 1 - (RELEASE_TAG) release tag for benchmark version (defaults to latest tag) -:: -:: Note: -:: Tests and benchmark files are stored in the project-nrtests repo. -:: This script retrieves them using a stable URL associated with a GitHub -:: release, stages the files, and sets up the environment for nrtest to run. -:: - -::@echo off - -:: set global default -set "TEST_HOME=nrtests" - -:: determine project directory -set "CUR_DIR=%CD%" -set "SCRIPT_HOME=%~dp0" -cd %SCRIPT_HOME% -cd .. - -setlocal - - -:: check that dependencies are installed -where curl > nul -if %ERRORLEVEL% neq 0 ( echo "ERROR: curl not installed" & exit /B 1 ) -where 7z > nul -if %ERRORLEVEL% neq 0 ( echo "ERROR: 7zip not installed" & exit /B 1 ) - - -:: set URL to github repo with test files -set "NRTESTS_URL=https://github.com/SWMM-Project/%PROJECT%-nrtestsuite" - - -:: if release tag isn't provided latest tag will be retrieved -if [%1] == [] (set "RELEASE_TAG=" -) else (set "RELEASE_TAG=%~1") - - -:: check env variables and apply defaults -if not defined PROJECT ( echo "ERROR: PROJECT must be defined" & exit /B 1 ) -if not defined BUILD_HOME ( echo "ERROR: BUILD_HOME must be defined" & exit /B 1 ) -if not defined PLATFORM ( echo "ERROR: PLATFORM must be defined" & exit /B 1 ) - - -echo INFO: Staging files for regression testing - - -:: determine latest tag in the tests repo -if [%RELEASE_TAG%] == [] ( - for /F delims^=^"^ tokens^=2 %%g in ('curl --silent %NRTESTS_URL%/releases/latest') do ( - set "RELEASE_TAG=%%~nxg" - ) -) - -if defined RELEASE_TAG ( - set "TESTFILES_URL=%NRTESTS_URL%/archive/%RELEASE_TAG%.zip" - set "BENCHFILES_URL=%NRTESTS_URL%/releases/download/%RELEASE_TAG%/benchmark-%PLATFORM%.zip" -) else ( echo ERROR: tag %RELEASE_TAG% is invalid & exit /B 1 ) - - -:: create a clean directory for staging regression tests -if exist %TEST_HOME% ( - rmdir /s /q %TEST_HOME% -) -mkdir %TEST_HOME% -if %ERRORLEVEL% NEQ 0 ( echo "ERROR: unable to make %TEST_HOME% dir" & exit /B 1 ) -cd %TEST_HOME% -if %ERRORLEVEL% NEQ 0 ( echo "ERROR: unable to cd %TEST_HOME% dir" & exit /B 1 ) - - -:: retrieve nrtest cases and benchmark results for regression testing -curl -fsSL -o nrtestfiles.zip %TESTFILES_URL% -curl -fsSL -o benchmark.zip %BENCHFILES_URL% - - -:: extract tests, scripts, benchmarks, and manifest -7z x nrtestfiles.zip * > nul -7z x benchmark.zip -obenchmark\ > nul -7z e benchmark.zip -o. manifest.json -r > nul - - -:: set up symlinks for tests directory -mklink /D .\tests .\%PROJECT%-nrtestsuite-%RELEASE_TAG:~1%\public > nul - - -endlocal - - -:: determine REF_BUILD_ID from manifest file -for /F delims^=^"^ tokens^=4 %%d in ( 'findstr %PLATFORM% %TEST_HOME%\manifest.json' ) do ( - for /F "tokens=2" %%r in ( 'echo %%d' ) do ( set "REF_BUILD_ID=%%r" ) -) -if not defined REF_BUILD_ID ( echo "ERROR: REF_BUILD_ID could not be determined" & exit /B 1 ) - - -:: return to users current directory -cd %CUR_DIR% diff --git a/tools/make.cmd b/tools/make.cmd deleted file mode 100644 index 3fabcc683..000000000 --- a/tools/make.cmd +++ /dev/null @@ -1,108 +0,0 @@ -:: -:: make.cmd - builds project -:: -:: Date Created: 10/15/2019 -:: Date Updated: -:: -:: Author: Michael E. Tryby -:: US EPA - ORD/CESER -:: -:: Requires: -:: Build Tools for Visual Studio download: -:: https://visualstudio.microsoft.com/downloads/ -:: -:: CMake download: -:: https://cmake.org/download/ -:: -:: Optional Arguments: -:: /g ("GENERATOR") defaults to "Visual Studio 15 2017" -:: /t builds and runs unit tests (requires Boost) -:: - - -::echo off - - -:: set global defaults -set "PROJECT=swmm" -set "BUILD_HOME=build" -set "PLATFORM=win32" - -:: determine project directory -set "CUR_DIR=%CD%" -set "SCRIPT_HOME=%~dp0" -cd %SCRIPT_HOME% -cd .. - -:: check for requirements -where cmake > nul -if %ERRORLEVEL% NEQ 0 ( echo "ERROR: cmake not installed" & exit /B 1 ) - - -setlocal EnableDelayedExpansion - - -echo INFO: Building %PROJECT% ... - - -:: set local defaults -set "GENERATOR=Visual Studio 15 2017" -set "TESTING=0" - -:: process arguments -:loop -if NOT [%1]==[] ( - if "%1"=="/g" ( - set "GENERATOR=%~2" - shift - ) - if "%1"=="/t" ( - set "TESTING=1" - ) - shift - goto :loop -) - - -:: if generator has changed delete the build folder -if exist %BUILD_HOME% ( - for /F "tokens=*" %%f in ( 'findstr CMAKE_GENERATOR:INTERNAL %BUILD_HOME%\CmakeCache.txt' ) do ( - for /F "delims=:= tokens=3" %%m in ( 'echo %%f' ) do ( - set CACHE_GEN=%%m - if not "!CACHE_GEN!" == "!GENERATOR!" ( rmdir /s /q %BUILD_HOME% & mkdir %BUILD_HOME% ) - ) - ) -) else ( - mkdir %BUILD_HOME%^ - & if %ERRORLEVEL% NEQ 0 ( echo "ERROR: unable to make %BUILD_HOME% dir" & exit /B 1 ) -) - - -:: perform the build -cd %BUILD_HOME% -if %ERRORLEVEL% NEQ 0 ( echo "ERROR: unable to cd %BUILD_HOME% dir" & exit /B 1 ) - -if %TESTING% EQU 1 ( - cmake -G"%GENERATOR%" -DBUILD_TESTS=ON -DBOOST_ROOT=C:\local\boost_1_67_0 ..^ - && cmake --build . --config Debug^ - & echo. && ctest -C Debug --output-on-failure -) else ( - cmake -G"%GENERATOR%" -DBUILD_TESTS=OFF ..^ - && cmake --build . --config Release --target install -) - - -endlocal - - -:: determine platform from CmakeCache.txt file -for /F "tokens=*" %%f in ( 'findstr CMAKE_SHARED_LINKER_FLAGS:STRING %BUILD_HOME%\CmakeCache.txt' ) do ( - for /F "delims=: tokens=3" %%m in ( 'echo %%f' ) do ( - if "%%m" == "X86" ( set "PLATFORM=win32" ) else if "%%m" == "x64" ( set "PLATFORM=win64" ) - ) -) -if not defined PLATFORM ( echo "ERROR: PLATFORM could not be determined" & exit /B 1 ) - - -:: return to users current dir -:: cd %CUR_DIR% diff --git a/tools/requirements-win.txt b/tools/requirements-win.txt deleted file mode 100644 index 0f18bb0ee..000000000 --- a/tools/requirements-win.txt +++ /dev/null @@ -1,22 +0,0 @@ -# -# requirements-win.txt - Python requirements for running nrtest on Win32/Win64 -# -# Date Created: 10/17/2019 -# Date Updated: 11/26/2019 -# -# Author: Michael E. Tryby -# US EPA ORD/CESER -# -# Useful for configuring a python environment to run nrtests on swmm. -# -# usage: -# pip install -r tools/requirements-win.txt -# - -nrtest - --f https://github.com/michaeltryby/swmm-python/releases/download/v0.3.0-dev3/swmm.output-0.4.0.dev3-cp36-cp36m-win_amd64.whl -swmm.output - --f https://github.com/michaeltryby/swmm-python/releases/download/v0.3.0-dev3/nrtest_swmm-0.5.0-py3-none-any.whl -nrtest-swmm diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd deleted file mode 100644 index c51d96c80..000000000 --- a/tools/run-nrtests.cmd +++ /dev/null @@ -1,114 +0,0 @@ -:: -:: run_nrtest.cmd - Runs numerical regression test -:: -:: Date Created: 10/16/2019 -:: Date Updated: -:: -:: Author: Michael E. Tryby -:: US EPA - ORD/CESER -:: -:: Dependencies: -:: python -m pip install -r requirements.txt -:: -:: Environment Variables: -:: PROJECT -:: BUILD_HOME - relative path -:: TEST_HOME - relative path -:: PLATFORM -:: REF_BUILD_ID -:: -:: Arguments: -:: 1 - (SUT_VERSION) - optional argument -:: 2 - (SUT_BUILD_ID) - optional argument -:: - -::@echo off -setlocal EnableDelayedExpansion - - -:: Check that required environment variables are set -if not defined PROJECT ( echo "ERROR: PROJECT must be defined" & exit /B 1 ) -if not defined BUILD_HOME ( echo "ERROR: BUILD_HOME must be defined" & exit /B 1 ) -if not defined TEST_HOME ( echo "ERROR: TEST_HOME must be defined" & exit /B 1 ) -if not defined PLATFORM ( echo "ERROR: PLATFORM must be defined" & exit /B 1 ) -if not defined REF_BUILD_ID ( echo "ERROR: REF_BUILD_ID must be defined" & exit /B 1 ) - - -:: determine project directory -set "CUR_DIR=%CD%" -set "SCRIPT_HOME=%~dp0" -cd %SCRIPT_HOME% -pushd .. -set PROJ_DIR=%CD% -popd - - -cd %PROJ_DIR%\%TEST_HOME% - -:: Process optional arguments -if [%1]==[] (set "SUT_VERSION=unknown" -) else ( set "SUT_VERSION=%~1" ) - -if [%2]==[] ( set "SUT_BUILD_ID=local" -) else ( set "SUT_BUILD_ID=%~2" ) - - -:: check if app config file exists -if not exist apps\%PROJECT%-%SUT_BUILD_ID%.json ( - mkdir apps - call %SCRIPT_HOME%\app-config.cmd %PROJ_DIR%\%BUILD_HOME%\bin\Release^ - %PLATFORM% %SUT_BUILD_ID% %SUT_VERSION% > apps\%PROJECT%-%SUT_BUILD_ID%.json -) - - -:: recursively build test list -set TESTS= -for /F "tokens=*" %%T in ('dir /b /s /a:d tests') do ( - set FULL_PATH=%%T - set TESTS=!TESTS! !FULL_PATH:*%TEST_HOME%\=! -) - - -:: determine location of python Scripts folder -for /F "tokens=*" %%G in ('where python.exe') do ( - set PYTHON_DIR=%%~dpG - goto break_loop_1 -) -:break_loop_1 -set "NRTEST_SCRIPT_PATH=%PYTHON_DIR%Scripts" - - -:: build nrtest execute command -set NRTEST_EXECUTE_CMD=python.exe %NRTEST_SCRIPT_PATH%\nrtest execute -set TEST_APP_PATH=apps\%PROJECT%-%SUT_BUILD_ID%.json -set TEST_OUTPUT_PATH=benchmark\%PROJECT%-%SUT_BUILD_ID% - -:: build nrtest compare command -set NRTEST_COMPARE_CMD=python.exe %NRTEST_SCRIPT_PATH%\nrtest compare -set REF_OUTPUT_PATH=benchmark\%PROJECT%-%REF_BUILD_ID% -set RTOL_VALUE=0.01 -set ATOL_VALUE=1.E-6 - -:: change current directory to test suite -::cd %TEST_HOME% - -:: if present clean test benchmark results -if exist %TEST_OUTPUT_PATH% ( - rmdir /s /q %TEST_OUTPUT_PATH% -) - -:: perform nrtest execute -echo INFO: Creating SUT %SUT_BUILD_ID% artifacts -set NRTEST_COMMAND=%NRTEST_EXECUTE_CMD% %TEST_APP_PATH% %TESTS% -o %TEST_OUTPUT_PATH% -:: if there is an error exit the script with error value 1 -%NRTEST_COMMAND% || exit /B 1 - -echo. - -:: perform nrtest compare -echo INFO: Comparing SUT artifacts to REF %REF_BUILD_ID% -set NRTEST_COMMAND=%NRTEST_COMPARE_CMD% %TEST_OUTPUT_PATH% %REF_OUTPUT_PATH% --rtol %RTOL_VALUE% --atol %ATOL_VALUE% -o benchmark\receipt.json -%NRTEST_COMMAND% - -:: Return user to their current dir -cd %CUR_DIR% From d3694815e3e80d0323e6b6dc033df221f051e547 Mon Sep 17 00:00:00 2001 From: calebbuahin <7217571+cbuahin@users.noreply.github.com> Date: Fri, 19 Feb 2021 19:38:14 -0500 Subject: [PATCH 026/190] Test single version dec location --- src/solver/text.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/solver/text.h b/src/solver/text.h index 53cc0817a..50532139c 100644 --- a/src/solver/text.h +++ b/src/solver/text.h @@ -20,6 +20,8 @@ // Text strings //----------------------------------------------------------------------------- +#include "consts.h" + #define FMT01 \ "\tswmm5 \n" @@ -29,7 +31,7 @@ #define FMT06 "\n o Retrieving project data" #define FMT07 "\n o Writing output report" #define FMT08 \ - "\n EPA STORM WATER MANAGEMENT MODEL - VERSION 5.1 (Build 5.1.014)" //(5.1.014) + "\n EPA STORM WATER MANAGEMENT MODEL - VERSION 5.1 (Build "SEMVERSION_MAJOR"."SEMVERSION_MINOR"."SEMVERSION_PATCH")" //(5.1.014) #define FMT09 \ "\n --------------------------------------------------------------" #define FMT10 "\n" From 35ce613b4a5726e898e569b205f08ef179fb21f9 Mon Sep 17 00:00:00 2001 From: calebbuahin <7217571+cbuahin@users.noreply.github.com> Date: Fri, 19 Feb 2021 19:49:57 -0500 Subject: [PATCH 027/190] Add math.h for darwin --- src/run/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/run/main.c b/src/run/main.c index 91f304e24..6940e055e 100644 --- a/src/run/main.c +++ b/src/run/main.c @@ -12,7 +12,7 @@ #include #include #include - +#include // Private project includes #include "timer.h" From 4cf7132f4dafe671287844a71b25fcaad7184d8d Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sat, 20 Feb 2021 16:12:47 -0500 Subject: [PATCH 028/190] updated with pollutant checks --- src/solver/include/toolkit.h | 19 + src/solver/include/toolkit_enums.h | 5 +- src/solver/objects.h | 7 + src/solver/project.c | 7 + src/solver/qualrout.c | 67 ++- src/solver/toolkit.c | 90 +++- src/solver/treatmnt.c | 34 +- tests/solver/CMakeLists.txt | 2 +- .../data/pollutants/link_constantinflow.inp | 158 +++++++ .../node_constantinflow_constanteffluent.inp | 133 ++++++ tests/solver/test_pollutants.cpp | 440 ++++++++++++++++++ tests/solver/test_pollutants.hpp | 57 +++ 12 files changed, 998 insertions(+), 21 deletions(-) create mode 100644 tests/solver/data/pollutants/link_constantinflow.inp create mode 100644 tests/solver/data/pollutants/node_constantinflow_constanteffluent.inp create mode 100644 tests/solver/test_pollutants.cpp create mode 100644 tests/solver/test_pollutants.hpp diff --git a/src/solver/include/toolkit.h b/src/solver/include/toolkit.h index 6861f64b0..cbbeb442f 100644 --- a/src/solver/include/toolkit.h +++ b/src/solver/include/toolkit.h @@ -392,6 +392,25 @@ int DLLEXPORT swmm_getNodeResult(int index, SM_NodeResult type, double *result); */ int DLLEXPORT swmm_getNodePollut(int index, SM_NodePollut type, double **pollutArray, int *length); +/** + @brief Sets pollutant values for a specified node. + @param index The index of a node + @param pollutant_index Pollutant index to set + @param pollutant_value Pollutant value to set + @return Error code +*/ +int DLLEXPORT swmm_setNodePollut(int index, int pollutant_index, double pollutant_value); + +/** + @brief Sets pollutant values for a specified link. + @param index The index of a link + @param pollutant_index Pollutant index to set + @param pollutant_value Pollutant value to set + @return Error code +*/ + +int DLLEXPORT swmm_setLinkPollut(int index, int type, int pollutant_index, double pollutant_value); + /** @brief Get a result value for specified link. @param index The index of a link diff --git a/src/solver/include/toolkit_enums.h b/src/solver/include/toolkit_enums.h index 25e5ef73f..7efbebcd8 100644 --- a/src/solver/include/toolkit_enums.h +++ b/src/solver/include/toolkit_enums.h @@ -132,12 +132,15 @@ typedef enum { SM_NODEFLOOD = 4, /**< Flooding Rate */ SM_NODEDEPTH = 5, /**< Node Depth */ SM_NODEHEAD = 6, /**< Node Head */ - SM_LATINFLOW = 7 /**< Lateral Inflow Rate */ + SM_LATINFLOW = 7, /**< Lateral Inflow Rate */ + SM_HRT = 8 /**< HRT */ } SM_NodeResult; /// Node pollutant result property codes typedef enum { SM_NODEQUAL = 0, /**< Current Node Quality */ + SM_NODECIN = 1, /**< Inflow Concentration */ + SM_NODEREACTORC = 2 /**< Reactor Concentation */ } SM_NodePollut; /// Link result property codes diff --git a/src/solver/objects.h b/src/solver/objects.h index ef4a23be2..07915c3d5 100644 --- a/src/solver/objects.h +++ b/src/solver/objects.h @@ -485,6 +485,7 @@ typedef struct char* ID; // node ID int type; // node type code int subIndex; // index of node's sub-category + int* extPollutFlag; // external pollutant flag char rptFlag; // reporting flag double invertElev; // invert elevation (ft) double initDepth; // initial storage level (ft) @@ -502,6 +503,7 @@ typedef struct double inflow; // total inflow (cfs) double outflow; // total outflow (cfs) double losses; // evap + exfiltration loss (ft3) + double hrt; // hydraulic retention time double oldVolume; // previous volume (ft3) double newVolume; // current volume (ft3) double fullVolume; // max. storage available (ft3) @@ -512,6 +514,9 @@ typedef struct double newLatFlow; // current lateral inflow (cfs) double* oldQual; // previous quality state double* newQual; // current quality state + double* extQual; // external quality state + double* inQual; // inflow quality state + double* reactorQual; // concentration in the mixed reactor double oldFlowInflow; // previous flow inflow double oldNetInflow; // previous net inflow } TNode; @@ -634,6 +639,7 @@ typedef struct char* ID; // link ID int type; // link type code int subIndex; // index of link's sub-category + int* extPollutFlag; // external pollutant flag char rptFlag; // reporting flag int node1; // start node index int node2; // end node index @@ -664,6 +670,7 @@ typedef struct double* oldQual; // previous quality state double* newQual; // current quality state double* totalLoad; // total quality mass loading + double* extQual; // external quality state int flowClass; // flow classification double dqdh; // change in flow w.r.t. head (ft2/sec) signed char direction; // flow direction flag diff --git a/src/solver/project.c b/src/solver/project.c index 5e9b8e751..48cac9836 100644 --- a/src/solver/project.c +++ b/src/solver/project.c @@ -1037,6 +1037,11 @@ void createObjects() { Node[j].oldQual = (double *) calloc(Nobjects[POLLUT], sizeof(double)); Node[j].newQual = (double *) calloc(Nobjects[POLLUT], sizeof(double)); + Node[j].extQual = (double *) calloc(Nobjects[POLLUT], sizeof(double)); + Node[j].inQual = (double *) calloc(Nobjects[POLLUT], sizeof(double)); + Node[j].reactorQual = (double *) calloc(Nobjects[POLLUT], sizeof(double)); + Node[j].extPollutFlag = (int *) calloc(Nobjects[POLLUT], sizeof(int)); + Node[j].rptFlag = 0; Node[j].extInflow = NULL; Node[j].dwfInflow = NULL; Node[j].rdiiInflow = NULL; @@ -1046,7 +1051,9 @@ void createObjects() { Link[j].oldQual = (double *) calloc(Nobjects[POLLUT], sizeof(double)); Link[j].newQual = (double *) calloc(Nobjects[POLLUT], sizeof(double)); + Link[j].extQual = (double *) calloc(Nobjects[POLLUT], sizeof(double)); Link[j].totalLoad = (double *) calloc(Nobjects[POLLUT], sizeof(double)); + Link[j].extPollutFlag = (int *) calloc(Nobjects[POLLUT], sizeof(int)); } // --- allocate memory for land use buildup/washoff functions diff --git a/src/solver/qualrout.c b/src/solver/qualrout.c index 7c16e45af..6bcbc73c6 100644 --- a/src/solver/qualrout.c +++ b/src/solver/qualrout.c @@ -101,25 +101,58 @@ void qualrout_execute(double tStep) // { int i, j; + int p, extPollutFlag; double qIn, vAvg; - + // --- find mass flow each link contributes to its downstream node for ( i = 0; i < Nobjects[LINK]; i++ ) findLinkMassFlow(i, tStep); + // --- check for external treatment in nodes or links + for (j = 0; j < Nobjects[NODE]; j++) + { + // --- check for external pollutant + for ( p = 0; p < Nobjects[POLLUT]; p++) + { + if (Node[j].extPollutFlag[p] == 1) + { + extPollutFlag = 1; + break; + } + + } + + } + + for (i = 0; i < Nobjects[LINK]; i++) + { + // --- check for external pollutant + for ( p = 0; p < Nobjects[POLLUT]; p++) + { + if (Link[i].extPollutFlag[p] == 1) + { + extPollutFlag = 1; + break; + } + + } + + } + // --- find new water quality concentration at each node for (j = 0; j < Nobjects[NODE]; j++) { + // --- get node inflow and average volume qIn = Node[j].inflow; vAvg = (Node[j].oldVolume + Node[j].newVolume) / 2.0; // --- save inflow concentrations if treatment applied - if ( Node[j].treatment ) + if ( Node[j].treatment || extPollutFlag == 1) { if ( qIn < ZERO ) qIn = 0.0; treatmnt_setInflow(qIn, Node[j].newQual); } - + // --- find new quality at the node if ( Node[j].type == STORAGE || Node[j].oldVolume > FUDGE ) { @@ -128,7 +161,7 @@ void qualrout_execute(double tStep) else findNodeQual(j); // --- apply treatment to new quality values - if ( Node[j].treatment ) treatmnt_treat(j, qIn, vAvg, tStep); + if ( Node[j].treatment || extPollutFlag == 1) treatmnt_treat(j, qIn, vAvg, tStep); } // --- find new water quality in each link @@ -254,7 +287,8 @@ void findLinkQual(int i, double tStep) vEvap, // volume lost to evaporation (ft3) vLosses, // evap. + seepage volume loss (ft3) fEvap, // evaporation concentration factor - barrels; // number of barrels in conduit + barrels, // number of barrels in conduit + lossExtQual; // loss value for external quality // --- identify index of upstream node j = Link[i].node1; @@ -310,7 +344,7 @@ void findLinkQual(int i, double tStep) // --- start with concen. at start of time step c1 = Link[i].oldQual[p]; - // --- update mass balance accounting for seepage loss + // --- update mass balance accounting for seepage loss massbal_addSeepageLoss(p, qSeep*c1); // --- increase concen. by evaporation factor @@ -330,8 +364,22 @@ void findLinkQual(int i, double tStep) c2 = 0.0; } - // --- assign new concen. to link - Link[i].newQual[p] = c2; + if (Link[i].extPollutFlag[p] == 0) + { + // --- assign new concen. to link + Link[i].newQual[p] = c2; + } + // --- update mass balance and set external pollutant + else if( Link[i].extPollutFlag[p] == 1) + { + // --- mass balance update + lossExtQual = c2 - Link[i].extQual[p]; + lossExtQual = lossExtQual * v1/ tStep; + massbal_addReactedMass(p, lossExtQual); + + Link[i].newQual[p] = Link[i].extQual[p]; + Link[i].extPollutFlag[p] = 0; + } } } @@ -452,6 +500,7 @@ void findStorageQual(int j, double tStep) // --- assign new concen. to node Node[j].newQual[p] = c2; + Node[j].reactorQual[p] = c2; } } @@ -498,4 +547,4 @@ double getReactedQual(int p, double c, double v1, double tStep) massbal_addReactedMass(p, lossRate); return c2; } - \ No newline at end of file + diff --git a/src/solver/toolkit.c b/src/solver/toolkit.c index 4976c7474..6471169ad 100644 --- a/src/solver/toolkit.c +++ b/src/solver/toolkit.c @@ -1874,6 +1874,8 @@ int DLLEXPORT swmm_getNodeResult(int index, SM_NodeResult type, double *result) + Node[index].invertElev) * UCF(LENGTH); break; case SM_LATINFLOW: *result = Node[index].newLatFlow * UCF(FLOW); break; + case SM_HRT: + *result = Node[index].hrt; break; default: error_code_index = ERR_API_OUTBOUNDS; break; } } @@ -1883,7 +1885,7 @@ int DLLEXPORT swmm_getNodeResult(int index, SM_NodeResult type, double *result) int DLLEXPORT swmm_getNodePollut(int index, SM_NodePollut type, double **pollutArray, int *length) /// /// Input: index = Index of desired ID -/// type = Result Type (SM_NodePollut) +/// type = Result Type (SM_NodePollut or SM_NODECIN or SM_NODEREACTORC) /// Output: PollutArray pointer (pollutant data desired, byref) /// Return: API Error /// Purpose: Gets Node Simulated Water Quality Value at Current Time @@ -1920,12 +1922,58 @@ int DLLEXPORT swmm_getNodePollut(int index, SM_NodePollut type, double **pollutA *pollutArray = result; *length = Nobjects[POLLUT]; } break; + case SM_NODECIN: + { + for (p=0; p < Nobjects[POLLUT]; p++) + { + result[p] = Node[index].inQual[p]; + } *pollutArray = result; + } break; + case SM_NODEREACTORC: + { + for (p=0; p < Nobjects[POLLUT]; p++) + { + result[p] = Node[index].reactorQual[p]; + } *pollutArray = result; + } break; default: error_code_index = ERR_API_OUTBOUNDS; break; } } return error_getCode(error_code_index); } +int DLLEXPORT swmm_setNodePollut(int index, int pollutant_index, double pollutant_value) +/// +/// Input: index = Index of desired ID +/// pollutant_index = Index of desired polluant +// pollutant_value = Value of the pollutant +/// Return: API Error +/// Purpose: Set pollutant concentration in nodes at the current time step +{ + int error_code_index = 0; + + // Check if Open + if(swmm_IsOpenFlag() == FALSE) + { + error_code_index = ERR_API_INPUTNOTOPEN; + } + // Check if object index is within bounds + else if (index < 0 || index >= Nobjects[NODE]) + { + error_code_index = ERR_API_OBJECT_INDEX; + } + else + { + if (pollutant_index <= Nobjects[POLLUT]) + { + Node[index].extQual[pollutant_index] = pollutant_value; + Node[index].extPollutFlag[pollutant_index] = 1; + } + } + return error_getCode(error_code_index); +} + + int DLLEXPORT swmm_getLinkResult(int index, SM_LinkResult type, double *result) /// /// Input: index = Index of desired ID @@ -2032,6 +2080,46 @@ int DLLEXPORT swmm_getLinkPollut(int index, SM_LinkPollut type, double **pollutA return error_getCode(error_code_index); } + +int DLLEXPORT swmm_setLinkPollut(int index, int type, int pollutant_index, double pollutant_value) +/// +/// Input: index = Index of the desired Link ID +/// type = SM_LINKQUAL - Sets link's qual and allows accounting for loss and mixing calculation +/// pollutant_index = index of pollutant to set +/// pollutant_value = concentration to set +/// Output: API error +/// Purponse: Set pollutant concentration in links +{ + int error_code_index = 0; + + // Check if Open + if(swmm_IsOpenFlag() == FALSE) + { + error_code_index = ERR_API_INPUTNOTOPEN; + } + // Check if object index is within bounds + else if (index < 0 || index >= Nobjects[NODE]) + { + error_code_index = ERR_API_OBJECT_INDEX; + } + else + { + if (pollutant_index <= Nobjects[POLLUT]) + { + switch(type) + { + case SM_LINKQUAL: + { + Link[index].extQual[pollutant_index] = pollutant_value; + Link[index].extPollutFlag[pollutant_index] = 1; + } break; + default: error_code_index = ERR_API_OUTBOUNDS; break; + } + } + } + return error_getCode(error_code_index); +} + int DLLEXPORT swmm_getSubcatchResult(int index, SM_SubcResult type, double* result) /// /// Input: index = Index of desired ID diff --git a/src/solver/treatmnt.c b/src/solver/treatmnt.c index 0843b5f96..1b1491e49 100644 --- a/src/solver/treatmnt.c +++ b/src/solver/treatmnt.c @@ -208,7 +208,7 @@ void treatmnt_treat(int j, double q, double v, double tStep) double cOut; // concentration after treatment double massLost; // mass lost by treatment per time step TTreatment* treatment; // pointer to treatment object - + // --- set locally shared variables for node j if ( Node[j].treatment == NULL ) return; ErrCode = 0; @@ -216,7 +216,6 @@ void treatmnt_treat(int j, double q, double v, double tStep) Dt = tStep; // current time step Q = q; // current inflow rate V = v; // current node volume - // --- initialze each removal to indicate no value for ( p = 0; p < Nobjects[POLLUT]; p++) R[p] = -1.0; @@ -228,7 +227,10 @@ void treatmnt_treat(int j, double q, double v, double tStep) if ( treatment->equation == NULL ) R[p] = 0.0; // --- no removal for removal-type expression when there is no inflow - else if ( treatment->treatType == REMOVAL && q <= ZERO ) R[p] = 0.0; + else if ( treatment->treatType == REMOVAL && q <= ZERO ) R[p] = 0.0; + + // --- check for external treatment + else if ( Node[j].extPollutFlag[p] == 1) R[p] = 0.0; // --- otherwise evaluate the treatment expression to find R[p] else getRemoval(p); @@ -239,16 +241,16 @@ void treatmnt_treat(int j, double q, double v, double tStep) { report_writeErrorMsg(ERR_CYCLIC_TREATMENT, Node[J].ID); } - + // --- update nodal concentrations and mass balances else for ( p = 0; p < Nobjects[POLLUT]; p++ ) { - if ( R[p] == 0.0 ) continue; + if ( R[p] == 0.0 && Node[j].extPollutFlag[p] != 1) continue; treatment = &Node[j].treatment[p]; // --- removal-type treatment equations get applied to inflow stream - if ( treatment->treatType == REMOVAL ) + if ( treatment->treatType == REMOVAL && Node[j].extPollutFlag[p] != 1) { // --- if no pollutant in inflow then cOut is current nodal concen. if ( Cin[p] == 0.0 ) cOut = Node[j].newQual[p]; @@ -261,16 +263,30 @@ void treatmnt_treat(int j, double q, double v, double tStep) cOut = MIN(cOut, Node[j].newQual[p]); } + // --- water quality set externally + else if( Node[j].extPollutFlag[p] == 1) + { + cOut = Node[j].extQual[p]; + } + // --- concentration-type equations get applied to nodal concentration else { cOut = (1.0 - R[p]) * Node[j].newQual[p]; } + + // --- store inflow concentration for the timestep + Node[j].inQual[p] = Cin[p]; - // --- mass lost must account for any initial mass in storage + // --- mass lost must account for any initial mass in storage massLost = (Cin[p]*q*tStep + Node[j].oldQual[p]*Node[j].oldVolume - - cOut*(q*tStep + Node[j].oldVolume)) / tStep; - massLost = MAX(0.0, massLost); + cOut*(q*tStep + Node[j].oldVolume)) / tStep; + + // --- mass can be gained in external treatment + if (Node[j].extPollutFlag[p] != 1) massLost = MAX(0.0, massLost); + + // --- reset the flag to default to swmm treatment + if (Node[j].extPollutFlag[p] == 1) Node[j].extPollutFlag[p] = 0; // --- add mass loss to mass balance totals and revise nodal concentration massbal_addReactedMass(p, massLost); diff --git a/tests/solver/CMakeLists.txt b/tests/solver/CMakeLists.txt index ae987411e..f8acc53a1 100644 --- a/tests/solver/CMakeLists.txt +++ b/tests/solver/CMakeLists.txt @@ -33,7 +33,7 @@ set_target_properties(test_lid set(solver_test_srcs test_canonical.cpp test_gage.cpp - test_pollut.cpp + test_pollutants.cpp test_toolkit.cpp test_solver.cpp test_stats.cpp diff --git a/tests/solver/data/pollutants/link_constantinflow.inp b/tests/solver/data/pollutants/link_constantinflow.inp new file mode 100644 index 000000000..4ca251907 --- /dev/null +++ b/tests/solver/data/pollutants/link_constantinflow.inp @@ -0,0 +1,158 @@ +[TITLE] +;;Project Title/Notes +Example 6 +Circular Culvert with Roadway Overtopping +and Upstream Storage + +[OPTIONS] +;;Option Value +FLOW_UNITS CFS +INFILTRATION HORTON +FLOW_ROUTING DYNWAVE +LINK_OFFSETS DEPTH +MIN_SLOPE 0 +ALLOW_PONDING NO +SKIP_STEADY_STATE NO + +START_DATE 06/08/2015 +START_TIME 00:00:00 +REPORT_START_DATE 06/08/2015 +REPORT_START_TIME 00:00:00 +END_DATE 06/08/2015 +END_TIME 05:00:00 +SWEEP_START 01/01 +SWEEP_END 12/31 +DRY_DAYS 0 +REPORT_STEP 00:00:01 +WET_STEP 00:00:01 +DRY_STEP 00:00:01 +ROUTING_STEP 00:00:01 + +INERTIAL_DAMPING PARTIAL +NORMAL_FLOW_LIMITED BOTH +FORCE_MAIN_EQUATION H-W +VARIABLE_STEP 0.75 +LENGTHENING_STEP 0 +MIN_SURFAREA 12.557 +MAX_TRIALS 8 +HEAD_TOLERANCE 0.005 +SYS_FLOW_TOL 5 +LAT_FLOW_TOL 5 +MINIMUM_STEP 0.5 +THREADS 1 + +[EVAPORATION] +;;Data Source Parameters +;;-------------- ---------------- +CONSTANT 0.0 +DRY_ONLY NO + +[JUNCTIONS] +;;Name Elevation MaxDepth InitDepth SurDepth Aponded +;;-------------- ---------- ---------- ---------- ---------- ---------- +Outlet 868 0 0 0 0 + +[OUTFALLS] +;;Name Elevation Type Stage Data Gated Route To +;;-------------- ---------- ---------- ---------------- -------- ---------------- +TailWater 858 FIXED 859.5 NO + +[STORAGE] +;;Name Elev. MaxDepth InitDepth Shape Curve Name/Params N/A Fevap Psi Ksat IMD +;;-------------- -------- ---------- ----------- ---------- ---------------------------- -------- -------- -------- -------- +Inlet 878 9 0 TABULAR StorageCurve 0 0 + +[CONDUITS] +;;Name From Node To Node Length Roughness InOffset OutOffset InitFlow MaxFlow +;;-------------- ---------------- ---------------- ---------- ---------- ---------- ---------- ---------- ---------- +Culvert Inlet Outlet 200 0.014 0 0 0 0 +Channel Outlet TailWater 200 0.03 0 0 0 0 + +[WEIRS] +;;Name From Node To Node Type CrestHt Qcoeff Gated EndCon EndCoeff Surcharge RoadWidth RoadSurf +;;-------------- ---------------- ---------------- ------------ ---------- ---------- -------- -------- ---------- ---------- ---------- ---------- +Roadway Inlet Outlet ROADWAY 9 3.33 NO 0 0 NO 40 GRAVEL + +[XSECTIONS] +;;Link Shape Geom1 Geom2 Geom3 Geom4 Barrels Culvert +;;-------------- ------------ ---------------- ---------- ---------- ---------- ---------- ---------- +Culvert CIRCULAR 3 0 0 0 2 4 +Channel TRAPEZOIDAL 9 10 2 2 1 +Roadway RECT_OPEN 50 200 0 0 + +[POLLUTANTS] +;;Name Units Crain Cgw Crdii Kdecay SnowOnly Co-Pollutant Co-Frac Cdwf Cinit +;;-------------- ------ ---------- ---------- ---------- ---------- ---------- ---------------- ---------- ---------- ---------- +P1 MG/L 0.0 0.0 0.0 0.0 NO * 0.0 0.0 0.0 + +[INFLOWS] +;;Node Constituent Time Series Type Mfactor Sfactor Baseline Pattern +;;-------------- ---------------- ---------------- -------- -------- -------- -------- -------- +Inlet FLOW "" FLOW 1.0 1.0 10.0 +Inlet P1 "" CONCENTRATION 1.0 1.0 10 + + +[CURVES] +;;Name Type X-Value Y-Value +;;-------------- ---------- ---------- ---------- +StorageCurve Storage 0 0 +StorageCurve 2 9583 +StorageCurve 4 33977 +StorageCurve 6 72310 +StorageCurve 8 136778 + +[TIMESERIES] +;;Name Date Time Value +;;-------------- ---------- ---------- ---------- +Inflow 0 0 +Inflow .125 9 +Inflow .25 10 +Inflow .375 11 +Inflow .5 13 +Inflow .625 17 +Inflow .75 28 +Inflow .875 40 +Inflow 1 80 +Inflow 1.125 136 +Inflow 1.25 190 +Inflow 1.375 220 +Inflow 1.5 220 +Inflow 1.625 201 +Inflow 1.75 170 +Inflow 1.875 140 +Inflow 2 120 +Inflow 2.125 98 +Inflow 2.25 82 +Inflow 2.375 70 +Inflow 2.5 60 +Inflow 2.625 53 +Inflow 2.75 47 +Inflow 2.875 41 + +[REPORT] +;;Reporting Options +INPUT NO +CONTROLS NO +SUBCATCHMENTS ALL +NODES ALL +LINKS ALL + +[TAGS] + +[MAP] +DIMENSIONS -59.264 5535.422 7089.237 6044.959 +Units None + +[COORDINATES] +;;Node X-Coord Y-Coord +;;-------------- ------------------ ------------------ +Outlet 4206.542 6357.994 +TailWater 6019.146 6168.726 +Inlet 1628.472 6476.537 + +[VERTICES] +;;Link X-Coord Y-Coord +;;-------------- ------------------ ------------------ +Roadway 2311.068 7166.633 +Roadway 3762.491 7151.463 + diff --git a/tests/solver/data/pollutants/node_constantinflow_constanteffluent.inp b/tests/solver/data/pollutants/node_constantinflow_constanteffluent.inp new file mode 100644 index 000000000..03947b0be --- /dev/null +++ b/tests/solver/data/pollutants/node_constantinflow_constanteffluent.inp @@ -0,0 +1,133 @@ +[TITLE] +;;Project Title/Notes + +[OPTIONS] +;;Option Value +FLOW_UNITS CMS +INFILTRATION HORTON +FLOW_ROUTING KINWAVE +LINK_OFFSETS DEPTH +MIN_SLOPE 0 +ALLOW_PONDING NO +SKIP_STEADY_STATE NO + +START_DATE 01/27/2020 +START_TIME 00:00:00 +REPORT_START_DATE 01/27/2020 +REPORT_START_TIME 00:00:00 +END_DATE 01/27/2020 +END_TIME 00:30:00 +SWEEP_START 01/01 +SWEEP_END 02/28 +DRY_DAYS 0 +REPORT_STEP 00:00:01 +WET_STEP 00:00:01 +DRY_STEP 00:00:01 +ROUTING_STEP 0:00:01 + +INERTIAL_DAMPING PARTIAL +NORMAL_FLOW_LIMITED BOTH +FORCE_MAIN_EQUATION H-W +VARIABLE_STEP 0.75 +LENGTHENING_STEP 0 +MIN_SURFAREA 1.14 +MAX_TRIALS 8 +HEAD_TOLERANCE 0.0015 +SYS_FLOW_TOL 5 +LAT_FLOW_TOL 5 +MINIMUM_STEP 0.5 +THREADS 1 + +[EVAPORATION] +;;Data Source Parameters +;;-------------- ---------------- +CONSTANT 0.0 +DRY_ONLY NO + +[OUTFALLS] +;;Name Elevation Type Stage Data Gated Route To +;;-------------- ---------- ---------- ---------------- -------- ---------------- +Outfall 0 FREE NO + +[STORAGE] +;;Name Elev. MaxDepth InitDepth Shape Curve Name/Params N/A Fevap Psi Ksat IMD +;;-------------- -------- ---------- ----------- ---------- ---------------------------- -------- -------- -------- -------- +Tank 10 5 0 TABULAR Tank_Curve 0 0 + +[ORIFICES] +;;Name From Node To Node Type Offset Qcoeff Gated CloseTime +;;-------------- ---------------- ---------------- ------------ ---------- ---------- -------- ---------- +Valve Tank Outfall BOTTOM 0 1 NO 0 + +[XSECTIONS] +;;Link Shape Geom1 Geom2 Geom3 Geom4 Barrels Culvert +;;-------------- ------------ ---------------- ---------- ---------- ---------- ---------- ---------- +Valve RECT_CLOSED 1 1 0 0 + +[POLLUTANTS] +;;Name Units Crain Cgw Crdii Kdecay SnowOnly Co-Pollutant Co-Frac Cdwf Cinit +;;-------------- ------ ---------- ---------- ---------- ---------- ---------- ---------------- ---------- ---------- ---------- +P1 MG/L 0.0 0.0 0 0.0 NO * 0.0 0.0 0 + + +[INFLOWS] +;;Node Constituent Time Series Type Mfactor Sfactor Baseline Pattern +;;-------------- ---------------- ---------------- -------- -------- -------- -------- -------- +Tank FLOW "" FLOW 1.0 1.0 5 +Tank P1 "" CONCENTRATION 1.0 1.0 10 + +[TREATMENT] +;;Node Pollutant Function +;;-------------- ---------------- ---------- +Tank P1 C = 2.0 + +[CURVES] +;;Name Type X-Value Y-Value +;;-------------- ---------- ---------- ---------- +Tank_Curve Storage 0 100 +Tank_Curve 1 100 +Tank_Curve 2 100 +Tank_Curve 3 100 +Tank_Curve 4 100 +Tank_Curve 5 100 + +[TIMESERIES] +;;Name Date Time Value +;;-------------- ---------- ---------- ---------- +TestRain 1 0 +TestRain 2 0.5 +TestRain 3 0.75 +TestRain 4 1 +TestRain 5 0.75 +TestRain 6 0.5 +TestRain 7 0 + +[PATTERNS] +;;Name Type Multipliers +;;-------------- ---------- ----------- +DailyX1 DAILY 1.0 1.0 1.0 1.0 1.0 1.0 1.0 + +[REPORT] +;;Reporting Options +INPUT NO +CONTROLS NO +SUBCATCHMENTS ALL +NODES ALL +LINKS ALL + +[TAGS] + +[MAP] +DIMENSIONS 0.000 0.000 10000.000 10000.000 +Units None + +[COORDINATES] +;;Node X-Coord Y-Coord +;;-------------- ------------------ ------------------ +Outfall -178.777 6435.986 +Tank -1101.499 6828.143 + +[VERTICES] +;;Link X-Coord Y-Coord +;;-------------- ------------------ ------------------ + diff --git a/tests/solver/test_pollutants.cpp b/tests/solver/test_pollutants.cpp new file mode 100644 index 000000000..1c919954d --- /dev/null +++ b/tests/solver/test_pollutants.cpp @@ -0,0 +1,440 @@ +/* + * test_toolkitAPI_pollut.cpp + * + * Created: 07/20/2018 + * Author: Katherine M. Ratliff + * + * Edited: 09/06/2020 + * Author: Abhiram Mullapudi + * + * Unit testing mechanics for the pollutant API using Boost Test. + */ + + #include + #include "test_pollutants.hpp" + +using namespace std; + +BOOST_AUTO_TEST_SUITE(test_toolkitapi_pollut) + +// Testing Pollutant Getter +BOOST_FIXTURE_TEST_CASE(get_pollut_values, FixtureBeforeStep){ + int error, step_ind; + int subc_ind; + int node_ind; + int link_ind; + double *buildup_array; + double *ponded_array; + double *runoff_qual; + double *runoff_load; + double *node_qual; + double *link_qual; + double *link_load; + int length; + double elapsedTime = 0.0; + // Pollutant IDs + int TSS = 0; + int Lead = 1; + + std::string subid = std::string("1"); + std::string nodeid = std::string("9"); + std::string linkid = std::string("1"); + + subc_ind = swmm_getObjectIndex(SM_SUBCATCH, (char *)subid.c_str(), &error); + BOOST_REQUIRE(error == ERR_NONE); + + node_ind = swmm_getObjectIndex(SM_NODE, (char *)nodeid.c_str(), &error); + BOOST_REQUIRE(error == ERR_NONE); + + link_ind = swmm_getObjectIndex(SM_LINK, (char *)linkid.c_str(), &error); + BOOST_REQUIRE(error == ERR_NONE); + + step_ind = 0; + + do + { + if (step_ind == 360) // (Jan 1, 1998 6:00am) + { + // subcatchment buildup + error = swmm_getSubcatchPollut(subc_ind, SM_BUILDUP, &buildup_array, &length); + BOOST_REQUIRE(error == ERR_NONE); + BOOST_CHECK_SMALL(buildup_array[TSS] - 31.906912, 0.0001); + BOOST_CHECK_SMALL(buildup_array[Lead] - 0.0, 0.0001); + + // subcatchment ponded concentration + error = swmm_getSubcatchPollut(subc_ind, SM_CPONDED, &ponded_array, &length); + BOOST_REQUIRE(error == ERR_NONE); + BOOST_CHECK_SMALL(ponded_array[TSS] - 0.0, 0.0001); + BOOST_CHECK_SMALL(ponded_array[Lead] - 0.0, 0.0001); + + // subcatchment runoff pollutant concentration + error = swmm_getSubcatchPollut(subc_ind, SM_SUBCQUAL, &runoff_qual, &length); + BOOST_CHECK_SMALL(runoff_qual[TSS] - 14.118948, 0.0001); + BOOST_CHECK_SMALL(runoff_qual[Lead] - 2.823790, 0.0001); + + // subcatchment runoff total pollutant loading + error = swmm_getSubcatchPollut(subc_ind, SM_SUBCTOTALLOAD, &runoff_load, &length); + BOOST_CHECK_SMALL(runoff_load[TSS] - 0.00242786, 0.0001); + BOOST_CHECK_SMALL(runoff_load[Lead] - 4.856e-10, 0.0001); + + // node pollutant concentration + error = swmm_getNodePollut(node_ind, SM_NODEQUAL, &node_qual, &length); + BOOST_CHECK_SMALL(node_qual[TSS] - 14.121316, 0.0001); + BOOST_CHECK_SMALL(node_qual[Lead] - 2.824263, 0.0001); + + // link pollutant concentration + error = swmm_getLinkPollut(node_ind, SM_LINKQUAL, &link_qual, &length); + BOOST_CHECK_SMALL(link_qual[TSS] - 14.124621, 0.0001); + BOOST_CHECK_SMALL(link_qual[Lead] - 2.824924, 0.0001); + + // link pollutant total load + error = swmm_getLinkPollut(node_ind, SM_TOTALLOAD, &link_load, &length); + BOOST_CHECK_SMALL(link_load[TSS] - 38.496695, 0.01); + BOOST_CHECK_SMALL(link_load[Lead] - 0.00769934, 0.0001); + } + + if (step_ind == 720) // (Jan 1, 1998 12:00pm) + { + // subcatchment buildup + error = swmm_getSubcatchPollut(subc_ind, SM_BUILDUP, &buildup_array, &length); + BOOST_REQUIRE(error == ERR_NONE); + BOOST_CHECK_SMALL(buildup_array[TSS] - 32.354460, 0.0001); + BOOST_CHECK_SMALL(buildup_array[Lead] - 0.0, 0.0001); + + // subcatchment ponded concentration + error = swmm_getSubcatchPollut(subc_ind, SM_CPONDED, &ponded_array, &length); + BOOST_REQUIRE(error == ERR_NONE); + BOOST_CHECK_SMALL(ponded_array[TSS] - 0.0, 0.0001); + BOOST_CHECK_SMALL(ponded_array[Lead] - 0.0, 0.0001); + + // subcatchment runoff pollutant concentration + error = swmm_getSubcatchPollut(subc_ind, SM_SUBCQUAL, &runoff_qual, &length); + BOOST_CHECK_SMALL(runoff_qual[TSS] - 0.0, 0.0001); + BOOST_CHECK_SMALL(runoff_qual[Lead] - 0.0, 0.0001); + + // subcatchment runoff total pollutant loading + error = swmm_getSubcatchPollut(subc_ind, SM_SUBCTOTALLOAD, &runoff_load, &length); + BOOST_CHECK_SMALL(runoff_load[TSS] - 0.00248221, 0.0001); + BOOST_CHECK_SMALL(runoff_load[Lead] - 4.964e-10, 0.0001); + + // node pollutant concentration + error = swmm_getNodePollut(node_ind, SM_NODEQUAL, &node_qual, &length); + BOOST_CHECK_SMALL(node_qual[TSS] - 0.0, 0.0001); + BOOST_CHECK_SMALL(node_qual[Lead] - 0.0, 0.0001); + + // link pollutant concentration + error = swmm_getLinkPollut(node_ind, SM_LINKQUAL, &link_qual, &length); + BOOST_CHECK_SMALL(link_qual[TSS] - 4.380e-11, 0.0001); + BOOST_CHECK_SMALL(link_qual[Lead] - 8.759e-12, 0.0001); + + // link pollutant total load + error = swmm_getLinkPollut(node_ind, SM_TOTALLOAD, &link_load, &length); + BOOST_CHECK_SMALL(link_load[TSS] - 39.780193, 0.01); + BOOST_CHECK_SMALL(link_load[Lead] - 0.00795604, 0.0001); + } + + // Route Model Forward + error = swmm_step(&elapsedTime); + step_ind+=1; + }while (elapsedTime != 0 && !error); + BOOST_REQUIRE(error == ERR_NONE); + + swmm_freeMemory(buildup_array); + swmm_freeMemory(ponded_array); + + swmm_end(); +} + + +// Testing Node influent - storage assets +BOOST_FIXTURE_TEST_CASE(get_node_pollutant_values_cin, FixtureBeforeStep_Pollut_Node){ + + int error, step_ind; + double* node_qual; + double elapsedTime = 0.0; + double total_pollutant = 0.0; + int length; + + // Pollutant IDs + int P1 = 0; + double cin = 10; + + step_ind = 0; + do + { + + error = swmm_getNodePollut(1, SM_NODECIN, &node_qual, &length); + BOOST_REQUIRE(error == ERR_NONE); + + // Check for constant influent + if (step_ind > 5) BOOST_CHECK_SMALL(cin - node_qual[P1], 0.00); + + // Route Model Forward + error = swmm_step(&elapsedTime); + step_ind+=1; + + }while (elapsedTime != 0 && !error); + BOOST_REQUIRE(error == ERR_NONE); + + swmm_end(); +} + +// Testing Reactor Concentration +BOOST_FIXTURE_TEST_CASE(get_node_reactor_pollutant, FixtureBeforeStep_Pollut_Node){ + + int error, step_ind; + double* old_qual; + double* new_qual; + double elapsedTime = 0.0; + double total_pollutant = 0.0; + int length; + + // Pollutant IDs + int P1 = 0; + + step_ind = 0; + do + { + // Check for steady state after 1000 steps. + // 1000 is a aribitarly long time duration, it can be any value as long + // the system reaches a steady state + + // Get reactor concentration + error = swmm_getNodePollut(1, SM_NODECIN, &new_qual, &length); + BOOST_REQUIRE(error == ERR_NONE); + + if (step_ind > 1000) + { + BOOST_CHECK_SMALL(old_qual[P1] - new_qual[P1], 0.00); + } + + old_qual = new_qual; + + // Route Model Forward + error = swmm_step(&elapsedTime); + step_ind+=1; + }while (elapsedTime != 0 && !error); + BOOST_REQUIRE(error == ERR_NONE); + swmm_end(); +} + + +// Testing Pollutant Setter - Node - Cumulative +BOOST_FIXTURE_TEST_CASE(set_node_pollutant_cumulative_values, FixtureBeforeStep_Pollut_Node){ + + int error; + double* node_qual; + double elapsedTime = 0.0; + double total_pollutant = 0.0; + int length; + + // Pollutant IDs + int P1 = 0; + + do + { + // Set pollutant + error = swmm_setNodePollut(1, P1, 0); + BOOST_REQUIRE(error == ERR_NONE); + // Get pollutant + error = swmm_getNodePollut(1, SM_NODEQUAL, &node_qual, &length); + BOOST_REQUIRE(error == ERR_NONE); + // Record cumulative pollutant + total_pollutant = total_pollutant + node_qual[P1]; + // Route Model Forward + error = swmm_step(&elapsedTime); + + }while (elapsedTime != 0 && !error); + BOOST_REQUIRE(error == ERR_NONE); + + // Cumulative must be 0.00 + BOOST_CHECK_SMALL(total_pollutant, 0.00); + swmm_end(); +} + +// Testing Pollutant Setter - Node - Stepwise and Mass balance less than inflow concentration of 10 +BOOST_FIXTURE_TEST_CASE(set_node_pollutant_stepwise_values, FixtureBeforeStep_Pollut_Node){ + + int error; + double* node_qual; + float runoff_error, flow_error, qual_error; + double elapsedTime = 0.0; + int length; + + // Pollutant IDs + int P1 = 0; + do + { + // Set pollutant + error = swmm_setNodePollut(1, P1, 1.234); + BOOST_REQUIRE(error == ERR_NONE); + + // Route Model Forward + error = swmm_step(&elapsedTime); + + // Get pollutant + error = swmm_getNodePollut(1, SM_NODEQUAL, &node_qual, &length); + BOOST_REQUIRE(error == ERR_NONE); + + // Check + BOOST_CHECK_SMALL(node_qual[P1] - 1.234, 0.00); + + }while (elapsedTime != 0 && !error); + BOOST_REQUIRE(error == ERR_NONE); + swmm_end(); + + // check mass balance error less than 1% + swmm_getMassBalErr(&runoff_error, &flow_error, &qual_error); + BOOST_TEST(abs(qual_error) <= 1); +} + + +// Testing Pollutant Setter - Node - Stepwise and Mass balance greater than inflow concentration of 10 +BOOST_FIXTURE_TEST_CASE(set_node_pollutant_stepwise_values_2, FixtureBeforeStep_Pollut_Node){ + + int error; + double* node_qual; + float runoff_error, flow_error, qual_error; + double elapsedTime = 0.0; + int length; + + // Pollutant IDs + int P1 = 0; + do + { + // Set pollutant + error = swmm_setNodePollut(1, P1, 50.0); + BOOST_REQUIRE(error == ERR_NONE); + + // Route Model Forward + error = swmm_step(&elapsedTime); + + // Get pollutant + error = swmm_getNodePollut(1, SM_NODEQUAL, &node_qual, &length); + BOOST_REQUIRE(error == ERR_NONE); + + // Check + BOOST_CHECK_SMALL(node_qual[P1] - 50.0, 0.00); + + }while (elapsedTime != 0 && !error); + BOOST_REQUIRE(error == ERR_NONE); + swmm_end(); + + // check mass balance error less than 1% + swmm_getMassBalErr(&runoff_error, &flow_error, &qual_error); + BOOST_TEST(abs(qual_error) <= 1); +} + + + +// Testing Pollutant Setter - Link - Stepwise - mass balance concentation less than 10 +BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values, FixtureBeforeStep_Pollut_Link){ + + int error, link_ind, node_ind; + int step; + double* link_qual; + double* node_qual; + double elapsedTime = 0.0; + double node_inflow; + float runoff_error, flow_error, qual_error; + char linkid[] = "Culvert"; + char nodeid[] = "Outlet"; + int length; + + // Pollutant ID + int P1 = 0; + + error = swmm_getObjectIndex(SM_LINK, linkid, &link_ind); + BOOST_REQUIRE(error == ERR_NONE); + error = swmm_getObjectIndex(SM_NODE, nodeid, &node_ind); + BOOST_REQUIRE(error == ERR_NONE); + + do + { + // Set pollutant in link and check the pollutant in the node + error = swmm_setLinkPollut(link_ind, SM_LINKQUAL, P1, 2.0); + BOOST_REQUIRE(error == ERR_NONE); + + // Route Model Forward + error = swmm_step(&elapsedTime); + BOOST_REQUIRE(error == ERR_NONE); + + if (step > 2) // Wait for water to reach node + { + // Get infows concentration in node + error = swmm_getNodePollut(node_ind, SM_NODEQUAL, &node_qual, &length); + BOOST_REQUIRE(error == ERR_NONE); + + error = swmm_getLinkPollut(link_ind, SM_LINKQUAL, &link_qual, &length); + + // Check + BOOST_CHECK_SMALL(abs(node_qual[P1] - link_qual[P1]), 0.01); + } + step += 1; + + }while (elapsedTime != 0 && !error); + BOOST_REQUIRE(error == ERR_NONE); + swmm_end(); + + // check mass balance error less than 5% + swmm_getMassBalErr(&runoff_error, &flow_error, &qual_error); + BOOST_TEST(abs(qual_error) <= 5); +} + + +// Testing Pollutant Setter - Link - Stepwise - mass balance concentation less than 10 +BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values_2, FixtureBeforeStep_Pollut_Link){ + + int error, link_ind, node_ind; + int step; + double* link_qual; + double* node_qual; + double elapsedTime = 0.0; + double node_inflow; + float runoff_error, flow_error, qual_error; + char linkid[] = "Culvert"; + char nodeid[] = "Outlet"; + int length; + + // Pollutant ID + int P1 = 0; + + error = swmm_getObjectIndex(SM_LINK, linkid, &link_ind); + BOOST_REQUIRE(error == ERR_NONE); + error = swmm_getObjectIndex(SM_NODE, nodeid, &node_ind); + BOOST_REQUIRE(error == ERR_NONE); + + do + { + // Set pollutant in link and check the pollutant in the node + error = swmm_setLinkPollut(link_ind, SM_LINKQUAL, P1, 24.0); + BOOST_REQUIRE(error == ERR_NONE); + + // Route Model Forward + error = swmm_step(&elapsedTime); + BOOST_REQUIRE(error == ERR_NONE); + + if (step > 2) // Wait for water to reach node + { + // Get infows concentration in node + error = swmm_getNodePollut(node_ind, SM_NODEQUAL, &node_qual, &length); + BOOST_REQUIRE(error == ERR_NONE); + + error = swmm_getLinkPollut(link_ind, SM_LINKQUAL, &link_qual, &length); + + // Check + BOOST_CHECK_SMALL(abs(node_qual[P1] - link_qual[P1]), 0.01); + } + step += 1; + + }while (elapsedTime != 0 && !error); + BOOST_REQUIRE(error == ERR_NONE); + swmm_end(); + + // check mass balance error less than 5% + swmm_getMassBalErr(&runoff_error, &flow_error, &qual_error); + BOOST_TEST(abs(qual_error) <= 5); + printf("%f", qual_error); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/solver/test_pollutants.hpp b/tests/solver/test_pollutants.hpp new file mode 100644 index 000000000..66887e046 --- /dev/null +++ b/tests/solver/test_pollutants.hpp @@ -0,0 +1,57 @@ +// +// test_pollutant.hpp +// +// Created: Aug 14, 2020 +// +// Author: Abhiram Mullapudi +// +// +#ifndef TEST_POLLUTANT_HPP +#define TEST_POLLUTANT_HPP + +#include "swmm5.h" +#include "toolkit.h" + +#define ERR_NONE 0 + +// Add shared data paths here +#define DATA_PATH_INP "test_example1.inp" +#define DATA_PATH_INP_POLLUT_NODE "pollutants/node_constantinflow_constanteffluent.inp" +#define DATA_PATH_INP_POLLUT_LINK "pollutants/link_constantinflow.inp" +#define DATA_PATH_RPT "tmp.rpt" +#define DATA_PATH_OUT "tmp.out" + + +struct FixtureBeforeStep{ + FixtureBeforeStep() { + swmm_open(DATA_PATH_INP, DATA_PATH_RPT, DATA_PATH_OUT); + swmm_start(0); + } + ~FixtureBeforeStep() { + swmm_close(); + } +}; + +struct FixtureBeforeStep_Pollut_Node{ + FixtureBeforeStep_Pollut_Node() { + swmm_open(DATA_PATH_INP_POLLUT_NODE, DATA_PATH_RPT, DATA_PATH_OUT); + swmm_start(0); + } + ~FixtureBeforeStep_Pollut_Node() { + swmm_close(); + } +}; + +struct FixtureBeforeStep_Pollut_Link{ + FixtureBeforeStep_Pollut_Link() { + swmm_open(DATA_PATH_INP_POLLUT_LINK, DATA_PATH_RPT, DATA_PATH_OUT); + swmm_start(0); + } + ~FixtureBeforeStep_Pollut_Link() { + swmm_close(); + } +}; + + + +#endif From 41b1a1be1febf4f19cce7b5615dc9f4fe734e5a6 Mon Sep 17 00:00:00 2001 From: calebbuahin <7217571+cbuahin@users.noreply.github.com> Date: Tue, 23 Feb 2021 20:35:06 -0500 Subject: [PATCH 029/190] Removed ci-tools submodule to address #327 --- .gitmodules | 3 --- CMakeLists.txt | 2 -- ci-tools | 1 - 3 files changed, 6 deletions(-) delete mode 160000 ci-tools diff --git a/.gitmodules b/.gitmodules index 9e5fd7a64..e69de29bb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "ci-tools"] - path = ci-tools - url = https://github.com/OpenWaterAnalytics/ci-tools.git diff --git a/CMakeLists.txt b/CMakeLists.txt index eca11e89b..cedf39c57 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,8 +7,6 @@ # Authors: Michael E. Tryby # US EPA ORD/CESER # -# Modified by: Caleb Buahin -# cmake_minimum_required (VERSION 3.13) diff --git a/ci-tools b/ci-tools deleted file mode 160000 index 4e02d7583..000000000 --- a/ci-tools +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4e02d758355933ff9f280537ebdd1da67bd74a61 From 4724b1463a237dca30888e0907e29ea20da3fae7 Mon Sep 17 00:00:00 2001 From: calebbuahin <7217571+cbuahin@users.noreply.github.com> Date: Tue, 23 Feb 2021 20:43:30 -0500 Subject: [PATCH 030/190] Checkout ci-tools through actions to address #327 --- .github/workflows/build-test.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 8080ca37b..379acb6e3 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -84,6 +84,13 @@ jobs: with: submodules: recursive + - name: Clone ci-tools repo + run: + git clone https://github.com/OpenWaterAnalytics/ci-tools.git + cd ci-tools + git checkout master + cd .. + - name: Setup python uses: actions/setup-python@v2 with: From 9f4fa272dd73936c6aa3601d3569edf9a0f71850 Mon Sep 17 00:00:00 2001 From: calebbuahin <7217571+cbuahin@users.noreply.github.com> Date: Tue, 23 Feb 2021 20:46:22 -0500 Subject: [PATCH 031/190] Checkout ci-tools through actions to address #327 --- .github/workflows/build-test.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 379acb6e3..fbd31afe8 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -87,9 +87,6 @@ jobs: - name: Clone ci-tools repo run: git clone https://github.com/OpenWaterAnalytics/ci-tools.git - cd ci-tools - git checkout master - cd .. - name: Setup python uses: actions/setup-python@v2 From d016aae97c392a030850aa5a8ccd32adedfa1ab1 Mon Sep 17 00:00:00 2001 From: calebbuahin <7217571+cbuahin@users.noreply.github.com> Date: Tue, 23 Feb 2021 20:50:06 -0500 Subject: [PATCH 032/190] Checkout ci-tools through actions to address #327 --- .github/workflows/build-test.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index fbd31afe8..9a0499905 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -85,9 +85,11 @@ jobs: submodules: recursive - name: Clone ci-tools repo - run: - git clone https://github.com/OpenWaterAnalytics/ci-tools.git - + uses: actions/checkout@v2 + with: + repository: OpenWaterAnalytics/ci-tools + path: ci-tools + - name: Setup python uses: actions/setup-python@v2 with: From 0749f0be5bb7b36e71580885beb25b143b5a83fa Mon Sep 17 00:00:00 2001 From: calebbuahin <7217571+cbuahin@users.noreply.github.com> Date: Tue, 23 Feb 2021 20:56:04 -0500 Subject: [PATCH 033/190] Checkout master branch for ci-tools --- .github/workflows/build-test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 9a0499905..68ea4988a 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -88,8 +88,9 @@ jobs: uses: actions/checkout@v2 with: repository: OpenWaterAnalytics/ci-tools + ref: master path: ci-tools - + - name: Setup python uses: actions/setup-python@v2 with: From 5a931741fdc00e4084f64e10046fb4fc7e6d8e28 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 24 Feb 2021 15:55:48 -0500 Subject: [PATCH 034/190] Rolling back libgomp relocation --- src/solver/CMakeLists.txt | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index 717539487..e7645f3ba 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -9,7 +9,7 @@ # ################################################################################ -################### CMAKELISTS FOR SWMM-SOLVER TARGET #################### +###################### CMAKELISTS FOR SOLVER TARGET ###################### ################################################################################ @@ -118,28 +118,19 @@ install( ) -# TODO: Figure out why relocation doesn't work for package target - -# When building on Linux/MacOS relocate OpenMP library to install for wheel build -if(NOT MSVC) - if (APPLE) - set(OMP_LIB_NAME ${OpenMP_libomp_LIBRARY}) - elseif (UNIX AND NOT APPLE) - set(OMP_LIB_NAME ${OpenMP_gomp_LIBRARY}) - endif() - - get_filename_component(EXTERN_LIB_PATH ${OMP_LIB_NAME} REALPATH) +# TODO: Figure out why this doesn't work for package target +# When building on MacOS relocate libomp to install package +if(APPLE) + get_filename_component(EXTERN_LIB_PATH ${OpenMP_libomp_LIBRARY} REALPATH) install( FILES ${EXTERN_LIB_PATH} DESTINATION - ${CMAKE_INSTALL_PREFIX}/lib - PERMISSIONS - OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE + ${CMAKE_INSTALL_PREFIX}/extern ) - # Pass the var up the project tree + # Pass the var up the project tree set(EXTERN_LIB_PATH ${EXTERN_LIB_PATH} PARENT_SCOPE) endif() From ff4d638b61b47d70046ecb497815e5db72b41aa2 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 2 Mar 2021 11:22:25 -0500 Subject: [PATCH 035/190] Updating submodule --- ci-tools | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci-tools b/ci-tools index 98e969009..13cbde7a5 160000 --- a/ci-tools +++ b/ci-tools @@ -1 +1 @@ -Subproject commit 98e969009b092d2a27550ba4f44f0eca1661a970 +Subproject commit 13cbde7a523c4265a0d4127612bc77dfab7aa96f From b99eac4511ac8689c5b426daca440a436324c235 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 2 Mar 2021 11:53:09 -0500 Subject: [PATCH 036/190] Checkout 13cbde7 --- ci-tools | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci-tools b/ci-tools index a3d6d55bb..13cbde7a5 160000 --- a/ci-tools +++ b/ci-tools @@ -1 +1 @@ -Subproject commit a3d6d55bbb11dc0e2a3c78ddfa0092f64b813959 +Subproject commit 13cbde7a523c4265a0d4127612bc77dfab7aa96f From 38e617a2201436865598568e68a8249998e567d2 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 2 Mar 2021 13:44:29 -0500 Subject: [PATCH 037/190] Before reg test detect branch --- .github/workflows/build-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 8080ca37b..72866e255 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -109,8 +109,8 @@ jobs: ./make.${{ matrix.script_extension }} -g "${{ matrix.generator }}" - name: Before reg test - run: | - ./before-nrtest.${{ matrix.script_extension }} + if: ${{ contains(github.ref, 'develop') }} + run: ./before-nrtest.${{ matrix.script_extension }} v1.0.3-dev - name: Run reg test run: | From b038371cddbdabaf88d95b36094f72374575c1d2 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 2 Mar 2021 14:05:29 -0500 Subject: [PATCH 038/190] Refactoring --- .github/workflows/build-test.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 72866e255..ede735865 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -110,7 +110,9 @@ jobs: - name: Before reg test if: ${{ contains(github.ref, 'develop') }} - run: ./before-nrtest.${{ matrix.script_extension }} v1.0.3-dev + env: + BENCHMARK_TAG: v1.0.3-dev + run: ./before-nrtest.${{ matrix.script_extension }} $BENCHMARK_TAG - name: Run reg test run: | From c0dec8673dc3d793dd8c38327fc09c571af12e37 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 2 Mar 2021 14:15:55 -0500 Subject: [PATCH 039/190] Refactoring --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index ede735865..681e4996b 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -112,7 +112,7 @@ jobs: if: ${{ contains(github.ref, 'develop') }} env: BENCHMARK_TAG: v1.0.3-dev - run: ./before-nrtest.${{ matrix.script_extension }} $BENCHMARK_TAG + run: ./before-nrtest.${{ matrix.script_extension }} "$BENCHMARK_TAG" - name: Run reg test run: | From 9fff37e076777428cac55a21a4942cb46d03ed8c Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 2 Mar 2021 14:24:19 -0500 Subject: [PATCH 040/190] Refactoring --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 681e4996b..360e20572 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -112,7 +112,7 @@ jobs: if: ${{ contains(github.ref, 'develop') }} env: BENCHMARK_TAG: v1.0.3-dev - run: ./before-nrtest.${{ matrix.script_extension }} "$BENCHMARK_TAG" + run: ./before-nrtest.${{ matrix.script_extension }} ${{ env.BENCHMARK_TAG }} - name: Run reg test run: | From 39e107da7c967d73333ddedcb60647bb272c0936 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 2 Mar 2021 14:31:42 -0500 Subject: [PATCH 041/190] Refactoring --- .github/workflows/build-test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 360e20572..e6da1dcbd 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -109,6 +109,9 @@ jobs: ./make.${{ matrix.script_extension }} -g "${{ matrix.generator }}" - name: Before reg test + if: ${{ contains(github.ref, 'master') }} + env: + BENCHMARK_TAG: v1.0.3-dev if: ${{ contains(github.ref, 'develop') }} env: BENCHMARK_TAG: v1.0.3-dev From b2ad78ab51f6466caee47d75ce2e09800c031e9a Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 2 Mar 2021 15:08:59 -0500 Subject: [PATCH 042/190] Refactoring --- .github/workflows/build-test.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index e6da1dcbd..9fedc5c46 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -76,7 +76,9 @@ jobs: BUILD_HOME: build TEST_HOME: nrtests NRTESTS_URL: https://github.com/OpenWaterAnalytics/swmm-nrtestsuite - # ACTIONS_ALLOW_UNSECURE_COMMANDS: true + BRANCH: ${GITHUB_REF##*/} + BENCHMARKS: [ { "branch": "master", "tag": v1.0.3-dev }, + { "branch": "develop", "tag": v1.0.3-dev } ] steps: - name: Checkout repo @@ -109,12 +111,9 @@ jobs: ./make.${{ matrix.script_extension }} -g "${{ matrix.generator }}" - name: Before reg test - if: ${{ contains(github.ref, 'master') }} env: - BENCHMARK_TAG: v1.0.3-dev - if: ${{ contains(github.ref, 'develop') }} - env: - BENCHMARK_TAG: v1.0.3-dev + + run: ./before-nrtest.${{ matrix.script_extension }} ${{ env.BENCHMARK_TAG }} - name: Run reg test From 80c7fb5dc3959110aa5b07ea94c5ef03e0992379 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 2 Mar 2021 16:34:58 -0500 Subject: [PATCH 043/190] Refactoring --- .github/workflows/build-test.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 9fedc5c46..97140192e 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -77,8 +77,6 @@ jobs: TEST_HOME: nrtests NRTESTS_URL: https://github.com/OpenWaterAnalytics/swmm-nrtestsuite BRANCH: ${GITHUB_REF##*/} - BENCHMARKS: [ { "branch": "master", "tag": v1.0.3-dev }, - { "branch": "develop", "tag": v1.0.3-dev } ] steps: - name: Checkout repo @@ -112,9 +110,9 @@ jobs: - name: Before reg test env: - - - run: ./before-nrtest.${{ matrix.script_extension }} ${{ env.BENCHMARK_TAG }} + MASTER: v1.0.3-dev + DEVELOP: v1.0.3.dev + run: ./before-nrtest.${{ matrix.script_extension }} ${{ format('env.{0}', env.BRANCH) }} - name: Run reg test run: | From 275ce7eaaf54b5e62ed0c4b31973b2de29cf9f29 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 2 Mar 2021 16:41:25 -0500 Subject: [PATCH 044/190] Refactoring --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 97140192e..119f4de42 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -76,7 +76,7 @@ jobs: BUILD_HOME: build TEST_HOME: nrtests NRTESTS_URL: https://github.com/OpenWaterAnalytics/swmm-nrtestsuite - BRANCH: ${GITHUB_REF##*/} + BRANCH: ${{ ${GITHUB_REF##*/} }} steps: - name: Checkout repo From 860b12870e1ccf45008e4b32f1a1529700454140 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 2 Mar 2021 16:43:02 -0500 Subject: [PATCH 045/190] Refactoring --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 119f4de42..0a7cf693a 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -112,7 +112,7 @@ jobs: env: MASTER: v1.0.3-dev DEVELOP: v1.0.3.dev - run: ./before-nrtest.${{ matrix.script_extension }} ${{ format('env.{0}', env.BRANCH) }} + run: ./before-nrtest.${{ matrix.script_extension }} ${{ format('env.{0}', ${GITHUB_REF##*/}) }} - name: Run reg test run: | From 174ad2e21a757bf63859157f471c8381c19323f8 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 2 Mar 2021 16:55:36 -0500 Subject: [PATCH 046/190] Refactoring --- .github/workflows/build-test.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 0a7cf693a..b6f803389 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -76,7 +76,6 @@ jobs: BUILD_HOME: build TEST_HOME: nrtests NRTESTS_URL: https://github.com/OpenWaterAnalytics/swmm-nrtestsuite - BRANCH: ${{ ${GITHUB_REF##*/} }} steps: - name: Checkout repo @@ -84,6 +83,9 @@ jobs: with: submodules: recursive + - name: Get branch name + uses: nelonoel/branch-name@v1 + - name: Setup python uses: actions/setup-python@v2 with: @@ -112,7 +114,7 @@ jobs: env: MASTER: v1.0.3-dev DEVELOP: v1.0.3.dev - run: ./before-nrtest.${{ matrix.script_extension }} ${{ format('env.{0}', ${GITHUB_REF##*/}) }} + run: ./before-nrtest.${{ matrix.script_extension }} ${{ format('env.{0}', ${BRANCH_NAME}) }} - name: Run reg test run: | From aa5588ca60d2bd2956e6ac72325d327d18b8b8aa Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 2 Mar 2021 17:05:53 -0500 Subject: [PATCH 047/190] Refactoring --- .github/workflows/build-test.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index b6f803389..882ac2511 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -112,9 +112,10 @@ jobs: - name: Before reg test env: - MASTER: v1.0.3-dev - DEVELOP: v1.0.3.dev - run: ./before-nrtest.${{ matrix.script_extension }} ${{ format('env.{0}', ${BRANCH_NAME}) }} + BRANCH: ${BRANCH_NAME} + MASTER: v1.0.3-dev + DEVELOP: v1.0.3-dev + run: ./before-nrtest.${{ matrix.script_extension }} ${{ format('env.{0}', env.BRANCH) }} - name: Run reg test run: | From 060244e0a9850c434e9e80f30851591ab9942da2 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 2 Mar 2021 17:08:01 -0500 Subject: [PATCH 048/190] Refactoring --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 882ac2511..da60cd907 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -84,7 +84,7 @@ jobs: submodules: recursive - name: Get branch name - uses: nelonoel/branch-name@v1 + uses: nelonoel/branch-name@v1.0.1 - name: Setup python uses: actions/setup-python@v2 From 5acd223226a470b514093630bd3c61467987a5ae Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 2 Mar 2021 17:10:04 -0500 Subject: [PATCH 049/190] Refactoring --- .github/workflows/build-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index da60cd907..d0ad0107f 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -113,8 +113,8 @@ jobs: - name: Before reg test env: BRANCH: ${BRANCH_NAME} - MASTER: v1.0.3-dev - DEVELOP: v1.0.3-dev + master: v1.0.3-dev + develop: v1.0.3-dev run: ./before-nrtest.${{ matrix.script_extension }} ${{ format('env.{0}', env.BRANCH) }} - name: Run reg test From b21f500fd6b3d79dbd2a0c8b4f47dbc7c04e4d09 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 2 Mar 2021 17:18:38 -0500 Subject: [PATCH 050/190] Refactoring --- .github/workflows/build-test.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index d0ad0107f..5bf0198fa 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -112,10 +112,9 @@ jobs: - name: Before reg test env: - BRANCH: ${BRANCH_NAME} master: v1.0.3-dev develop: v1.0.3-dev - run: ./before-nrtest.${{ matrix.script_extension }} ${{ format('env.{0}', env.BRANCH) }} + run: ./before-nrtest.${{ matrix.script_extension }} ${{ format('env.{0}', env.BRANCH_NAME) }} - name: Run reg test run: | From 161f258cfd68f03db665f8c2356f051aeb66f97d Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 2 Mar 2021 17:32:57 -0500 Subject: [PATCH 051/190] Refactoring --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 5bf0198fa..58af30c23 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -114,7 +114,7 @@ jobs: env: master: v1.0.3-dev develop: v1.0.3-dev - run: ./before-nrtest.${{ matrix.script_extension }} ${{ format('env.{0}', env.BRANCH_NAME) }} + run: ./before-nrtest.${{ matrix.script_extension }} ${format('{{ env.{0} }}', env.BRANCH_NAME) } - name: Run reg test run: | From 32df3466136eade95fcf941b3d62acc5f6359136 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 2 Mar 2021 17:47:53 -0500 Subject: [PATCH 052/190] Refactoring --- .github/workflows/build-test.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 58af30c23..568280d74 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -83,9 +83,6 @@ jobs: with: submodules: recursive - - name: Get branch name - uses: nelonoel/branch-name@v1.0.1 - - name: Setup python uses: actions/setup-python@v2 with: @@ -110,11 +107,14 @@ jobs: run: | ./make.${{ matrix.script_extension }} -g "${{ matrix.generator }}" + - name: Set nrtest benchmark tags + if: ${{ contains(github.ref, 'master') }} + run: echo "BENCHMARK_TAG=v.1.0.3-dev" >> $GITHUB_ENV + - if: ${{ contains(github.ref, 'develop') }} + run: echo "BENCHMARK_TAG=v.1.0.3-dev" >> $GITHUB_ENV + - name: Before reg test - env: - master: v1.0.3-dev - develop: v1.0.3-dev - run: ./before-nrtest.${{ matrix.script_extension }} ${format('{{ env.{0} }}', env.BRANCH_NAME) } + run: ./before-nrtest.${{ matrix.script_extension }} ${{env.BENCHMARK_TAG)}} - name: Run reg test run: | From 77cc55a18edd3da2a6608cec743035b2bbcb56c5 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 2 Mar 2021 17:49:31 -0500 Subject: [PATCH 053/190] Fix typo --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 568280d74..8b0fddaa6 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -114,7 +114,7 @@ jobs: run: echo "BENCHMARK_TAG=v.1.0.3-dev" >> $GITHUB_ENV - name: Before reg test - run: ./before-nrtest.${{ matrix.script_extension }} ${{env.BENCHMARK_TAG)}} + run: ./before-nrtest.${{ matrix.script_extension }} ${{ env.BENCHMARK_TAG }} - name: Run reg test run: | From 27015552f17e9169064fbb25c3207446d6298f3a Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 2 Mar 2021 17:53:25 -0500 Subject: [PATCH 054/190] Fix typo --- .github/workflows/build-test.yml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 8b0fddaa6..69445e1a4 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -100,25 +100,22 @@ jobs: brew install boost - name: Build and unit test - run: | - ./make.${{ matrix.script_extension }} -t -g "${{ matrix.generator }}" + run: ./make.${{ matrix.script_extension }} -t -g "${{ matrix.generator }}" - name: Build for reg test - run: | - ./make.${{ matrix.script_extension }} -g "${{ matrix.generator }}" + run: ./make.${{ matrix.script_extension }} -g "${{ matrix.generator }}" - name: Set nrtest benchmark tags if: ${{ contains(github.ref, 'master') }} - run: echo "BENCHMARK_TAG=v.1.0.3-dev" >> $GITHUB_ENV + run: echo "BENCHMARK_TAG=v1.0.3-dev" >> $GITHUB_ENV - if: ${{ contains(github.ref, 'develop') }} - run: echo "BENCHMARK_TAG=v.1.0.3-dev" >> $GITHUB_ENV + run: echo "BENCHMARK_TAG=v1.0.3-dev" >> $GITHUB_ENV - name: Before reg test run: ./before-nrtest.${{ matrix.script_extension }} ${{ env.BENCHMARK_TAG }} - name: Run reg test - run: | - ./run-nrtests.${{ matrix.script_extension }} ${{ matrix.gh_run_number }} + run: ./run-nrtests.${{ matrix.script_extension }} ${{ matrix.gh_run_number }} - name: Upload artifacts if: ${{ always() }} From daaab69cad92bfb79ceac3f02594765ad07facf5 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 3 Mar 2021 09:12:47 -0500 Subject: [PATCH 055/190] Object test --- .github/workflows/build-test.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 69445e1a4..c843c8abb 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -76,7 +76,12 @@ jobs: BUILD_HOME: build TEST_HOME: nrtests NRTESTS_URL: https://github.com/OpenWaterAnalytics/swmm-nrtestsuite - + BENCHMARK_TAGS: + "[ + { "branch": "master", "tag": "v1.0.3-dev" }, + { "branch": "develop", "tag": "v1.0.3-dev" } + ]" + steps: - name: Checkout repo uses: actions/checkout@v2 From 0945384619c9524c84334f16fdaa75c65dca0c97 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 3 Mar 2021 09:15:11 -0500 Subject: [PATCH 056/190] Object test --- .github/workflows/build-test.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index c843c8abb..c8d629275 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -76,12 +76,9 @@ jobs: BUILD_HOME: build TEST_HOME: nrtests NRTESTS_URL: https://github.com/OpenWaterAnalytics/swmm-nrtestsuite - BENCHMARK_TAGS: - "[ - { "branch": "master", "tag": "v1.0.3-dev" }, - { "branch": "develop", "tag": "v1.0.3-dev" } - ]" - + BENCHMARK_TAGS: "[ { "branch": "master", "tag": "v1.0.3-dev" }, + { "branch": "develop", "tag": "v1.0.3-dev" } ]" + steps: - name: Checkout repo uses: actions/checkout@v2 From 816708a78e79c637dab9061dea1931636138b09b Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 3 Mar 2021 09:16:58 -0500 Subject: [PATCH 057/190] Object test --- .github/workflows/build-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index c8d629275..da55bb58a 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -76,8 +76,8 @@ jobs: BUILD_HOME: build TEST_HOME: nrtests NRTESTS_URL: https://github.com/OpenWaterAnalytics/swmm-nrtestsuite - BENCHMARK_TAGS: "[ { "branch": "master", "tag": "v1.0.3-dev" }, - { "branch": "develop", "tag": "v1.0.3-dev" } ]" + BENCHMARK_TAGS: [ { "branch": "master", "tag": "v1.0.3-dev" }, + { "branch": "develop", "tag": "v1.0.3-dev" } ] steps: - name: Checkout repo From 9fa7aa16fd3a3e3cef75ad963bb72106fe462cf4 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 3 Mar 2021 09:31:03 -0500 Subject: [PATCH 058/190] Test --- .github/workflows/build-test.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index da55bb58a..902a0c39f 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -76,8 +76,7 @@ jobs: BUILD_HOME: build TEST_HOME: nrtests NRTESTS_URL: https://github.com/OpenWaterAnalytics/swmm-nrtestsuite - BENCHMARK_TAGS: [ { "branch": "master", "tag": "v1.0.3-dev" }, - { "branch": "develop", "tag": "v1.0.3-dev" } ] + BENCHMARK_TAGS: "" steps: - name: Checkout repo From 4aa98962dc00bf91b5fa42e7e264dea481f54c13 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 3 Mar 2021 09:32:15 -0500 Subject: [PATCH 059/190] Test --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 902a0c39f..768768a7b 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -76,7 +76,7 @@ jobs: BUILD_HOME: build TEST_HOME: nrtests NRTESTS_URL: https://github.com/OpenWaterAnalytics/swmm-nrtestsuite - BENCHMARK_TAGS: "" + BENCHMARK_TAGS: "{}" steps: - name: Checkout repo From ab296b85aea7b9f65ba2f2c05613d29fb30b72bc Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 3 Mar 2021 09:33:16 -0500 Subject: [PATCH 060/190] Test --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 768768a7b..fde4e8e0f 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -76,7 +76,7 @@ jobs: BUILD_HOME: build TEST_HOME: nrtests NRTESTS_URL: https://github.com/OpenWaterAnalytics/swmm-nrtestsuite - BENCHMARK_TAGS: "{}" + BENCHMARK_TAGS: "{"branch":"tag"}" steps: - name: Checkout repo From 1ad14ec344ec450fe432c1a694a6516fa13829ca Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 3 Mar 2021 09:34:26 -0500 Subject: [PATCH 061/190] Test --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index fde4e8e0f..4b0303eef 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -76,7 +76,7 @@ jobs: BUILD_HOME: build TEST_HOME: nrtests NRTESTS_URL: https://github.com/OpenWaterAnalytics/swmm-nrtestsuite - BENCHMARK_TAGS: "{"branch":"tag"}" + BENCHMARK_TAGS: {"branch":"tag"} steps: - name: Checkout repo From 5e1439f42a11e5ff3d7cc44ede647fe386c01aa7 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 3 Mar 2021 10:04:50 -0500 Subject: [PATCH 062/190] Applying KISS principle --- .github/workflows/build-test.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 4b0303eef..17f102faf 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -76,7 +76,6 @@ jobs: BUILD_HOME: build TEST_HOME: nrtests NRTESTS_URL: https://github.com/OpenWaterAnalytics/swmm-nrtestsuite - BENCHMARK_TAGS: {"branch":"tag"} steps: - name: Checkout repo @@ -106,13 +105,9 @@ jobs: - name: Build for reg test run: ./make.${{ matrix.script_extension }} -g "${{ matrix.generator }}" - - name: Set nrtest benchmark tags - if: ${{ contains(github.ref, 'master') }} - run: echo "BENCHMARK_TAG=v1.0.3-dev" >> $GITHUB_ENV - - if: ${{ contains(github.ref, 'develop') }} - run: echo "BENCHMARK_TAG=v1.0.3-dev" >> $GITHUB_ENV - - name: Before reg test + env: + BENCHMARK_TAG: v1.0.3-dev run: ./before-nrtest.${{ matrix.script_extension }} ${{ env.BENCHMARK_TAG }} - name: Run reg test From 4a952682f90f3afeb6642bccbcf1ac9196a4d371 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 3 Mar 2021 11:08:51 -0500 Subject: [PATCH 063/190] Refactoring --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 17f102faf..43c5af58f 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -75,7 +75,6 @@ jobs: PROJECT: swmm BUILD_HOME: build TEST_HOME: nrtests - NRTESTS_URL: https://github.com/OpenWaterAnalytics/swmm-nrtestsuite steps: - name: Checkout repo @@ -107,6 +106,7 @@ jobs: - name: Before reg test env: + NRTESTS_URL: https://github.com/OpenWaterAnalytics/swmm-nrtestsuite BENCHMARK_TAG: v1.0.3-dev run: ./before-nrtest.${{ matrix.script_extension }} ${{ env.BENCHMARK_TAG }} From 327388ddc945635b8f2d881be047a8bf82396723 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 3 Mar 2021 14:03:58 -0500 Subject: [PATCH 064/190] Update ci-tools --- ci-tools | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci-tools b/ci-tools index 13cbde7a5..1a29973e6 160000 --- a/ci-tools +++ b/ci-tools @@ -1 +1 @@ -Subproject commit 13cbde7a523c4265a0d4127612bc77dfab7aa96f +Subproject commit 1a29973e62f4b0c7bac05e9985e89b461492dd87 From 4d4a94f8d7c746fd2d0af300ce7949663427c035 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 3 Mar 2021 14:15:49 -0500 Subject: [PATCH 065/190] Updating master --- ci-tools | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci-tools b/ci-tools index 13cbde7a5..1a29973e6 160000 --- a/ci-tools +++ b/ci-tools @@ -1 +1 @@ -Subproject commit 13cbde7a523c4265a0d4127612bc77dfab7aa96f +Subproject commit 1a29973e62f4b0c7bac05e9985e89b461492dd87 From 76477bc46abef4c9bad00d4c7fa69a2f41947f0e Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 4 Mar 2021 12:29:14 -0500 Subject: [PATCH 066/190] Tweaking Mac OS wheel build --- CMakeLists.txt | 2 +- src/solver/CMakeLists.txt | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d25f5f8df..ba5608ee7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,7 +66,7 @@ endif() # Pass var up the project tree used for library reloacation -if(NOT MSVC) +if(APPLE) set(EXTERN_LIB_PATH ${EXTERN_LIB_PATH} PARENT_SCOPE) endif() diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index e7645f3ba..aa5962e00 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -43,7 +43,7 @@ if(BUILD_DEF) ) else() - # Performs standard library build + # Performs standard library build add_library(swmm5 SHARED ${SWMM_SOURCES} @@ -89,7 +89,7 @@ set_target_properties(swmm5 MACOSX_RPATH TRUE SKIP_BUILD_RPATH FALSE BUILD_WITH_INSTALL_RPATH FALSE - #INSTALL_RPATH "${PACKAGE_RPATH}" + INSTALL_RPATH "${PACKAGE_RPATH}" INSTALL_RPATH_USE_LINK_PATH TRUE ) @@ -127,7 +127,7 @@ if(APPLE) FILES ${EXTERN_LIB_PATH} DESTINATION - ${CMAKE_INSTALL_PREFIX}/extern + ${LIBRARY_DIST} ) # Pass the var up the project tree From 44dd51f8390f817950b7200596a427a320f4f72d Mon Sep 17 00:00:00 2001 From: calebbuahin <7217571+cbuahin@users.noreply.github.com> Date: Thu, 4 Mar 2021 22:07:10 -0500 Subject: [PATCH 067/190] Updated nrtestsuite version --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index c560afd43..2a829146a 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -114,7 +114,7 @@ jobs: - name: Before reg test env: NRTESTS_URL: https://github.com/OpenWaterAnalytics/swmm-nrtestsuite - BENCHMARK_TAG: v1.0.3-dev + BENCHMARK_TAG: v1.0.4-dev run: ./before-nrtest.${{ matrix.script_extension }} ${{ env.BENCHMARK_TAG }} - name: Run reg test From dca89c71bf8afd61e4d5d2360d225a6bd1605016 Mon Sep 17 00:00:00 2001 From: calebbuahin <7217571+cbuahin@users.noreply.github.com> Date: Sun, 7 Mar 2021 21:45:34 -0500 Subject: [PATCH 068/190] Fixed swmm output temp array initialization bug --- src/outfile/swmm_output.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/outfile/swmm_output.c b/src/outfile/swmm_output.c index de2f2da94..f80df2fe9 100644 --- a/src/outfile/swmm_output.c +++ b/src/outfile/swmm_output.c @@ -715,7 +715,7 @@ int EXPORT_OUT_API SMO_getSystemAttribute(SMO_Handle p_handle, int periodIndex, // { int errorcode = 0; - float temp; + float *temp; data_t *p_data; p_data = (data_t *)p_handle; From e3af8f05de0d7bee1e33bbcbd3055653b34381c8 Mon Sep 17 00:00:00 2001 From: calebbuahin <7217571+cbuahin@users.noreply.github.com> Date: Sun, 7 Mar 2021 21:55:12 -0500 Subject: [PATCH 069/190] Fixed swmm output temp array initialization bug to address #66 --- src/outfile/swmm_output.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/outfile/swmm_output.c b/src/outfile/swmm_output.c index f80df2fe9..124807844 100644 --- a/src/outfile/swmm_output.c +++ b/src/outfile/swmm_output.c @@ -724,9 +724,11 @@ int EXPORT_OUT_API SMO_getSystemAttribute(SMO_Handle p_handle, int periodIndex, errorcode = -1; else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) errorcode = 422; + else if + MEMCHECK(temp = newFloatArray(1)) errorcode = 411; else { // don't need to loop since there's only one system - temp = getSystemValue(p_data, periodIndex, attr); + temp[0] = getSystemValue(p_data, periodIndex, attr); *outValueArray = &temp; *length = 1; From 65e12e36e88f180831fd5c9d8cb6589d6b784d9b Mon Sep 17 00:00:00 2001 From: calebbuahin <7217571+cbuahin@users.noreply.github.com> Date: Mon, 8 Mar 2021 00:00:39 -0500 Subject: [PATCH 070/190] Linux only build test --- .github/workflows/build-test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 2a829146a..1cd995d8a 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -26,7 +26,8 @@ jobs: strategy: fail-fast: false matrix: - os: [windows-2016, ubuntu-16.04, macos-latest] + # os: [windows-2016, ubuntu-16.04, macos-latest] + os: [ubuntu-16.04] requirements: [requirements-swmm.txt] include: - os: windows-2016 From a5bd1f655f75ad6fbdadeedb2802f6598e2631a3 Mon Sep 17 00:00:00 2001 From: calebbuahin <7217571+cbuahin@users.noreply.github.com> Date: Mon, 8 Mar 2021 00:03:17 -0500 Subject: [PATCH 071/190] Work in progress --- .github/workflows/build-test.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 1cd995d8a..2a829146a 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -26,8 +26,7 @@ jobs: strategy: fail-fast: false matrix: - # os: [windows-2016, ubuntu-16.04, macos-latest] - os: [ubuntu-16.04] + os: [windows-2016, ubuntu-16.04, macos-latest] requirements: [requirements-swmm.txt] include: - os: windows-2016 From 4116d533ccecb5a0780963ddfca2f30a05a617dc Mon Sep 17 00:00:00 2001 From: calebbuahin <7217571+cbuahin@users.noreply.github.com> Date: Mon, 8 Mar 2021 00:11:18 -0500 Subject: [PATCH 072/190] Trigger build --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 2a829146a..b4cf6299b 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -26,7 +26,7 @@ jobs: strategy: fail-fast: false matrix: - os: [windows-2016, ubuntu-16.04, macos-latest] + os: [windows-2016, ubuntu-16.04, macos-latest] requirements: [requirements-swmm.txt] include: - os: windows-2016 From e041dfbf2a8528a41f56329e9c211a5b19bee543 Mon Sep 17 00:00:00 2001 From: calebbuahin <7217571+cbuahin@users.noreply.github.com> Date: Mon, 8 Mar 2021 10:18:21 -0500 Subject: [PATCH 073/190] Trigger rebuild to check ci-tools --- src/outfile/swmm_output.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/outfile/swmm_output.c b/src/outfile/swmm_output.c index 124807844..dacbd6961 100644 --- a/src/outfile/swmm_output.c +++ b/src/outfile/swmm_output.c @@ -723,7 +723,7 @@ int EXPORT_OUT_API SMO_getSystemAttribute(SMO_Handle p_handle, int periodIndex, if (p_data == NULL) errorcode = -1; else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) - errorcode = 422; + errorcode = 422; else if MEMCHECK(temp = newFloatArray(1)) errorcode = 411; else { From 28553cfdecfd960461a5476c565fd1f7af153dbc Mon Sep 17 00:00:00 2001 From: calebbuahin <7217571+cbuahin@users.noreply.github.com> Date: Mon, 8 Mar 2021 10:23:32 -0500 Subject: [PATCH 074/190] Trigger rebuild to check ci-tools --- src/outfile/swmm_output.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/outfile/swmm_output.c b/src/outfile/swmm_output.c index dacbd6961..124807844 100644 --- a/src/outfile/swmm_output.c +++ b/src/outfile/swmm_output.c @@ -723,7 +723,7 @@ int EXPORT_OUT_API SMO_getSystemAttribute(SMO_Handle p_handle, int periodIndex, if (p_data == NULL) errorcode = -1; else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) - errorcode = 422; + errorcode = 422; else if MEMCHECK(temp = newFloatArray(1)) errorcode = 411; else { From eaf4603f7bf31cef77055a87521bf6206ac74e29 Mon Sep 17 00:00:00 2001 From: calebbuahin <7217571+cbuahin@users.noreply.github.com> Date: Mon, 8 Mar 2021 14:02:43 -0500 Subject: [PATCH 075/190] Trigger rebuild --- src/solver/climate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solver/climate.c b/src/solver/climate.c index a44c5faae..67fe51dc3 100644 --- a/src/solver/climate.c +++ b/src/solver/climate.c @@ -39,7 +39,7 @@ #include #include #include -#include "headers.h" +#include "headers.h" //----------------------------------------------------------------------------- // Constants From fc0bf8cfd4970d34441ff8d37de7b5c4370624fd Mon Sep 17 00:00:00 2001 From: calebbuahin <7217571+cbuahin@users.noreply.github.com> Date: Mon, 8 Mar 2021 14:34:57 -0500 Subject: [PATCH 076/190] Trigger rebuild --- src/solver/climate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solver/climate.c b/src/solver/climate.c index 67fe51dc3..cebb96527 100644 --- a/src/solver/climate.c +++ b/src/solver/climate.c @@ -150,7 +150,7 @@ static void parseGhcndFileLine(void); //============================================================================= -int climate_readParams(char* tok[], int ntoks) +int climate_readParams(char* tok[], int ntoks) // // Input: tok[] = array of string tokens // ntoks = number of tokens From e653582f10abd8489ed8effbf7b46f51f7cf8abf Mon Sep 17 00:00:00 2001 From: calebbuahin <7217571+cbuahin@users.noreply.github.com> Date: Mon, 8 Mar 2021 14:53:44 -0500 Subject: [PATCH 077/190] Trigger build to test ci-tools fix --- src/solver/climate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solver/climate.c b/src/solver/climate.c index cebb96527..67fe51dc3 100644 --- a/src/solver/climate.c +++ b/src/solver/climate.c @@ -150,7 +150,7 @@ static void parseGhcndFileLine(void); //============================================================================= -int climate_readParams(char* tok[], int ntoks) +int climate_readParams(char* tok[], int ntoks) // // Input: tok[] = array of string tokens // ntoks = number of tokens From ae527a79606fdfdffa5a1a695aeafe59a278d757 Mon Sep 17 00:00:00 2001 From: calebbuahin <7217571+cbuahin@users.noreply.github.com> Date: Mon, 8 Mar 2021 15:47:16 -0500 Subject: [PATCH 078/190] Trigger rebuild to finalize ci-tools debugging --- src/solver/climate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solver/climate.c b/src/solver/climate.c index 67fe51dc3..cebb96527 100644 --- a/src/solver/climate.c +++ b/src/solver/climate.c @@ -150,7 +150,7 @@ static void parseGhcndFileLine(void); //============================================================================= -int climate_readParams(char* tok[], int ntoks) +int climate_readParams(char* tok[], int ntoks) // // Input: tok[] = array of string tokens // ntoks = number of tokens From 9afb4be73e54dbbc75a97dee364556c8bd763a5c Mon Sep 17 00:00:00 2001 From: calebbuahin <7217571+cbuahin@users.noreply.github.com> Date: Mon, 8 Mar 2021 16:46:02 -0500 Subject: [PATCH 079/190] Trigger rebuild --- src/solver/climate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solver/climate.c b/src/solver/climate.c index cebb96527..67fe51dc3 100644 --- a/src/solver/climate.c +++ b/src/solver/climate.c @@ -150,7 +150,7 @@ static void parseGhcndFileLine(void); //============================================================================= -int climate_readParams(char* tok[], int ntoks) +int climate_readParams(char* tok[], int ntoks) // // Input: tok[] = array of string tokens // ntoks = number of tokens From a164ba3889734e4f4ece6f214329c1a716814b74 Mon Sep 17 00:00:00 2001 From: calebbuahin <7217571+cbuahin@users.noreply.github.com> Date: Mon, 8 Mar 2021 17:10:05 -0500 Subject: [PATCH 080/190] Trigger rebuild --- src/solver/climate.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/solver/climate.c b/src/solver/climate.c index 67fe51dc3..29fb613ca 100644 --- a/src/solver/climate.c +++ b/src/solver/climate.c @@ -168,8 +168,8 @@ int climate_readParams(char* tok[], int ntoks) { int i, j, k; double x[6], y; - DateTime aDate; - + DateTime aDate; + // --- identify keyword k = findmatch(tok[0], TempKeyWords); if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[0]); From d4502fa73a9cac1cef30f574d916056f1a27cc55 Mon Sep 17 00:00:00 2001 From: michaeltryby Date: Tue, 9 Mar 2021 17:01:08 -0500 Subject: [PATCH 081/190] Makes OpenMP an optional component --- src/solver/CMakeLists.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index 91c66a2b9..44f5d9904 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -14,7 +14,10 @@ ################################################################################ -find_package(OpenMP REQUIRED) +find_package(OpenMP + OPTIONAL_COMPONENTS + C +) # configure file groups @@ -73,7 +76,7 @@ target_link_options(swmm5 target_link_libraries(swmm5 PUBLIC $<$>>:m> - $<$:OpenMP::OpenMP_C> + $<$:OpenMP::OpenMP_C> ) target_include_directories(swmm5 From 1ccb1fdbba96ec6f992829f4b6de423e611f5a5d Mon Sep 17 00:00:00 2001 From: michaeltryby Date: Thu, 11 Mar 2021 14:31:49 -0500 Subject: [PATCH 082/190] Make OpenMP optional for wheel build --- CMakeLists.txt | 4 +++- src/solver/CMakeLists.txt | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 458d3e945..ca32fea2f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,7 +72,9 @@ endif() # Create install rules for vcruntime.dll, msvcp.dll, vcomp.dll etc. -set(CMAKE_INSTALL_OPENMP_LIBRARIES TRUE) +if(OpenMP_FOUND) + set(CMAKE_INSTALL_OPENMP_LIBRARIES TRUE) +endif() include(InstallRequiredSystemLibraries) diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index 44f5d9904..90c598f0b 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -124,7 +124,7 @@ install( # TODO: Figure out why this doesn't work for package target # When building on MacOS relocate libomp to install package -if(APPLE) +if(APPLE AND OpenMP_FOUND) get_filename_component(EXTERN_LIB_PATH ${OpenMP_libomp_LIBRARY} REALPATH) install( From f943c68ac96a7df6f891f31712d8188b61a279c2 Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sun, 14 Mar 2021 15:19:57 -0400 Subject: [PATCH 083/190] increase tolerance and update authors --- AUTHORS | 1 + tests/solver/test_pollutants.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index f76d9c764..d86917893 100644 --- a/AUTHORS +++ b/AUTHORS @@ -24,3 +24,4 @@ Caleb Buahin Hsi-Nien Tan Mingda Zhang Laurent Courty +Brooke E. Mason \ No newline at end of file diff --git a/tests/solver/test_pollutants.cpp b/tests/solver/test_pollutants.cpp index 1c919954d..3c15a726e 100644 --- a/tests/solver/test_pollutants.cpp +++ b/tests/solver/test_pollutants.cpp @@ -368,7 +368,7 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values, FixtureBeforeStep_Po error = swmm_getLinkPollut(link_ind, SM_LINKQUAL, &link_qual, &length); // Check - BOOST_CHECK_SMALL(abs(node_qual[P1] - link_qual[P1]), 0.01); + BOOST_CHECK_SMALL(abs(node_qual[P1] - link_qual[P1]), 0.1); } step += 1; @@ -423,7 +423,7 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values_2, FixtureBeforeStep_ error = swmm_getLinkPollut(link_ind, SM_LINKQUAL, &link_qual, &length); // Check - BOOST_CHECK_SMALL(abs(node_qual[P1] - link_qual[P1]), 0.01); + BOOST_CHECK_SMALL(abs(node_qual[P1] - link_qual[P1]), 0.1); } step += 1; From 3cb1eb9450a10ebad65567a9b61341dbb8f379db Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sun, 14 Mar 2021 15:47:17 -0400 Subject: [PATCH 084/190] updated with larger tol --- tests/solver/test_pollutants.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/solver/test_pollutants.cpp b/tests/solver/test_pollutants.cpp index 3c15a726e..8a4d4d421 100644 --- a/tests/solver/test_pollutants.cpp +++ b/tests/solver/test_pollutants.cpp @@ -359,7 +359,7 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values, FixtureBeforeStep_Po error = swmm_step(&elapsedTime); BOOST_REQUIRE(error == ERR_NONE); - if (step > 2) // Wait for water to reach node + if (step > 10) // Wait for water to reach node { // Get infows concentration in node error = swmm_getNodePollut(node_ind, SM_NODEQUAL, &node_qual, &length); @@ -368,7 +368,7 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values, FixtureBeforeStep_Po error = swmm_getLinkPollut(link_ind, SM_LINKQUAL, &link_qual, &length); // Check - BOOST_CHECK_SMALL(abs(node_qual[P1] - link_qual[P1]), 0.1); + BOOST_CHECK_SMALL(abs(node_qual[P1] - link_qual[P1]), 5.0); } step += 1; @@ -414,7 +414,7 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values_2, FixtureBeforeStep_ error = swmm_step(&elapsedTime); BOOST_REQUIRE(error == ERR_NONE); - if (step > 2) // Wait for water to reach node + if (step > 10) // Wait for water to reach node { // Get infows concentration in node error = swmm_getNodePollut(node_ind, SM_NODEQUAL, &node_qual, &length); @@ -423,7 +423,7 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values_2, FixtureBeforeStep_ error = swmm_getLinkPollut(link_ind, SM_LINKQUAL, &link_qual, &length); // Check - BOOST_CHECK_SMALL(abs(node_qual[P1] - link_qual[P1]), 0.1); + BOOST_CHECK_SMALL(abs(node_qual[P1] - link_qual[P1]), 5.0); } step += 1; From 35b234b047e345017a5a8e4ccd3a07a84a46fa61 Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sun, 14 Mar 2021 15:50:51 -0400 Subject: [PATCH 085/190] updated with even larger tol --- tests/solver/test_pollutants.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/solver/test_pollutants.cpp b/tests/solver/test_pollutants.cpp index 8a4d4d421..d6e64e3f9 100644 --- a/tests/solver/test_pollutants.cpp +++ b/tests/solver/test_pollutants.cpp @@ -407,7 +407,7 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values_2, FixtureBeforeStep_ do { // Set pollutant in link and check the pollutant in the node - error = swmm_setLinkPollut(link_ind, SM_LINKQUAL, P1, 24.0); + error = swmm_setLinkPollut(link_ind, SM_LINKQUAL, P1, 10.0); BOOST_REQUIRE(error == ERR_NONE); // Route Model Forward From 3b3ca813368323e875069f63a2211686825aaa3d Mon Sep 17 00:00:00 2001 From: michaeltryby Date: Mon, 15 Mar 2021 09:33:32 -0400 Subject: [PATCH 086/190] Configure fetch content or libomp --- CMakeLists.txt | 4 +++- extern/openmp.cmake | 19 +++++++++++++++++++ src/solver/CMakeLists.txt | 14 +++++++++----- 3 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 extern/openmp.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 458d3e945..ca32fea2f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,7 +72,9 @@ endif() # Create install rules for vcruntime.dll, msvcp.dll, vcomp.dll etc. -set(CMAKE_INSTALL_OPENMP_LIBRARIES TRUE) +if(OpenMP_FOUND) + set(CMAKE_INSTALL_OPENMP_LIBRARIES TRUE) +endif() include(InstallRequiredSystemLibraries) diff --git a/extern/openmp.cmake b/extern/openmp.cmake new file mode 100644 index 000000000..fdeb99b59 --- /dev/null +++ b/extern/openmp.cmake @@ -0,0 +1,19 @@ + + + +#cmake_minimum_required(VERSION 3.19) +#project(openmp) + +include(FetchContent) + + +FetchContent_Declare( + openmp + URL https://github.com/llvm/llvm-project/releases/download/llvmorg-11.1.0/openmp-11.1.0.src.tar.xz + URL_HASH SHA256=d187483b75b39acb3ff8ea1b7d98524d95322e3cb148842957e9b0fbb866052e +) + +set(OPENMP_STANDALONE_BUILD TRUE) +set(LIBOMP_INSTALL_ALIASES OFF) +FetchContent_MakeAvailable(openmp) + diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index 44f5d9904..4b4517e83 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -14,11 +14,15 @@ ################################################################################ -find_package(OpenMP - OPTIONAL_COMPONENTS - C -) +#find_package(OpenMP +# OPTIONAL_COMPONENTS +# C +#) +if(APPLE) + set(CMAKE_OSX_DEPLOYMENT_TARGET:STRING 10.9) + include(../../extern/openmp.cmake) +endif() # configure file groups set(SWMM_PUBLIC_HEADERS @@ -124,7 +128,7 @@ install( # TODO: Figure out why this doesn't work for package target # When building on MacOS relocate libomp to install package -if(APPLE) +if(APPLE AND OpenMP_FOUND) get_filename_component(EXTERN_LIB_PATH ${OpenMP_libomp_LIBRARY} REALPATH) install( From 894f6701c5a31ad7fc51c94e17ce90bb827a9700 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 15 Mar 2021 14:31:43 -0400 Subject: [PATCH 087/190] Configuring build for omp --- extern/openmp.cmake | 31 ++++++++++++++++++++++++------- src/solver/CMakeLists.txt | 35 ++++++++++++++++++----------------- 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/extern/openmp.cmake b/extern/openmp.cmake index fdeb99b59..142444bdd 100644 --- a/extern/openmp.cmake +++ b/extern/openmp.cmake @@ -1,19 +1,36 @@ -#cmake_minimum_required(VERSION 3.19) -#project(openmp) - include(FetchContent) -FetchContent_Declare( - openmp - URL https://github.com/llvm/llvm-project/releases/download/llvmorg-11.1.0/openmp-11.1.0.src.tar.xz - URL_HASH SHA256=d187483b75b39acb3ff8ea1b7d98524d95322e3cb148842957e9b0fbb866052e +FetchContent_Declare(openmp + URL + https://github.com/llvm/llvm-project/releases/download/llvmorg-11.1.0/openmp-11.1.0.src.tar.xz + URL_HASH + SHA256=d187483b75b39acb3ff8ea1b7d98524d95322e3cb148842957e9b0fbb866052e ) set(OPENMP_STANDALONE_BUILD TRUE) set(LIBOMP_INSTALL_ALIASES OFF) FetchContent_MakeAvailable(openmp) + +target_link_directories(omp + PUBLIC + $ + $ +) + +install(TARGETS omp EXPORT ompTargets + LIBRARY DESTINATION "${LIBRARY_DIST}" +) + +install( + EXPORT + ompTargets + DESTINATION + "${CONFIG_DIST}" + FILE + omp-config.cmake +) diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index 4b4517e83..005a558ff 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -6,7 +6,7 @@ # # Author: Michael E. Tryby # US EPA ORD/CESER -# +# ################################################################################ @@ -14,14 +14,14 @@ ################################################################################ -#find_package(OpenMP -# OPTIONAL_COMPONENTS -# C -#) - if(APPLE) - set(CMAKE_OSX_DEPLOYMENT_TARGET:STRING 10.9) include(../../extern/openmp.cmake) + +else() + find_package(OpenMP + OPTIONAL_COMPONENTS + C + ) endif() # configure file groups @@ -81,6 +81,7 @@ target_link_libraries(swmm5 PUBLIC $<$>>:m> $<$:OpenMP::OpenMP_C> + $<$:omp> ) target_include_directories(swmm5 @@ -128,16 +129,16 @@ install( # TODO: Figure out why this doesn't work for package target # When building on MacOS relocate libomp to install package -if(APPLE AND OpenMP_FOUND) - get_filename_component(EXTERN_LIB_PATH ${OpenMP_libomp_LIBRARY} REALPATH) - - install( - FILES - ${EXTERN_LIB_PATH} - DESTINATION - ${LIBRARY_DIST} - ) - +if(APPLE) + get_filename_component(EXTERN_LIB_PATH omp REALPATH) +# +# install( +# FILES +# ${EXTERN_LIB_PATH} +# DESTINATION +# ${LIBRARY_DIST} +# ) +# # Pass the var up the project tree set(EXTERN_LIB_PATH ${EXTERN_LIB_PATH} PARENT_SCOPE) endif() From 427cb6c81acd546f9e58a7c2fb493dc8674ad8c6 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 15 Mar 2021 14:33:59 -0400 Subject: [PATCH 088/190] WIP --- src/solver/CMakeLists.txt | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index 005a558ff..fb1068e70 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -127,18 +127,10 @@ install( ) -# TODO: Figure out why this doesn't work for package target -# When building on MacOS relocate libomp to install package +# When building on MacOS save libomp filepath if(APPLE) get_filename_component(EXTERN_LIB_PATH omp REALPATH) -# -# install( -# FILES -# ${EXTERN_LIB_PATH} -# DESTINATION -# ${LIBRARY_DIST} -# ) -# + # Pass the var up the project tree set(EXTERN_LIB_PATH ${EXTERN_LIB_PATH} PARENT_SCOPE) endif() From 6d954cd4c5e0371480c1cdff9c5e385dc8958e01 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 15 Mar 2021 14:55:29 -0400 Subject: [PATCH 089/190] WIP --- extern/boost.cmake | 6 ++++++ extern/openmp.cmake | 8 ++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/extern/boost.cmake b/extern/boost.cmake index 4f9c10aaa..e9f0e001b 100644 --- a/extern/boost.cmake +++ b/extern/boost.cmake @@ -24,12 +24,18 @@ if (DEFINED ENV{BOOST_ROOT_1_72_0}) elseif(DEFINED ENV{BOOST_ROOT_1_67_0}) set(BOOST_ROOT $ENV{BOOST_ROOT_1_67_0}) +# Boost location on Actions build runner +else() + include(/opt/hostedtoolcache/boost/1.72.0/x64/lib/cmake/Boost-1.72.0/BoostConfig.cmake) + endif() +set(CMAKE_FIND_DEBUG_MODE TRUE) find_package(Boost 1.67.0 COMPONENTS unit_test_framework ) +set(CMAKE_FIND_DEBUG_MODE FALSE) include_directories (${Boost_INCLUDE_DIRS}) diff --git a/extern/openmp.cmake b/extern/openmp.cmake index 142444bdd..144f8004f 100644 --- a/extern/openmp.cmake +++ b/extern/openmp.cmake @@ -22,8 +22,12 @@ target_link_directories(omp $ ) -install(TARGETS omp EXPORT ompTargets - LIBRARY DESTINATION "${LIBRARY_DIST}" +install(TARGETS omp + EXPORT + ompTargets + LIBRARY + DESTINATION + "${LIBRARY_DIST}" ) install( From 4061a0f53ea5ff8b73162745c70a4638f30dbf17 Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Tue, 16 Mar 2021 12:43:51 -0400 Subject: [PATCH 090/190] updated tol to 0.001 --- tests/solver/test_pollutants.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/solver/test_pollutants.cpp b/tests/solver/test_pollutants.cpp index d6e64e3f9..7a042be9b 100644 --- a/tests/solver/test_pollutants.cpp +++ b/tests/solver/test_pollutants.cpp @@ -368,7 +368,7 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values, FixtureBeforeStep_Po error = swmm_getLinkPollut(link_ind, SM_LINKQUAL, &link_qual, &length); // Check - BOOST_CHECK_SMALL(abs(node_qual[P1] - link_qual[P1]), 5.0); + BOOST_CHECK_SMALL(abs(node_qual[P1] - link_qual[P1]), 0.001); } step += 1; @@ -407,7 +407,7 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values_2, FixtureBeforeStep_ do { // Set pollutant in link and check the pollutant in the node - error = swmm_setLinkPollut(link_ind, SM_LINKQUAL, P1, 10.0); + error = swmm_setLinkPollut(link_ind, SM_LINKQUAL, P1, 20.0); BOOST_REQUIRE(error == ERR_NONE); // Route Model Forward @@ -423,7 +423,7 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values_2, FixtureBeforeStep_ error = swmm_getLinkPollut(link_ind, SM_LINKQUAL, &link_qual, &length); // Check - BOOST_CHECK_SMALL(abs(node_qual[P1] - link_qual[P1]), 5.0); + BOOST_CHECK_SMALL(abs(node_qual[P1] - link_qual[P1]), 0.001); } step += 1; @@ -433,7 +433,7 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values_2, FixtureBeforeStep_ // check mass balance error less than 5% swmm_getMassBalErr(&runoff_error, &flow_error, &qual_error); - BOOST_TEST(abs(qual_error) <= 5); + BOOST_TEST(abs(qual_error) <= 10); printf("%f", qual_error); } From d6b416393e1c81d823c5dc118aef3e3bc11dbccf Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 17 Mar 2021 14:53:47 -0400 Subject: [PATCH 091/190] WIP --- extern/openmp.cmake | 23 +++++++++++++++++++---- src/solver/CMakeLists.txt | 1 - 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/extern/openmp.cmake b/extern/openmp.cmake index 144f8004f..7e3223ba9 100644 --- a/extern/openmp.cmake +++ b/extern/openmp.cmake @@ -1,5 +1,20 @@ - - +# +# CMakeLists.txt - CMake configuration file for OpenMP Library on Darwin +# +# Created: Mar 17, 2021 +# Updated: +# +# Author: Michael E. Tryby +# US EPA ORD/CESER +# +# Note: +# OpenMP library build fails for Xcode generator. Use Ninja or Unix Makefiles +# instead. +# + +################################################################################ +##################### CMAKELISTS FOR OPENMP LIBRARY ###################### +################################################################################ include(FetchContent) @@ -13,8 +28,8 @@ FetchContent_Declare(openmp set(OPENMP_STANDALONE_BUILD TRUE) set(LIBOMP_INSTALL_ALIASES OFF) -FetchContent_MakeAvailable(openmp) +FetchContent_MakeAvailable(openmp) target_link_directories(omp PUBLIC @@ -25,7 +40,7 @@ target_link_directories(omp install(TARGETS omp EXPORT ompTargets - LIBRARY + LIBRARY DESTINATION "${LIBRARY_DIST}" ) diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index fb1068e70..ab935d2ff 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -16,7 +16,6 @@ if(APPLE) include(../../extern/openmp.cmake) - else() find_package(OpenMP OPTIONAL_COMPONENTS From 3686ac1442caa7565740b8e75c65f2dc4b0b546e Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 18 Mar 2021 12:30:06 -0400 Subject: [PATCH 092/190] Moving fetch openmp to wheel build --- CMakeLists.txt | 6 +++--- src/solver/CMakeLists.txt | 24 ++++++++++-------------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ca32fea2f..3798e73c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,9 +66,9 @@ endif() # Pass var up the project tree used for library reloacation -if(APPLE) - set(EXTERN_LIB_PATH ${EXTERN_LIB_PATH} PARENT_SCOPE) -endif() +#if(APPLE) +# set(EXTERN_LIB_PATH ${EXTERN_LIB_PATH} PARENT_SCOPE) +#endif() # Create install rules for vcruntime.dll, msvcp.dll, vcomp.dll etc. diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index ab935d2ff..6a2d42d54 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -14,14 +14,10 @@ ################################################################################ -if(APPLE) - include(../../extern/openmp.cmake) -else() - find_package(OpenMP - OPTIONAL_COMPONENTS - C - ) -endif() +find_package(OpenMP + OPTIONAL_COMPONENTS + C +) # configure file groups set(SWMM_PUBLIC_HEADERS @@ -127,12 +123,12 @@ install( # When building on MacOS save libomp filepath -if(APPLE) - get_filename_component(EXTERN_LIB_PATH omp REALPATH) - - # Pass the var up the project tree - set(EXTERN_LIB_PATH ${EXTERN_LIB_PATH} PARENT_SCOPE) -endif() +#if(APPLE) +# get_filename_component(EXTERN_LIB_PATH omp REALPATH) +# +# # Pass the var up the project tree +# set(EXTERN_LIB_PATH ${EXTERN_LIB_PATH} PARENT_SCOPE) +#endif() # copy swmm5 to build tree for testing From 36b5113cef89b95f2d16c8136b6798cb201d9210 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 18 Mar 2021 12:35:24 -0400 Subject: [PATCH 093/190] Ignore ci-tools --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9aed7e952..a591f28cd 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ # Build files +ci-tools/ build/ *.o /Debug/ From e4f175fdd4a40947231af5f93b4a2d99224472ba Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 18 Mar 2021 15:22:06 -0400 Subject: [PATCH 094/190] Cleaning up --- CMakeLists.txt | 8 +----- extern/openmp.cmake | 55 --------------------------------------- src/solver/CMakeLists.txt | 9 ------- 3 files changed, 1 insertion(+), 71 deletions(-) delete mode 100644 extern/openmp.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 3798e73c8..cad8fb4ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,7 +24,7 @@ endif() project(swmm-solver VERSION 5.1.14 LANGUAGES C CXX - ) +) # Append local dir to module search path # list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) @@ -65,12 +65,6 @@ if(BUILD_TESTS) endif() -# Pass var up the project tree used for library reloacation -#if(APPLE) -# set(EXTERN_LIB_PATH ${EXTERN_LIB_PATH} PARENT_SCOPE) -#endif() - - # Create install rules for vcruntime.dll, msvcp.dll, vcomp.dll etc. if(OpenMP_FOUND) set(CMAKE_INSTALL_OPENMP_LIBRARIES TRUE) diff --git a/extern/openmp.cmake b/extern/openmp.cmake deleted file mode 100644 index 7e3223ba9..000000000 --- a/extern/openmp.cmake +++ /dev/null @@ -1,55 +0,0 @@ -# -# CMakeLists.txt - CMake configuration file for OpenMP Library on Darwin -# -# Created: Mar 17, 2021 -# Updated: -# -# Author: Michael E. Tryby -# US EPA ORD/CESER -# -# Note: -# OpenMP library build fails for Xcode generator. Use Ninja or Unix Makefiles -# instead. -# - -################################################################################ -##################### CMAKELISTS FOR OPENMP LIBRARY ###################### -################################################################################ - -include(FetchContent) - - -FetchContent_Declare(openmp - URL - https://github.com/llvm/llvm-project/releases/download/llvmorg-11.1.0/openmp-11.1.0.src.tar.xz - URL_HASH - SHA256=d187483b75b39acb3ff8ea1b7d98524d95322e3cb148842957e9b0fbb866052e -) - -set(OPENMP_STANDALONE_BUILD TRUE) -set(LIBOMP_INSTALL_ALIASES OFF) - -FetchContent_MakeAvailable(openmp) - -target_link_directories(omp - PUBLIC - $ - $ -) - -install(TARGETS omp - EXPORT - ompTargets - LIBRARY - DESTINATION - "${LIBRARY_DIST}" -) - -install( - EXPORT - ompTargets - DESTINATION - "${CONFIG_DIST}" - FILE - omp-config.cmake -) diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index 6a2d42d54..017118c50 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -122,15 +122,6 @@ install( ) -# When building on MacOS save libomp filepath -#if(APPLE) -# get_filename_component(EXTERN_LIB_PATH omp REALPATH) -# -# # Pass the var up the project tree -# set(EXTERN_LIB_PATH ${EXTERN_LIB_PATH} PARENT_SCOPE) -#endif() - - # copy swmm5 to build tree for testing add_custom_command(TARGET swmm5 POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy From 93891ff1e3775073895a3b96f38fa6999e961ed9 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 19 Mar 2021 17:10:45 -0400 Subject: [PATCH 095/190] Set -O3 flag --- src/solver/CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index 017118c50..b76b374ef 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -63,6 +63,9 @@ target_compile_options(swmm5 "$<$:/fp:fast>" "$<$:/Zi>" ">" + $<$: + $<$:-O3> + > ) target_link_options(swmm5 @@ -76,7 +79,7 @@ target_link_libraries(swmm5 PUBLIC $<$>>:m> $<$:OpenMP::OpenMP_C> - $<$:omp> + $<$:omp> ) target_include_directories(swmm5 From bda8aa158bb7fe065120efb289861f823ba22793 Mon Sep 17 00:00:00 2001 From: Jennifer Wu Date: Tue, 23 Mar 2021 10:20:50 -0400 Subject: [PATCH 096/190] Issue #334 Update Github Action to install boost manually for Linux and Mac --- .github/workflows/build-test.yml | 8 +++++++- extern/boost.cmake | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index b4cf6299b..db0f257c1 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -98,7 +98,13 @@ jobs: run: | python -m pip install --upgrade pip python -m pip install -r ${{ matrix.requirements }} - + + - name: Install Linux requirements + if: ${{ contains(matrix.os, 'ubuntu') }} + run: | + sudo apt-get update + sudo apt install libboost-dev libboost-all-dev + - name: Install Darwin requirements if: ${{ contains(matrix.os, 'macos') }} run: | diff --git a/extern/boost.cmake b/extern/boost.cmake index e9f0e001b..37711d09a 100644 --- a/extern/boost.cmake +++ b/extern/boost.cmake @@ -25,8 +25,8 @@ elseif(DEFINED ENV{BOOST_ROOT_1_67_0}) set(BOOST_ROOT $ENV{BOOST_ROOT_1_67_0}) # Boost location on Actions build runner -else() - include(/opt/hostedtoolcache/boost/1.72.0/x64/lib/cmake/Boost-1.72.0/BoostConfig.cmake) +# else() +# include(/opt/hostedtoolcache/boost/1.72.0/x64/lib/cmake/Boost-1.72.0/BoostConfig.cmake) endif() From 4c7868a723b5aa2419716c6dfb3ef08f724ca6ba Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Mon, 12 Apr 2021 22:27:58 -0400 Subject: [PATCH 097/190] removed node rpt flag --- src/solver/project.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/solver/project.c b/src/solver/project.c index 48cac9836..9a612f30a 100644 --- a/src/solver/project.c +++ b/src/solver/project.c @@ -1041,7 +1041,6 @@ void createObjects() Node[j].inQual = (double *) calloc(Nobjects[POLLUT], sizeof(double)); Node[j].reactorQual = (double *) calloc(Nobjects[POLLUT], sizeof(double)); Node[j].extPollutFlag = (int *) calloc(Nobjects[POLLUT], sizeof(int)); - Node[j].rptFlag = 0; Node[j].extInflow = NULL; Node[j].dwfInflow = NULL; Node[j].rdiiInflow = NULL; From 6b82d5b64356d2d81f4a7dda34d3e55f6f391568 Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sat, 17 Apr 2021 15:31:53 -0400 Subject: [PATCH 098/190] removed loop for external pollutant flag check and replaced with global variable --- src/solver/globals.h | 3 ++- src/solver/qualrout.c | 37 +++---------------------------------- src/solver/swmm5.c | 3 +++ src/solver/toolkit.c | 4 ++++ 4 files changed, 12 insertions(+), 35 deletions(-) diff --git a/src/solver/globals.h b/src/solver/globals.h index 1d7c9207e..b3d2ca403 100644 --- a/src/solver/globals.h +++ b/src/solver/globals.h @@ -97,7 +97,8 @@ EXTERN int SweepEnd, // Day of year when sweeping ends MaxTrials, // Max. trials for DW routing NumThreads, // Number of parallel threads used - NumEvents; // Number of detailed events + NumEvents, // Number of detailed events + ExtPollutFlag; // Enable external pollutant injection //InSteadyState; // System flows remain constant EXTERN double diff --git a/src/solver/qualrout.c b/src/solver/qualrout.c index 6bcbc73c6..70791096f 100644 --- a/src/solver/qualrout.c +++ b/src/solver/qualrout.c @@ -101,43 +101,12 @@ void qualrout_execute(double tStep) // { int i, j; - int p, extPollutFlag; + int p; double qIn, vAvg; // --- find mass flow each link contributes to its downstream node for ( i = 0; i < Nobjects[LINK]; i++ ) findLinkMassFlow(i, tStep); - // --- check for external treatment in nodes or links - for (j = 0; j < Nobjects[NODE]; j++) - { - // --- check for external pollutant - for ( p = 0; p < Nobjects[POLLUT]; p++) - { - if (Node[j].extPollutFlag[p] == 1) - { - extPollutFlag = 1; - break; - } - - } - - } - - for (i = 0; i < Nobjects[LINK]; i++) - { - // --- check for external pollutant - for ( p = 0; p < Nobjects[POLLUT]; p++) - { - if (Link[i].extPollutFlag[p] == 1) - { - extPollutFlag = 1; - break; - } - - } - - } - // --- find new water quality concentration at each node for (j = 0; j < Nobjects[NODE]; j++) { @@ -147,7 +116,7 @@ void qualrout_execute(double tStep) vAvg = (Node[j].oldVolume + Node[j].newVolume) / 2.0; // --- save inflow concentrations if treatment applied - if ( Node[j].treatment || extPollutFlag == 1) + if ( Node[j].treatment || ExtPollutFlag == 1) { if ( qIn < ZERO ) qIn = 0.0; treatmnt_setInflow(qIn, Node[j].newQual); @@ -161,7 +130,7 @@ void qualrout_execute(double tStep) else findNodeQual(j); // --- apply treatment to new quality values - if ( Node[j].treatment || extPollutFlag == 1) treatmnt_treat(j, qIn, vAvg, tStep); + if ( Node[j].treatment || ExtPollutFlag == 1) treatmnt_treat(j, qIn, vAvg, tStep); } // --- find new water quality in each link diff --git a/src/solver/swmm5.c b/src/solver/swmm5.c index 0be2b4364..c97141383 100644 --- a/src/solver/swmm5.c +++ b/src/solver/swmm5.c @@ -290,6 +290,9 @@ int DLLEXPORT swmm_start(int saveResults) NonConvergeCount = 0; IsStartedFlag = TRUE; + // --- initialize external pollutant control + ExtPollutFlag = 0; + // --- initialize global continuity errors RunoffError = 0.0; GwaterError = 0.0; diff --git a/src/solver/toolkit.c b/src/solver/toolkit.c index 6471169ad..e08b52363 100644 --- a/src/solver/toolkit.c +++ b/src/solver/toolkit.c @@ -1964,6 +1964,10 @@ int DLLEXPORT swmm_setNodePollut(int index, int pollutant_index, double pollutan } else { + if (ExtPollutFlag == 0) + { + ExtPollutFlag = 1; + } if (pollutant_index <= Nobjects[POLLUT]) { Node[index].extQual[pollutant_index] = pollutant_value; From f9fdee91e46b6fde2cdb0d56f168a8fa5366b289 Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sat, 17 Apr 2021 16:39:57 -0400 Subject: [PATCH 099/190] flag get reset after each step --- src/solver/qualrout.c | 4 ++++ tests/solver/test_pollutants.cpp | 14 +++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/solver/qualrout.c b/src/solver/qualrout.c index 70791096f..e72d94485 100644 --- a/src/solver/qualrout.c +++ b/src/solver/qualrout.c @@ -135,6 +135,10 @@ void qualrout_execute(double tStep) // --- find new water quality in each link for ( i = 0; i < Nobjects[LINK]; i++ ) findLinkQual(i, tStep); + + // --- default to swmm treatment + if (ExtPollutFlag == 1){ExtPollutFlag = 0;} + } //============================================================================= diff --git a/tests/solver/test_pollutants.cpp b/tests/solver/test_pollutants.cpp index 7a042be9b..0bf4d4caa 100644 --- a/tests/solver/test_pollutants.cpp +++ b/tests/solver/test_pollutants.cpp @@ -146,7 +146,7 @@ BOOST_FIXTURE_TEST_CASE(get_pollut_values, FixtureBeforeStep){ } -// Testing Node influent - storage assets +// Testing Node influent concentration- storage assets BOOST_FIXTURE_TEST_CASE(get_node_pollutant_values_cin, FixtureBeforeStep_Pollut_Node){ int error, step_ind; @@ -200,12 +200,12 @@ BOOST_FIXTURE_TEST_CASE(get_node_reactor_pollutant, FixtureBeforeStep_Pollut_Nod // the system reaches a steady state // Get reactor concentration - error = swmm_getNodePollut(1, SM_NODECIN, &new_qual, &length); + error = swmm_getNodePollut(1, SM_NODEREACTORC, &new_qual, &length); BOOST_REQUIRE(error == ERR_NONE); if (step_ind > 1000) { - BOOST_CHECK_SMALL(old_qual[P1] - new_qual[P1], 0.00); + BOOST_CHECK_SMALL(old_qual[P1] - new_qual[P1], 0.00001); } old_qual = new_qual; @@ -236,13 +236,13 @@ BOOST_FIXTURE_TEST_CASE(set_node_pollutant_cumulative_values, FixtureBeforeStep_ // Set pollutant error = swmm_setNodePollut(1, P1, 0); BOOST_REQUIRE(error == ERR_NONE); + // Route Model Forward + error = swmm_step(&elapsedTime); // Get pollutant error = swmm_getNodePollut(1, SM_NODEQUAL, &node_qual, &length); BOOST_REQUIRE(error == ERR_NONE); // Record cumulative pollutant total_pollutant = total_pollutant + node_qual[P1]; - // Route Model Forward - error = swmm_step(&elapsedTime); }while (elapsedTime != 0 && !error); BOOST_REQUIRE(error == ERR_NONE); @@ -407,14 +407,14 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values_2, FixtureBeforeStep_ do { // Set pollutant in link and check the pollutant in the node - error = swmm_setLinkPollut(link_ind, SM_LINKQUAL, P1, 20.0); + error = swmm_setLinkPollut(link_ind, SM_LINKQUAL, P1, 2.0); BOOST_REQUIRE(error == ERR_NONE); // Route Model Forward error = swmm_step(&elapsedTime); BOOST_REQUIRE(error == ERR_NONE); - if (step > 10) // Wait for water to reach node + if (step > 100) // Wait for water to reach node { // Get infows concentration in node error = swmm_getNodePollut(node_ind, SM_NODEQUAL, &node_qual, &length); From bf4f289297a49687741afb76e5b3cc366edaedaf Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sat, 17 Apr 2021 18:32:49 -0400 Subject: [PATCH 100/190] updated with error log --- tests/solver/test_pollutants.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/solver/test_pollutants.cpp b/tests/solver/test_pollutants.cpp index 0bf4d4caa..c054c285d 100644 --- a/tests/solver/test_pollutants.cpp +++ b/tests/solver/test_pollutants.cpp @@ -407,14 +407,14 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values_2, FixtureBeforeStep_ do { // Set pollutant in link and check the pollutant in the node - error = swmm_setLinkPollut(link_ind, SM_LINKQUAL, P1, 2.0); + error = swmm_setLinkPollut(link_ind, SM_LINKQUAL, P1, 20.0); BOOST_REQUIRE(error == ERR_NONE); // Route Model Forward error = swmm_step(&elapsedTime); BOOST_REQUIRE(error == ERR_NONE); - if (step > 100) // Wait for water to reach node + if (step > 1000) // Wait for water to reach node { // Get infows concentration in node error = swmm_getNodePollut(node_ind, SM_NODEQUAL, &node_qual, &length); From 3764a2ab07b840e3daf047f9d11638c8793004f2 Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sat, 17 Apr 2021 18:50:47 -0400 Subject: [PATCH 101/190] updated with global variable for link pollutant setter --- src/solver/toolkit.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/solver/toolkit.c b/src/solver/toolkit.c index e08b52363..b52d47ac1 100644 --- a/src/solver/toolkit.c +++ b/src/solver/toolkit.c @@ -2108,6 +2108,10 @@ int DLLEXPORT swmm_setLinkPollut(int index, int type, int pollutant_index, doubl } else { + if (ExtPollutFlag == 0) + { + ExtPollutFlag = 1; + } if (pollutant_index <= Nobjects[POLLUT]) { switch(type) From d5cd16bc84a770ea7c2e4b95b1fac2fa3b7f00fb Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sun, 30 May 2021 13:27:07 -0400 Subject: [PATCH 102/190] updated to latest ubuntu --- .github/workflows/build-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index db0f257c1..e652f283c 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -26,7 +26,7 @@ jobs: strategy: fail-fast: false matrix: - os: [windows-2016, ubuntu-16.04, macos-latest] + os: [windows-2016, ubuntu-20.04, macos-latest] requirements: [requirements-swmm.txt] include: - os: windows-2016 @@ -40,7 +40,7 @@ jobs: run: shell: cmd working-directory: ./ci-tools/windows - - os: ubuntu-16.04 + - os: ubuntu-20.04 generator: "Unix Makefiles" experimental: true script_extension: sh From c4797c9c4963c965bc4219cf8e75045d440b31a9 Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sun, 30 May 2021 13:44:45 -0400 Subject: [PATCH 103/190] updated to latest ubuntu 18.04 --- .github/workflows/build-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index e652f283c..561297b6b 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -26,7 +26,7 @@ jobs: strategy: fail-fast: false matrix: - os: [windows-2016, ubuntu-20.04, macos-latest] + os: [windows-2016, ubuntu-18.04, macos-latest] requirements: [requirements-swmm.txt] include: - os: windows-2016 @@ -40,7 +40,7 @@ jobs: run: shell: cmd working-directory: ./ci-tools/windows - - os: ubuntu-20.04 + - os: ubuntu-18.04 generator: "Unix Makefiles" experimental: true script_extension: sh From a2ee8eb60bb445e0876da2e579584f1b59c75fce Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sun, 13 Jun 2021 16:55:06 -0400 Subject: [PATCH 104/190] updated ubuntu 16.04 --- .github/workflows/build-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 561297b6b..db0f257c1 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -26,7 +26,7 @@ jobs: strategy: fail-fast: false matrix: - os: [windows-2016, ubuntu-18.04, macos-latest] + os: [windows-2016, ubuntu-16.04, macos-latest] requirements: [requirements-swmm.txt] include: - os: windows-2016 @@ -40,7 +40,7 @@ jobs: run: shell: cmd working-directory: ./ci-tools/windows - - os: ubuntu-18.04 + - os: ubuntu-16.04 generator: "Unix Makefiles" experimental: true script_extension: sh From 4ef3b84c2d748f4733f7ff9acf480b3e3025ec7e Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sun, 13 Jun 2021 17:12:48 -0400 Subject: [PATCH 105/190] ubuntu 16.10 --- .github/workflows/build-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index db0f257c1..5cc483049 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -26,7 +26,7 @@ jobs: strategy: fail-fast: false matrix: - os: [windows-2016, ubuntu-16.04, macos-latest] + os: [windows-2016, ubuntu-16.10, macos-latest] requirements: [requirements-swmm.txt] include: - os: windows-2016 @@ -40,7 +40,7 @@ jobs: run: shell: cmd working-directory: ./ci-tools/windows - - os: ubuntu-16.04 + - os: ubuntu-16.10 generator: "Unix Makefiles" experimental: true script_extension: sh From cae5d1ac00d185cd4a6674ff7cf54ab907274c04 Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sun, 13 Jun 2021 17:19:58 -0400 Subject: [PATCH 106/190] back to 18.10 --- .github/workflows/build-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 5cc483049..4f51824a6 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -26,7 +26,7 @@ jobs: strategy: fail-fast: false matrix: - os: [windows-2016, ubuntu-16.10, macos-latest] + os: [windows-2016, ubuntu-18.10, macos-latest] requirements: [requirements-swmm.txt] include: - os: windows-2016 @@ -40,7 +40,7 @@ jobs: run: shell: cmd working-directory: ./ci-tools/windows - - os: ubuntu-16.10 + - os: ubuntu-18.10 generator: "Unix Makefiles" experimental: true script_extension: sh From 2b2ff6e63a917d96526fd7b64a378f3eca3640cd Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sun, 13 Jun 2021 17:24:35 -0400 Subject: [PATCH 107/190] back to 20.04 --- .github/workflows/build-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 4f51824a6..e652f283c 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -26,7 +26,7 @@ jobs: strategy: fail-fast: false matrix: - os: [windows-2016, ubuntu-18.10, macos-latest] + os: [windows-2016, ubuntu-20.04, macos-latest] requirements: [requirements-swmm.txt] include: - os: windows-2016 @@ -40,7 +40,7 @@ jobs: run: shell: cmd working-directory: ./ci-tools/windows - - os: ubuntu-18.10 + - os: ubuntu-20.04 generator: "Unix Makefiles" experimental: true script_extension: sh From 224cec76423668dc14cec59e4d68fbaac3f28101 Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sun, 13 Jun 2021 17:31:07 -0400 Subject: [PATCH 108/190] update 16.04 --- .github/workflows/build-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index e652f283c..db0f257c1 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -26,7 +26,7 @@ jobs: strategy: fail-fast: false matrix: - os: [windows-2016, ubuntu-20.04, macos-latest] + os: [windows-2016, ubuntu-16.04, macos-latest] requirements: [requirements-swmm.txt] include: - os: windows-2016 @@ -40,7 +40,7 @@ jobs: run: shell: cmd working-directory: ./ci-tools/windows - - os: ubuntu-20.04 + - os: ubuntu-16.04 generator: "Unix Makefiles" experimental: true script_extension: sh From 340788a4a70b7c9c5a7a653f9fbfcdd7f8b460ce Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sun, 13 Jun 2021 18:14:28 -0400 Subject: [PATCH 109/190] updated with boost 67 --- .github/workflows/build-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index db0f257c1..faf64e9bc 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -103,8 +103,8 @@ jobs: if: ${{ contains(matrix.os, 'ubuntu') }} run: | sudo apt-get update - sudo apt install libboost-dev libboost-all-dev - + sudo apt install libboost1.67-dev + - name: Install Darwin requirements if: ${{ contains(matrix.os, 'macos') }} run: | From 04395db9ac9de4c775bc4333340b379638f40de4 Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sun, 13 Jun 2021 18:16:47 -0400 Subject: [PATCH 110/190] updated with boost 74 --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index faf64e9bc..1dbb7aaa8 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -103,7 +103,7 @@ jobs: if: ${{ contains(matrix.os, 'ubuntu') }} run: | sudo apt-get update - sudo apt install libboost1.67-dev + sudo apt install libboost1.74-dev - name: Install Darwin requirements if: ${{ contains(matrix.os, 'macos') }} From 348cba84691cd4ceaaa9d520fe1e9af4661675d6 Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sun, 13 Jun 2021 18:19:46 -0400 Subject: [PATCH 111/190] updated to ubuntu 20.04 --- .github/workflows/build-test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 1dbb7aaa8..e652f283c 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -26,7 +26,7 @@ jobs: strategy: fail-fast: false matrix: - os: [windows-2016, ubuntu-16.04, macos-latest] + os: [windows-2016, ubuntu-20.04, macos-latest] requirements: [requirements-swmm.txt] include: - os: windows-2016 @@ -40,7 +40,7 @@ jobs: run: shell: cmd working-directory: ./ci-tools/windows - - os: ubuntu-16.04 + - os: ubuntu-20.04 generator: "Unix Makefiles" experimental: true script_extension: sh @@ -103,8 +103,8 @@ jobs: if: ${{ contains(matrix.os, 'ubuntu') }} run: | sudo apt-get update - sudo apt install libboost1.74-dev - + sudo apt install libboost-dev libboost-all-dev + - name: Install Darwin requirements if: ${{ contains(matrix.os, 'macos') }} run: | From 0dc9c319a6effc7840937cc30516e5419cde851d Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sun, 13 Jun 2021 18:26:44 -0400 Subject: [PATCH 112/190] added included to unit test --- tests/solver/test_pollutants.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/solver/test_pollutants.cpp b/tests/solver/test_pollutants.cpp index c054c285d..5e01c5917 100644 --- a/tests/solver/test_pollutants.cpp +++ b/tests/solver/test_pollutants.cpp @@ -10,7 +10,7 @@ * Unit testing mechanics for the pollutant API using Boost Test. */ - #include + #include #include "test_pollutants.hpp" using namespace std; From ee6862020aa2f7285bd4be1919b7bb78cc6eef8a Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sun, 13 Jun 2021 18:30:16 -0400 Subject: [PATCH 113/190] removed ref in pollutant --- tests/solver/test_pollutants.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/solver/test_pollutants.cpp b/tests/solver/test_pollutants.cpp index 5e01c5917..e2b43d0e2 100644 --- a/tests/solver/test_pollutants.cpp +++ b/tests/solver/test_pollutants.cpp @@ -10,7 +10,6 @@ * Unit testing mechanics for the pollutant API using Boost Test. */ - #include #include "test_pollutants.hpp" using namespace std; From 24f526ef75f2cf029e3d7b537fdc154157aeb313 Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sun, 13 Jun 2021 18:33:43 -0400 Subject: [PATCH 114/190] added boost import --- tests/solver/test_pollutants.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/solver/test_pollutants.cpp b/tests/solver/test_pollutants.cpp index e2b43d0e2..a9d2a0830 100644 --- a/tests/solver/test_pollutants.cpp +++ b/tests/solver/test_pollutants.cpp @@ -9,8 +9,8 @@ * * Unit testing mechanics for the pollutant API using Boost Test. */ - - #include "test_pollutants.hpp" +#include +#include "test_pollutants.hpp" using namespace std; From 825e4a0e6becc2b3501655e5821e74dd0768dc8e Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sun, 13 Jun 2021 18:45:56 -0400 Subject: [PATCH 115/190] update tests with ubuntu and cmake --- .github/workflows/build-test.yml | 4 ++-- tests/solver/CMakeLists.txt | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index e652f283c..561297b6b 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -26,7 +26,7 @@ jobs: strategy: fail-fast: false matrix: - os: [windows-2016, ubuntu-20.04, macos-latest] + os: [windows-2016, ubuntu-18.04, macos-latest] requirements: [requirements-swmm.txt] include: - os: windows-2016 @@ -40,7 +40,7 @@ jobs: run: shell: cmd working-directory: ./ci-tools/windows - - os: ubuntu-20.04 + - os: ubuntu-18.04 generator: "Unix Makefiles" experimental: true script_extension: sh diff --git a/tests/solver/CMakeLists.txt b/tests/solver/CMakeLists.txt index f8acc53a1..402a6c5e1 100644 --- a/tests/solver/CMakeLists.txt +++ b/tests/solver/CMakeLists.txt @@ -19,15 +19,14 @@ add_executable(test_lid ${lid_test_srcs} ) -target_link_libraries(test_lid - ${Boost_LIBRARIES} - swmm5 -) - set_target_properties(test_lid PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin ) +target_link_libraries(test_lid + ${Boost_LIBRARIES} + swmm5 +) # Toolkit Test Module set(solver_test_srcs @@ -48,12 +47,13 @@ target_compile_features(test_solver PUBLIC cxx_generalized_initializers ) - + +set_target_properties(test_solver + PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin +) + target_link_libraries(test_solver ${Boost_LIBRARIES} swmm5 ) -set_target_properties(test_solver - PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin -) From cb1be75e15d6bb5468f9c843af7f3e20702940f3 Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sun, 13 Jun 2021 18:58:40 -0400 Subject: [PATCH 116/190] add extern --- tests/solver/test_pollutants.cpp | 1 + tests/solver/test_pollutants.hpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/tests/solver/test_pollutants.cpp b/tests/solver/test_pollutants.cpp index a9d2a0830..f9b637cd0 100644 --- a/tests/solver/test_pollutants.cpp +++ b/tests/solver/test_pollutants.cpp @@ -9,6 +9,7 @@ * * Unit testing mechanics for the pollutant API using Boost Test. */ + #include #include "test_pollutants.hpp" diff --git a/tests/solver/test_pollutants.hpp b/tests/solver/test_pollutants.hpp index 66887e046..8de25fc2b 100644 --- a/tests/solver/test_pollutants.hpp +++ b/tests/solver/test_pollutants.hpp @@ -9,8 +9,10 @@ #ifndef TEST_POLLUTANT_HPP #define TEST_POLLUTANT_HPP +extern "C" { #include "swmm5.h" #include "toolkit.h" +} #define ERR_NONE 0 From e0dec495936b5b6af9150d69f07447c45f5742fa Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sun, 13 Jun 2021 19:03:35 -0400 Subject: [PATCH 117/190] extern in solver --- tests/solver/test_lid.hpp | 2 ++ tests/solver/test_solver.hpp | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/solver/test_lid.hpp b/tests/solver/test_lid.hpp index baa5986f9..a5c690557 100644 --- a/tests/solver/test_lid.hpp +++ b/tests/solver/test_lid.hpp @@ -18,8 +18,10 @@ #include +extern "C" { #include "swmm5.h" #include "toolkit.h" +} #define ERR_NONE 0 diff --git a/tests/solver/test_solver.hpp b/tests/solver/test_solver.hpp index 1d52b21dc..260ada01c 100644 --- a/tests/solver/test_solver.hpp +++ b/tests/solver/test_solver.hpp @@ -14,9 +14,10 @@ #ifndef TEST_SOLVER_HPP #define TEST_SOLVER_HPP +extern "C" { #include "swmm5.h" #include "toolkit.h" - +} // Add shared data paths here #define DATA_PATH_INP "test_example1.inp" From b8e4a44643dd8004b5e28053c3de8d1eddbd11e0 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 21 Jun 2021 14:15:31 -0400 Subject: [PATCH 118/190] Fixing tests --- extern/boost.cmake | 8 +- tests/solver/CMakeLists.txt | 3 +- .../{pollutants => }/link_constantinflow.inp | 314 ++++++------- .../node_constantinflow_constanteffluent.inp | 262 +++++------ tests/solver/test_pollut.cpp | 292 ++++++++++++ tests/solver/test_pollutants.cpp | 440 ------------------ tests/solver/test_pollutants.hpp | 59 --- tests/solver/test_solver.hpp | 22 + 8 files changed, 605 insertions(+), 795 deletions(-) rename tests/solver/data/{pollutants => }/link_constantinflow.inp (97%) rename tests/solver/data/{pollutants => }/node_constantinflow_constanteffluent.inp (97%) delete mode 100644 tests/solver/test_pollutants.cpp delete mode 100644 tests/solver/test_pollutants.hpp diff --git a/extern/boost.cmake b/extern/boost.cmake index 37711d09a..7b3bd811f 100644 --- a/extern/boost.cmake +++ b/extern/boost.cmake @@ -24,15 +24,11 @@ if (DEFINED ENV{BOOST_ROOT_1_72_0}) elseif(DEFINED ENV{BOOST_ROOT_1_67_0}) set(BOOST_ROOT $ENV{BOOST_ROOT_1_67_0}) -# Boost location on Actions build runner -# else() -# include(/opt/hostedtoolcache/boost/1.72.0/x64/lib/cmake/Boost-1.72.0/BoostConfig.cmake) - endif() -set(CMAKE_FIND_DEBUG_MODE TRUE) -find_package(Boost 1.67.0 +set(CMAKE_FIND_DEBUG_MODE FALSE) +find_package(Boost 1.65.0 COMPONENTS unit_test_framework ) diff --git a/tests/solver/CMakeLists.txt b/tests/solver/CMakeLists.txt index 402a6c5e1..a0d6540f9 100644 --- a/tests/solver/CMakeLists.txt +++ b/tests/solver/CMakeLists.txt @@ -32,7 +32,7 @@ target_link_libraries(test_lid set(solver_test_srcs test_canonical.cpp test_gage.cpp - test_pollutants.cpp + test_pollut.cpp test_toolkit.cpp test_solver.cpp test_stats.cpp @@ -56,4 +56,3 @@ target_link_libraries(test_solver ${Boost_LIBRARIES} swmm5 ) - diff --git a/tests/solver/data/pollutants/link_constantinflow.inp b/tests/solver/data/link_constantinflow.inp similarity index 97% rename from tests/solver/data/pollutants/link_constantinflow.inp rename to tests/solver/data/link_constantinflow.inp index 4ca251907..28ec478e4 100644 --- a/tests/solver/data/pollutants/link_constantinflow.inp +++ b/tests/solver/data/link_constantinflow.inp @@ -1,158 +1,158 @@ -[TITLE] -;;Project Title/Notes -Example 6 -Circular Culvert with Roadway Overtopping -and Upstream Storage - -[OPTIONS] -;;Option Value -FLOW_UNITS CFS -INFILTRATION HORTON -FLOW_ROUTING DYNWAVE -LINK_OFFSETS DEPTH -MIN_SLOPE 0 -ALLOW_PONDING NO -SKIP_STEADY_STATE NO - -START_DATE 06/08/2015 -START_TIME 00:00:00 -REPORT_START_DATE 06/08/2015 -REPORT_START_TIME 00:00:00 -END_DATE 06/08/2015 -END_TIME 05:00:00 -SWEEP_START 01/01 -SWEEP_END 12/31 -DRY_DAYS 0 -REPORT_STEP 00:00:01 -WET_STEP 00:00:01 -DRY_STEP 00:00:01 -ROUTING_STEP 00:00:01 - -INERTIAL_DAMPING PARTIAL -NORMAL_FLOW_LIMITED BOTH -FORCE_MAIN_EQUATION H-W -VARIABLE_STEP 0.75 -LENGTHENING_STEP 0 -MIN_SURFAREA 12.557 -MAX_TRIALS 8 -HEAD_TOLERANCE 0.005 -SYS_FLOW_TOL 5 -LAT_FLOW_TOL 5 -MINIMUM_STEP 0.5 -THREADS 1 - -[EVAPORATION] -;;Data Source Parameters -;;-------------- ---------------- -CONSTANT 0.0 -DRY_ONLY NO - -[JUNCTIONS] -;;Name Elevation MaxDepth InitDepth SurDepth Aponded -;;-------------- ---------- ---------- ---------- ---------- ---------- -Outlet 868 0 0 0 0 - -[OUTFALLS] -;;Name Elevation Type Stage Data Gated Route To -;;-------------- ---------- ---------- ---------------- -------- ---------------- -TailWater 858 FIXED 859.5 NO - -[STORAGE] -;;Name Elev. MaxDepth InitDepth Shape Curve Name/Params N/A Fevap Psi Ksat IMD -;;-------------- -------- ---------- ----------- ---------- ---------------------------- -------- -------- -------- -------- -Inlet 878 9 0 TABULAR StorageCurve 0 0 - -[CONDUITS] -;;Name From Node To Node Length Roughness InOffset OutOffset InitFlow MaxFlow -;;-------------- ---------------- ---------------- ---------- ---------- ---------- ---------- ---------- ---------- -Culvert Inlet Outlet 200 0.014 0 0 0 0 -Channel Outlet TailWater 200 0.03 0 0 0 0 - -[WEIRS] -;;Name From Node To Node Type CrestHt Qcoeff Gated EndCon EndCoeff Surcharge RoadWidth RoadSurf -;;-------------- ---------------- ---------------- ------------ ---------- ---------- -------- -------- ---------- ---------- ---------- ---------- -Roadway Inlet Outlet ROADWAY 9 3.33 NO 0 0 NO 40 GRAVEL - -[XSECTIONS] -;;Link Shape Geom1 Geom2 Geom3 Geom4 Barrels Culvert -;;-------------- ------------ ---------------- ---------- ---------- ---------- ---------- ---------- -Culvert CIRCULAR 3 0 0 0 2 4 -Channel TRAPEZOIDAL 9 10 2 2 1 -Roadway RECT_OPEN 50 200 0 0 - -[POLLUTANTS] -;;Name Units Crain Cgw Crdii Kdecay SnowOnly Co-Pollutant Co-Frac Cdwf Cinit -;;-------------- ------ ---------- ---------- ---------- ---------- ---------- ---------------- ---------- ---------- ---------- -P1 MG/L 0.0 0.0 0.0 0.0 NO * 0.0 0.0 0.0 - -[INFLOWS] -;;Node Constituent Time Series Type Mfactor Sfactor Baseline Pattern -;;-------------- ---------------- ---------------- -------- -------- -------- -------- -------- +[TITLE] +;;Project Title/Notes +Example 6 +Circular Culvert with Roadway Overtopping +and Upstream Storage + +[OPTIONS] +;;Option Value +FLOW_UNITS CFS +INFILTRATION HORTON +FLOW_ROUTING DYNWAVE +LINK_OFFSETS DEPTH +MIN_SLOPE 0 +ALLOW_PONDING NO +SKIP_STEADY_STATE NO + +START_DATE 06/08/2015 +START_TIME 00:00:00 +REPORT_START_DATE 06/08/2015 +REPORT_START_TIME 00:00:00 +END_DATE 06/08/2015 +END_TIME 05:00:00 +SWEEP_START 01/01 +SWEEP_END 12/31 +DRY_DAYS 0 +REPORT_STEP 00:00:01 +WET_STEP 00:00:01 +DRY_STEP 00:00:01 +ROUTING_STEP 00:00:01 + +INERTIAL_DAMPING PARTIAL +NORMAL_FLOW_LIMITED BOTH +FORCE_MAIN_EQUATION H-W +VARIABLE_STEP 0.75 +LENGTHENING_STEP 0 +MIN_SURFAREA 12.557 +MAX_TRIALS 8 +HEAD_TOLERANCE 0.005 +SYS_FLOW_TOL 5 +LAT_FLOW_TOL 5 +MINIMUM_STEP 0.5 +THREADS 1 + +[EVAPORATION] +;;Data Source Parameters +;;-------------- ---------------- +CONSTANT 0.0 +DRY_ONLY NO + +[JUNCTIONS] +;;Name Elevation MaxDepth InitDepth SurDepth Aponded +;;-------------- ---------- ---------- ---------- ---------- ---------- +Outlet 868 0 0 0 0 + +[OUTFALLS] +;;Name Elevation Type Stage Data Gated Route To +;;-------------- ---------- ---------- ---------------- -------- ---------------- +TailWater 858 FIXED 859.5 NO + +[STORAGE] +;;Name Elev. MaxDepth InitDepth Shape Curve Name/Params N/A Fevap Psi Ksat IMD +;;-------------- -------- ---------- ----------- ---------- ---------------------------- -------- -------- -------- -------- +Inlet 878 9 0 TABULAR StorageCurve 0 0 + +[CONDUITS] +;;Name From Node To Node Length Roughness InOffset OutOffset InitFlow MaxFlow +;;-------------- ---------------- ---------------- ---------- ---------- ---------- ---------- ---------- ---------- +Culvert Inlet Outlet 200 0.014 0 0 0 0 +Channel Outlet TailWater 200 0.03 0 0 0 0 + +[WEIRS] +;;Name From Node To Node Type CrestHt Qcoeff Gated EndCon EndCoeff Surcharge RoadWidth RoadSurf +;;-------------- ---------------- ---------------- ------------ ---------- ---------- -------- -------- ---------- ---------- ---------- ---------- +Roadway Inlet Outlet ROADWAY 9 3.33 NO 0 0 NO 40 GRAVEL + +[XSECTIONS] +;;Link Shape Geom1 Geom2 Geom3 Geom4 Barrels Culvert +;;-------------- ------------ ---------------- ---------- ---------- ---------- ---------- ---------- +Culvert CIRCULAR 3 0 0 0 2 4 +Channel TRAPEZOIDAL 9 10 2 2 1 +Roadway RECT_OPEN 50 200 0 0 + +[POLLUTANTS] +;;Name Units Crain Cgw Crdii Kdecay SnowOnly Co-Pollutant Co-Frac Cdwf Cinit +;;-------------- ------ ---------- ---------- ---------- ---------- ---------- ---------------- ---------- ---------- ---------- +P1 MG/L 0.0 0.0 0.0 0.0 NO * 0.0 0.0 0.0 + +[INFLOWS] +;;Node Constituent Time Series Type Mfactor Sfactor Baseline Pattern +;;-------------- ---------------- ---------------- -------- -------- -------- -------- -------- Inlet FLOW "" FLOW 1.0 1.0 10.0 -Inlet P1 "" CONCENTRATION 1.0 1.0 10 - - -[CURVES] -;;Name Type X-Value Y-Value -;;-------------- ---------- ---------- ---------- -StorageCurve Storage 0 0 -StorageCurve 2 9583 -StorageCurve 4 33977 -StorageCurve 6 72310 -StorageCurve 8 136778 - -[TIMESERIES] -;;Name Date Time Value -;;-------------- ---------- ---------- ---------- -Inflow 0 0 -Inflow .125 9 -Inflow .25 10 -Inflow .375 11 -Inflow .5 13 -Inflow .625 17 -Inflow .75 28 -Inflow .875 40 -Inflow 1 80 -Inflow 1.125 136 -Inflow 1.25 190 -Inflow 1.375 220 -Inflow 1.5 220 -Inflow 1.625 201 -Inflow 1.75 170 -Inflow 1.875 140 -Inflow 2 120 -Inflow 2.125 98 -Inflow 2.25 82 -Inflow 2.375 70 -Inflow 2.5 60 -Inflow 2.625 53 -Inflow 2.75 47 -Inflow 2.875 41 - -[REPORT] -;;Reporting Options -INPUT NO -CONTROLS NO -SUBCATCHMENTS ALL -NODES ALL -LINKS ALL - -[TAGS] - -[MAP] -DIMENSIONS -59.264 5535.422 7089.237 6044.959 -Units None - -[COORDINATES] -;;Node X-Coord Y-Coord -;;-------------- ------------------ ------------------ -Outlet 4206.542 6357.994 -TailWater 6019.146 6168.726 -Inlet 1628.472 6476.537 - -[VERTICES] -;;Link X-Coord Y-Coord -;;-------------- ------------------ ------------------ -Roadway 2311.068 7166.633 -Roadway 3762.491 7151.463 - +Inlet P1 "" CONCENTRATION 1.0 1.0 10 + + +[CURVES] +;;Name Type X-Value Y-Value +;;-------------- ---------- ---------- ---------- +StorageCurve Storage 0 0 +StorageCurve 2 9583 +StorageCurve 4 33977 +StorageCurve 6 72310 +StorageCurve 8 136778 + +[TIMESERIES] +;;Name Date Time Value +;;-------------- ---------- ---------- ---------- +Inflow 0 0 +Inflow .125 9 +Inflow .25 10 +Inflow .375 11 +Inflow .5 13 +Inflow .625 17 +Inflow .75 28 +Inflow .875 40 +Inflow 1 80 +Inflow 1.125 136 +Inflow 1.25 190 +Inflow 1.375 220 +Inflow 1.5 220 +Inflow 1.625 201 +Inflow 1.75 170 +Inflow 1.875 140 +Inflow 2 120 +Inflow 2.125 98 +Inflow 2.25 82 +Inflow 2.375 70 +Inflow 2.5 60 +Inflow 2.625 53 +Inflow 2.75 47 +Inflow 2.875 41 + +[REPORT] +;;Reporting Options +INPUT NO +CONTROLS NO +SUBCATCHMENTS ALL +NODES ALL +LINKS ALL + +[TAGS] + +[MAP] +DIMENSIONS -59.264 5535.422 7089.237 6044.959 +Units None + +[COORDINATES] +;;Node X-Coord Y-Coord +;;-------------- ------------------ ------------------ +Outlet 4206.542 6357.994 +TailWater 6019.146 6168.726 +Inlet 1628.472 6476.537 + +[VERTICES] +;;Link X-Coord Y-Coord +;;-------------- ------------------ ------------------ +Roadway 2311.068 7166.633 +Roadway 3762.491 7151.463 + diff --git a/tests/solver/data/pollutants/node_constantinflow_constanteffluent.inp b/tests/solver/data/node_constantinflow_constanteffluent.inp similarity index 97% rename from tests/solver/data/pollutants/node_constantinflow_constanteffluent.inp rename to tests/solver/data/node_constantinflow_constanteffluent.inp index 03947b0be..dd164967c 100644 --- a/tests/solver/data/pollutants/node_constantinflow_constanteffluent.inp +++ b/tests/solver/data/node_constantinflow_constanteffluent.inp @@ -1,133 +1,133 @@ -[TITLE] -;;Project Title/Notes - -[OPTIONS] -;;Option Value -FLOW_UNITS CMS -INFILTRATION HORTON -FLOW_ROUTING KINWAVE -LINK_OFFSETS DEPTH -MIN_SLOPE 0 -ALLOW_PONDING NO -SKIP_STEADY_STATE NO - -START_DATE 01/27/2020 -START_TIME 00:00:00 -REPORT_START_DATE 01/27/2020 -REPORT_START_TIME 00:00:00 -END_DATE 01/27/2020 -END_TIME 00:30:00 -SWEEP_START 01/01 -SWEEP_END 02/28 -DRY_DAYS 0 -REPORT_STEP 00:00:01 -WET_STEP 00:00:01 -DRY_STEP 00:00:01 -ROUTING_STEP 0:00:01 - -INERTIAL_DAMPING PARTIAL -NORMAL_FLOW_LIMITED BOTH -FORCE_MAIN_EQUATION H-W -VARIABLE_STEP 0.75 -LENGTHENING_STEP 0 -MIN_SURFAREA 1.14 -MAX_TRIALS 8 -HEAD_TOLERANCE 0.0015 -SYS_FLOW_TOL 5 -LAT_FLOW_TOL 5 -MINIMUM_STEP 0.5 -THREADS 1 - -[EVAPORATION] -;;Data Source Parameters -;;-------------- ---------------- -CONSTANT 0.0 -DRY_ONLY NO - -[OUTFALLS] -;;Name Elevation Type Stage Data Gated Route To -;;-------------- ---------- ---------- ---------------- -------- ---------------- -Outfall 0 FREE NO - -[STORAGE] -;;Name Elev. MaxDepth InitDepth Shape Curve Name/Params N/A Fevap Psi Ksat IMD -;;-------------- -------- ---------- ----------- ---------- ---------------------------- -------- -------- -------- -------- -Tank 10 5 0 TABULAR Tank_Curve 0 0 - -[ORIFICES] -;;Name From Node To Node Type Offset Qcoeff Gated CloseTime -;;-------------- ---------------- ---------------- ------------ ---------- ---------- -------- ---------- -Valve Tank Outfall BOTTOM 0 1 NO 0 - -[XSECTIONS] -;;Link Shape Geom1 Geom2 Geom3 Geom4 Barrels Culvert -;;-------------- ------------ ---------------- ---------- ---------- ---------- ---------- ---------- -Valve RECT_CLOSED 1 1 0 0 - -[POLLUTANTS] -;;Name Units Crain Cgw Crdii Kdecay SnowOnly Co-Pollutant Co-Frac Cdwf Cinit -;;-------------- ------ ---------- ---------- ---------- ---------- ---------- ---------------- ---------- ---------- ---------- -P1 MG/L 0.0 0.0 0 0.0 NO * 0.0 0.0 0 - - -[INFLOWS] -;;Node Constituent Time Series Type Mfactor Sfactor Baseline Pattern -;;-------------- ---------------- ---------------- -------- -------- -------- -------- -------- +[TITLE] +;;Project Title/Notes + +[OPTIONS] +;;Option Value +FLOW_UNITS CMS +INFILTRATION HORTON +FLOW_ROUTING KINWAVE +LINK_OFFSETS DEPTH +MIN_SLOPE 0 +ALLOW_PONDING NO +SKIP_STEADY_STATE NO + +START_DATE 01/27/2020 +START_TIME 00:00:00 +REPORT_START_DATE 01/27/2020 +REPORT_START_TIME 00:00:00 +END_DATE 01/27/2020 +END_TIME 00:30:00 +SWEEP_START 01/01 +SWEEP_END 02/28 +DRY_DAYS 0 +REPORT_STEP 00:00:01 +WET_STEP 00:00:01 +DRY_STEP 00:00:01 +ROUTING_STEP 0:00:01 + +INERTIAL_DAMPING PARTIAL +NORMAL_FLOW_LIMITED BOTH +FORCE_MAIN_EQUATION H-W +VARIABLE_STEP 0.75 +LENGTHENING_STEP 0 +MIN_SURFAREA 1.14 +MAX_TRIALS 8 +HEAD_TOLERANCE 0.0015 +SYS_FLOW_TOL 5 +LAT_FLOW_TOL 5 +MINIMUM_STEP 0.5 +THREADS 1 + +[EVAPORATION] +;;Data Source Parameters +;;-------------- ---------------- +CONSTANT 0.0 +DRY_ONLY NO + +[OUTFALLS] +;;Name Elevation Type Stage Data Gated Route To +;;-------------- ---------- ---------- ---------------- -------- ---------------- +Outfall 0 FREE NO + +[STORAGE] +;;Name Elev. MaxDepth InitDepth Shape Curve Name/Params N/A Fevap Psi Ksat IMD +;;-------------- -------- ---------- ----------- ---------- ---------------------------- -------- -------- -------- -------- +Tank 10 5 0 TABULAR Tank_Curve 0 0 + +[ORIFICES] +;;Name From Node To Node Type Offset Qcoeff Gated CloseTime +;;-------------- ---------------- ---------------- ------------ ---------- ---------- -------- ---------- +Valve Tank Outfall BOTTOM 0 1 NO 0 + +[XSECTIONS] +;;Link Shape Geom1 Geom2 Geom3 Geom4 Barrels Culvert +;;-------------- ------------ ---------------- ---------- ---------- ---------- ---------- ---------- +Valve RECT_CLOSED 1 1 0 0 + +[POLLUTANTS] +;;Name Units Crain Cgw Crdii Kdecay SnowOnly Co-Pollutant Co-Frac Cdwf Cinit +;;-------------- ------ ---------- ---------- ---------- ---------- ---------- ---------------- ---------- ---------- ---------- +P1 MG/L 0.0 0.0 0 0.0 NO * 0.0 0.0 0 + + +[INFLOWS] +;;Node Constituent Time Series Type Mfactor Sfactor Baseline Pattern +;;-------------- ---------------- ---------------- -------- -------- -------- -------- -------- Tank FLOW "" FLOW 1.0 1.0 5 -Tank P1 "" CONCENTRATION 1.0 1.0 10 - -[TREATMENT] -;;Node Pollutant Function -;;-------------- ---------------- ---------- +Tank P1 "" CONCENTRATION 1.0 1.0 10 + +[TREATMENT] +;;Node Pollutant Function +;;-------------- ---------------- ---------- Tank P1 C = 2.0 - -[CURVES] -;;Name Type X-Value Y-Value -;;-------------- ---------- ---------- ---------- -Tank_Curve Storage 0 100 -Tank_Curve 1 100 -Tank_Curve 2 100 -Tank_Curve 3 100 -Tank_Curve 4 100 -Tank_Curve 5 100 - -[TIMESERIES] -;;Name Date Time Value -;;-------------- ---------- ---------- ---------- -TestRain 1 0 -TestRain 2 0.5 -TestRain 3 0.75 -TestRain 4 1 -TestRain 5 0.75 -TestRain 6 0.5 -TestRain 7 0 - -[PATTERNS] -;;Name Type Multipliers -;;-------------- ---------- ----------- -DailyX1 DAILY 1.0 1.0 1.0 1.0 1.0 1.0 1.0 - -[REPORT] -;;Reporting Options -INPUT NO -CONTROLS NO -SUBCATCHMENTS ALL -NODES ALL -LINKS ALL - -[TAGS] - -[MAP] -DIMENSIONS 0.000 0.000 10000.000 10000.000 -Units None - -[COORDINATES] -;;Node X-Coord Y-Coord -;;-------------- ------------------ ------------------ -Outfall -178.777 6435.986 -Tank -1101.499 6828.143 - -[VERTICES] -;;Link X-Coord Y-Coord -;;-------------- ------------------ ------------------ - + +[CURVES] +;;Name Type X-Value Y-Value +;;-------------- ---------- ---------- ---------- +Tank_Curve Storage 0 100 +Tank_Curve 1 100 +Tank_Curve 2 100 +Tank_Curve 3 100 +Tank_Curve 4 100 +Tank_Curve 5 100 + +[TIMESERIES] +;;Name Date Time Value +;;-------------- ---------- ---------- ---------- +TestRain 1 0 +TestRain 2 0.5 +TestRain 3 0.75 +TestRain 4 1 +TestRain 5 0.75 +TestRain 6 0.5 +TestRain 7 0 + +[PATTERNS] +;;Name Type Multipliers +;;-------------- ---------- ----------- +DailyX1 DAILY 1.0 1.0 1.0 1.0 1.0 1.0 1.0 + +[REPORT] +;;Reporting Options +INPUT NO +CONTROLS NO +SUBCATCHMENTS ALL +NODES ALL +LINKS ALL + +[TAGS] + +[MAP] +DIMENSIONS 0.000 0.000 10000.000 10000.000 +Units None + +[COORDINATES] +;;Node X-Coord Y-Coord +;;-------------- ------------------ ------------------ +Outfall -178.777 6435.986 +Tank -1101.499 6828.143 + +[VERTICES] +;;Link X-Coord Y-Coord +;;-------------- ------------------ ------------------ + diff --git a/tests/solver/test_pollut.cpp b/tests/solver/test_pollut.cpp index c6cabed90..e1eb3f46c 100644 --- a/tests/solver/test_pollut.cpp +++ b/tests/solver/test_pollut.cpp @@ -148,5 +148,297 @@ BOOST_FIXTURE_TEST_CASE(get_pollut_values, FixtureBeforeStep){ swmm_end(); } +// Testing Node influent concentration- storage assets +BOOST_FIXTURE_TEST_CASE(get_node_pollutant_values_cin, FixtureBeforeStep_Pollut_Node){ + + int error, step_ind; + double* node_qual; + double elapsedTime = 0.0; + double total_pollutant = 0.0; + int length; + + // Pollutant IDs + int P1 = 0; + double cin = 10; + + step_ind = 0; + do + { + + error = swmm_getNodePollut(1, SM_NODECIN, &node_qual, &length); + BOOST_REQUIRE(error == ERR_NONE); + + // Check for constant influent + if (step_ind > 5) + BOOST_CHECK_SMALL(cin - node_qual[P1], 0.001); + + // Route Model Forward + error = swmm_step(&elapsedTime); + step_ind+=1; + + }while (elapsedTime != 0 && !error); + BOOST_REQUIRE(error == ERR_NONE); + + swmm_end(); +} + +// Testing Reactor Concentration +BOOST_FIXTURE_TEST_CASE(get_node_reactor_pollutant, FixtureBeforeStep_Pollut_Node){ + + int error, step_ind; + double* old_qual; + double* new_qual; + double elapsedTime = 0.0; + double total_pollutant = 0.0; + int length; + + // Pollutant IDs + int P1 = 0; + + step_ind = 0; + do + { + // Check for steady state after 1000 steps. + // 1000 is a aribitarly long time duration, it can be any value as long + // the system reaches a steady state + + // Get reactor concentration + error = swmm_getNodePollut(1, SM_NODEREACTORC, &new_qual, &length); + BOOST_REQUIRE(error == ERR_NONE); + + if (step_ind > 1000) + { + BOOST_CHECK_SMALL(old_qual[P1] - new_qual[P1], 0.00001); + } + + old_qual = new_qual; + + // Route Model Forward + error = swmm_step(&elapsedTime); + step_ind+=1; + }while (elapsedTime != 0 && !error); + BOOST_REQUIRE(error == ERR_NONE); + swmm_end(); +} + + +// Testing Pollutant Setter - Node - Cumulative +BOOST_FIXTURE_TEST_CASE(set_node_pollutant_cumulative_values, FixtureBeforeStep_Pollut_Node){ + + int error; + double* node_qual; + double elapsedTime = 0.0; + double total_pollutant = 0.0; + int length; + + // Pollutant IDs + int P1 = 0; + + do + { + // Set pollutant + error = swmm_setNodePollut(1, P1, 0); + BOOST_REQUIRE(error == ERR_NONE); + // Route Model Forward + error = swmm_step(&elapsedTime); + // Get pollutant + error = swmm_getNodePollut(1, SM_NODEQUAL, &node_qual, &length); + BOOST_REQUIRE(error == ERR_NONE); + // Record cumulative pollutant + total_pollutant = total_pollutant + node_qual[P1]; + + }while (elapsedTime != 0 && !error); + BOOST_REQUIRE(error == ERR_NONE); + + // Cumulative must be 0.00 + BOOST_CHECK_SMALL(total_pollutant, 1.0e-06); + swmm_end(); +} + +// Testing Pollutant Setter - Node - Stepwise and Mass balance less than inflow concentration of 10 +BOOST_FIXTURE_TEST_CASE(set_node_pollutant_stepwise_values, FixtureBeforeStep_Pollut_Node){ + + int error; + double* node_qual; + float runoff_error, flow_error, qual_error; + double elapsedTime = 0.0; + int length; + + // Pollutant IDs + int P1 = 0; + do + { + // Set pollutant + error = swmm_setNodePollut(1, P1, 1.234); + BOOST_REQUIRE(error == ERR_NONE); + + // Route Model Forward + error = swmm_step(&elapsedTime); + + // Get pollutant + error = swmm_getNodePollut(1, SM_NODEQUAL, &node_qual, &length); + BOOST_REQUIRE(error == ERR_NONE); + + // Check + BOOST_CHECK_SMALL(node_qual[P1] - 1.234, 0.001); + + }while (elapsedTime != 0 && !error); + BOOST_REQUIRE(error == ERR_NONE); + swmm_end(); + + // check mass balance error less than 1% + swmm_getMassBalErr(&runoff_error, &flow_error, &qual_error); + BOOST_CHECK(abs(qual_error) <= 1.0); +} + + +// Testing Pollutant Setter - Node - Stepwise and Mass balance greater than inflow concentration of 10 +BOOST_FIXTURE_TEST_CASE(set_node_pollutant_stepwise_values_2, FixtureBeforeStep_Pollut_Node){ + + int error; + double* node_qual; + float runoff_error, flow_error, qual_error; + double elapsedTime = 0.0; + int length; + + // Pollutant IDs + int P1 = 0; + do + { + // Set pollutant + error = swmm_setNodePollut(1, P1, 50.0); + BOOST_REQUIRE(error == ERR_NONE); + + // Route Model Forward + error = swmm_step(&elapsedTime); + + // Get pollutant + error = swmm_getNodePollut(1, SM_NODEQUAL, &node_qual, &length); + BOOST_REQUIRE(error == ERR_NONE); + + // Check + BOOST_CHECK_SMALL(node_qual[P1] - 50.0, 0.001); + + }while (elapsedTime != 0 && !error); + BOOST_REQUIRE(error == ERR_NONE); + swmm_end(); + + // check mass balance error less than 1% + swmm_getMassBalErr(&runoff_error, &flow_error, &qual_error); + BOOST_CHECK(abs(qual_error) <= 1.0); +} + + + +// Testing Pollutant Setter - Link - Stepwise - mass balance concentation less than 10 +BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values, FixtureBeforeStep_Pollut_Link){ + + int error, link_ind, node_ind; + int step; + double* link_qual; + double* node_qual; + double elapsedTime = 0.0; + double node_inflow; + float runoff_error, flow_error, qual_error; + char linkid[] = "Culvert"; + char nodeid[] = "Outlet"; + int length; + + // Pollutant ID + int P1 = 0; + + error = swmm_getObjectIndex(SM_LINK, linkid, &link_ind); + BOOST_REQUIRE(error == ERR_NONE); + error = swmm_getObjectIndex(SM_NODE, nodeid, &node_ind); + BOOST_REQUIRE(error == ERR_NONE); + + do + { + // Set pollutant in link and check the pollutant in the node + error = swmm_setLinkPollut(link_ind, SM_LINKQUAL, P1, 2.0); + BOOST_REQUIRE(error == ERR_NONE); + + // Route Model Forward + error = swmm_step(&elapsedTime); + BOOST_REQUIRE(error == ERR_NONE); + + if (step > 10) // Wait for water to reach node + { + // Get infows concentration in node + error = swmm_getNodePollut(node_ind, SM_NODEQUAL, &node_qual, &length); + BOOST_REQUIRE(error == ERR_NONE); + + error = swmm_getLinkPollut(link_ind, SM_LINKQUAL, &link_qual, &length); + + // Check + BOOST_CHECK_SMALL(abs(node_qual[P1] - link_qual[P1]), 0.001); + } + step += 1; + + }while (elapsedTime != 0 && !error); + BOOST_REQUIRE(error == ERR_NONE); + swmm_end(); + + // check mass balance error less than 5% + swmm_getMassBalErr(&runoff_error, &flow_error, &qual_error); + BOOST_CHECK(abs(qual_error) <= 5.0); +} + + +// Testing Pollutant Setter - Link - Stepwise - mass balance concentation less than 10 +BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values_2, FixtureBeforeStep_Pollut_Link){ + + int error, link_ind, node_ind; + int step; + double* link_qual; + double* node_qual; + double elapsedTime = 0.0; + double node_inflow; + float runoff_error, flow_error, qual_error; + char linkid[] = "Culvert"; + char nodeid[] = "Outlet"; + int length; + + // Pollutant ID + int P1 = 0; + + error = swmm_getObjectIndex(SM_LINK, linkid, &link_ind); + BOOST_REQUIRE(error == ERR_NONE); + error = swmm_getObjectIndex(SM_NODE, nodeid, &node_ind); + BOOST_REQUIRE(error == ERR_NONE); + + do + { + // Set pollutant in link and check the pollutant in the node + error = swmm_setLinkPollut(link_ind, SM_LINKQUAL, P1, 20.0); + BOOST_REQUIRE(error == ERR_NONE); + + // Route Model Forward + error = swmm_step(&elapsedTime); + BOOST_REQUIRE(error == ERR_NONE); + + if (step > 1000) // Wait for water to reach node + { + // Get infows concentration in node + error = swmm_getNodePollut(node_ind, SM_NODEQUAL, &node_qual, &length); + BOOST_REQUIRE(error == ERR_NONE); + + error = swmm_getLinkPollut(link_ind, SM_LINKQUAL, &link_qual, &length); + + // Check + BOOST_CHECK_SMALL(abs(node_qual[P1] - link_qual[P1]), 0.001); + } + step += 1; + + }while (elapsedTime != 0 && !error); + BOOST_REQUIRE(error == ERR_NONE); + swmm_end(); + + // check mass balance error less than 5% + swmm_getMassBalErr(&runoff_error, &flow_error, &qual_error); + BOOST_CHECK(abs(qual_error) <= 10.0); + printf("%f", qual_error); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/solver/test_pollutants.cpp b/tests/solver/test_pollutants.cpp deleted file mode 100644 index f9b637cd0..000000000 --- a/tests/solver/test_pollutants.cpp +++ /dev/null @@ -1,440 +0,0 @@ -/* - * test_toolkitAPI_pollut.cpp - * - * Created: 07/20/2018 - * Author: Katherine M. Ratliff - * - * Edited: 09/06/2020 - * Author: Abhiram Mullapudi - * - * Unit testing mechanics for the pollutant API using Boost Test. - */ - -#include -#include "test_pollutants.hpp" - -using namespace std; - -BOOST_AUTO_TEST_SUITE(test_toolkitapi_pollut) - -// Testing Pollutant Getter -BOOST_FIXTURE_TEST_CASE(get_pollut_values, FixtureBeforeStep){ - int error, step_ind; - int subc_ind; - int node_ind; - int link_ind; - double *buildup_array; - double *ponded_array; - double *runoff_qual; - double *runoff_load; - double *node_qual; - double *link_qual; - double *link_load; - int length; - double elapsedTime = 0.0; - // Pollutant IDs - int TSS = 0; - int Lead = 1; - - std::string subid = std::string("1"); - std::string nodeid = std::string("9"); - std::string linkid = std::string("1"); - - subc_ind = swmm_getObjectIndex(SM_SUBCATCH, (char *)subid.c_str(), &error); - BOOST_REQUIRE(error == ERR_NONE); - - node_ind = swmm_getObjectIndex(SM_NODE, (char *)nodeid.c_str(), &error); - BOOST_REQUIRE(error == ERR_NONE); - - link_ind = swmm_getObjectIndex(SM_LINK, (char *)linkid.c_str(), &error); - BOOST_REQUIRE(error == ERR_NONE); - - step_ind = 0; - - do - { - if (step_ind == 360) // (Jan 1, 1998 6:00am) - { - // subcatchment buildup - error = swmm_getSubcatchPollut(subc_ind, SM_BUILDUP, &buildup_array, &length); - BOOST_REQUIRE(error == ERR_NONE); - BOOST_CHECK_SMALL(buildup_array[TSS] - 31.906912, 0.0001); - BOOST_CHECK_SMALL(buildup_array[Lead] - 0.0, 0.0001); - - // subcatchment ponded concentration - error = swmm_getSubcatchPollut(subc_ind, SM_CPONDED, &ponded_array, &length); - BOOST_REQUIRE(error == ERR_NONE); - BOOST_CHECK_SMALL(ponded_array[TSS] - 0.0, 0.0001); - BOOST_CHECK_SMALL(ponded_array[Lead] - 0.0, 0.0001); - - // subcatchment runoff pollutant concentration - error = swmm_getSubcatchPollut(subc_ind, SM_SUBCQUAL, &runoff_qual, &length); - BOOST_CHECK_SMALL(runoff_qual[TSS] - 14.118948, 0.0001); - BOOST_CHECK_SMALL(runoff_qual[Lead] - 2.823790, 0.0001); - - // subcatchment runoff total pollutant loading - error = swmm_getSubcatchPollut(subc_ind, SM_SUBCTOTALLOAD, &runoff_load, &length); - BOOST_CHECK_SMALL(runoff_load[TSS] - 0.00242786, 0.0001); - BOOST_CHECK_SMALL(runoff_load[Lead] - 4.856e-10, 0.0001); - - // node pollutant concentration - error = swmm_getNodePollut(node_ind, SM_NODEQUAL, &node_qual, &length); - BOOST_CHECK_SMALL(node_qual[TSS] - 14.121316, 0.0001); - BOOST_CHECK_SMALL(node_qual[Lead] - 2.824263, 0.0001); - - // link pollutant concentration - error = swmm_getLinkPollut(node_ind, SM_LINKQUAL, &link_qual, &length); - BOOST_CHECK_SMALL(link_qual[TSS] - 14.124621, 0.0001); - BOOST_CHECK_SMALL(link_qual[Lead] - 2.824924, 0.0001); - - // link pollutant total load - error = swmm_getLinkPollut(node_ind, SM_TOTALLOAD, &link_load, &length); - BOOST_CHECK_SMALL(link_load[TSS] - 38.496695, 0.01); - BOOST_CHECK_SMALL(link_load[Lead] - 0.00769934, 0.0001); - } - - if (step_ind == 720) // (Jan 1, 1998 12:00pm) - { - // subcatchment buildup - error = swmm_getSubcatchPollut(subc_ind, SM_BUILDUP, &buildup_array, &length); - BOOST_REQUIRE(error == ERR_NONE); - BOOST_CHECK_SMALL(buildup_array[TSS] - 32.354460, 0.0001); - BOOST_CHECK_SMALL(buildup_array[Lead] - 0.0, 0.0001); - - // subcatchment ponded concentration - error = swmm_getSubcatchPollut(subc_ind, SM_CPONDED, &ponded_array, &length); - BOOST_REQUIRE(error == ERR_NONE); - BOOST_CHECK_SMALL(ponded_array[TSS] - 0.0, 0.0001); - BOOST_CHECK_SMALL(ponded_array[Lead] - 0.0, 0.0001); - - // subcatchment runoff pollutant concentration - error = swmm_getSubcatchPollut(subc_ind, SM_SUBCQUAL, &runoff_qual, &length); - BOOST_CHECK_SMALL(runoff_qual[TSS] - 0.0, 0.0001); - BOOST_CHECK_SMALL(runoff_qual[Lead] - 0.0, 0.0001); - - // subcatchment runoff total pollutant loading - error = swmm_getSubcatchPollut(subc_ind, SM_SUBCTOTALLOAD, &runoff_load, &length); - BOOST_CHECK_SMALL(runoff_load[TSS] - 0.00248221, 0.0001); - BOOST_CHECK_SMALL(runoff_load[Lead] - 4.964e-10, 0.0001); - - // node pollutant concentration - error = swmm_getNodePollut(node_ind, SM_NODEQUAL, &node_qual, &length); - BOOST_CHECK_SMALL(node_qual[TSS] - 0.0, 0.0001); - BOOST_CHECK_SMALL(node_qual[Lead] - 0.0, 0.0001); - - // link pollutant concentration - error = swmm_getLinkPollut(node_ind, SM_LINKQUAL, &link_qual, &length); - BOOST_CHECK_SMALL(link_qual[TSS] - 4.380e-11, 0.0001); - BOOST_CHECK_SMALL(link_qual[Lead] - 8.759e-12, 0.0001); - - // link pollutant total load - error = swmm_getLinkPollut(node_ind, SM_TOTALLOAD, &link_load, &length); - BOOST_CHECK_SMALL(link_load[TSS] - 39.780193, 0.01); - BOOST_CHECK_SMALL(link_load[Lead] - 0.00795604, 0.0001); - } - - // Route Model Forward - error = swmm_step(&elapsedTime); - step_ind+=1; - }while (elapsedTime != 0 && !error); - BOOST_REQUIRE(error == ERR_NONE); - - swmm_freeMemory(buildup_array); - swmm_freeMemory(ponded_array); - - swmm_end(); -} - - -// Testing Node influent concentration- storage assets -BOOST_FIXTURE_TEST_CASE(get_node_pollutant_values_cin, FixtureBeforeStep_Pollut_Node){ - - int error, step_ind; - double* node_qual; - double elapsedTime = 0.0; - double total_pollutant = 0.0; - int length; - - // Pollutant IDs - int P1 = 0; - double cin = 10; - - step_ind = 0; - do - { - - error = swmm_getNodePollut(1, SM_NODECIN, &node_qual, &length); - BOOST_REQUIRE(error == ERR_NONE); - - // Check for constant influent - if (step_ind > 5) BOOST_CHECK_SMALL(cin - node_qual[P1], 0.00); - - // Route Model Forward - error = swmm_step(&elapsedTime); - step_ind+=1; - - }while (elapsedTime != 0 && !error); - BOOST_REQUIRE(error == ERR_NONE); - - swmm_end(); -} - -// Testing Reactor Concentration -BOOST_FIXTURE_TEST_CASE(get_node_reactor_pollutant, FixtureBeforeStep_Pollut_Node){ - - int error, step_ind; - double* old_qual; - double* new_qual; - double elapsedTime = 0.0; - double total_pollutant = 0.0; - int length; - - // Pollutant IDs - int P1 = 0; - - step_ind = 0; - do - { - // Check for steady state after 1000 steps. - // 1000 is a aribitarly long time duration, it can be any value as long - // the system reaches a steady state - - // Get reactor concentration - error = swmm_getNodePollut(1, SM_NODEREACTORC, &new_qual, &length); - BOOST_REQUIRE(error == ERR_NONE); - - if (step_ind > 1000) - { - BOOST_CHECK_SMALL(old_qual[P1] - new_qual[P1], 0.00001); - } - - old_qual = new_qual; - - // Route Model Forward - error = swmm_step(&elapsedTime); - step_ind+=1; - }while (elapsedTime != 0 && !error); - BOOST_REQUIRE(error == ERR_NONE); - swmm_end(); -} - - -// Testing Pollutant Setter - Node - Cumulative -BOOST_FIXTURE_TEST_CASE(set_node_pollutant_cumulative_values, FixtureBeforeStep_Pollut_Node){ - - int error; - double* node_qual; - double elapsedTime = 0.0; - double total_pollutant = 0.0; - int length; - - // Pollutant IDs - int P1 = 0; - - do - { - // Set pollutant - error = swmm_setNodePollut(1, P1, 0); - BOOST_REQUIRE(error == ERR_NONE); - // Route Model Forward - error = swmm_step(&elapsedTime); - // Get pollutant - error = swmm_getNodePollut(1, SM_NODEQUAL, &node_qual, &length); - BOOST_REQUIRE(error == ERR_NONE); - // Record cumulative pollutant - total_pollutant = total_pollutant + node_qual[P1]; - - }while (elapsedTime != 0 && !error); - BOOST_REQUIRE(error == ERR_NONE); - - // Cumulative must be 0.00 - BOOST_CHECK_SMALL(total_pollutant, 0.00); - swmm_end(); -} - -// Testing Pollutant Setter - Node - Stepwise and Mass balance less than inflow concentration of 10 -BOOST_FIXTURE_TEST_CASE(set_node_pollutant_stepwise_values, FixtureBeforeStep_Pollut_Node){ - - int error; - double* node_qual; - float runoff_error, flow_error, qual_error; - double elapsedTime = 0.0; - int length; - - // Pollutant IDs - int P1 = 0; - do - { - // Set pollutant - error = swmm_setNodePollut(1, P1, 1.234); - BOOST_REQUIRE(error == ERR_NONE); - - // Route Model Forward - error = swmm_step(&elapsedTime); - - // Get pollutant - error = swmm_getNodePollut(1, SM_NODEQUAL, &node_qual, &length); - BOOST_REQUIRE(error == ERR_NONE); - - // Check - BOOST_CHECK_SMALL(node_qual[P1] - 1.234, 0.00); - - }while (elapsedTime != 0 && !error); - BOOST_REQUIRE(error == ERR_NONE); - swmm_end(); - - // check mass balance error less than 1% - swmm_getMassBalErr(&runoff_error, &flow_error, &qual_error); - BOOST_TEST(abs(qual_error) <= 1); -} - - -// Testing Pollutant Setter - Node - Stepwise and Mass balance greater than inflow concentration of 10 -BOOST_FIXTURE_TEST_CASE(set_node_pollutant_stepwise_values_2, FixtureBeforeStep_Pollut_Node){ - - int error; - double* node_qual; - float runoff_error, flow_error, qual_error; - double elapsedTime = 0.0; - int length; - - // Pollutant IDs - int P1 = 0; - do - { - // Set pollutant - error = swmm_setNodePollut(1, P1, 50.0); - BOOST_REQUIRE(error == ERR_NONE); - - // Route Model Forward - error = swmm_step(&elapsedTime); - - // Get pollutant - error = swmm_getNodePollut(1, SM_NODEQUAL, &node_qual, &length); - BOOST_REQUIRE(error == ERR_NONE); - - // Check - BOOST_CHECK_SMALL(node_qual[P1] - 50.0, 0.00); - - }while (elapsedTime != 0 && !error); - BOOST_REQUIRE(error == ERR_NONE); - swmm_end(); - - // check mass balance error less than 1% - swmm_getMassBalErr(&runoff_error, &flow_error, &qual_error); - BOOST_TEST(abs(qual_error) <= 1); -} - - - -// Testing Pollutant Setter - Link - Stepwise - mass balance concentation less than 10 -BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values, FixtureBeforeStep_Pollut_Link){ - - int error, link_ind, node_ind; - int step; - double* link_qual; - double* node_qual; - double elapsedTime = 0.0; - double node_inflow; - float runoff_error, flow_error, qual_error; - char linkid[] = "Culvert"; - char nodeid[] = "Outlet"; - int length; - - // Pollutant ID - int P1 = 0; - - error = swmm_getObjectIndex(SM_LINK, linkid, &link_ind); - BOOST_REQUIRE(error == ERR_NONE); - error = swmm_getObjectIndex(SM_NODE, nodeid, &node_ind); - BOOST_REQUIRE(error == ERR_NONE); - - do - { - // Set pollutant in link and check the pollutant in the node - error = swmm_setLinkPollut(link_ind, SM_LINKQUAL, P1, 2.0); - BOOST_REQUIRE(error == ERR_NONE); - - // Route Model Forward - error = swmm_step(&elapsedTime); - BOOST_REQUIRE(error == ERR_NONE); - - if (step > 10) // Wait for water to reach node - { - // Get infows concentration in node - error = swmm_getNodePollut(node_ind, SM_NODEQUAL, &node_qual, &length); - BOOST_REQUIRE(error == ERR_NONE); - - error = swmm_getLinkPollut(link_ind, SM_LINKQUAL, &link_qual, &length); - - // Check - BOOST_CHECK_SMALL(abs(node_qual[P1] - link_qual[P1]), 0.001); - } - step += 1; - - }while (elapsedTime != 0 && !error); - BOOST_REQUIRE(error == ERR_NONE); - swmm_end(); - - // check mass balance error less than 5% - swmm_getMassBalErr(&runoff_error, &flow_error, &qual_error); - BOOST_TEST(abs(qual_error) <= 5); -} - - -// Testing Pollutant Setter - Link - Stepwise - mass balance concentation less than 10 -BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values_2, FixtureBeforeStep_Pollut_Link){ - - int error, link_ind, node_ind; - int step; - double* link_qual; - double* node_qual; - double elapsedTime = 0.0; - double node_inflow; - float runoff_error, flow_error, qual_error; - char linkid[] = "Culvert"; - char nodeid[] = "Outlet"; - int length; - - // Pollutant ID - int P1 = 0; - - error = swmm_getObjectIndex(SM_LINK, linkid, &link_ind); - BOOST_REQUIRE(error == ERR_NONE); - error = swmm_getObjectIndex(SM_NODE, nodeid, &node_ind); - BOOST_REQUIRE(error == ERR_NONE); - - do - { - // Set pollutant in link and check the pollutant in the node - error = swmm_setLinkPollut(link_ind, SM_LINKQUAL, P1, 20.0); - BOOST_REQUIRE(error == ERR_NONE); - - // Route Model Forward - error = swmm_step(&elapsedTime); - BOOST_REQUIRE(error == ERR_NONE); - - if (step > 1000) // Wait for water to reach node - { - // Get infows concentration in node - error = swmm_getNodePollut(node_ind, SM_NODEQUAL, &node_qual, &length); - BOOST_REQUIRE(error == ERR_NONE); - - error = swmm_getLinkPollut(link_ind, SM_LINKQUAL, &link_qual, &length); - - // Check - BOOST_CHECK_SMALL(abs(node_qual[P1] - link_qual[P1]), 0.001); - } - step += 1; - - }while (elapsedTime != 0 && !error); - BOOST_REQUIRE(error == ERR_NONE); - swmm_end(); - - // check mass balance error less than 5% - swmm_getMassBalErr(&runoff_error, &flow_error, &qual_error); - BOOST_TEST(abs(qual_error) <= 10); - printf("%f", qual_error); -} - -BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/solver/test_pollutants.hpp b/tests/solver/test_pollutants.hpp deleted file mode 100644 index 8de25fc2b..000000000 --- a/tests/solver/test_pollutants.hpp +++ /dev/null @@ -1,59 +0,0 @@ -// -// test_pollutant.hpp -// -// Created: Aug 14, 2020 -// -// Author: Abhiram Mullapudi -// -// -#ifndef TEST_POLLUTANT_HPP -#define TEST_POLLUTANT_HPP - -extern "C" { -#include "swmm5.h" -#include "toolkit.h" -} - -#define ERR_NONE 0 - -// Add shared data paths here -#define DATA_PATH_INP "test_example1.inp" -#define DATA_PATH_INP_POLLUT_NODE "pollutants/node_constantinflow_constanteffluent.inp" -#define DATA_PATH_INP_POLLUT_LINK "pollutants/link_constantinflow.inp" -#define DATA_PATH_RPT "tmp.rpt" -#define DATA_PATH_OUT "tmp.out" - - -struct FixtureBeforeStep{ - FixtureBeforeStep() { - swmm_open(DATA_PATH_INP, DATA_PATH_RPT, DATA_PATH_OUT); - swmm_start(0); - } - ~FixtureBeforeStep() { - swmm_close(); - } -}; - -struct FixtureBeforeStep_Pollut_Node{ - FixtureBeforeStep_Pollut_Node() { - swmm_open(DATA_PATH_INP_POLLUT_NODE, DATA_PATH_RPT, DATA_PATH_OUT); - swmm_start(0); - } - ~FixtureBeforeStep_Pollut_Node() { - swmm_close(); - } -}; - -struct FixtureBeforeStep_Pollut_Link{ - FixtureBeforeStep_Pollut_Link() { - swmm_open(DATA_PATH_INP_POLLUT_LINK, DATA_PATH_RPT, DATA_PATH_OUT); - swmm_start(0); - } - ~FixtureBeforeStep_Pollut_Link() { - swmm_close(); - } -}; - - - -#endif diff --git a/tests/solver/test_solver.hpp b/tests/solver/test_solver.hpp index 260ada01c..0aa07f2a8 100644 --- a/tests/solver/test_solver.hpp +++ b/tests/solver/test_solver.hpp @@ -21,6 +21,8 @@ extern "C" { // Add shared data paths here #define DATA_PATH_INP "test_example1.inp" +#define DATA_PATH_INP_POLLUT_NODE "node_constantinflow_constanteffluent.inp" +#define DATA_PATH_INP_POLLUT_LINK "link_constantinflow.inp" #define DATA_PATH_RPT "tmp.rpt" #define DATA_PATH_OUT "tmp.out" @@ -69,6 +71,26 @@ struct FixtureBeforeEnd{ } }; +struct FixtureBeforeStep_Pollut_Node{ + FixtureBeforeStep_Pollut_Node() { + swmm_open(DATA_PATH_INP_POLLUT_NODE, DATA_PATH_RPT, DATA_PATH_OUT); + swmm_start(0); + } + ~FixtureBeforeStep_Pollut_Node() { + swmm_close(); + } +}; + +struct FixtureBeforeStep_Pollut_Link{ + FixtureBeforeStep_Pollut_Link() { + swmm_open(DATA_PATH_INP_POLLUT_LINK, DATA_PATH_RPT, DATA_PATH_OUT); + swmm_start(0); + } + ~FixtureBeforeStep_Pollut_Link() { + swmm_close(); + } +}; + // Declare shared test predicates here boost::test_tools::predicate_result check_cdd_double(std::vector& test, std::vector& ref, long cdd_tol); From 9c66ab8c5b541d59ef9204bd519b3fd4ac61c42a Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 21 Jun 2021 14:44:58 -0400 Subject: [PATCH 119/190] Fixing tests on Linux --- tests/solver/test_pollut.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/solver/test_pollut.cpp b/tests/solver/test_pollut.cpp index e1eb3f46c..8ec0a215b 100644 --- a/tests/solver/test_pollut.cpp +++ b/tests/solver/test_pollut.cpp @@ -170,7 +170,7 @@ BOOST_FIXTURE_TEST_CASE(get_node_pollutant_values_cin, FixtureBeforeStep_Pollut_ // Check for constant influent if (step_ind > 5) - BOOST_CHECK_SMALL(cin - node_qual[P1], 0.001); + BOOST_CHECK_CLOSE(cin, node_qual[P1], 0.001); // Route Model Forward error = swmm_step(&elapsedTime); @@ -208,7 +208,7 @@ BOOST_FIXTURE_TEST_CASE(get_node_reactor_pollutant, FixtureBeforeStep_Pollut_Nod if (step_ind > 1000) { - BOOST_CHECK_SMALL(old_qual[P1] - new_qual[P1], 0.00001); + BOOST_CHECK_CLOSE(old_qual[P1], new_qual[P1], 0.001); } old_qual = new_qual; @@ -280,7 +280,7 @@ BOOST_FIXTURE_TEST_CASE(set_node_pollutant_stepwise_values, FixtureBeforeStep_Po BOOST_REQUIRE(error == ERR_NONE); // Check - BOOST_CHECK_SMALL(node_qual[P1] - 1.234, 0.001); + BOOST_CHECK_CLOSE(node_qual[P1], 1.234, 0.001); }while (elapsedTime != 0 && !error); BOOST_REQUIRE(error == ERR_NONE); @@ -317,7 +317,7 @@ BOOST_FIXTURE_TEST_CASE(set_node_pollutant_stepwise_values_2, FixtureBeforeStep_ BOOST_REQUIRE(error == ERR_NONE); // Check - BOOST_CHECK_SMALL(node_qual[P1] - 50.0, 0.001); + BOOST_CHECK_CLOSE(node_qual[P1], 50.0, 0.001); }while (elapsedTime != 0 && !error); BOOST_REQUIRE(error == ERR_NONE); @@ -371,7 +371,7 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values, FixtureBeforeStep_Po error = swmm_getLinkPollut(link_ind, SM_LINKQUAL, &link_qual, &length); // Check - BOOST_CHECK_SMALL(abs(node_qual[P1] - link_qual[P1]), 0.001); + BOOST_CHECK_CLOSE(node_qual[P1], link_qual[P1], 0.001); } step += 1; @@ -426,7 +426,7 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values_2, FixtureBeforeStep_ error = swmm_getLinkPollut(link_ind, SM_LINKQUAL, &link_qual, &length); // Check - BOOST_CHECK_SMALL(abs(node_qual[P1] - link_qual[P1]), 0.001); + BOOST_CHECK_CLOSE(node_qual[P1], link_qual[P1], 0.001); } step += 1; From b2b3e2444fd908e5b31d243d9415a17af56cff2c Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 21 Jun 2021 15:38:08 -0400 Subject: [PATCH 120/190] Fussing --- tests/solver/CMakeLists.txt | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/solver/CMakeLists.txt b/tests/solver/CMakeLists.txt index a0d6540f9..da8a5d375 100644 --- a/tests/solver/CMakeLists.txt +++ b/tests/solver/CMakeLists.txt @@ -19,15 +19,16 @@ add_executable(test_lid ${lid_test_srcs} ) -set_target_properties(test_lid - PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin -) - target_link_libraries(test_lid ${Boost_LIBRARIES} swmm5 ) +set_target_properties(test_lid + PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin +) + + # Toolkit Test Module set(solver_test_srcs test_canonical.cpp @@ -48,11 +49,11 @@ target_compile_features(test_solver cxx_generalized_initializers ) -set_target_properties(test_solver - PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin -) - target_link_libraries(test_solver ${Boost_LIBRARIES} swmm5 ) + +set_target_properties(test_solver + PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin +) From 4ae952ec71d8ae99957fea850c288a0f0d2bed0b Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 22 Jun 2021 12:21:36 -0400 Subject: [PATCH 121/190] merge dev <-- master --- .github/workflows/build-test.yml | 31 ++++++++++++++++--------------- CMakeLists.txt | 4 ++-- src/solver/CMakeLists.txt | 10 +++------- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index db0f257c1..959718706 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -26,10 +26,11 @@ jobs: strategy: fail-fast: false matrix: - os: [windows-2016, ubuntu-16.04, macos-latest] + os: [windows-2016, ubuntu-16.04, macos-10.15] requirements: [requirements-swmm.txt] include: - os: windows-2016 + sys_pkgs: choco install boost-msvc-14.1 generator: Visual Studio 15 2017 Win64 experimental: true script_extension: cmd @@ -40,7 +41,9 @@ jobs: run: shell: cmd working-directory: ./ci-tools/windows + - os: ubuntu-16.04 + sys_pkgs: sudo apt install libboost-dev libboost-all-dev generator: "Unix Makefiles" experimental: true script_extension: sh @@ -51,7 +54,9 @@ jobs: run: shell: bash working-directory: ./ci-tools/linux - - os: macos-latest + + - os: macos-10.15 + sys_pkgs: brew install libomp boost generator: Xcode experimental: true ci_tools_path: ci-tools/darwin @@ -79,8 +84,13 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@v2 + + - name: Clone ci-tools repo + uses: actions/checkout@v2 with: - submodules: recursive + repository: OpenWaterAnalytics/ci-tools + ref: master + path: ci-tools - name: Clone ci-tools repo uses: actions/checkout@v2 @@ -98,18 +108,9 @@ jobs: run: | python -m pip install --upgrade pip python -m pip install -r ${{ matrix.requirements }} - - - name: Install Linux requirements - if: ${{ contains(matrix.os, 'ubuntu') }} - run: | - sudo apt-get update - sudo apt install libboost-dev libboost-all-dev - - - name: Install Darwin requirements - if: ${{ contains(matrix.os, 'macos') }} - run: | - brew install libomp - brew install boost + + - name: Install required system packages + run: ${{ matrix.sys_pkgs }} - name: Build and unit test run: ./make.${{ matrix.script_extension }} -t -g "${{ matrix.generator }}" diff --git a/CMakeLists.txt b/CMakeLists.txt index cad8fb4ee..6ccc5cf15 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ # CMakeLists.txt - CMake configuration file for swmm-solver # # Created: July 11, 2019 -# Updated: Jan 14, 2021 +# Updated: May 19, 2021 # # Authors: Michael E. Tryby # US EPA ORD/CESER @@ -67,7 +67,7 @@ endif() # Create install rules for vcruntime.dll, msvcp.dll, vcomp.dll etc. if(OpenMP_FOUND) - set(CMAKE_INSTALL_OPENMP_LIBRARIES TRUE) + set(CMAKE_INSTALL_OPENMP_LIBRARIES TRUE) endif() include(InstallRequiredSystemLibraries) diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index b76b374ef..dea6931f5 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -2,23 +2,19 @@ # CMakeLists.txt - CMake configuration file for swmm-solver/library # # Created: Jul 11, 2019 -# Updated: Jan 14, 2021 +# Updated: May 19, 2021 # # Author: Michael E. Tryby # US EPA ORD/CESER # -################################################################################ -###################### CMAKELISTS FOR SOLVER TARGET ###################### -################################################################################ - - find_package(OpenMP OPTIONAL_COMPONENTS C ) + # configure file groups set(SWMM_PUBLIC_HEADERS include/swmm5.h @@ -78,7 +74,7 @@ target_link_options(swmm5 target_link_libraries(swmm5 PUBLIC $<$>>:m> - $<$:OpenMP::OpenMP_C> + $<$:OpenMP::OpenMP_C> $<$:omp> ) From 51e97328b123a5c9251f70c63926486099d1478f Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 22 Jun 2021 13:01:30 -0400 Subject: [PATCH 122/190] Refactoring --- .github/workflows/build-test.yml | 68 +++++++++++--------------------- 1 file changed, 23 insertions(+), 45 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 959718706..ae3cdcc82 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -27,16 +27,16 @@ jobs: fail-fast: false matrix: os: [windows-2016, ubuntu-16.04, macos-10.15] - requirements: [requirements-swmm.txt] include: - os: windows-2016 sys_pkgs: choco install boost-msvc-14.1 - generator: Visual Studio 15 2017 Win64 - experimental: true - script_extension: cmd + build_unit_test: make.cmd -t -g "Visual Studio 15 2017 Win64" + build_reg_test: make.cmd -g "Visual Studio 15 2017 Win64" + before_reg_test: before-nrtest.cmd + run_reg_test: run-nrtests.cmd + experimental: false artifacts_ext: zip artifact_content_type: zip - gh_run_number: "%GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER%" defaults: run: shell: cmd @@ -44,10 +44,11 @@ jobs: - os: ubuntu-16.04 sys_pkgs: sudo apt install libboost-dev libboost-all-dev - generator: "Unix Makefiles" + build_unit_test: make.sh -t -g "Unix Makefiles" + build_reg_test: make.sh -g "Unix Makefiles" + before_reg_test: before-nrtest.sh + run_reg_test: run-nrtests.sh experimental: true - script_extension: sh - gh_run_number: ${GITHUB_RUN_ID}_${GITHUB_RUN_NUMBER} artifacts_ext: tar.gz artifact_content_type: tar defaults: @@ -57,11 +58,12 @@ jobs: - os: macos-10.15 sys_pkgs: brew install libomp boost - generator: Xcode + build_unit_test: make.zsh -t -g "Xcode" + build_reg_test: make.zsh -g "Xcode" + before_reg_test: before-nrtest.zsh + run_reg_test: run-nrtests.zsh experimental: true ci_tools_path: ci-tools/darwin - script_extension: zsh - gh_run_number: ${GITHUB_RUN_ID}_${GITHUB_RUN_NUMBER} artifacts_ext: tar.gz artifact_content_type: tar defaults: @@ -81,6 +83,7 @@ jobs: BUILD_HOME: build TEST_HOME: nrtests + steps: - name: Checkout repo uses: actions/checkout@v2 @@ -92,40 +95,37 @@ jobs: ref: master path: ci-tools - - name: Clone ci-tools repo - uses: actions/checkout@v2 - with: - repository: OpenWaterAnalytics/ci-tools - ref: master - path: ci-tools - - name: Setup python uses: actions/setup-python@v2 with: python-version: "3.7" - name: Install requirements + env: + REQUIREMENTS: requirements-swmm.txt run: | python -m pip install --upgrade pip - python -m pip install -r ${{ matrix.requirements }} + python -m pip install -r ${{ env.REQUIREMENTS }} - name: Install required system packages run: ${{ matrix.sys_pkgs }} - name: Build and unit test - run: ./make.${{ matrix.script_extension }} -t -g "${{ matrix.generator }}" + run: ${{ matrix.build_unit_test }} - name: Build for reg test - run: ./make.${{ matrix.script_extension }} -g "${{ matrix.generator }}" + run: ${{ matrix.build_reg_test }} - name: Before reg test env: NRTESTS_URL: https://github.com/OpenWaterAnalytics/swmm-nrtestsuite BENCHMARK_TAG: v1.0.4-dev - run: ./before-nrtest.${{ matrix.script_extension }} ${{ env.BENCHMARK_TAG }} + run: ${{ matrix.before_reg_test }} ${{ env.BENCHMARK_TAG }} - name: Run reg test - run: ./run-nrtests.${{ matrix.script_extension }} ${{ matrix.gh_run_number }} + env: + BUILD_ID: ${GITHUB_RUN_ID}_${GITHUB_RUN_NUMBER} + run: ${{ matrix.run_reg_test }} ${{ env.BUILD_ID }} - name: Upload artifacts if: ${{ always() }} @@ -133,25 +133,3 @@ jobs: with: name: build-test-artifacts path: upload/*.* - - # - name: Create release - # id: create_release - # uses: actions/create-release@v1 - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # with: - # tag_name: ${{ github.ref }} - # release_name: Release ${{ github.ref }} - # draft: true - # prerelease: true - - # - name: Upload release assets - # id: upload-release-asset - # uses: actions/upload-release-asset@v1 - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # with: - # upload_url: ${{ steps.create_release.outputs.upload_url }} - # asset_path: upload/${PROJECT}*.${{ matrix.artifacts_ext }} - # asset_name: owa-${PROJECT}-${{ matrix.os }}-${{ github.ref }}.${{ matrix.artifacts_ext }} - # asset_content_type: application/${{ matrix.artifact_content_type }} From f7d1128e0dea16b76f6483cdc00520f6986a6c45 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 22 Jun 2021 13:06:33 -0400 Subject: [PATCH 123/190] Fixing typo --- .github/workflows/build-test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index ae3cdcc82..780c26ea3 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -111,21 +111,21 @@ jobs: run: ${{ matrix.sys_pkgs }} - name: Build and unit test - run: ${{ matrix.build_unit_test }} + run: ./${{ matrix.build_unit_test }} - name: Build for reg test - run: ${{ matrix.build_reg_test }} + run: ./${{ matrix.build_reg_test }} - name: Before reg test env: NRTESTS_URL: https://github.com/OpenWaterAnalytics/swmm-nrtestsuite BENCHMARK_TAG: v1.0.4-dev - run: ${{ matrix.before_reg_test }} ${{ env.BENCHMARK_TAG }} + run: ./${{ matrix.before_reg_test }} ${{ env.BENCHMARK_TAG }} - name: Run reg test env: BUILD_ID: ${GITHUB_RUN_ID}_${GITHUB_RUN_NUMBER} - run: ${{ matrix.run_reg_test }} ${{ env.BUILD_ID }} + run: ./${{ matrix.run_reg_test }} ${{ env.BUILD_ID }} - name: Upload artifacts if: ${{ always() }} From 10c733e1d0ea53c219757b855498adf3556ae818 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 22 Jun 2021 13:37:37 -0400 Subject: [PATCH 124/190] Clean-up --- .github/workflows/build-test.yml | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 780c26ea3..17bbacf61 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -2,7 +2,7 @@ # build-and-test.yml - GitHub Actions CI for swmm-solver # # Created: May 19, 2020 -# Updated: Aug 13, 2020 +# Updated: Jun 22, 2021 # # Author: Michael E. Tryby # US EPA - ORD/CESER @@ -14,19 +14,13 @@ name: Build and Test on: [push, pull_request] -# push: -# branches: [master release develop] -# tags: -# - 'v*.*.*' -# pull_request: -# branches: [master release develop] jobs: build: strategy: fail-fast: false matrix: - os: [windows-2016, ubuntu-16.04, macos-10.15] + os: [windows-2016, ubuntu-18.04, macos-10.15] include: - os: windows-2016 sys_pkgs: choco install boost-msvc-14.1 @@ -42,7 +36,7 @@ jobs: shell: cmd working-directory: ./ci-tools/windows - - os: ubuntu-16.04 + - os: ubuntu-18.04 sys_pkgs: sudo apt install libboost-dev libboost-all-dev build_unit_test: make.sh -t -g "Unix Makefiles" build_reg_test: make.sh -g "Unix Makefiles" @@ -63,7 +57,6 @@ jobs: before_reg_test: before-nrtest.zsh run_reg_test: run-nrtests.zsh experimental: true - ci_tools_path: ci-tools/darwin artifacts_ext: tar.gz artifact_content_type: tar defaults: @@ -101,11 +94,9 @@ jobs: python-version: "3.7" - name: Install requirements - env: - REQUIREMENTS: requirements-swmm.txt run: | python -m pip install --upgrade pip - python -m pip install -r ${{ env.REQUIREMENTS }} + python -m pip install -r requirements-swmm.txt - name: Install required system packages run: ${{ matrix.sys_pkgs }} From 593b50b101f2f9b7b0371719854793d91252c686 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 22 Jun 2021 13:43:37 -0400 Subject: [PATCH 125/190] Ubuntu 16.04 --- .github/workflows/build-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 17bbacf61..7ed0d9bf5 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -20,7 +20,7 @@ jobs: strategy: fail-fast: false matrix: - os: [windows-2016, ubuntu-18.04, macos-10.15] + os: [windows-2016, ubuntu-16.04, macos-10.15] include: - os: windows-2016 sys_pkgs: choco install boost-msvc-14.1 @@ -36,7 +36,7 @@ jobs: shell: cmd working-directory: ./ci-tools/windows - - os: ubuntu-18.04 + - os: ubuntu-16.04 sys_pkgs: sudo apt install libboost-dev libboost-all-dev build_unit_test: make.sh -t -g "Unix Makefiles" build_reg_test: make.sh -g "Unix Makefiles" From 5d781d281454d075933674a1c4066d0817aba02a Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 23 Jun 2021 10:58:23 -0400 Subject: [PATCH 126/190] Fixing build_id --- .github/workflows/build-test.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 7ed0d9bf5..55714627b 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -28,6 +28,7 @@ jobs: build_reg_test: make.cmd -g "Visual Studio 15 2017 Win64" before_reg_test: before-nrtest.cmd run_reg_test: run-nrtests.cmd + build_id: "%GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER%" experimental: false artifacts_ext: zip artifact_content_type: zip @@ -42,6 +43,7 @@ jobs: build_reg_test: make.sh -g "Unix Makefiles" before_reg_test: before-nrtest.sh run_reg_test: run-nrtests.sh + build_id: ${GITHUB_RUN_ID}_${GITHUB_RUN_NUMBER} experimental: true artifacts_ext: tar.gz artifact_content_type: tar @@ -56,6 +58,7 @@ jobs: build_reg_test: make.zsh -g "Xcode" before_reg_test: before-nrtest.zsh run_reg_test: run-nrtests.zsh + build_id: ${GITHUB_RUN_ID}_${GITHUB_RUN_NUMBER} experimental: true artifacts_ext: tar.gz artifact_content_type: tar @@ -114,9 +117,7 @@ jobs: run: ./${{ matrix.before_reg_test }} ${{ env.BENCHMARK_TAG }} - name: Run reg test - env: - BUILD_ID: ${GITHUB_RUN_ID}_${GITHUB_RUN_NUMBER} - run: ./${{ matrix.run_reg_test }} ${{ env.BUILD_ID }} + run: ./${{ matrix.run_reg_test }} ${{ matrix.build_id }} - name: Upload artifacts if: ${{ always() }} From 7d2249cc11a6a2252663f3394b6fa359216a39f9 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 23 Jun 2021 11:04:42 -0400 Subject: [PATCH 127/190] Turn off find boost debug --- extern/boost.cmake | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/extern/boost.cmake b/extern/boost.cmake index 37711d09a..f1f815626 100644 --- a/extern/boost.cmake +++ b/extern/boost.cmake @@ -24,14 +24,10 @@ if (DEFINED ENV{BOOST_ROOT_1_72_0}) elseif(DEFINED ENV{BOOST_ROOT_1_67_0}) set(BOOST_ROOT $ENV{BOOST_ROOT_1_67_0}) -# Boost location on Actions build runner -# else() -# include(/opt/hostedtoolcache/boost/1.72.0/x64/lib/cmake/Boost-1.72.0/BoostConfig.cmake) - endif() -set(CMAKE_FIND_DEBUG_MODE TRUE) +set(CMAKE_FIND_DEBUG_MODE FALSE) find_package(Boost 1.67.0 COMPONENTS unit_test_framework From bebc3003ec667f403abb471128dfbc6717c6e874 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 24 Jun 2021 16:35:20 -0400 Subject: [PATCH 128/190] Fix typo --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 1f01c0102..757800157 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -20,7 +20,7 @@ jobs: strategy: fail-fast: false matrix: - os: [windows-2016, ubuntu-18.04, macos-latest] + os: [windows-2016, ubuntu-16.04, macos-latest] requirements: [requirements-swmm.txt] include: - os: windows-2016 From f271e7c7d8a333dbc9838f02b8a2c59b95892646 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 24 Jun 2021 16:37:09 -0400 Subject: [PATCH 129/190] Fix typo --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 757800157..c9c5b5554 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -20,7 +20,7 @@ jobs: strategy: fail-fast: false matrix: - os: [windows-2016, ubuntu-16.04, macos-latest] + os: [windows-2016, ubuntu-16.04, macos-10.15] requirements: [requirements-swmm.txt] include: - os: windows-2016 From 7ec2ebf75815cd1ea60ade32666cebe5baf109c6 Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sun, 4 Jul 2021 11:37:43 -0400 Subject: [PATCH 130/190] updated tests --- tests/solver/test_pollut.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/solver/test_pollut.cpp b/tests/solver/test_pollut.cpp index 8ec0a215b..e01d02e0f 100644 --- a/tests/solver/test_pollut.cpp +++ b/tests/solver/test_pollut.cpp @@ -362,7 +362,7 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values, FixtureBeforeStep_Po error = swmm_step(&elapsedTime); BOOST_REQUIRE(error == ERR_NONE); - if (step > 10) // Wait for water to reach node + if (step > 100) // Wait for water to reach node { // Get infows concentration in node error = swmm_getNodePollut(node_ind, SM_NODEQUAL, &node_qual, &length); @@ -371,7 +371,7 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values, FixtureBeforeStep_Po error = swmm_getLinkPollut(link_ind, SM_LINKQUAL, &link_qual, &length); // Check - BOOST_CHECK_CLOSE(node_qual[P1], link_qual[P1], 0.001); + BOOST_CHECK_CLOSE(node_qual[P1], link_qual[P1], 0.1); } step += 1; @@ -410,7 +410,7 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values_2, FixtureBeforeStep_ do { // Set pollutant in link and check the pollutant in the node - error = swmm_setLinkPollut(link_ind, SM_LINKQUAL, P1, 20.0); + error = swmm_setLinkPollut(link_ind, SM_LINKQUAL, P1, 5.0); BOOST_REQUIRE(error == ERR_NONE); // Route Model Forward From 614cbd2bee2418e51a8fd2f5fb28b0e513bc4390 Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sun, 4 Jul 2021 11:58:40 -0400 Subject: [PATCH 131/190] update tolerace to determine windows and ubuntu error --- tests/solver/test_pollut.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/solver/test_pollut.cpp b/tests/solver/test_pollut.cpp index e01d02e0f..e5c421245 100644 --- a/tests/solver/test_pollut.cpp +++ b/tests/solver/test_pollut.cpp @@ -371,7 +371,7 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values, FixtureBeforeStep_Po error = swmm_getLinkPollut(link_ind, SM_LINKQUAL, &link_qual, &length); // Check - BOOST_CHECK_CLOSE(node_qual[P1], link_qual[P1], 0.1); + BOOST_CHECK_CLOSE(node_qual[P1], link_qual[P1], 0.50); } step += 1; @@ -426,7 +426,7 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values_2, FixtureBeforeStep_ error = swmm_getLinkPollut(link_ind, SM_LINKQUAL, &link_qual, &length); // Check - BOOST_CHECK_CLOSE(node_qual[P1], link_qual[P1], 0.001); + BOOST_CHECK_CLOSE(node_qual[P1], link_qual[P1], 0.25); } step += 1; @@ -434,9 +434,8 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values_2, FixtureBeforeStep_ BOOST_REQUIRE(error == ERR_NONE); swmm_end(); - // check mass balance error less than 5% swmm_getMassBalErr(&runoff_error, &flow_error, &qual_error); - BOOST_CHECK(abs(qual_error) <= 10.0); + BOOST_CHECK(abs(qual_error) <= 20.0); printf("%f", qual_error); } From 126c0517b34aa46f7ab759bc41a689827afe48c2 Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sun, 4 Jul 2021 12:04:43 -0400 Subject: [PATCH 132/190] update tolerace to 20% --- tests/solver/test_pollut.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/solver/test_pollut.cpp b/tests/solver/test_pollut.cpp index e5c421245..ffe5c363f 100644 --- a/tests/solver/test_pollut.cpp +++ b/tests/solver/test_pollut.cpp @@ -371,7 +371,7 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values, FixtureBeforeStep_Po error = swmm_getLinkPollut(link_ind, SM_LINKQUAL, &link_qual, &length); // Check - BOOST_CHECK_CLOSE(node_qual[P1], link_qual[P1], 0.50); + BOOST_CHECK_CLOSE(node_qual[P1], link_qual[P1], 20.0); } step += 1; @@ -426,7 +426,7 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values_2, FixtureBeforeStep_ error = swmm_getLinkPollut(link_ind, SM_LINKQUAL, &link_qual, &length); // Check - BOOST_CHECK_CLOSE(node_qual[P1], link_qual[P1], 0.25); + BOOST_CHECK_CLOSE(node_qual[P1], link_qual[P1], 20.0); } step += 1; @@ -436,7 +436,6 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values_2, FixtureBeforeStep_ swmm_getMassBalErr(&runoff_error, &flow_error, &qual_error); BOOST_CHECK(abs(qual_error) <= 20.0); - printf("%f", qual_error); } From 594f2ce315100e646b0854a1cdfaead5c248daa3 Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sun, 4 Jul 2021 12:17:00 -0400 Subject: [PATCH 133/190] removed link pollutant tests --- tests/solver/test_pollut.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/solver/test_pollut.cpp b/tests/solver/test_pollut.cpp index ffe5c363f..ca7ddb2f6 100644 --- a/tests/solver/test_pollut.cpp +++ b/tests/solver/test_pollut.cpp @@ -329,7 +329,7 @@ BOOST_FIXTURE_TEST_CASE(set_node_pollutant_stepwise_values_2, FixtureBeforeStep_ } - +/* // Testing Pollutant Setter - Link - Stepwise - mass balance concentation less than 10 BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values, FixtureBeforeStep_Pollut_Link){ @@ -437,6 +437,6 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values_2, FixtureBeforeStep_ swmm_getMassBalErr(&runoff_error, &flow_error, &qual_error); BOOST_CHECK(abs(qual_error) <= 20.0); } - +*/ BOOST_AUTO_TEST_SUITE_END() From 43a4c4c4881859e68d8b5bf350f600a8513f85b6 Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sun, 4 Jul 2021 18:34:14 -0400 Subject: [PATCH 134/190] added support for link reactor concentration --- src/solver/include/toolkit_enums.h | 1 + src/solver/objects.h | 1 + src/solver/project.c | 1 + src/solver/qualrout.c | 3 +++ 4 files changed, 6 insertions(+) diff --git a/src/solver/include/toolkit_enums.h b/src/solver/include/toolkit_enums.h index 7efbebcd8..44c5e8f2c 100644 --- a/src/solver/include/toolkit_enums.h +++ b/src/solver/include/toolkit_enums.h @@ -159,6 +159,7 @@ typedef enum { typedef enum { SM_LINKQUAL = 0, /**< Current Link Quality */ SM_TOTALLOAD = 1, /**< Total Quality Mass Loading */ + SM_LINKREACTORC = 2 /**< Reactor Concentration */ } SM_LinkPollut; /// Subcatchment result property codes diff --git a/src/solver/objects.h b/src/solver/objects.h index 07915c3d5..96e09e158 100644 --- a/src/solver/objects.h +++ b/src/solver/objects.h @@ -671,6 +671,7 @@ typedef struct double* newQual; // current quality state double* totalLoad; // total quality mass loading double* extQual; // external quality state + double* reactorQual; // concentration in the mixed reactor int flowClass; // flow classification double dqdh; // change in flow w.r.t. head (ft2/sec) signed char direction; // flow direction flag diff --git a/src/solver/project.c b/src/solver/project.c index 9a612f30a..70b5a8c56 100644 --- a/src/solver/project.c +++ b/src/solver/project.c @@ -1052,6 +1052,7 @@ void createObjects() Link[j].newQual = (double *) calloc(Nobjects[POLLUT], sizeof(double)); Link[j].extQual = (double *) calloc(Nobjects[POLLUT], sizeof(double)); Link[j].totalLoad = (double *) calloc(Nobjects[POLLUT], sizeof(double)); + Link[j].reactorQual = (double *) calloc(Nobjects[POLLUT], sizeof(double)); Link[j].extPollutFlag = (int *) calloc(Nobjects[POLLUT], sizeof(int)); } diff --git a/src/solver/qualrout.c b/src/solver/qualrout.c index e72d94485..8b55b3318 100644 --- a/src/solver/qualrout.c +++ b/src/solver/qualrout.c @@ -336,6 +336,9 @@ void findLinkQual(int i, double tStep) massbal_addToFinalStorage(p, c2 * v2); c2 = 0.0; } + + // --- set reactor qual for external pollutant handling + Link[i].reactorQual[p] = c2; if (Link[i].extPollutFlag[p] == 0) { From 3aa75fdad5fe185cfd11e2d4d23bf3bac9a3fb17 Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sun, 4 Jul 2021 21:24:45 -0400 Subject: [PATCH 135/190] updated with link pollutant tests --- tests/solver/data/link_constantinflow.inp | 323 +++++++++++----------- tests/solver/test_pollut.cpp | 22 +- 2 files changed, 176 insertions(+), 169 deletions(-) diff --git a/tests/solver/data/link_constantinflow.inp b/tests/solver/data/link_constantinflow.inp index 28ec478e4..d4256b8ee 100644 --- a/tests/solver/data/link_constantinflow.inp +++ b/tests/solver/data/link_constantinflow.inp @@ -1,158 +1,165 @@ -[TITLE] -;;Project Title/Notes -Example 6 -Circular Culvert with Roadway Overtopping -and Upstream Storage - -[OPTIONS] -;;Option Value -FLOW_UNITS CFS -INFILTRATION HORTON -FLOW_ROUTING DYNWAVE -LINK_OFFSETS DEPTH -MIN_SLOPE 0 -ALLOW_PONDING NO -SKIP_STEADY_STATE NO - -START_DATE 06/08/2015 -START_TIME 00:00:00 -REPORT_START_DATE 06/08/2015 -REPORT_START_TIME 00:00:00 -END_DATE 06/08/2015 -END_TIME 05:00:00 -SWEEP_START 01/01 -SWEEP_END 12/31 -DRY_DAYS 0 -REPORT_STEP 00:00:01 -WET_STEP 00:00:01 -DRY_STEP 00:00:01 -ROUTING_STEP 00:00:01 - -INERTIAL_DAMPING PARTIAL -NORMAL_FLOW_LIMITED BOTH -FORCE_MAIN_EQUATION H-W -VARIABLE_STEP 0.75 -LENGTHENING_STEP 0 -MIN_SURFAREA 12.557 -MAX_TRIALS 8 -HEAD_TOLERANCE 0.005 -SYS_FLOW_TOL 5 -LAT_FLOW_TOL 5 -MINIMUM_STEP 0.5 -THREADS 1 - -[EVAPORATION] -;;Data Source Parameters -;;-------------- ---------------- -CONSTANT 0.0 -DRY_ONLY NO - -[JUNCTIONS] -;;Name Elevation MaxDepth InitDepth SurDepth Aponded -;;-------------- ---------- ---------- ---------- ---------- ---------- -Outlet 868 0 0 0 0 - -[OUTFALLS] -;;Name Elevation Type Stage Data Gated Route To -;;-------------- ---------- ---------- ---------------- -------- ---------------- -TailWater 858 FIXED 859.5 NO - -[STORAGE] -;;Name Elev. MaxDepth InitDepth Shape Curve Name/Params N/A Fevap Psi Ksat IMD -;;-------------- -------- ---------- ----------- ---------- ---------------------------- -------- -------- -------- -------- -Inlet 878 9 0 TABULAR StorageCurve 0 0 - -[CONDUITS] -;;Name From Node To Node Length Roughness InOffset OutOffset InitFlow MaxFlow -;;-------------- ---------------- ---------------- ---------- ---------- ---------- ---------- ---------- ---------- -Culvert Inlet Outlet 200 0.014 0 0 0 0 -Channel Outlet TailWater 200 0.03 0 0 0 0 - -[WEIRS] -;;Name From Node To Node Type CrestHt Qcoeff Gated EndCon EndCoeff Surcharge RoadWidth RoadSurf -;;-------------- ---------------- ---------------- ------------ ---------- ---------- -------- -------- ---------- ---------- ---------- ---------- -Roadway Inlet Outlet ROADWAY 9 3.33 NO 0 0 NO 40 GRAVEL - -[XSECTIONS] -;;Link Shape Geom1 Geom2 Geom3 Geom4 Barrels Culvert -;;-------------- ------------ ---------------- ---------- ---------- ---------- ---------- ---------- -Culvert CIRCULAR 3 0 0 0 2 4 -Channel TRAPEZOIDAL 9 10 2 2 1 -Roadway RECT_OPEN 50 200 0 0 - -[POLLUTANTS] -;;Name Units Crain Cgw Crdii Kdecay SnowOnly Co-Pollutant Co-Frac Cdwf Cinit -;;-------------- ------ ---------- ---------- ---------- ---------- ---------- ---------------- ---------- ---------- ---------- -P1 MG/L 0.0 0.0 0.0 0.0 NO * 0.0 0.0 0.0 - -[INFLOWS] -;;Node Constituent Time Series Type Mfactor Sfactor Baseline Pattern -;;-------------- ---------------- ---------------- -------- -------- -------- -------- -------- -Inlet FLOW "" FLOW 1.0 1.0 10.0 -Inlet P1 "" CONCENTRATION 1.0 1.0 10 - - -[CURVES] -;;Name Type X-Value Y-Value -;;-------------- ---------- ---------- ---------- -StorageCurve Storage 0 0 -StorageCurve 2 9583 -StorageCurve 4 33977 -StorageCurve 6 72310 -StorageCurve 8 136778 - -[TIMESERIES] -;;Name Date Time Value -;;-------------- ---------- ---------- ---------- -Inflow 0 0 -Inflow .125 9 -Inflow .25 10 -Inflow .375 11 -Inflow .5 13 -Inflow .625 17 -Inflow .75 28 -Inflow .875 40 -Inflow 1 80 -Inflow 1.125 136 -Inflow 1.25 190 -Inflow 1.375 220 -Inflow 1.5 220 -Inflow 1.625 201 -Inflow 1.75 170 -Inflow 1.875 140 -Inflow 2 120 -Inflow 2.125 98 -Inflow 2.25 82 -Inflow 2.375 70 -Inflow 2.5 60 -Inflow 2.625 53 -Inflow 2.75 47 -Inflow 2.875 41 - -[REPORT] -;;Reporting Options -INPUT NO -CONTROLS NO -SUBCATCHMENTS ALL -NODES ALL -LINKS ALL - -[TAGS] - -[MAP] -DIMENSIONS -59.264 5535.422 7089.237 6044.959 -Units None - -[COORDINATES] -;;Node X-Coord Y-Coord -;;-------------- ------------------ ------------------ -Outlet 4206.542 6357.994 -TailWater 6019.146 6168.726 -Inlet 1628.472 6476.537 - -[VERTICES] -;;Link X-Coord Y-Coord -;;-------------- ------------------ ------------------ -Roadway 2311.068 7166.633 -Roadway 3762.491 7151.463 - +[TITLE] +;;Project Title/Notes +Example 6 +Circular Culvert with Roadway Overtopping +and Upstream Storage + +[OPTIONS] +;;Option Value +FLOW_UNITS CFS +INFILTRATION HORTON +FLOW_ROUTING DYNWAVE +LINK_OFFSETS DEPTH +MIN_SLOPE 0 +ALLOW_PONDING NO +SKIP_STEADY_STATE NO + +START_DATE 06/08/2015 +START_TIME 00:00:00 +REPORT_START_DATE 06/08/2015 +REPORT_START_TIME 00:00:00 +END_DATE 06/08/2015 +END_TIME 05:00:00 +SWEEP_START 01/01 +SWEEP_END 12/31 +DRY_DAYS 0 +REPORT_STEP 00:00:01 +WET_STEP 00:00:01 +DRY_STEP 00:00:01 +ROUTING_STEP 1 +RULE_STEP 00:00:00 + +INERTIAL_DAMPING PARTIAL +NORMAL_FLOW_LIMITED BOTH +FORCE_MAIN_EQUATION H-W +VARIABLE_STEP 0.75 +LENGTHENING_STEP 0 +MIN_SURFAREA 12.557 +MAX_TRIALS 8 +HEAD_TOLERANCE 0.005 +SYS_FLOW_TOL 5 +LAT_FLOW_TOL 5 +MINIMUM_STEP 0.5 +THREADS 1 + +[EVAPORATION] +;;Data Source Parameters +;;-------------- ---------------- +CONSTANT 0.0 +DRY_ONLY NO + +[JUNCTIONS] +;;Name Elevation MaxDepth InitDepth SurDepth Aponded +;;-------------- ---------- ---------- ---------- ---------- ---------- +J1 860 0 0 0 0 +Outlet 868 0 0 0 0 + +[OUTFALLS] +;;Name Elevation Type Stage Data Gated Route To +;;-------------- ---------- ---------- ---------------- -------- ---------------- +TailWater 855 FIXED 859.5 NO + +[STORAGE] +;;Name Elev. MaxDepth InitDepth Shape Curve Name/Params N/A Fevap Psi Ksat IMD +;;-------------- -------- ---------- ---------- ---------- ---------------------------- -------- -------- -------- -------- +Inlet 878 9 0 TABULAR StorageCurve 0 0 + +[CONDUITS] +;;Name From Node To Node Length Roughness InOffset OutOffset InitFlow MaxFlow +;;-------------- ---------------- ---------------- ---------- ---------- ---------- ---------- ---------- ---------- +C1 Outlet J1 400 0.01 0 0 0 0 +C2 J1 TailWater 400 0.01 0 0 0 0 +Culvert Inlet Outlet 200 0.014 0 0 0 0 + +[WEIRS] +;;Name From Node To Node Type CrestHt Qcoeff Gated EndCon EndCoeff Surcharge RoadWidth RoadSurf Coeff. Curve +;;-------------- ---------------- ---------------- ------------ ---------- ---------- -------- -------- ---------- ---------- ---------- ---------- ---------------- +Roadway Inlet Outlet ROADWAY 9 3.33 NO 0 0 NO 40 GRAVEL + +[XSECTIONS] +;;Link Shape Geom1 Geom2 Geom3 Geom4 Barrels Culvert +;;-------------- ------------ ---------------- ---------- ---------- ---------- ---------- ---------- +C1 CIRCULAR 1 0 0 0 1 +C2 CIRCULAR 1 0 0 0 1 +Culvert CIRCULAR 3 0 0 0 2 4 +Roadway RECT_OPEN 50 200 0 0 + +[LOSSES] +;;Link Kentry Kexit Kavg Flap Gate Seepage +;;-------------- ---------- ---------- ---------- ---------- ---------- + +[POLLUTANTS] +;;Name Units Crain Cgw Crdii Kdecay SnowOnly Co-Pollutant Co-Frac Cdwf Cinit +;;-------------- ------ ---------- ---------- ---------- ---------- ---------- ---------------- ---------- ---------- ---------- +P1 MG/L 0.0 0.0 0.0 0.0 NO * 0.0 0.0 0.0 + +[INFLOWS] +;;Node Constituent Time Series Type Mfactor Sfactor Baseline Pattern +;;-------------- ---------------- ---------------- -------- -------- -------- -------- -------- +Inlet FLOW "" FLOW 1.0 1.0 10.0 +Inlet P1 "" CONCENTRATION 1.0 1.0 10 + +[CURVES] +;;Name Type X-Value Y-Value +;;-------------- ---------- ---------- ---------- +StorageCurve Storage 0 0 +StorageCurve 2 9583 +StorageCurve 4 33977 +StorageCurve 6 72310 +StorageCurve 8 136778 + +[TIMESERIES] +;;Name Date Time Value +;;-------------- ---------- ---------- ---------- +Inflow 0 0 +Inflow .125 9 +Inflow .25 10 +Inflow .375 11 +Inflow .5 13 +Inflow .625 17 +Inflow .75 28 +Inflow .875 40 +Inflow 1 80 +Inflow 1.125 136 +Inflow 1.25 190 +Inflow 1.375 220 +Inflow 1.5 220 +Inflow 1.625 201 +Inflow 1.75 170 +Inflow 1.875 140 +Inflow 2 120 +Inflow 2.125 98 +Inflow 2.25 82 +Inflow 2.375 70 +Inflow 2.5 60 +Inflow 2.625 53 +Inflow 2.75 47 +Inflow 2.875 41 + +[REPORT] +;;Reporting Options +INPUT NO +CONTROLS NO +SUBCATCHMENTS ALL +NODES ALL +LINKS ALL + +[TAGS] + +[MAP] +DIMENSIONS 1408.9383 6118.83065 6238.6797 7216.52835 +UNITS None + +[COORDINATES] +;;Node X-Coord Y-Coord +;;-------------- ------------------ ------------------ +J1 5131.441 6259.528 +Outlet 4206.542 6357.994 +TailWater 6019.146 6168.726 +Inlet 1628.472 6476.537 + +[VERTICES] +;;Link X-Coord Y-Coord +;;-------------- ------------------ ------------------ +Roadway 2311.068 7166.633 +Roadway 3762.491 7151.463 diff --git a/tests/solver/test_pollut.cpp b/tests/solver/test_pollut.cpp index ca7ddb2f6..76384cbcc 100644 --- a/tests/solver/test_pollut.cpp +++ b/tests/solver/test_pollut.cpp @@ -329,8 +329,8 @@ BOOST_FIXTURE_TEST_CASE(set_node_pollutant_stepwise_values_2, FixtureBeforeStep_ } -/* -// Testing Pollutant Setter - Link - Stepwise - mass balance concentation less than 10 + +// Testing Pollutant Setter - Link - Stepwise - mass balance concentation less than 5 BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values, FixtureBeforeStep_Pollut_Link){ int error, link_ind, node_ind; @@ -340,8 +340,8 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values, FixtureBeforeStep_Po double elapsedTime = 0.0; double node_inflow; float runoff_error, flow_error, qual_error; - char linkid[] = "Culvert"; - char nodeid[] = "Outlet"; + char linkid[] = "C1"; + char nodeid[] = "J1"; int length; // Pollutant ID @@ -355,7 +355,7 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values, FixtureBeforeStep_Po do { // Set pollutant in link and check the pollutant in the node - error = swmm_setLinkPollut(link_ind, SM_LINKQUAL, P1, 2.0); + error = swmm_setLinkPollut(link_ind, SM_LINKQUAL, P1, 20.0); BOOST_REQUIRE(error == ERR_NONE); // Route Model Forward @@ -371,7 +371,7 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values, FixtureBeforeStep_Po error = swmm_getLinkPollut(link_ind, SM_LINKQUAL, &link_qual, &length); // Check - BOOST_CHECK_CLOSE(node_qual[P1], link_qual[P1], 20.0); + BOOST_CHECK_CLOSE(node_qual[P1], link_qual[P1], 0.01); } step += 1; @@ -395,8 +395,8 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values_2, FixtureBeforeStep_ double elapsedTime = 0.0; double node_inflow; float runoff_error, flow_error, qual_error; - char linkid[] = "Culvert"; - char nodeid[] = "Outlet"; + char linkid[] = "C1"; + char nodeid[] = "J1"; int length; // Pollutant ID @@ -426,7 +426,7 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values_2, FixtureBeforeStep_ error = swmm_getLinkPollut(link_ind, SM_LINKQUAL, &link_qual, &length); // Check - BOOST_CHECK_CLOSE(node_qual[P1], link_qual[P1], 20.0); + BOOST_CHECK_CLOSE(node_qual[P1], link_qual[P1], 0.01); } step += 1; @@ -435,8 +435,8 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values_2, FixtureBeforeStep_ swmm_end(); swmm_getMassBalErr(&runoff_error, &flow_error, &qual_error); - BOOST_CHECK(abs(qual_error) <= 20.0); + BOOST_CHECK(abs(qual_error) <= 5.0); } -*/ + BOOST_AUTO_TEST_SUITE_END() From 4507d196957f8cb95b67f6e953c3dc8e11fe7c55 Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Mon, 5 Jul 2021 12:42:33 -0400 Subject: [PATCH 136/190] updates to quality tests --- src/solver/qualrout.c | 1 - tests/solver/data/link_constantinflow.inp | 6 ++-- tests/solver/test_pollut.cpp | 41 +++++++++++++---------- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/solver/qualrout.c b/src/solver/qualrout.c index 8b55b3318..fd94ccd60 100644 --- a/src/solver/qualrout.c +++ b/src/solver/qualrout.c @@ -352,7 +352,6 @@ void findLinkQual(int i, double tStep) lossExtQual = c2 - Link[i].extQual[p]; lossExtQual = lossExtQual * v1/ tStep; massbal_addReactedMass(p, lossExtQual); - Link[i].newQual[p] = Link[i].extQual[p]; Link[i].extPollutFlag[p] = 0; } diff --git a/tests/solver/data/link_constantinflow.inp b/tests/solver/data/link_constantinflow.inp index d4256b8ee..77a1123d0 100644 --- a/tests/solver/data/link_constantinflow.inp +++ b/tests/solver/data/link_constantinflow.inp @@ -79,8 +79,8 @@ Roadway Inlet Outlet ROADWAY 9 3.33 [XSECTIONS] ;;Link Shape Geom1 Geom2 Geom3 Geom4 Barrels Culvert ;;-------------- ------------ ---------------- ---------- ---------- ---------- ---------- ---------- -C1 CIRCULAR 1 0 0 0 1 -C2 CIRCULAR 1 0 0 0 1 +C1 CIRCULAR 3 0 0 0 1 +C2 CIRCULAR 3 0 0 0 1 Culvert CIRCULAR 3 0 0 0 2 4 Roadway RECT_OPEN 50 200 0 0 @@ -97,7 +97,7 @@ P1 MG/L 0.0 0.0 0.0 0.0 NO * ;;Node Constituent Time Series Type Mfactor Sfactor Baseline Pattern ;;-------------- ---------------- ---------------- -------- -------- -------- -------- -------- Inlet FLOW "" FLOW 1.0 1.0 10.0 -Inlet P1 "" CONCENTRATION 1.0 1.0 10 +Inlet P1 "" CONCENTRATION 1.0 1.0 10.0 [CURVES] ;;Name Type X-Value Y-Value diff --git a/tests/solver/test_pollut.cpp b/tests/solver/test_pollut.cpp index 76384cbcc..1cb505709 100644 --- a/tests/solver/test_pollut.cpp +++ b/tests/solver/test_pollut.cpp @@ -330,11 +330,11 @@ BOOST_FIXTURE_TEST_CASE(set_node_pollutant_stepwise_values_2, FixtureBeforeStep_ -// Testing Pollutant Setter - Link - Stepwise - mass balance concentation less than 5 +// Testing Pollutant Setter - Link - Stepwise - mass balance concentation less than 10 BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values, FixtureBeforeStep_Pollut_Link){ int error, link_ind, node_ind; - int step; + int step = 0; double* link_qual; double* node_qual; double elapsedTime = 0.0; @@ -353,16 +353,17 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values, FixtureBeforeStep_Po BOOST_REQUIRE(error == ERR_NONE); do - { - // Set pollutant in link and check the pollutant in the node - error = swmm_setLinkPollut(link_ind, SM_LINKQUAL, P1, 20.0); - BOOST_REQUIRE(error == ERR_NONE); - + { + if (step > 1000 and step < 2000) + { + // Set pollutant in link and check the pollutant in the node + error = swmm_setLinkPollut(link_ind, SM_LINKQUAL, P1, 1.0); + BOOST_REQUIRE(error == ERR_NONE); + } // Route Model Forward error = swmm_step(&elapsedTime); BOOST_REQUIRE(error == ERR_NONE); - - if (step > 100) // Wait for water to reach node + if (step > 1500 and step < 2000) // Wait for water to reach node { // Get infows concentration in node error = swmm_getNodePollut(node_ind, SM_NODEQUAL, &node_qual, &length); @@ -381,15 +382,16 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values, FixtureBeforeStep_Po // check mass balance error less than 5% swmm_getMassBalErr(&runoff_error, &flow_error, &qual_error); + printf("\n Quality Error: %f \n", qual_error); BOOST_CHECK(abs(qual_error) <= 5.0); } -// Testing Pollutant Setter - Link - Stepwise - mass balance concentation less than 10 +// Testing Pollutant Setter - Link - Stepwise - mass balance concentation greater than 10 BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values_2, FixtureBeforeStep_Pollut_Link){ int error, link_ind, node_ind; - int step; + int step = 0; double* link_qual; double* node_qual; double elapsedTime = 0.0; @@ -408,16 +410,18 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values_2, FixtureBeforeStep_ BOOST_REQUIRE(error == ERR_NONE); do - { - // Set pollutant in link and check the pollutant in the node - error = swmm_setLinkPollut(link_ind, SM_LINKQUAL, P1, 5.0); - BOOST_REQUIRE(error == ERR_NONE); - + { + if (step > 1000 and step < 2000) + { + // Set pollutant in link and check the pollutant in the node + error = swmm_setLinkPollut(link_ind, SM_LINKQUAL, P1, 20.0); + BOOST_REQUIRE(error == ERR_NONE); + } // Route Model Forward error = swmm_step(&elapsedTime); BOOST_REQUIRE(error == ERR_NONE); - - if (step > 1000) // Wait for water to reach node + + if (step > 1500 and step < 2000) // Wait for water to reach node { // Get infows concentration in node error = swmm_getNodePollut(node_ind, SM_NODEQUAL, &node_qual, &length); @@ -435,6 +439,7 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values_2, FixtureBeforeStep_ swmm_end(); swmm_getMassBalErr(&runoff_error, &flow_error, &qual_error); + printf("\n Quality Error: %f \n", qual_error); BOOST_CHECK(abs(qual_error) <= 5.0); } From 11b08ac15086353e352654614a1f945c5f58eef6 Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Mon, 5 Jul 2021 13:03:33 -0400 Subject: [PATCH 137/190] adding documentation --- docs/PollutantMods_UseCase.png | Bin 0 -> 174298 bytes docs/PollutantMods_UseCase2.png | Bin 0 -> 155939 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/PollutantMods_UseCase.png create mode 100644 docs/PollutantMods_UseCase2.png diff --git a/docs/PollutantMods_UseCase.png b/docs/PollutantMods_UseCase.png new file mode 100644 index 0000000000000000000000000000000000000000..d5c4baef3e160da3552d72edd30b2ad7344cfa02 GIT binary patch literal 174298 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

tcd{V79YjR1Ce8j@% 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 JLc%!k{{Rdhfzki~ literal 0 HcmV?d00001 diff --git a/docs/PollutantMods_UseCase2.png b/docs/PollutantMods_UseCase2.png new file mode 100644 index 0000000000000000000000000000000000000000..272dda718320aac3e861b916275ba99f123dfd23 GIT binary patch literal 155939 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 z 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 JrJr?u{vSp)=@ Date: Mon, 5 Jul 2021 14:23:31 -0400 Subject: [PATCH 138/190] updated for errors in windows test --- tests/solver/test_pollut.cpp | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/tests/solver/test_pollut.cpp b/tests/solver/test_pollut.cpp index 1cb505709..bddd52627 100644 --- a/tests/solver/test_pollut.cpp +++ b/tests/solver/test_pollut.cpp @@ -352,10 +352,8 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values, FixtureBeforeStep_Po error = swmm_getObjectIndex(SM_NODE, nodeid, &node_ind); BOOST_REQUIRE(error == ERR_NONE); - do - { - if (step > 1000 and step < 2000) - { + do{ + if (step > 1000 and step < 2000){ // Set pollutant in link and check the pollutant in the node error = swmm_setLinkPollut(link_ind, SM_LINKQUAL, P1, 1.0); BOOST_REQUIRE(error == ERR_NONE); @@ -373,8 +371,7 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values, FixtureBeforeStep_Po // Check BOOST_CHECK_CLOSE(node_qual[P1], link_qual[P1], 0.01); - } - step += 1; + }step += 1; }while (elapsedTime != 0 && !error); BOOST_REQUIRE(error == ERR_NONE); @@ -382,7 +379,6 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values, FixtureBeforeStep_Po // check mass balance error less than 5% swmm_getMassBalErr(&runoff_error, &flow_error, &qual_error); - printf("\n Quality Error: %f \n", qual_error); BOOST_CHECK(abs(qual_error) <= 5.0); } @@ -409,10 +405,8 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values_2, FixtureBeforeStep_ error = swmm_getObjectIndex(SM_NODE, nodeid, &node_ind); BOOST_REQUIRE(error == ERR_NONE); - do - { - if (step > 1000 and step < 2000) - { + do{ + if (step > 1000 & step < 2000){ // Set pollutant in link and check the pollutant in the node error = swmm_setLinkPollut(link_ind, SM_LINKQUAL, P1, 20.0); BOOST_REQUIRE(error == ERR_NONE); @@ -420,9 +414,9 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values_2, FixtureBeforeStep_ // Route Model Forward error = swmm_step(&elapsedTime); BOOST_REQUIRE(error == ERR_NONE); - - if (step > 1500 and step < 2000) // Wait for water to reach node - { + + // Wait for water to reach node + if (step > 1500 & step < 2000){ // Get infows concentration in node error = swmm_getNodePollut(node_ind, SM_NODEQUAL, &node_qual, &length); BOOST_REQUIRE(error == ERR_NONE); @@ -431,15 +425,13 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values_2, FixtureBeforeStep_ // Check BOOST_CHECK_CLOSE(node_qual[P1], link_qual[P1], 0.01); - } - step += 1; + }step += 1; }while (elapsedTime != 0 && !error); BOOST_REQUIRE(error == ERR_NONE); swmm_end(); swmm_getMassBalErr(&runoff_error, &flow_error, &qual_error); - printf("\n Quality Error: %f \n", qual_error); BOOST_CHECK(abs(qual_error) <= 5.0); } From 831c483717810d3ebc081c1fe781ea2ccb917863 Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sat, 31 Jul 2021 19:38:57 -0400 Subject: [PATCH 139/190] mass balance support for 0 concentration --- tests/solver/test_pollut.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/solver/test_pollut.cpp b/tests/solver/test_pollut.cpp index bddd52627..1e506b9b6 100644 --- a/tests/solver/test_pollut.cpp +++ b/tests/solver/test_pollut.cpp @@ -222,7 +222,7 @@ BOOST_FIXTURE_TEST_CASE(get_node_reactor_pollutant, FixtureBeforeStep_Pollut_Nod } -// Testing Pollutant Setter - Node - Cumulative +// Testing Pollutant Setter - Node - Cumulative and mass balance BOOST_FIXTURE_TEST_CASE(set_node_pollutant_cumulative_values, FixtureBeforeStep_Pollut_Node){ int error; @@ -253,6 +253,9 @@ BOOST_FIXTURE_TEST_CASE(set_node_pollutant_cumulative_values, FixtureBeforeStep_ // Cumulative must be 0.00 BOOST_CHECK_SMALL(total_pollutant, 1.0e-06); swmm_end(); + // check mass balance error less than 5% + swmm_getMassBalErr(&runoff_error, &flow_error, &qual_error); + BOOST_CHECK(abs(qual_error) <= 1.0); } // Testing Pollutant Setter - Node - Stepwise and Mass balance less than inflow concentration of 10 From 683f836a5e55f3a8d6bc2bfa3d39f04883682c5d Mon Sep 17 00:00:00 2001 From: Abhiram Mullapudi Date: Sat, 31 Jul 2021 19:41:53 -0400 Subject: [PATCH 140/190] declare variables --- tests/solver/test_pollut.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/solver/test_pollut.cpp b/tests/solver/test_pollut.cpp index 1e506b9b6..55132d33b 100644 --- a/tests/solver/test_pollut.cpp +++ b/tests/solver/test_pollut.cpp @@ -227,6 +227,7 @@ BOOST_FIXTURE_TEST_CASE(set_node_pollutant_cumulative_values, FixtureBeforeStep_ int error; double* node_qual; + float runoff_error, flow_error, qual_error; double elapsedTime = 0.0; double total_pollutant = 0.0; int length; From d36eda51c70b1783b7a6b74cc097b063c3071d5c Mon Sep 17 00:00:00 2001 From: cbuahin Date: Fri, 3 Sep 2021 19:58:26 -0400 Subject: [PATCH 141/190] Added citation file to address #346 --- CITATION.cff | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 CITATION.cff diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 000000000..6e7ec5c9e --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,62 @@ +cff-version: 1.2.0 +message: "If you use this software, please cite it as below." +authors: +- family-names: "Lew" + given-names: "Rosmman" + orcid: "https://orcid.org/0000-0000-0000-0000" +- family-names: "Robert" + given-names: "Dickinson" + orcid: "https://orcid.org/0000-0000-0000-0000" +- family-names: "Michael" + given-names: "Tryby" + orcid: "https://orcid.org/0000-0000-0000-0000" +- family-names: "Katherine" + given-names: "Ratliff" + orcid: "https://orcid.org/0000-0003-1410-2756" +- family-names: "Michelle" + given-names: "Simon" + orcid: "https://orcid.org/0000-0000-0000-0000" +- family-names: "Bryant" + given-names: "McDonnell" + orcid: "https://orcid.org/0000-0002-6250-2220" +- family-names: "Adam" + given-names: "Erispaha" + orcid: "https://orcid.org/0000-0000-0000-0000" +- family-names: "Sam" + given-names: "Hatchett" + orcid: "https://orcid.org/0000-0000-0000-0000" +- family-names: "Gonzalo" + given-names: "Peña-Castellano" + orcid: "https://orcid.org/0000-0000-0000-0000" +- family-names: "Abhiram" + given-names: "Mullapudi" + orcid: "https://orcid.org/0000-0000-0000-0000" +- family-names: "Jennifer" + given-names: "Wu" + orcid: "https://orcid.org/0000-0000-0000-0000" +- family-names: "Dominik" + given-names: "Leutnant" + orcid: "https://orcid.org/0000-0000-0000-0000" +- family-names: "Xu" + given-names: "Xi" + orcid: "https://orcid.org/0000-0000-0000-0000" +- family-names: "Caleb" + given-names: "Buahin" + orcid: "https://orcid.org/0000-0002-9859-2264" +- family-names: "Hsi-Nien" + given-names: "Tan" + orcid: "https://orcid.org/0000-0000-0000-0000" +- family-names: "Mingda" + given-names: "Zhang" + orcid: "https://orcid.org/0000-0000-0000-0000" +- family-names: "Laurent" + given-names: "Courty" + orcid: "https://orcid.org/0000-0000-0000-0000" +- family-names: "Brooke" + given-names: "Mason" + orcid: "https://orcid.org/0000-0000-0000-0000" +title: "Open Water Analytics Stormwater Management Model" +version: 5.1.13 +doi: +date-released: 2021-01-12 +url: "https://github.com/OpenWaterAnalytics/Stormwater-Management-Model" \ No newline at end of file From 0afda0e7eeeea8c102bad5d672052356d724b600 Mon Sep 17 00:00:00 2001 From: cbuahin Date: Mon, 6 Sep 2021 22:26:45 -0400 Subject: [PATCH 142/190] Fixed given name family name swap. --- CITATION.cff | 74 ++++++++++++++++++++++++++-------------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 6e7ec5c9e..7334b1732 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -1,62 +1,62 @@ cff-version: 1.2.0 message: "If you use this software, please cite it as below." authors: -- family-names: "Lew" - given-names: "Rosmman" +- family-names: "Rosmman" + given-names: "Lew" orcid: "https://orcid.org/0000-0000-0000-0000" -- family-names: "Robert" - given-names: "Dickinson" +- family-names: "Dickinson" + given-names: "Robert" orcid: "https://orcid.org/0000-0000-0000-0000" -- family-names: "Michael" - given-names: "Tryby" +- family-names: "Tryby" + given-names: "Michael" orcid: "https://orcid.org/0000-0000-0000-0000" -- family-names: "Katherine" - given-names: "Ratliff" +- family-names: "Ratliff" + given-names: "Katherine" orcid: "https://orcid.org/0000-0003-1410-2756" -- family-names: "Michelle" - given-names: "Simon" +- family-names: "Simon" + given-names: "Michelle" orcid: "https://orcid.org/0000-0000-0000-0000" -- family-names: "Bryant" - given-names: "McDonnell" +- family-names: "McDonnell" + given-names: "Bryant" orcid: "https://orcid.org/0000-0002-6250-2220" -- family-names: "Adam" - given-names: "Erispaha" +- family-names: "Erispaha" + given-names: "Adam" orcid: "https://orcid.org/0000-0000-0000-0000" -- family-names: "Sam" - given-names: "Hatchett" +- family-names: "Hatchett" + given-names: "Sam" orcid: "https://orcid.org/0000-0000-0000-0000" -- family-names: "Gonzalo" - given-names: "Peña-Castellano" +- family-names: "Peña-Castellano" + given-names: "Gonzalo" orcid: "https://orcid.org/0000-0000-0000-0000" -- family-names: "Abhiram" - given-names: "Mullapudi" +- family-names: "Mullapudi" + given-names: "Abhiram" orcid: "https://orcid.org/0000-0000-0000-0000" -- family-names: "Jennifer" - given-names: "Wu" +- family-names: "Wu" + given-names: "Jia Xin" orcid: "https://orcid.org/0000-0000-0000-0000" -- family-names: "Dominik" - given-names: "Leutnant" +- family-names: "Leutnant" + given-names: "Dominik" orcid: "https://orcid.org/0000-0000-0000-0000" -- family-names: "Xu" - given-names: "Xi" +- family-names: "Xi" + given-names: "Xu" orcid: "https://orcid.org/0000-0000-0000-0000" -- family-names: "Caleb" - given-names: "Buahin" +- family-names: "Buahin" + given-names: "Caleb" orcid: "https://orcid.org/0000-0002-9859-2264" -- family-names: "Hsi-Nien" - given-names: "Tan" +- family-names: "Tan" + given-names: "Hsi-Nien" orcid: "https://orcid.org/0000-0000-0000-0000" -- family-names: "Mingda" - given-names: "Zhang" +- family-names: "Zhang" + given-names: "Mingda" orcid: "https://orcid.org/0000-0000-0000-0000" -- family-names: "Laurent" - given-names: "Courty" +- family-names: "Courty" + given-names: "Laurent" orcid: "https://orcid.org/0000-0000-0000-0000" -- family-names: "Brooke" - given-names: "Mason" +- family-names: "Mason" + given-names: "Brooke" orcid: "https://orcid.org/0000-0000-0000-0000" title: "Open Water Analytics Stormwater Management Model" version: 5.1.13 -doi: +doi: http://doi.org date-released: 2021-01-12 url: "https://github.com/OpenWaterAnalytics/Stormwater-Management-Model" \ No newline at end of file From 8589cd573c52080b80105fde2302054a560a9bfd Mon Sep 17 00:00:00 2001 From: Caleb <7217571+cbuahin@users.noreply.github.com> Date: Tue, 7 Sep 2021 15:18:09 -0400 Subject: [PATCH 143/190] Added DOI --- CITATION.cff | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 7334b1732..fa516c787 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -57,6 +57,6 @@ authors: orcid: "https://orcid.org/0000-0000-0000-0000" title: "Open Water Analytics Stormwater Management Model" version: 5.1.13 -doi: http://doi.org +doi: 10.5281/zenodo.5484299 date-released: 2021-01-12 -url: "https://github.com/OpenWaterAnalytics/Stormwater-Management-Model" \ No newline at end of file +url: "https://github.com/OpenWaterAnalytics/Stormwater-Management-Model" From 4fcf812195972ebc83bdfc2a7be85dd747106c94 Mon Sep 17 00:00:00 2001 From: Caleb <7217571+cbuahin@users.noreply.github.com> Date: Tue, 7 Sep 2021 15:19:19 -0400 Subject: [PATCH 144/190] Added Zenodo DOI badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a74ea77ad..ed93f8719 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ ORD Stormwater Management Model (aka "SWMM") [![license](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/OpenWaterAnalytics/Stormwater-Management-Model) [![Join the chat at https://gitter.im/OpenWaterAnalytics/lobby](https://badges.gitter.im/OpenWaterAnalytics/Stormwater-Management-Model.svg)](https://gitter.im/OpenWaterAnalytics/lobby) [![docs](https://img.shields.io/badge/docs-passing-green.svg)](http://wateranalytics.org/Stormwater-Management-Model/) +[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.5484299.svg)](https://doi.org/10.5281/zenodo.5484299) ## Build Status ![build](https://github.com/OpenWaterAnalytics/Stormwater-Management-Model/workflows/Build%20and%20Test/badge.svg?branch=master) From 6570ef7f2461cce12ce377cff6f9395ec8d90cb4 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 9 Sep 2021 14:01:12 -0400 Subject: [PATCH 145/190] Initial commit CONTRIBUTORS file --- CONTRIBUTORS | 140 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 CONTRIBUTORS diff --git a/CONTRIBUTORS b/CONTRIBUTORS new file mode 100644 index 000000000..a69298d0e --- /dev/null +++ b/CONTRIBUTORS @@ -0,0 +1,140 @@ +# +# Contributors file of people whose contributions reside in the default branch +# of OWA SWMM ordered alphabetically by last name. This file is modeled after +# the CREDITS file maintained by the linux project. +# +# Fields are: +# name (N), email (E), license (L), commits (C), description (D) +# + + +N: Colleen Barr +E: barr.colleen@epa.gov +L: Public Domain +C: 0 +D: output file API + + +N: Caleb Buahin +E: caleb.buahin@gmail.com +L: MIT License +C: 129 +D: Development operations, bug fix, v5.1.14 merge + +N: Laurent Courty +E: lrntct@gmail.com +L: MIT License +C: 1 +D: Bug fix + + +N: Robert Dickinson +E: robert.dickinson@gmail.com +L: Public Domain +C: 0 +D: Legacy maintainer + + +N: Adam Erispaha +E: aerispaha@gmail.com +L: MIT License +C: 5 +D: Development operations + + +N: Sam Hatchett +E: samhatchett@gmail.com +L: MIT License +C: 2 +D: Development operations + + +N: Dominik Leutnant +E: leutnant@fh-muenster.de +L: MIT License +C: 11 +D: Mac OS build + + +N: Brooke E. Mason +E: bemason@umich.edu +L: MIT License +C: 0 +D: Water quality API, testing + + +N: Bryant E. McDonnell +E: bemcdonnell@gmail.com +L: MIT License +C: 147 +D: Founder, maintainer, toolkit API, testing, documentation, paper + + +N: Abhiram Mullapudi +E: abhiramm@umich.edu +L: MIT License +C: 20 +D: Rain API, testing, water quality API, development operations, paper + + +N: Gonzalo Peña-Castellanos +E: goanpeca@gmail.com +L: MIT License +C: 63 +D: Development operations, testing, documentation + + +N: Katherine Ratliff +E: ratliff.katherine@epa.gov +L: MIT License +C: 41 +D: Stats API, bug fix, testing, paper + + +N: Lewis Rossman +E: LRossman@cinci.rr.com +L: Public Domain +C: 0 +D: Legacy developer + + +N: Michelle Simon +E: simon.michelle@epa.gov +L: Public Domain +C: 1 +D: Documentation + + +N: Hsi-Nien Tan +E: hsi-nien.tan@vumc.org +L: MIT License +C: 4 +D: Bug fix, testing + + +N: Michael Tryby +E: tryby.michael@epa.gov +L: Public Domain +C: 386 +D: Mentor, Development operations, Python wrapper, paper + + +N: Jennifer Wu +E: wuu.jennifer@gmail.com +L: MIT License +C: 152 +D: Maintainer, LID API, bug fixes, testing, development operations, Python wrapper + + +N: Xu Xi +E: 12907470@qq.com +L: MIT License +C: 1 +D: Bug fix + + +N: Mingda Zhang +E: mingda_zhang@163.com +L: MIT License +C: 2 +D: Bug fix, testing From c0fd0f74cf127982f0ef844b56b2c532c4e6bc18 Mon Sep 17 00:00:00 2001 From: Jenn Date: Thu, 9 Sep 2021 14:28:16 -0400 Subject: [PATCH 146/190] Update CONTRIBUTORS Adjust name in CONTRIBUTOR file --- CONTRIBUTORS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index a69298d0e..2afe137be 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -119,7 +119,7 @@ C: 386 D: Mentor, Development operations, Python wrapper, paper -N: Jennifer Wu +N: Jia Xin Wu E: wuu.jennifer@gmail.com L: MIT License C: 152 From 3cf48c8e778c3beece9ffc1a9a31dc814e3ef8e7 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 9 Sep 2021 15:18:01 -0400 Subject: [PATCH 147/190] Fussing --- CONTRIBUTORS | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index a69298d0e..f3f431ffd 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -1,5 +1,7 @@ # -# Contributors file of people whose contributions reside in the default branch +# CONTRIBUTORS +# +# List of people whose contributions reside in the default branch # of OWA SWMM ordered alphabetically by last name. This file is modeled after # the CREDITS file maintained by the linux project. # @@ -8,11 +10,12 @@ # + N: Colleen Barr E: barr.colleen@epa.gov L: Public Domain C: 0 -D: output file API +D: Output file API N: Caleb Buahin @@ -21,6 +24,7 @@ L: MIT License C: 129 D: Development operations, bug fix, v5.1.14 merge + N: Laurent Courty E: lrntct@gmail.com L: MIT License @@ -67,14 +71,14 @@ N: Bryant E. McDonnell E: bemcdonnell@gmail.com L: MIT License C: 147 -D: Founder, maintainer, toolkit API, testing, documentation, paper +D: Founder, maintainer, toolkit API, testing, documentation N: Abhiram Mullapudi E: abhiramm@umich.edu L: MIT License C: 20 -D: Rain API, testing, water quality API, development operations, paper +D: Rain API, testing, water quality API, development operations N: Gonzalo Peña-Castellanos @@ -88,7 +92,7 @@ N: Katherine Ratliff E: ratliff.katherine@epa.gov L: MIT License C: 41 -D: Stats API, bug fix, testing, paper +D: Stats API, bug fix, testing N: Lewis Rossman @@ -116,7 +120,7 @@ N: Michael Tryby E: tryby.michael@epa.gov L: Public Domain C: 386 -D: Mentor, Development operations, Python wrapper, paper +D: Mentor, output file API, testing framework, development operations, Python wrapper N: Jennifer Wu From fba59d0a29c410aee07e779132e943182463a25e Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 10 Sep 2021 12:39:34 -0400 Subject: [PATCH 148/190] Finalize contributors and citation --- AUTHORS | 27 -------------------------- CITATION.cff | 48 ++++++--------------------------------------- CONTRIBUTORS | 55 ++++++++-------------------------------------------- LICENSE | 40 +++++++++++++++++++++----------------- 4 files changed, 36 insertions(+), 134 deletions(-) delete mode 100644 AUTHORS diff --git a/AUTHORS b/AUTHORS deleted file mode 100644 index d86917893..000000000 --- a/AUTHORS +++ /dev/null @@ -1,27 +0,0 @@ -# Authors ordered by first contribution. - - -Authors with Contributions in the Public Domain: - -Lewis Rossman -Robert Dickinson -Michael Tryby -Katherine Ratliff -Michelle Simon - - -Authors with Contributions Subject to Copyright (see LICENSE): - -Bryant E. McDonnell -Adam Erispaha -Sam Hatchett -Gonzalo Peña-Castellanos -Abhiram Mullapudi -Jennifer Wu -Dominik Leutnant -Xu Xi <12907470@qq.com> -Caleb Buahin -Hsi-Nien Tan -Mingda Zhang -Laurent Courty -Brooke E. Mason \ No newline at end of file diff --git a/CITATION.cff b/CITATION.cff index fa516c787..99a5610ec 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -1,60 +1,24 @@ cff-version: 1.2.0 message: "If you use this software, please cite it as below." authors: -- family-names: "Rosmman" - given-names: "Lew" - orcid: "https://orcid.org/0000-0000-0000-0000" -- family-names: "Dickinson" - given-names: "Robert" - orcid: "https://orcid.org/0000-0000-0000-0000" +- family-names: "McDonnell" + given-names: "Bryant" + orcid: "https://orcid.org/0000-0002-6250-2220" - family-names: "Tryby" given-names: "Michael" orcid: "https://orcid.org/0000-0000-0000-0000" +- family-names: "Wu" + given-names: "Jia Xin" + orcid: "https://orcid.org/0000-0000-0000-0000" - family-names: "Ratliff" given-names: "Katherine" orcid: "https://orcid.org/0000-0003-1410-2756" -- family-names: "Simon" - given-names: "Michelle" - orcid: "https://orcid.org/0000-0000-0000-0000" -- family-names: "McDonnell" - given-names: "Bryant" - orcid: "https://orcid.org/0000-0002-6250-2220" -- family-names: "Erispaha" - given-names: "Adam" - orcid: "https://orcid.org/0000-0000-0000-0000" -- family-names: "Hatchett" - given-names: "Sam" - orcid: "https://orcid.org/0000-0000-0000-0000" -- family-names: "Peña-Castellano" - given-names: "Gonzalo" - orcid: "https://orcid.org/0000-0000-0000-0000" - family-names: "Mullapudi" given-names: "Abhiram" orcid: "https://orcid.org/0000-0000-0000-0000" -- family-names: "Wu" - given-names: "Jia Xin" - orcid: "https://orcid.org/0000-0000-0000-0000" -- family-names: "Leutnant" - given-names: "Dominik" - orcid: "https://orcid.org/0000-0000-0000-0000" -- family-names: "Xi" - given-names: "Xu" - orcid: "https://orcid.org/0000-0000-0000-0000" - family-names: "Buahin" given-names: "Caleb" orcid: "https://orcid.org/0000-0002-9859-2264" -- family-names: "Tan" - given-names: "Hsi-Nien" - orcid: "https://orcid.org/0000-0000-0000-0000" -- family-names: "Zhang" - given-names: "Mingda" - orcid: "https://orcid.org/0000-0000-0000-0000" -- family-names: "Courty" - given-names: "Laurent" - orcid: "https://orcid.org/0000-0000-0000-0000" -- family-names: "Mason" - given-names: "Brooke" - orcid: "https://orcid.org/0000-0000-0000-0000" title: "Open Water Analytics Stormwater Management Model" version: 5.1.13 doi: 10.5281/zenodo.5484299 diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 1093c557a..432b2821e 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -3,142 +3,103 @@ # # List of people whose contributions reside in the default branch # of OWA SWMM ordered alphabetically by last name. This file is modeled after -# the CREDITS file maintained by the linux project. +# the CREDITS file maintained by the Linux project. # # Fields are: -# name (N), email (E), license (L), commits (C), description (D) +# name (N), email (E), license (L), description (D) # - - N: Colleen Barr E: barr.colleen@epa.gov L: Public Domain -C: 0 D: Output file API - N: Caleb Buahin E: caleb.buahin@gmail.com L: MIT License -C: 129 -D: Development operations, bug fix, v5.1.14 merge - +D: Development operations, bug fix, v5.1.14 merge, reviews N: Laurent Courty E: lrntct@gmail.com L: MIT License -C: 1 -D: Bug fix - +D: Bug fix, testing N: Robert Dickinson E: robert.dickinson@gmail.com L: Public Domain -C: 0 D: Legacy maintainer - N: Adam Erispaha E: aerispaha@gmail.com L: MIT License -C: 5 -D: Development operations - +D: Linux makefile N: Sam Hatchett E: samhatchett@gmail.com L: MIT License -C: 2 D: Development operations - N: Dominik Leutnant E: leutnant@fh-muenster.de L: MIT License -C: 11 D: Mac OS build - -N: Brooke E. Mason +N: Brooke Mason E: bemason@umich.edu L: MIT License -C: 0 D: Water quality API, testing - -N: Bryant E. McDonnell +N: Bryant McDonnell E: bemcdonnell@gmail.com L: MIT License -C: 147 -D: Founder, maintainer, toolkit API, testing, documentation - +D: Founder, maintainer, toolkit API, testing, review, bug fix, documentation N: Abhiram Mullapudi E: abhiramm@umich.edu L: MIT License -C: 20 D: Rain API, testing, water quality API, development operations - N: Gonzalo Peña-Castellanos E: goanpeca@gmail.com L: MIT License -C: 63 D: Development operations, testing, documentation - N: Katherine Ratliff E: ratliff.katherine@epa.gov L: MIT License -C: 41 D: Stats API, bug fix, testing - N: Lewis Rossman E: LRossman@cinci.rr.com L: Public Domain -C: 0 D: Legacy developer - N: Michelle Simon E: simon.michelle@epa.gov L: Public Domain -C: 1 D: Documentation - N: Hsi-Nien Tan E: hsi-nien.tan@vumc.org L: MIT License -C: 4 D: Bug fix, testing - N: Michael Tryby E: tryby.michael@epa.gov L: Public Domain -C: 386 D: Mentor, output file API, testing framework, development operations, Python wrapper - N: Jia Xin Wu E: wuu.jennifer@gmail.com L: MIT License -C: 152 D: Maintainer, LID API, bug fixes, testing, development operations, Python wrapper - N: Xu Xi E: 12907470@qq.com L: MIT License -C: 1 D: Bug fix - N: Mingda Zhang E: mingda_zhang@163.com L: MIT License -C: 2 D: Bug fix, testing diff --git a/LICENSE b/LICENSE index 0226bd9e4..eaaeba53f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,32 +1,17 @@ -This project contains original material released as part of USEPA SWMM v5.1.14. -Original material residing in the public domain is unclaimable. - -This project also contains new material prepared by the United States -Government for which domestic copyright protection is not available under 17 -USC § 105. -Public Domain - -Material original to USEPA SWMM v5.1.14s No Copyright Restrictions - -Output library, shared objects, CLI, build and test systems No Copyright -Restrictions 2020 U.S. Federal Government - -=============================================================================== This project contains material from https://github.com/OpenWaterAnalytics/ -Stormwater-Management-Model. All material created by the AUTHORS is released -under the MIT License. +Stormwater-Management-Model. All material created by the CONTRIBUTORS is released +under the MIT License unless noted otherwise. OWA SWMM may also includes third-party software libraries which have separate licensing policies. - MIT License -Copyright (c) 2020 see AUTHORS, excluding material in the public +Copyright (c) 2020-2021 see CONTRIBUTORS, excluding material in the public domain. @@ -49,6 +34,25 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +=============================================================================== + +This project contains original material released as part of USEPA SWMM v5.1.14. +Original material residing in the public domain is unclaimable. + +This project also contains new material prepared by the United States +Government for which domestic copyright protection is not available under 17 +USC § 105. + + +Public Domain + +Material original to USEPA SWMM v5.1.14 No Copyright Restrictions + +Output library, shared objects, CLI, build and test systems No Copyright +Restrictions 2020-2021 U.S. Federal Government + + +=============================================================================== Third-Party Libraries From a9f0febd02d8657d08ae27cd55987ac09b164da1 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 10 Sep 2021 12:42:54 -0400 Subject: [PATCH 149/190] Finalize contributors --- CONTRIBUTORS | 10 +++++----- LICENSE | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 432b2821e..0607fd21f 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -17,7 +17,7 @@ D: Output file API N: Caleb Buahin E: caleb.buahin@gmail.com L: MIT License -D: Development operations, bug fix, v5.1.14 merge, reviews +D: Development operations, bug fix, v5.1.14 merge, review N: Laurent Courty E: lrntct@gmail.com @@ -57,7 +57,7 @@ D: Founder, maintainer, toolkit API, testing, review, bug fix, documentation N: Abhiram Mullapudi E: abhiramm@umich.edu L: MIT License -D: Rain API, testing, water quality API, development operations +D: Rain API, testing, water quality API, development operations, review N: Gonzalo Peña-Castellanos E: goanpeca@gmail.com @@ -67,7 +67,7 @@ D: Development operations, testing, documentation N: Katherine Ratliff E: ratliff.katherine@epa.gov L: MIT License -D: Stats API, bug fix, testing +D: Stats API, bug fix, testing, review N: Lewis Rossman E: LRossman@cinci.rr.com @@ -87,12 +87,12 @@ D: Bug fix, testing N: Michael Tryby E: tryby.michael@epa.gov L: Public Domain -D: Mentor, output file API, testing framework, development operations, Python wrapper +D: Mentor, output file API, testing framework, development operations, Python wrapper, review N: Jia Xin Wu E: wuu.jennifer@gmail.com L: MIT License -D: Maintainer, LID API, bug fixes, testing, development operations, Python wrapper +D: Maintainer, LID API, bug fixes, testing, development operations, Python wrapper, review N: Xu Xi E: 12907470@qq.com diff --git a/LICENSE b/LICENSE index eaaeba53f..1d04a2c91 100644 --- a/LICENSE +++ b/LICENSE @@ -3,7 +3,7 @@ This project contains material from https://github.com/OpenWaterAnalytics/ Stormwater-Management-Model. All material created by the CONTRIBUTORS is released -under the MIT License unless noted otherwise. +under the MIT License unless otherwise noted. OWA SWMM may also includes third-party software libraries which have separate licensing policies. From 805ded02bb4ee983544e8adb6bc9884d8a36f603 Mon Sep 17 00:00:00 2001 From: Caleb <7217571+cbuahin@users.noreply.github.com> Date: Fri, 10 Sep 2021 13:14:27 -0400 Subject: [PATCH 150/190] Update CITATION.cff --- CITATION.cff | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 99a5610ec..84a261634 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -16,10 +16,6 @@ authors: - family-names: "Mullapudi" given-names: "Abhiram" orcid: "https://orcid.org/0000-0000-0000-0000" -- family-names: "Buahin" - given-names: "Caleb" - orcid: "https://orcid.org/0000-0002-9859-2264" -title: "Open Water Analytics Stormwater Management Model" version: 5.1.13 doi: 10.5281/zenodo.5484299 date-released: 2021-01-12 From c7767cc7ad8559e21573e8b44251af538c43f246 Mon Sep 17 00:00:00 2001 From: Caleb <7217571+cbuahin@users.noreply.github.com> Date: Fri, 10 Sep 2021 13:17:17 -0400 Subject: [PATCH 151/190] Update CITATION.cff --- CITATION.cff | 1 + 1 file changed, 1 insertion(+) diff --git a/CITATION.cff b/CITATION.cff index 84a261634..a8b2400f7 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -16,6 +16,7 @@ authors: - family-names: "Mullapudi" given-names: "Abhiram" orcid: "https://orcid.org/0000-0000-0000-0000" +title: "Open Water Analytics Stormwater Management Model" version: 5.1.13 doi: 10.5281/zenodo.5484299 date-released: 2021-01-12 From 0839398ce2071fb3a4f626986436ebff5f1a1a67 Mon Sep 17 00:00:00 2001 From: Abhiram Date: Fri, 10 Sep 2021 14:21:32 -0400 Subject: [PATCH 152/190] updated orcid --- CITATION.cff | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CITATION.cff b/CITATION.cff index a8b2400f7..56474a6ac 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -15,7 +15,7 @@ authors: orcid: "https://orcid.org/0000-0003-1410-2756" - family-names: "Mullapudi" given-names: "Abhiram" - orcid: "https://orcid.org/0000-0000-0000-0000" + orcid: "https://orcid.org/0000-0001-8141-3621" title: "Open Water Analytics Stormwater Management Model" version: 5.1.13 doi: 10.5281/zenodo.5484299 From 34310338564711a8c0722ad6e7314b9850542264 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 13 Sep 2021 15:04:12 -0400 Subject: [PATCH 153/190] Adding orcid, moving to last author --- CITATION.cff | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 56474a6ac..7b534137b 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -4,9 +4,6 @@ authors: - family-names: "McDonnell" given-names: "Bryant" orcid: "https://orcid.org/0000-0002-6250-2220" -- family-names: "Tryby" - given-names: "Michael" - orcid: "https://orcid.org/0000-0000-0000-0000" - family-names: "Wu" given-names: "Jia Xin" orcid: "https://orcid.org/0000-0000-0000-0000" @@ -16,7 +13,10 @@ authors: - family-names: "Mullapudi" given-names: "Abhiram" orcid: "https://orcid.org/0000-0001-8141-3621" -title: "Open Water Analytics Stormwater Management Model" +- family-names: "Tryby" + given-names: "Michael" + orcid: "https://orcid.org/0000-0001-5525-0734" +title: "Open Water Analytics Stormwater Management Model" version: 5.1.13 doi: 10.5281/zenodo.5484299 date-released: 2021-01-12 From 42fca47be1bfb906df05b8808bc517a7857e34c3 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 17 Sep 2021 12:02:51 -0400 Subject: [PATCH 154/190] Fix build, add ORCID --- CITATION.cff | 2 +- CMakeLists.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 7b534137b..4723ae477 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -6,7 +6,7 @@ authors: orcid: "https://orcid.org/0000-0002-6250-2220" - family-names: "Wu" given-names: "Jia Xin" - orcid: "https://orcid.org/0000-0000-0000-0000" + orcid: "https://orcid.org/0000-0003-2233-9542" - family-names: "Ratliff" given-names: "Katherine" orcid: "https://orcid.org/0000-0003-1410-2756" diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ccc5cf15..8e9f05503 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,11 +72,11 @@ endif() include(InstallRequiredSystemLibraries) -# Create install target for License and Authors files +# Create install target for License and Contributors files install( FILES "LICENSE" - "AUTHORS" + "CONTRIBUTORS" DESTINATION "." ) From d5aa0e964e3e3e5e80f97f7c594b9b799697213e Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 17 Sep 2021 12:35:13 -0400 Subject: [PATCH 155/190] Update AUTHORS.dox --- docs/AUTHORS.dox | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/docs/AUTHORS.dox b/docs/AUTHORS.dox index bb8b2ce4a..5f1c8696b 100644 --- a/docs/AUTHORS.dox +++ b/docs/AUTHORS.dox @@ -1,22 +1,16 @@ -/** +/** @defgroup AUTHORS Authors at Open Water Analytics -The OWA-SWMM project builds on top of the contributions put forward by Lew Rossman with his original work on delivering EPA-SWMM5. We are grateful for all the work that has been put into SWMM5 and we are committed to delivering advances to the engine in a community driven approach. +The OWA-SWMM project builds on top of the contributions put forward by Lew Rossman with his original work on delivering EPA-SWMM5. We are grateful for all the work that has been put into SWMM5 and we are committed to delivering advances to the engine in a community driven approach. -- Lewis Rossman * -- Robert Dickinson * -- Michael Tryby * - Bryant E. McDonnell -- Adam Erispaha -- Sam Hatchett -- Gonzalo Peña-Castellanos +- Jennifer Wu - Katherine Ratliff * - Abhiram Mullapudi -- Jennifer Wu -- Hsi-Nien Tan -- Caleb Buahin +- Michael Tryby * + -Authors ordered by first contribution. Author contributions are made under the @subpage MIT-License, except for authors whose contributions are made in the public domain, who are denoted with *. +Authors ordered by contribution to project. Author contributions are made under the @subpage MIT-License, except for authors whose contributions are made in the public domain, who are denoted with *. Stand on the shoulders of Giants! For a list of all the contributors over time (SWMM5 and before), please see [Acknowledgements](https://github.com/OpenWaterAnalytics/Stormwater-Management-Model/wiki/Acknowledgements) -*/ \ No newline at end of file +*/ From dd4bc97278bc81c3cec9872701747993668f8cf9 Mon Sep 17 00:00:00 2001 From: karosc Date: Tue, 21 Sep 2021 08:34:59 -0400 Subject: [PATCH 156/190] Fix get SMO_getSystemAttribute error --- src/outfile/swmm_output.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/outfile/swmm_output.c b/src/outfile/swmm_output.c index 124807844..57fa847d6 100644 --- a/src/outfile/swmm_output.c +++ b/src/outfile/swmm_output.c @@ -730,7 +730,7 @@ int EXPORT_OUT_API SMO_getSystemAttribute(SMO_Handle p_handle, int periodIndex, // don't need to loop since there's only one system temp[0] = getSystemValue(p_data, periodIndex, attr); - *outValueArray = &temp; + *outValueArray = temp; *length = 1; } From a139116ad6eb483256e6c28c7a113d2bf7cf5b1c Mon Sep 17 00:00:00 2001 From: karosc Date: Tue, 21 Sep 2021 09:30:46 -0400 Subject: [PATCH 157/190] add PET to output system attr enum --- src/outfile/include/swmm_output_enums.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/outfile/include/swmm_output_enums.h b/src/outfile/include/swmm_output_enums.h index 23c53c78e..d31e70863 100644 --- a/src/outfile/include/swmm_output_enums.h +++ b/src/outfile/include/swmm_output_enums.h @@ -94,7 +94,7 @@ typedef enum { SMO_outfall_flows, // (flow units), SMO_volume_stored, // (ft3 or m3), SMO_evap_rate // (in/day or mm/day), - //p_evap_rate // (in/day or mm/day) + SMO_p_evap_rate // (in/day or mm/day) } SMO_systemAttribute; From e52340ceb5d871056f7881e587d5cad77bf6b8d1 Mon Sep 17 00:00:00 2001 From: Constantine Karos <36245370+karosc@users.noreply.github.com> Date: Tue, 21 Sep 2021 16:19:54 -0400 Subject: [PATCH 158/190] Fix missing comma in SMO_systemAttribute enum --- src/outfile/include/swmm_output_enums.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/outfile/include/swmm_output_enums.h b/src/outfile/include/swmm_output_enums.h index d31e70863..e4ab35bf9 100644 --- a/src/outfile/include/swmm_output_enums.h +++ b/src/outfile/include/swmm_output_enums.h @@ -93,8 +93,8 @@ typedef enum { SMO_flood_losses, // (flow units), SMO_outfall_flows, // (flow units), SMO_volume_stored, // (ft3 or m3), - SMO_evap_rate // (in/day or mm/day), - SMO_p_evap_rate // (in/day or mm/day) + SMO_evap_rate, // (in/day or mm/day), + SMO_p_evap_rate // (in/day or mm/day) } SMO_systemAttribute; From 2db4a68e1dff006595ce2dee8af81f02408899e4 Mon Sep 17 00:00:00 2001 From: karosc Date: Thu, 23 Sep 2021 20:51:13 -0400 Subject: [PATCH 159/190] Update contributors file with ckaros contact info --- CONTRIBUTORS | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 0607fd21f..7d55fc639 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -39,6 +39,11 @@ E: samhatchett@gmail.com L: MIT License D: Development operations +N: Constantine Karos +E: ckaros@outlook.com +L: MIT License +D: Bug fix, testing + N: Dominik Leutnant E: leutnant@fh-muenster.de L: MIT License From 16fe86446e36c39ca7ce5136c44447c40cb3c740 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 2 Nov 2021 16:26:11 -0400 Subject: [PATCH 160/190] Auto generates version header --- .gitignore | 1 + CMakeLists.txt | 2 ++ extern/version.cmake | 40 +++++++++++++++++++++++++++++++++++++++ extern/version.h.in | 27 ++++++++++++++++++++++++++ src/solver/CMakeLists.txt | 4 ++++ 5 files changed, 74 insertions(+) create mode 100644 extern/version.cmake create mode 100644 extern/version.h.in diff --git a/.gitignore b/.gitignore index a591f28cd..fec94365b 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,4 @@ tests/solver/data/feng* # Generated files *_export.h +version.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e9f05503..0a5c7af9f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,8 +45,10 @@ set(LIBRARY_DIST "lib") set(CONFIG_DIST "cmake") +# Define build options option(BUILD_TESTS "Build component tests (requires Boost)" OFF) option(BUILD_DOCS "Build toolkit docs (requires Doxygen)" OFF) +option(GEN_VER_HEADER "Automatically update version header" OFF) # Add project subdirectories diff --git a/extern/version.cmake b/extern/version.cmake new file mode 100644 index 000000000..67a5ac93e --- /dev/null +++ b/extern/version.cmake @@ -0,0 +1,40 @@ +# +# version.cmake - generate SWMM version information header +# +# Created: November 2, 2021 +# Updated: +# +# Author: Michael E. Tryby +# US EPA - ORD/CESER +# +# Usage: +# To overwrite version.h file with current info into set GEN_VER_HEADER=ON. + +if(GEN_VER_HEADER) + + # Get the latest commit hash for the working branch + execute_process( + COMMAND + git rev-parse HEAD + WORKING_DIRECTORY + ${CMAKE_CURRENT_LIST_DIR} + OUTPUT_VARIABLE + GIT_HASH + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + # Generate a build id + string( + TIMESTAMP BUILD_ID UTC + ) + + # Configure the version header + configure_file( + ../../extern/version.h.in version.h + ) + + file(COPY ${CMAKE_CURRENT_BINARY_DIR}/version.h + DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/ + ) + +endif() diff --git a/extern/version.h.in b/extern/version.h.in new file mode 100644 index 000000000..9c74927f1 --- /dev/null +++ b/extern/version.h.in @@ -0,0 +1,27 @@ +/* + * version.h - SWMM version information + * + * Created on: Nov 2, 2021 + * + * Author: Michael E. Tryby + * US EPA - ORD/CESER + */ + + +#ifndef VERSION_H_ +#define VERSION_H_ + + + +#define VERSION "@swmm-solver_VERSION@" +#define VERSION_MAJOR @swmm-solver_VERSION_MAJOR@ +#define VERSION_MINOR @swmm-solver_VERSION_MINOR@ +#define VERSION_PATCH @swmm-solver_VERSION_PATCH@ + +#define GIT_HASH "@GIT_HASH@" + +#define BUILD_ID "@BUILD_ID@" + + + +#endif /* VERSION_H_ */ diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index dea6931f5..0e5e1f222 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -14,6 +14,9 @@ find_package(OpenMP C ) +# Generate version header +include(../../extern/version.cmake) + # configure file groups set(SWMM_PUBLIC_HEADERS @@ -28,6 +31,7 @@ file(GLOB RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.c *.h ) + if(BUILD_DEF) # Builds library with def file interface for backward compatibility set_source_files_properties(${PROJECT_SOURCE_DIR}/bindings/swmm5.def From 9a771f65ac87b8d83f9e67e4101c7153966fba11 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 3 Nov 2021 12:31:42 -0400 Subject: [PATCH 161/190] Update report and output headers --- extern/boost.cmake | 6 +++- extern/version.cmake | 4 ++- extern/version.h.in | 17 +++++++++-- src/run/main.c | 57 ++++++++++++++++-------------------- src/solver/consts.h | 10 +++---- src/solver/funcs.h | 1 - src/solver/include/swmm5.h | 2 +- src/solver/include/toolkit.h | 22 +++++++------- src/solver/output.c | 5 ++-- src/solver/report.c | 9 +++--- src/solver/swmm5.c | 20 +++---------- src/solver/toolkit.c | 21 ++++--------- 12 files changed, 82 insertions(+), 92 deletions(-) diff --git a/extern/boost.cmake b/extern/boost.cmake index f1f815626..7b7e57144 100644 --- a/extern/boost.cmake +++ b/extern/boost.cmake @@ -7,6 +7,10 @@ # Author: Michael E. Tryby # US EPA - ORD/CESER # +# Usage: +# Create environment variable with the following pattern -- "BOOST_ROOT_X_XX_X" +# where Xs are the Boost version -- pointing to install location. +# if(WIN32) @@ -17,7 +21,7 @@ else() endif() -# Environment variable "BOOST_ROOT_X_XX_X" points to local install location +# ADD NEW BOOST LIBRARIES VERSIONS HERE if (DEFINED ENV{BOOST_ROOT_1_72_0}) set(BOOST_ROOT $ENV{BOOST_ROOT_1_72_0}) diff --git a/extern/version.cmake b/extern/version.cmake index 67a5ac93e..721f9e51a 100644 --- a/extern/version.cmake +++ b/extern/version.cmake @@ -8,7 +8,9 @@ # US EPA - ORD/CESER # # Usage: -# To overwrite version.h file with current info into set GEN_VER_HEADER=ON. +# To overwrite version.h file with current info set GEN_VER_HEADER=ON. +# + if(GEN_VER_HEADER) diff --git a/extern/version.h.in b/extern/version.h.in index 9c74927f1..fcdc70d5e 100644 --- a/extern/version.h.in +++ b/extern/version.h.in @@ -1,10 +1,12 @@ /* - * version.h - SWMM version information + * version.h - SWMM version header file * * Created on: Nov 2, 2021 * - * Author: Michael E. Tryby - * US EPA - ORD/CESER + * Author: see CONTRIBUTORS + * + * Note: + * The build process automatically generates this file. Do not edit. */ @@ -13,6 +15,8 @@ +#define ORGANIZATION "OWA" + #define VERSION "@swmm-solver_VERSION@" #define VERSION_MAJOR @swmm-solver_VERSION_MAJOR@ #define VERSION_MINOR @swmm-solver_VERSION_MINOR@ @@ -23,5 +27,12 @@ #define BUILD_ID "@BUILD_ID@" +static inline char *get_version() { return VERSION; } +static inline int get_version_legacy() { \ + return VERSION_MAJOR * 10000 + VERSION_MINOR * 1000 + VERSION_PATCH; \ +} +static inline char *get_buildid() { return BUILD_ID; } + + #endif /* VERSION_H_ */ diff --git a/src/run/main.c b/src/run/main.c index 6940e055e..46ff5eca7 100644 --- a/src/run/main.c +++ b/src/run/main.c @@ -1,13 +1,13 @@ -//----------------------------------------------------------------------------- -// main.c -// -// Project: EPA SWMM5 -// Version: 5.1 -// Date: 05/10/2018 -// Author: L. Rossman - -// Main stub for the command line version of EPA SWMM 5.1 -// to be run with swmm5.dll. +/* + * main.c - Main stub for the command line version of EPA SWMM 5.1 + * + * Created on: October 9, 2020 + * Updated on: + * + * Author: Michael E. Tryby + * US EPA - ORD/CESER + * + */ #include #include @@ -25,6 +25,15 @@ #define BAR_LEN 50l #define MSG_LEN 84 +#define FMT_HELP \ +"\n\nOWA Stormwater Management Model (SWMM5) Help\n\n \ + Commands:\n \ + \t--help (-h) Help Docs\n \ + \t--version (-v) Build Version\n \ + \nUsage:\n \ + \t swmm5 \n\n" + + static long Start; @@ -76,9 +85,7 @@ int main(int argc, char *argv[]) // f3 = name of binary output file if saved (or blank if not saved). // { - // --- check for proper number of command line arguments if (argc == 4) { - // --- extract file names from command line arguments char *inputFile = argv[1]; char *reportFile = argv[2]; @@ -87,13 +94,12 @@ int main(int argc, char *argv[]) binaryFile = argv[3]; Start = current_time_millis(); - // --- run SWMM swmm_run_cb(inputFile, reportFile, binaryFile, &progress_bar); long stop = current_time_millis(); char time[TIMER_LEN + 1] = {'\0'}; - printf("\n\n... EPA-SWMM completed in %s", format_time(time, stop - Start)); + printf("\n\n... SWMM completed in %s", format_time(time, stop - Start)); char errMsg[128]; int msgLen = 127; @@ -109,33 +115,22 @@ int main(int argc, char *argv[]) } else if (argc == 2) { - // --- extract first argument char *arg1 = argv[1]; - if (strcmp(arg1, "--help") == 0 || strcmp(arg1, "-h") == 0) { - // Help - printf("\n\nEPA Stormwater Management Model (SWMM5) Help\n\n"); - printf("Commands:\n"); - printf("\t--help (-h) Help Docs\n"); - printf("\t--version (-v) Build Version\n"); - printf("\nUsage:\n"); - printf("\t swmm5 \n\n"); - } - else if (strcmp(arg1, "--version") == 0 || strcmp(arg1, "-v") == 0) { - int version = swmm_getVersion(); - int vMajor = version / 10000; - int vMinor = (version - 10000 * vMajor) / 1000; - int vRelease = (version - 10000 * vMajor - 1000 * vMinor); + if (strcmp(arg1, "--help") == 0 || strcmp(arg1, "-h") == 0) + printf(FMT_HELP); - // Output version number + else if (strcmp(arg1, "--version") == 0 || strcmp(arg1, "-v") == 0) { printf("\nVersion:\n"); - printf("\tEPA-SWMM %d.%d.%0d\n\n", vMajor, vMinor, vRelease); + printf("\tOWA-SWMM v%s\n\n", swmm_getSemVersion()); } + else { printf("\nError:\n"); printf("\tUnknown Argument (See Help --help)\n\n"); } } + else { printf("\nUsage:\n"); printf("\trunswmm \n\n"); diff --git a/src/solver/consts.h b/src/solver/consts.h index ad72d126b..1a689fb32 100644 --- a/src/solver/consts.h +++ b/src/solver/consts.h @@ -17,11 +17,11 @@ //------------------ // Update VERSION and SEMVERSION Simultaneously -#define VERSION 51014 // Eventually will be deprecated. -#define SEMVERSION_MAJOR "5" // Major Semantic Version -#define SEMVERSION_MINOR "1" // Minor Semantic Version -#define SEMVERSION_PATCH "014" // Patch Semantic Version -#define SEMVERSION_LEN 20 // Version String Len +//#define VERSION 51014 // Eventually will be deprecated. +//#define SEMVERSION_MAJOR "5" // Major Semantic Version +//#define SEMVERSION_MINOR "1" // Minor Semantic Version +//#define SEMVERSION_PATCH "014" // Patch Semantic Version +//#define SEMVERSION_LEN 20 // Version String Len #define MAGICNUMBER 516114522 #define EOFMARK 0x1A // Use 0x04 for UNIX systems diff --git a/src/solver/funcs.h b/src/solver/funcs.h index 3eaa57f3a..4199e54c3 100644 --- a/src/solver/funcs.h +++ b/src/solver/funcs.h @@ -521,4 +521,3 @@ void writecon(char *s); // writes string to console DateTime getDateTime(double elapsedMsec); // convert elapsed time to date void getElapsedTime(DateTime aDate, // convert elapsed date int* days, int* hrs, int* mins); -void getSemVersion(char* semver); // get semantic version diff --git a/src/solver/include/swmm5.h b/src/solver/include/swmm5.h index 0aa59c62f..a3875927f 100644 --- a/src/solver/include/swmm5.h +++ b/src/solver/include/swmm5.h @@ -117,7 +117,7 @@ int DLLEXPORT swmm_getMassBalErr(float* runoffErr, float* flowErr, float* qualEr int DLLEXPORT swmm_close(void); /** - @brief Get Legacy SWMM version number + @brief Get SWMM version number @return Version */ int DLLEXPORT swmm_getVersion(void); diff --git a/src/solver/include/toolkit.h b/src/solver/include/toolkit.h index cbbeb442f..723218c18 100644 --- a/src/solver/include/toolkit.h +++ b/src/solver/include/toolkit.h @@ -34,18 +34,18 @@ extern "C" { // /** // @brief Get full semantic version number -// @param[out] semver sematic version (char array) +// @return sematic version (char array) // */ -// void DLLEXPORT swmm_getSemVersion(char* semver); - -/** - @brief Get full semantic version number info - @param[out] major sematic version major number - @param[out] minor sematic version minor number - @param[out] patch sematic version patch number - @return error code -*/ -int DLLEXPORT swmm_getVersionInfo(char **major, char **minor, char **patch); +char * DLLEXPORT swmm_getSemVersion(); + +///** +// @brief Get full semantic version number info +// @param[out] major sematic version major number +// @param[out] minor sematic version minor number +// @param[out] patch sematic version patch number +// @return error code +//*/ +//int DLLEXPORT swmm_getVersionInfo(char **major, char **minor, char **patch); /** @brief Opens SWMM input file, reads in network data, runs, and closes diff --git a/src/solver/output.c b/src/solver/output.c index 47cd9df93..7eca52e7c 100644 --- a/src/solver/output.c +++ b/src/solver/output.c @@ -34,8 +34,9 @@ #include #include #include -#include "headers.h" +#include "headers.h" +#include "version.h" // Definition of 4-byte integer, 4-byte real and 8-byte real types #define INT4 int @@ -182,7 +183,7 @@ int output_open() fseek(Fout.file, 0, SEEK_SET); k = MAGICNUMBER; fwrite(&k, sizeof(INT4), 1, Fout.file); // Magic number - k = VERSION; + k = get_version_legacy(); fwrite(&k, sizeof(INT4), 1, Fout.file); // Version number k = FlowUnits; fwrite(&k, sizeof(INT4), 1, Fout.file); // Flow units diff --git a/src/solver/report.c b/src/solver/report.c index a8eadf872..3f5999fc3 100644 --- a/src/solver/report.c +++ b/src/solver/report.c @@ -50,7 +50,10 @@ #include #include #include + #include "headers.h" +#include "version.h" + #define WRITE(x) (report_writeLine((x))) #define LINE_10 "----------" @@ -240,11 +243,9 @@ void report_writeLogo() // Purpose: writes report header lines to report file. // { - char SEMVERSION[SEMVERSION_LEN]; - getSemVersion(SEMVERSION); - sprintf(Msg, \ - "\n EPA STORM WATER MANAGEMENT MODEL - VERSION 5.1 (Build %s)", SEMVERSION); + "\n OWA STORM WATER MANAGEMENT MODEL - VERSION v%s (Build %s)", + get_version(), get_buildid()); fprintf(Frpt.file, "%s", Msg); fprintf(Frpt.file, FMT09); diff --git a/src/solver/swmm5.c b/src/solver/swmm5.c index c97141383..bb5341676 100644 --- a/src/solver/swmm5.c +++ b/src/solver/swmm5.c @@ -112,6 +112,7 @@ #include "text.h" // listing of all text strings #define EXTERN // defined as 'extern' in headers.h #include "globals.h" // declaration of all global variables +#include "version.h" #include "swmm5.h" // declaration of exportable functions // callable from other programs @@ -606,10 +607,10 @@ int DLLEXPORT swmm_getMassBalErr(float* runoffErr, float* flowErr, //============================================================================= -int DLLEXPORT swmm_getVersion(void) +int DLLEXPORT swmm_getVersion(void) // // Input: none -// Output: returns SWMM engine version number +// Output: returns SWMM engine version number in legacy format // Purpose: retrieves version number of current SWMM engine which // uses a format of xyzzz where x = major version number, // y = minor version number, and zzz = build number. @@ -617,7 +618,7 @@ int DLLEXPORT swmm_getVersion(void) // NOTE: Each New Release should be updated in consts.h // THIS FUNCTION WILL EVENTUALLY BE DEPRECATED { - return VERSION; + return get_version_legacy(); } //============================================================================= @@ -894,16 +895,3 @@ int swmm_IsStartedFlag() // TRUE if a simulation has been started return IsStartedFlag; } - - -void getSemVersion(char* semver) -// -// Output: Returns Semantic Version -// Purpose: retrieves the current semantic version -// -// NOTE: Each New Release should be updated in consts.h -{ - snprintf(semver, SEMVERSION_LEN, "%s.%s.%s", - SEMVERSION_MAJOR, SEMVERSION_MINOR, SEMVERSION_PATCH); -} -//============================================================================= diff --git a/src/solver/toolkit.c b/src/solver/toolkit.c index b52d47ac1..54abfbe31 100644 --- a/src/solver/toolkit.c +++ b/src/solver/toolkit.c @@ -15,6 +15,7 @@ #include #include "headers.h" +#include "version.h" #include "shared/cstr_helper.h" #include "swmm5.h" @@ -39,29 +40,17 @@ double* newDoubleArray(int n); -// void DLLEXPORT swmm_getSemVersion(char* semver) -// // -// // Output: Returns Semantic Version -// // Purpose: retrieves the current semantic version -// // -// // NOTE: Each New Release should be updated in consts.h -// { -// getSemVersion(semver); -// } - -int DLLEXPORT swmm_getVersionInfo(char** major, char** minor, char** patch) +char* DLLEXPORT swmm_getSemVersion(char* semver) // -// Output: Returns Semantic Version Info +// Output: Returns Semantic Version // Purpose: retrieves the current semantic version // // NOTE: Each New Release should be updated in consts.h { - cstr_duplicate(major, SEMVERSION_MAJOR); - cstr_duplicate(minor, SEMVERSION_MINOR); - cstr_duplicate(patch, SEMVERSION_PATCH); - return 0; + return get_version(); } + //----------------------------------------------------------------------------- // Extended API Functions //----------------------------------------------------------------------------- From 366c3408d1f99beda2bb773dbeec951eb8235edf Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 4 Nov 2021 09:47:16 -0400 Subject: [PATCH 162/190] Simplifying --- CMakeLists.txt | 1 - extern/boost.cmake | 5 ++++- extern/version.cmake | 7 ++++--- extern/version.h.in | 7 +++++-- src/run/main.c | 3 ++- src/solver/consts.h | 8 -------- src/solver/include/toolkit.h | 26 ++++++++++++-------------- src/solver/report.c | 4 ++-- src/solver/toolkit.c | 14 +++++++++++--- 9 files changed, 40 insertions(+), 35 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a5c7af9f..5ea074f4e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,7 +48,6 @@ set(CONFIG_DIST "cmake") # Define build options option(BUILD_TESTS "Build component tests (requires Boost)" OFF) option(BUILD_DOCS "Build toolkit docs (requires Doxygen)" OFF) -option(GEN_VER_HEADER "Automatically update version header" OFF) # Add project subdirectories diff --git a/extern/boost.cmake b/extern/boost.cmake index 7b7e57144..34ffb6be6 100644 --- a/extern/boost.cmake +++ b/extern/boost.cmake @@ -22,7 +22,10 @@ endif() # ADD NEW BOOST LIBRARIES VERSIONS HERE -if (DEFINED ENV{BOOST_ROOT_1_72_0}) +if (DEFINED ENV{BOOST_ROOT_1_76_0}) + set(BOOST_ROOT $ENV{BOOST_ROOT_1_76_0}) + +elseif (DEFINED ENV{BOOST_ROOT_1_72_0}) set(BOOST_ROOT $ENV{BOOST_ROOT_1_72_0}) elseif(DEFINED ENV{BOOST_ROOT_1_67_0}) diff --git a/extern/version.cmake b/extern/version.cmake index 721f9e51a..202d59963 100644 --- a/extern/version.cmake +++ b/extern/version.cmake @@ -4,14 +4,15 @@ # Created: November 2, 2021 # Updated: # -# Author: Michael E. Tryby -# US EPA - ORD/CESER +# Author: see CONTRIBUTORS # # Usage: -# To overwrite version.h file with current info set GEN_VER_HEADER=ON. +# GEN_VER_HEADER=ON -- overwrites version.h file with current info # +option(GEN_VER_HEADER "Automatically update version header" ON) + if(GEN_VER_HEADER) # Get the latest commit hash for the working branch diff --git a/extern/version.h.in b/extern/version.h.in index fcdc70d5e..e32951202 100644 --- a/extern/version.h.in +++ b/extern/version.h.in @@ -15,6 +15,11 @@ +#include + +#define SHORT 8 + + #define ORGANIZATION "OWA" #define VERSION "@swmm-solver_VERSION@" @@ -27,11 +32,9 @@ #define BUILD_ID "@BUILD_ID@" -static inline char *get_version() { return VERSION; } static inline int get_version_legacy() { \ return VERSION_MAJOR * 10000 + VERSION_MINOR * 1000 + VERSION_PATCH; \ } -static inline char *get_buildid() { return BUILD_ID; } diff --git a/src/run/main.c b/src/run/main.c index 46ff5eca7..5032873fe 100644 --- a/src/run/main.c +++ b/src/run/main.c @@ -122,7 +122,8 @@ int main(int argc, char *argv[]) else if (strcmp(arg1, "--version") == 0 || strcmp(arg1, "-v") == 0) { printf("\nVersion:\n"); - printf("\tOWA-SWMM v%s\n\n", swmm_getSemVersion()); + printf("\tOWA-SWMM v%s (Build %.10s)\n\n", swmm_getSemVersion(), + swmm_getBuildId()); } else { diff --git a/src/solver/consts.h b/src/solver/consts.h index 1a689fb32..62bf52747 100644 --- a/src/solver/consts.h +++ b/src/solver/consts.h @@ -15,14 +15,6 @@ //------------------ // General Constants //------------------ - -// Update VERSION and SEMVERSION Simultaneously -//#define VERSION 51014 // Eventually will be deprecated. -//#define SEMVERSION_MAJOR "5" // Major Semantic Version -//#define SEMVERSION_MINOR "1" // Minor Semantic Version -//#define SEMVERSION_PATCH "014" // Patch Semantic Version -//#define SEMVERSION_LEN 20 // Version String Len - #define MAGICNUMBER 516114522 #define EOFMARK 0x1A // Use 0x04 for UNIX systems #define MAXTITLE 3 // Max. # title lines diff --git a/src/solver/include/toolkit.h b/src/solver/include/toolkit.h index 723218c18..fdea70b2a 100644 --- a/src/solver/include/toolkit.h +++ b/src/solver/include/toolkit.h @@ -32,20 +32,18 @@ extern "C" { #include "toolkit_structs.h" -// /** -// @brief Get full semantic version number -// @return sematic version (char array) -// */ -char * DLLEXPORT swmm_getSemVersion(); - -///** -// @brief Get full semantic version number info -// @param[out] major sematic version major number -// @param[out] minor sematic version minor number -// @param[out] patch sematic version patch number -// @return error code -//*/ -//int DLLEXPORT swmm_getVersionInfo(char **major, char **minor, char **patch); +/** + @brief Get full semantic version number + @return sematic version +*/ +char* DLLEXPORT swmm_getSemVersion(); + +/** + @brief Get Build Id + @return build id string +*/ +char* DLLEXPORT swmm_getBuildId(); + /** @brief Opens SWMM input file, reads in network data, runs, and closes diff --git a/src/solver/report.c b/src/solver/report.c index 3f5999fc3..6052ec1fe 100644 --- a/src/solver/report.c +++ b/src/solver/report.c @@ -244,8 +244,8 @@ void report_writeLogo() // { sprintf(Msg, \ - "\n OWA STORM WATER MANAGEMENT MODEL - VERSION v%s (Build %s)", - get_version(), get_buildid()); + "\n OWA STORM WATER MANAGEMENT MODEL - VERSION v%s (Build %.10s)", + VERSION, BUILD_ID); fprintf(Frpt.file, "%s", Msg); fprintf(Frpt.file, FMT09); diff --git a/src/solver/toolkit.c b/src/solver/toolkit.c index 54abfbe31..9ca61a977 100644 --- a/src/solver/toolkit.c +++ b/src/solver/toolkit.c @@ -40,17 +40,25 @@ double* newDoubleArray(int n); -char* DLLEXPORT swmm_getSemVersion(char* semver) +char* DLLEXPORT swmm_getSemVersion() // // Output: Returns Semantic Version // Purpose: retrieves the current semantic version // -// NOTE: Each New Release should be updated in consts.h { - return get_version(); + return VERSION; } +char* DLLEXPORT swmm_getBuildId() +// +// Output: Returns short Build Id +// Purpose: retrieves the current build id +// +{ + return BUILD_ID; +} + //----------------------------------------------------------------------------- // Extended API Functions //----------------------------------------------------------------------------- From 1856664d71db0a23529de49bde599004a43daf1a Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 4 Nov 2021 11:05:22 -0400 Subject: [PATCH 163/190] Cleaning up --- CMakeLists.txt | 2 +- CONTRIBUTORS | 2 +- extern/version.h.in | 12 ++++-------- src/run/main.c | 7 +++++-- src/solver/include/swmm5.h | 2 +- src/solver/include/toolkit.h | 2 +- src/solver/swmm5.c | 2 -- src/solver/text.h | 10 +++++----- src/solver/toolkit.c | 2 +- 9 files changed, 19 insertions(+), 22 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ea074f4e..d3255fbac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -85,7 +85,7 @@ install( # Configure CPack driven installer package set(CPACK_GENERATOR "ZIP;TGZ") -set(CPACK_PACKAGE_VENDOR "US_EPA") +set(CPACK_PACKAGE_VENDOR "OWA") set(CPACK_ARCHIVE_FILE_NAME "swmm") include(CPack) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 7d55fc639..ac4880d3d 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -92,7 +92,7 @@ D: Bug fix, testing N: Michael Tryby E: tryby.michael@epa.gov L: Public Domain -D: Mentor, output file API, testing framework, development operations, Python wrapper, review +D: Mentor, output file API, testing framework, CLI, development operations, Python wrapper, review N: Jia Xin Wu E: wuu.jennifer@gmail.com diff --git a/extern/version.h.in b/extern/version.h.in index e32951202..db3e47f7f 100644 --- a/extern/version.h.in +++ b/extern/version.h.in @@ -6,7 +6,7 @@ * Author: see CONTRIBUTORS * * Note: - * The build process automatically generates this file. Do not edit. + * The cmake build process automatically generates this file. Do not edit. */ @@ -15,12 +15,8 @@ -#include - -#define SHORT 8 - - -#define ORGANIZATION "OWA" +#define ORGANIZATION "Open Water Analytics" +#define ORG "OWA" #define VERSION "@swmm-solver_VERSION@" #define VERSION_MAJOR @swmm-solver_VERSION_MAJOR@ @@ -32,7 +28,7 @@ #define BUILD_ID "@BUILD_ID@" -static inline int get_version_legacy() { \ +static inline int get_version_legacy() { \ return VERSION_MAJOR * 10000 + VERSION_MINOR * 1000 + VERSION_PATCH; \ } diff --git a/src/run/main.c b/src/run/main.c index 5032873fe..b4aeaa5d0 100644 --- a/src/run/main.c +++ b/src/run/main.c @@ -25,6 +25,10 @@ #define BAR_LEN 50l #define MSG_LEN 84 +#define FMT_USAGE \ +"\nUsage:\n \ + \trunswmm \n\n" + #define FMT_HELP \ "\n\nOWA Stormwater Management Model (SWMM5) Help\n\n \ Commands:\n \ @@ -133,8 +137,7 @@ int main(int argc, char *argv[]) } else { - printf("\nUsage:\n"); - printf("\trunswmm \n\n"); + printf(FMT_USAGE); } return 0; diff --git a/src/solver/include/swmm5.h b/src/solver/include/swmm5.h index a3875927f..7753a0553 100644 --- a/src/solver/include/swmm5.h +++ b/src/solver/include/swmm5.h @@ -118,7 +118,7 @@ int DLLEXPORT swmm_close(void); /** @brief Get SWMM version number - @return Version + @return Version as integer */ int DLLEXPORT swmm_getVersion(void); diff --git a/src/solver/include/toolkit.h b/src/solver/include/toolkit.h index fdea70b2a..a89ec92ad 100644 --- a/src/solver/include/toolkit.h +++ b/src/solver/include/toolkit.h @@ -34,7 +34,7 @@ extern "C" { /** @brief Get full semantic version number - @return sematic version + @return sematic version string */ char* DLLEXPORT swmm_getSemVersion(); diff --git a/src/solver/swmm5.c b/src/solver/swmm5.c index bb5341676..517436d62 100644 --- a/src/solver/swmm5.c +++ b/src/solver/swmm5.c @@ -615,8 +615,6 @@ int DLLEXPORT swmm_getVersion(void) // uses a format of xyzzz where x = major version number, // y = minor version number, and zzz = build number. // -// NOTE: Each New Release should be updated in consts.h -// THIS FUNCTION WILL EVENTUALLY BE DEPRECATED { return get_version_legacy(); } diff --git a/src/solver/text.h b/src/solver/text.h index 50532139c..f8224a77c 100644 --- a/src/solver/text.h +++ b/src/solver/text.h @@ -20,18 +20,18 @@ // Text strings //----------------------------------------------------------------------------- -#include "consts.h" +//#include "consts.h" -#define FMT01 \ - "\tswmm5 \n" +//#define FMT01 \ +// "\tswmm5 \n" #define FMT03 " There are errors.\n" #define FMT04 " There are warnings.\n" #define FMT05 "\n" #define FMT06 "\n o Retrieving project data" #define FMT07 "\n o Writing output report" -#define FMT08 \ - "\n EPA STORM WATER MANAGEMENT MODEL - VERSION 5.1 (Build "SEMVERSION_MAJOR"."SEMVERSION_MINOR"."SEMVERSION_PATCH")" //(5.1.014) +//#define FMT08 \ +// "\n EPA STORM WATER MANAGEMENT MODEL - VERSION 5.1 (Build "SEMVERSION_MAJOR"."SEMVERSION_MINOR"."SEMVERSION_PATCH")" //(5.1.014) #define FMT09 \ "\n --------------------------------------------------------------" #define FMT10 "\n" diff --git a/src/solver/toolkit.c b/src/solver/toolkit.c index 9ca61a977..d8a63c7cc 100644 --- a/src/solver/toolkit.c +++ b/src/solver/toolkit.c @@ -52,7 +52,7 @@ char* DLLEXPORT swmm_getSemVersion() char* DLLEXPORT swmm_getBuildId() // -// Output: Returns short Build Id +// Output: Returns Build Id // Purpose: retrieves the current build id // { From 57b1742c5e3f45acfb09a5486b8c82b15ab6b82f Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 4 Nov 2021 11:19:07 -0400 Subject: [PATCH 164/190] Fixing MSVC syntax error --- src/solver/include/toolkit.h | 4 ++-- src/solver/toolkit.c | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/solver/include/toolkit.h b/src/solver/include/toolkit.h index a89ec92ad..19e312621 100644 --- a/src/solver/include/toolkit.h +++ b/src/solver/include/toolkit.h @@ -36,13 +36,13 @@ extern "C" { @brief Get full semantic version number @return sematic version string */ -char* DLLEXPORT swmm_getSemVersion(); +char DLLEXPORT *swmm_getSemVersion(); /** @brief Get Build Id @return build id string */ -char* DLLEXPORT swmm_getBuildId(); +char DLLEXPORT *swmm_getBuildId(); /** diff --git a/src/solver/toolkit.c b/src/solver/toolkit.c index d8a63c7cc..9d9c1e266 100644 --- a/src/solver/toolkit.c +++ b/src/solver/toolkit.c @@ -36,11 +36,11 @@ int stats_getSubcatchStat(int index, TSubcatchStats **subcatchStats); // Utilty Function Declarations -double* newDoubleArray(int n); +double *newDoubleArray(int n); -char* DLLEXPORT swmm_getSemVersion() +char DLLEXPORT *swmm_getSemVersion() // // Output: Returns Semantic Version // Purpose: retrieves the current semantic version @@ -50,7 +50,7 @@ char* DLLEXPORT swmm_getSemVersion() } -char* DLLEXPORT swmm_getBuildId() +char DLLEXPORT *swmm_getBuildId() // // Output: Returns Build Id // Purpose: retrieves the current build id From df8a378a19ad137e78a151df7bd936f017d64d75 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 4 Nov 2021 15:59:41 -0400 Subject: [PATCH 165/190] Fix MSVC syntax error --- src/outfile/include/swmm_output.h | 6 ++ src/run/main.c | 4 +- src/solver/CMakeLists.txt | 12 +++ src/solver/include/toolkit.h | 173 +++++++++++++++--------------- src/solver/toolkit.c | 165 ++++++++++++++-------------- 5 files changed, 183 insertions(+), 177 deletions(-) diff --git a/src/outfile/include/swmm_output.h b/src/outfile/include/swmm_output.h index 3c57ad29e..fbaa394e9 100644 --- a/src/outfile/include/swmm_output.h +++ b/src/outfile/include/swmm_output.h @@ -12,6 +12,8 @@ #ifndef SWMM_OUTPUT_H_ #define SWMM_OUTPUT_H_ + + #define MAXFILENAME 259 // Max characters in file path #define MAXELENAME 31 // Max characters in element name @@ -26,6 +28,7 @@ typedef struct Handle *SMO_Handle; extern "C" { #endif + int EXPORT_OUT_API SMO_init(SMO_Handle *p_handle); int EXPORT_OUT_API SMO_close(SMO_Handle p_handle); int EXPORT_OUT_API SMO_open(SMO_Handle p_handle, const char *path); @@ -56,8 +59,11 @@ void EXPORT_OUT_API SMO_freeMemory(void *array); void EXPORT_OUT_API SMO_clearError(SMO_Handle p_handle_in); int EXPORT_OUT_API SMO_checkError(SMO_Handle p_handle_in, char **msg_buffer); + #ifdef __cplusplus } #endif + + #endif /* SWMM_OUTPUT_H_ */ diff --git a/src/run/main.c b/src/run/main.c index b4aeaa5d0..dee6418d2 100644 --- a/src/run/main.c +++ b/src/run/main.c @@ -4,9 +4,7 @@ * Created on: October 9, 2020 * Updated on: * - * Author: Michael E. Tryby - * US EPA - ORD/CESER - * + * Author: See CONTRIBUTORS */ #include diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index 0e5e1f222..db0d8636f 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -90,6 +90,18 @@ target_include_directories(swmm5 ${CMAKE_CURRENT_SOURCE_DIR}/.. ) +include(GenerateExportHeader) +generate_export_header(swmm5 + BASE_NAME toolkit + EXPORT_MACRO_NAME EXPORT_TOOLKIT + EXPORT_FILE_NAME toolkit_export.h + STATIC_DEFINE SHARED_EXPORTS_BUILT_AS_STATIC +) + +file(COPY ${CMAKE_CURRENT_BINARY_DIR}/toolkit_export.h + DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/include +) + set_target_properties(swmm5 PROPERTIES diff --git a/src/solver/include/toolkit.h b/src/solver/include/toolkit.h index 19e312621..7a112df9b 100644 --- a/src/solver/include/toolkit.h +++ b/src/solver/include/toolkit.h @@ -1,48 +1,45 @@ -/** @file toolkit.h - @see http://github.com/openwateranalytics/stormwater-management-model +/* + * toolkit.h - OWA SWMM Toolkit API + * + * Created on: Aug 30, 2016 + * Updated on: + * + * Author: See CONTRIBUTORS + * + * Note: + * Originally developed by Bryant McDonnell + */ - toolkit.h - @brief Exportable Functions for Toolkit API. - @date 08/30/2016 (First Contribution) - @authors B. McDonnell (EmNet LLC), OpenWaterAnalytics members: see AUTHORS. +#ifndef TOOLKIT_H +#define TOOLKIT_H -*/ -#ifndef TOOLKITAPI_H -#define TOOLKITAPI_H - -#ifdef WINDOWS -#ifdef __MINGW32__ -#define DLLEXPORT __declspec(dllexport) __cdecl -#else -#define DLLEXPORT __declspec(dllexport) __stdcall -#endif -#else -#define DLLEXPORT -#endif - -#ifdef __cplusplus -extern "C" { -#endif #define _CRT_SECURE_NO_DEPRECATE - #include "toolkit_enums.h" #include "toolkit_structs.h" +#include "toolkit_export.h" + + +#ifdef __cplusplus +extern "C" { +#endif /** @brief Get full semantic version number - @return sematic version string + @param[out] Pointer to version number string + @returns error code */ -char DLLEXPORT *swmm_getSemVersion(); +EXPORT_TOOLKIT char *swmm_getSemVersion(); /** @brief Get Build Id - @return build id string + @param[out] Pointer to build id string + @returns error code */ -char DLLEXPORT *swmm_getBuildId(); +EXPORT_TOOLKIT char *swmm_getBuildId(); /** @@ -53,7 +50,7 @@ char DLLEXPORT *swmm_getBuildId(); @param pointer to callback function (for printing progress) @return error code */ -int DLLEXPORT swmm_run_cb(const char *f1, const char *f2, const char *f3, +EXPORT_TOOLKIT int swmm_run_cb(const char *f1, const char *f2, const char *f3, void (*callback) (double *)); /** @@ -62,7 +59,7 @@ int DLLEXPORT swmm_run_cb(const char *f1, const char *f2, const char *f3, @param[out] errorMsg The error string represented by the code @return Error code */ -int DLLEXPORT swmm_getAPIError(int errorCode, char **errorMsg); +EXPORT_TOOLKIT int swmm_getAPIError(int errorCode, char **errorMsg); /** @brief Finds the index of an object given its ID. @@ -71,7 +68,7 @@ int DLLEXPORT swmm_getAPIError(int errorCode, char **errorMsg); @param[out] index The objects index @return Error code */ -int DLLEXPORT swmm_project_findObject(SM_ObjectType type, char *id, int *index); +EXPORT_TOOLKIT int swmm_project_findObject(SM_ObjectType type, char *id, int *index); /** @brief Gets Simulation Unit @@ -79,7 +76,7 @@ int DLLEXPORT swmm_project_findObject(SM_ObjectType type, char *id, int *index); @param[out] value Option value @return Error code */ -int DLLEXPORT swmm_getSimulationUnit(SM_Units type, int *value); +EXPORT_TOOLKIT int swmm_getSimulationUnit(SM_Units type, int *value); /** @brief Gets Simulation Analysis Setting @@ -87,7 +84,7 @@ int DLLEXPORT swmm_getSimulationUnit(SM_Units type, int *value); @param[out] value Option value @return Error code */ -int DLLEXPORT swmm_getSimulationAnalysisSetting(SM_SimOption type, int *value); +EXPORT_TOOLKIT int swmm_getSimulationAnalysisSetting(SM_SimOption type, int *value); /** @brief Gets Simulation Analysis Setting @@ -95,7 +92,7 @@ int DLLEXPORT swmm_getSimulationAnalysisSetting(SM_SimOption type, int *value); @param[out] value Option value @return Error code */ -int DLLEXPORT swmm_getSimulationParam(SM_SimSetting type, double *value); +EXPORT_TOOLKIT int swmm_getSimulationParam(SM_SimSetting type, double *value); /** @brief Gets Object Count @@ -103,7 +100,7 @@ int DLLEXPORT swmm_getSimulationParam(SM_SimSetting type, double *value); @param[out] count Option value @return Error code */ -int DLLEXPORT swmm_countObjects(SM_ObjectType type, int *count); +EXPORT_TOOLKIT int swmm_countObjects(SM_ObjectType type, int *count); /** @brief Gets Object ID @@ -112,7 +109,7 @@ int DLLEXPORT swmm_countObjects(SM_ObjectType type, int *count); @param[out] id The string ID of object. @return Error code */ -int DLLEXPORT swmm_getObjectId(SM_ObjectType type, int index, char **id); +EXPORT_TOOLKIT int swmm_getObjectId(SM_ObjectType type, int index, char **id); /** @brief Gets Object Index @@ -121,7 +118,7 @@ int DLLEXPORT swmm_getObjectId(SM_ObjectType type, int index, char **id); @param[out] index of the Object @return errcode Error Code */ -int DLLEXPORT swmm_getObjectIndex(SM_ObjectType type, char *id, int *index); +EXPORT_TOOLKIT int swmm_getObjectIndex(SM_ObjectType type, char *id, int *index); /** @brief Get the type of node with specified index. @@ -130,7 +127,7 @@ int DLLEXPORT swmm_getObjectIndex(SM_ObjectType type, char *id, int *index); id must be pre-allocated by the caller. @return Error code */ -int DLLEXPORT swmm_getNodeType(int index, SM_NodeType *Ntype); +EXPORT_TOOLKIT int swmm_getNodeType(int index, SM_NodeType *Ntype); /** @brief Get the type of link with specified index. @@ -138,7 +135,7 @@ int DLLEXPORT swmm_getNodeType(int index, SM_NodeType *Ntype); @param[out] Ltype The type code for the link (@ref SM_LinkType). @return Error code */ -int DLLEXPORT swmm_getLinkType(int index, SM_LinkType *Ltype); +EXPORT_TOOLKIT int swmm_getLinkType(int index, SM_LinkType *Ltype); /** @brief Get the link Connection Node Indeces. If the conduit has a @@ -149,7 +146,7 @@ int DLLEXPORT swmm_getLinkType(int index, SM_LinkType *Ltype); @param[out] Node2 The downstream node index. @return Error code */ -int DLLEXPORT swmm_getLinkConnections(int index, int *node1, int *node2); +EXPORT_TOOLKIT int swmm_getLinkConnections(int index, int *node1, int *node2); /** @brief Get the link flow direction (see @ref swmm_getLinkType() for notes. @@ -157,7 +154,7 @@ int DLLEXPORT swmm_getLinkConnections(int index, int *node1, int *node2); @param[out] value The link flow direction. @return Error code */ -int DLLEXPORT swmm_getLinkDirection(int index, signed char *value); +EXPORT_TOOLKIT int swmm_getLinkDirection(int index, signed char *value); /** @brief Get the Subcatchment connection. Subcatchments can load to a @@ -167,7 +164,7 @@ int DLLEXPORT swmm_getLinkDirection(int index, signed char *value); @param[out] out_index The object index @return Error code */ -int DLLEXPORT swmm_getSubcatchOutConnection(int index, SM_ObjectType *type, int *out_index); +EXPORT_TOOLKIT int swmm_getSubcatchOutConnection(int index, SM_ObjectType *type, int *out_index); /** @brief Get the number of lid units on a subcatchment. @@ -175,7 +172,7 @@ int DLLEXPORT swmm_getSubcatchOutConnection(int index, SM_ObjectType *type, int @param[out] value The number of lid units on a subcatchment @return Error code */ -int DLLEXPORT swmm_getLidUCount(int index, int *value); +EXPORT_TOOLKIT int swmm_getLidUCount(int index, int *value); /** @brief Get a property value for a specified lid unit on a specified subcatchment @@ -185,7 +182,7 @@ int DLLEXPORT swmm_getLidUCount(int index, int *value); @param[out] value The value of the lid unit's property @return Error code */ -int DLLEXPORT swmm_getLidUParam(int index, int lidIndex, SM_LidUProperty param, double *value); +EXPORT_TOOLKIT int swmm_getLidUParam(int index, int lidIndex, SM_LidUProperty param, double *value); /** @brief Set a property value for a specified lid unit on a specified subcatchment @@ -195,7 +192,7 @@ int DLLEXPORT swmm_getLidUParam(int index, int lidIndex, SM_LidUProperty param, @param value The new value of the lid unit's property @return Error code */ -int DLLEXPORT swmm_setLidUParam(int index, int lidIndex, SM_LidUProperty param, double value); +EXPORT_TOOLKIT int swmm_setLidUParam(int index, int lidIndex, SM_LidUProperty param, double value); /** @brief Get the lid option for a specified lid unit on a specified subcatchment @@ -205,7 +202,7 @@ int DLLEXPORT swmm_setLidUParam(int index, int lidIndex, SM_LidUProperty param, @param[out] value The value of the option for the lid unit @return Error code */ -int DLLEXPORT swmm_getLidUOption(int index, int lidIndex, SM_LidUOptions param, int *value); +EXPORT_TOOLKIT int swmm_getLidUOption(int index, int lidIndex, SM_LidUOptions param, int *value); /** @brief Set the lid option for a specified lid unit on a specified subcatchment @@ -215,7 +212,7 @@ int DLLEXPORT swmm_getLidUOption(int index, int lidIndex, SM_LidUOptions param, @param value The new value of the option for the lid unit @return Error code */ -int DLLEXPORT swmm_setLidUOption(int index, int lidIndex, SM_LidUOptions param, int value); +EXPORT_TOOLKIT int swmm_setLidUOption(int index, int lidIndex, SM_LidUOptions param, int value); /** @brief Get the lid control surface immediate overflow condition @@ -223,7 +220,7 @@ int DLLEXPORT swmm_setLidUOption(int index, int lidIndex, SM_LidUOptions param, @param[out] condition The value of surface immediate overflow condition @return Error code */ -int DLLEXPORT swmm_getLidCOverflow(int lidControlIndex, int *condition); +EXPORT_TOOLKIT int swmm_getLidCOverflow(int lidControlIndex, int *condition); /** @brief Get a property value for specified lid control @@ -233,7 +230,7 @@ int DLLEXPORT swmm_getLidCOverflow(int lidControlIndex, int *condition); @param[out] value The value of lid control's property @return Error code */ -int DLLEXPORT swmm_getLidCParam(int lidControlIndex, SM_LidLayer layerIndex, SM_LidLayerProperty param, double *value); +EXPORT_TOOLKIT int swmm_getLidCParam(int lidControlIndex, SM_LidLayer layerIndex, SM_LidLayerProperty param, double *value); /** @brief Set a property value for specified lid control @@ -243,7 +240,7 @@ int DLLEXPORT swmm_getLidCParam(int lidControlIndex, SM_LidLayer layerIndex, SM_ @param value The new value for the lid control's property @return Error code */ -int DLLEXPORT swmm_setLidCParam(int lidControlIndex, SM_LidLayer layerIndex, SM_LidLayerProperty param, double value); +EXPORT_TOOLKIT int swmm_setLidCParam(int lidControlIndex, SM_LidLayer layerIndex, SM_LidLayerProperty param, double value); /** @brief Get the lid unit water balance simulated value at current time @@ -254,7 +251,7 @@ int DLLEXPORT swmm_setLidCParam(int lidControlIndex, SM_LidLayer layerIndex, SM_ @param[out] result The result for the specified lid unit @return Error code */ -int DLLEXPORT swmm_getLidUFluxRates(int index, int lidIndex, SM_LidLayer layerIndex, double *result); +EXPORT_TOOLKIT int swmm_getLidUFluxRates(int index, int lidIndex, SM_LidLayer layerIndex, double *result); /** @brief Get the lid group of a specified subcatchment result at current time @@ -263,7 +260,7 @@ int DLLEXPORT swmm_getLidUFluxRates(int index, int lidIndex, SM_LidLayer layerIn @param[out] result The result for the specified lid group @return Error code */ -int DLLEXPORT swmm_getLidGResult(int index, SM_LidResult type, double *result); +EXPORT_TOOLKIT int swmm_getLidGResult(int index, SM_LidResult type, double *result); /** @brief Get the lid unit of a specified subcatchment result at current time @@ -273,7 +270,7 @@ int DLLEXPORT swmm_getLidGResult(int index, SM_LidResult type, double *result); @param[out] result The result for the specified lid unit @return Error code */ -int DLLEXPORT swmm_getLidUResult(int index, int lidIndex, SM_LidResult type, double *result); +EXPORT_TOOLKIT int swmm_getLidUResult(int index, int lidIndex, SM_LidResult type, double *result); /** @brief Get a property value for specified node. @@ -282,7 +279,7 @@ int DLLEXPORT swmm_getLidUResult(int index, int lidIndex, SM_LidResult type, dou @param[out] value The value of the node's property @return Error code */ -int DLLEXPORT swmm_getNodeParam(int index, SM_NodeProperty param, double *value); +EXPORT_TOOLKIT int swmm_getNodeParam(int index, SM_NodeProperty param, double *value); /** @brief Set a property value for specified node. @@ -291,7 +288,7 @@ int DLLEXPORT swmm_getNodeParam(int index, SM_NodeProperty param, double *value) @param value The new value of the node's property @return Error code */ -int DLLEXPORT swmm_setNodeParam(int index, SM_NodeProperty param, double value); +EXPORT_TOOLKIT int swmm_setNodeParam(int index, SM_NodeProperty param, double value); /** @brief Get a property value for specified link. @@ -300,7 +297,7 @@ int DLLEXPORT swmm_setNodeParam(int index, SM_NodeProperty param, double value); @param[out] value The value of the link's property @return Error code */ -int DLLEXPORT swmm_getLinkParam(int index, SM_LinkProperty param, double *value); +EXPORT_TOOLKIT int swmm_getLinkParam(int index, SM_LinkProperty param, double *value); /** @brief Set a property value for specified link. @@ -309,7 +306,7 @@ int DLLEXPORT swmm_getLinkParam(int index, SM_LinkProperty param, double *value) @param value The new value of the link's property @return Error code */ -int DLLEXPORT swmm_setLinkParam(int index, SM_LinkProperty param, double value); +EXPORT_TOOLKIT int swmm_setLinkParam(int index, SM_LinkProperty param, double value); /** @brief Get a property value for specified subcatchment. @@ -318,7 +315,7 @@ int DLLEXPORT swmm_setLinkParam(int index, SM_LinkProperty param, double value); @param[out] value The value of the subcatchment's property @return Error code */ -int DLLEXPORT swmm_getSubcatchParam(int index, SM_SubcProperty param, double *value); +EXPORT_TOOLKIT int swmm_getSubcatchParam(int index, SM_SubcProperty param, double *value); /** @brief Set a property value for specified subcatchment. @@ -327,7 +324,7 @@ int DLLEXPORT swmm_getSubcatchParam(int index, SM_SubcProperty param, double *va @param value The new value of the subcatchment's property @return Error code */ -int DLLEXPORT swmm_setSubcatchParam(int index, SM_SubcProperty param, double value); +EXPORT_TOOLKIT int swmm_setSubcatchParam(int index, SM_SubcProperty param, double value); /** @brief Get the current simulation datetime information. @@ -340,7 +337,7 @@ int DLLEXPORT swmm_setSubcatchParam(int index, SM_SubcProperty param, double val @param[out] second The seconds @return Error code */ -int DLLEXPORT swmm_getSimulationDateTime(SM_TimePropety type, int *year, int *month, +EXPORT_TOOLKIT int swmm_getSimulationDateTime(SM_TimePropety type, int *year, int *month, int *day, int *hour, int *minute, int *second); @@ -355,7 +352,7 @@ int DLLEXPORT swmm_getSimulationDateTime(SM_TimePropety type, int *year, int *mo @param second The second @return Error code */ -int DLLEXPORT swmm_setSimulationDateTime(SM_TimePropety type, int year, int month, +EXPORT_TOOLKIT int swmm_setSimulationDateTime(SM_TimePropety type, int year, int month, int day, int hour, int minute, int second); @@ -369,7 +366,7 @@ int DLLEXPORT swmm_setSimulationDateTime(SM_TimePropety type, int year, int mont @param[out] second The seconds @return Error code */ -int DLLEXPORT swmm_getCurrentDateTime(int *year, int *month, int *day, +EXPORT_TOOLKIT int swmm_getCurrentDateTime(int *year, int *month, int *day, int *hour, int *minute, int *second); /** @@ -379,7 +376,7 @@ int DLLEXPORT swmm_getCurrentDateTime(int *year, int *month, int *day, @param[out] result The result of the node's property @return Error code */ -int DLLEXPORT swmm_getNodeResult(int index, SM_NodeResult type, double *result); +EXPORT_TOOLKIT int swmm_getNodeResult(int index, SM_NodeResult type, double *result); /** @brief Gets pollutant values for a specified node. @@ -388,7 +385,7 @@ int DLLEXPORT swmm_getNodeResult(int index, SM_NodeResult type, double *result); @param[out] PollutArray result array @return Error code */ -int DLLEXPORT swmm_getNodePollut(int index, SM_NodePollut type, double **pollutArray, int *length); +EXPORT_TOOLKIT int swmm_getNodePollut(int index, SM_NodePollut type, double **pollutArray, int *length); /** @brief Sets pollutant values for a specified node. @@ -397,7 +394,7 @@ int DLLEXPORT swmm_getNodePollut(int index, SM_NodePollut type, double **pollutA @param pollutant_value Pollutant value to set @return Error code */ -int DLLEXPORT swmm_setNodePollut(int index, int pollutant_index, double pollutant_value); +EXPORT_TOOLKIT int swmm_setNodePollut(int index, int pollutant_index, double pollutant_value); /** @brief Sets pollutant values for a specified link. @@ -407,7 +404,7 @@ int DLLEXPORT swmm_setNodePollut(int index, int pollutant_index, double pollutan @return Error code */ -int DLLEXPORT swmm_setLinkPollut(int index, int type, int pollutant_index, double pollutant_value); +EXPORT_TOOLKIT int swmm_setLinkPollut(int index, int type, int pollutant_index, double pollutant_value); /** @brief Get a result value for specified link. @@ -416,7 +413,7 @@ int DLLEXPORT swmm_setLinkPollut(int index, int type, int pollutant_index, doubl @param[out] result The result of the link's property @return Error code */ -int DLLEXPORT swmm_getLinkResult(int index, SM_LinkResult type, double *result); +EXPORT_TOOLKIT int swmm_getLinkResult(int index, SM_LinkResult type, double *result); /** @brief Gets pollutant values for a specified link. @@ -425,7 +422,7 @@ int DLLEXPORT swmm_getLinkResult(int index, SM_LinkResult type, double *result); @param[out] PollutArray result array @return Error code */ -int DLLEXPORT swmm_getLinkPollut(int index, SM_LinkPollut type, double **pollutArray, int *length); +EXPORT_TOOLKIT int swmm_getLinkPollut(int index, SM_LinkPollut type, double **pollutArray, int *length); /** @brief Get a result value for specified subcatchment. @@ -434,7 +431,7 @@ int DLLEXPORT swmm_getLinkPollut(int index, SM_LinkPollut type, double **pollutA @param[out] result The result of the subcatchment's property @return Error code */ -int DLLEXPORT swmm_getSubcatchResult(int index, SM_SubcResult type, double *result); +EXPORT_TOOLKIT int swmm_getSubcatchResult(int index, SM_SubcResult type, double *result); /** @brief Gets pollutant values for a specified subcatchment. @@ -443,7 +440,7 @@ int DLLEXPORT swmm_getSubcatchResult(int index, SM_SubcResult type, double *resu @param[out] PollutArray result array @return Error code */ -int DLLEXPORT swmm_getSubcatchPollut(int index, SM_SubcPollut type, double **pollutArray, int *length); +EXPORT_TOOLKIT int swmm_getSubcatchPollut(int index, SM_SubcPollut type, double **pollutArray, int *length); /** @brief Get precipitation rates for a gage. @@ -452,7 +449,7 @@ int DLLEXPORT swmm_getSubcatchPollut(int index, SM_SubcPollut type, double **pol @param[out] GageArray precipitation rate @return Error code */ -int DLLEXPORT swmm_getGagePrecip(int index, SM_GagePrecip type, double *result); +EXPORT_TOOLKIT int swmm_getGagePrecip(int index, SM_GagePrecip type, double *result); /** @brief Get a node statistics. @@ -461,7 +458,7 @@ int DLLEXPORT swmm_getGagePrecip(int index, SM_GagePrecip type, double *result); pre-allocated by the caller. @return Error code */ -int DLLEXPORT swmm_getNodeStats(int index, SM_NodeStats *nodeStats); +EXPORT_TOOLKIT int swmm_getNodeStats(int index, SM_NodeStats *nodeStats); /** @brief Get the cumulative inflow for a node. @@ -469,7 +466,7 @@ int DLLEXPORT swmm_getNodeStats(int index, SM_NodeStats *nodeStats); @param[out] value The total inflow. @return Error code */ -int DLLEXPORT swmm_getNodeTotalInflow(int index, double *value); +EXPORT_TOOLKIT int swmm_getNodeTotalInflow(int index, double *value); /** @brief Get a storage statistics. @@ -478,7 +475,7 @@ int DLLEXPORT swmm_getNodeTotalInflow(int index, double *value); pre-allocated by the caller. @return Error code */ -int DLLEXPORT swmm_getStorageStats(int index, SM_StorageStats *storageStats); +EXPORT_TOOLKIT int swmm_getStorageStats(int index, SM_StorageStats *storageStats); /** @brief Get outfall statistics. @@ -489,7 +486,7 @@ int DLLEXPORT swmm_getStorageStats(int index, SM_StorageStats *storageStats); pollutants array. @return Error code */ -int DLLEXPORT swmm_getOutfallStats(int index, SM_OutfallStats *outfallStats); +EXPORT_TOOLKIT int swmm_getOutfallStats(int index, SM_OutfallStats *outfallStats); // /** // @brief Free outfall statistics structure. @@ -497,7 +494,7 @@ int DLLEXPORT swmm_getOutfallStats(int index, SM_OutfallStats *outfallStats); // pollutants array. // @return Error code // */ -// void DLLEXPORT swmm_freeOutfallStats(SM_OutfallStats *outfallStats); +// void EXPORT_TOOLKIT_API swmm_freeOutfallStats(SM_OutfallStats *outfallStats); /** @brief Get link statistics. @@ -506,7 +503,7 @@ int DLLEXPORT swmm_getOutfallStats(int index, SM_OutfallStats *outfallStats); pre-allocated by the caller. @return Error code */ -int DLLEXPORT swmm_getLinkStats(int index, SM_LinkStats *linkStats); +EXPORT_TOOLKIT int swmm_getLinkStats(int index, SM_LinkStats *linkStats); /** @brief Get pump statistics. @@ -515,7 +512,7 @@ int DLLEXPORT swmm_getLinkStats(int index, SM_LinkStats *linkStats); pre-allocated by the caller. @return Error code */ -int DLLEXPORT swmm_getPumpStats(int index, SM_PumpStats *pumpStats); +EXPORT_TOOLKIT int swmm_getPumpStats(int index, SM_PumpStats *pumpStats); /** @brief Get subcatchment statistics. @@ -526,7 +523,7 @@ int DLLEXPORT swmm_getPumpStats(int index, SM_PumpStats *pumpStats); pollutants array. @return Error code */ -int DLLEXPORT swmm_getSubcatchStats(int index, SM_SubcatchStats *subcatchStats); +EXPORT_TOOLKIT int swmm_getSubcatchStats(int index, SM_SubcatchStats *subcatchStats); /** @brief Get system routing totals. @@ -534,7 +531,7 @@ int DLLEXPORT swmm_getSubcatchStats(int index, SM_SubcatchStats *subcatchStats); pre-allocated by the caller. @return Error code */ -int DLLEXPORT swmm_getSystemRoutingTotals(SM_RoutingTotals *routingTotals); +EXPORT_TOOLKIT int swmm_getSystemRoutingTotals(SM_RoutingTotals *routingTotals); /** @brief Get system runoff totals. @@ -542,7 +539,7 @@ int DLLEXPORT swmm_getSystemRoutingTotals(SM_RoutingTotals *routingTotals); pre-allocated by the caller. @return Error code */ -int DLLEXPORT swmm_getSystemRunoffTotals(SM_RunoffTotals *runoffTotals); +EXPORT_TOOLKIT int swmm_getSystemRunoffTotals(SM_RunoffTotals *runoffTotals); /** @brief Set a link setting (pump, orifice, or weir). Setting for an orifice @@ -552,7 +549,7 @@ int DLLEXPORT swmm_getSystemRunoffTotals(SM_RunoffTotals *runoffTotals); @param setting The new setting for the link. @return Error code */ -int DLLEXPORT swmm_setLinkSetting(int index, double setting); +EXPORT_TOOLKIT int swmm_setLinkSetting(int index, double setting); /** @brief Set an inflow rate to a node. The inflow rate is held constant @@ -561,7 +558,7 @@ int DLLEXPORT swmm_setLinkSetting(int index, double setting); @param flowrate The new node inflow rate. @return Error code */ -int DLLEXPORT swmm_setNodeInflow(int index, double flowrate); +EXPORT_TOOLKIT int swmm_setNodeInflow(int index, double flowrate); /** @brief Set outfall stage. @@ -569,7 +566,7 @@ int DLLEXPORT swmm_setNodeInflow(int index, double flowrate); @param stage The outfall node stage (head). @return Error code */ -int DLLEXPORT swmm_setOutfallStage(int index, double stage); +EXPORT_TOOLKIT int swmm_setOutfallStage(int index, double stage); /** @brief Set a total precipitation intensity to the gage. @@ -577,18 +574,20 @@ int DLLEXPORT swmm_setOutfallStage(int index, double stage); @param total_precip The new total precipitation intensity. @return Error code */ -int DLLEXPORT swmm_setGagePrecip(int index, double total_precip); +EXPORT_TOOLKIT int swmm_setGagePrecip(int index, double total_precip); /** @brief Helper function to free memory array allocated in SWMM. @param array The pointer to the array @return Void. */ -void DLLEXPORT swmm_freeMemory(void *memory); +EXPORT_TOOLKIT void swmm_freeMemory(void *memory); + #ifdef __cplusplus } // matches the linkage specification from above */ #endif -#endif + +#endif // TOOLKIT_H diff --git a/src/solver/toolkit.c b/src/solver/toolkit.c index 9d9c1e266..193d1287b 100644 --- a/src/solver/toolkit.c +++ b/src/solver/toolkit.c @@ -1,13 +1,15 @@ -/** @file toolkit.c - @see http://github.com/openwateranalytics/stormwater-management-model +/* + * toolkit.c - OWA SWMM Toolkit API + * + * Created on: Aug 30, 2016 + * Updated on: + * + * Author: See CONTRIBUTORS + * + * Note: + * Originally developed by Bryant McDonnell + */ - toolkit.c - @brief Exportable Functions for Toolkit API. - @date 08/30/2016 (First Contribution) - @authors B. McDonnell (EmNet LLC), OpenWaterAnalytics members: see AUTHORS. - - -*/ #define _CRT_SECURE_NO_DEPRECATE @@ -40,7 +42,7 @@ double *newDoubleArray(int n); -char DLLEXPORT *swmm_getSemVersion() +EXPORT_TOOLKIT char *swmm_getSemVersion() // // Output: Returns Semantic Version // Purpose: retrieves the current semantic version @@ -49,8 +51,7 @@ char DLLEXPORT *swmm_getSemVersion() return VERSION; } - -char DLLEXPORT *swmm_getBuildId() +EXPORT_TOOLKIT char *swmm_getBuildId() // // Output: Returns Build Id // Purpose: retrieves the current build id @@ -63,7 +64,7 @@ char DLLEXPORT *swmm_getBuildId() // Extended API Functions //----------------------------------------------------------------------------- -int DLLEXPORT swmm_run_cb(const char* f1, const char* f2, const char* f3, +EXPORT_TOOLKIT int swmm_run_cb(const char* f1, const char* f2, const char* f3, void (*callback) (double *)) // // Input: f1 = name of input file @@ -132,7 +133,7 @@ int DLLEXPORT swmm_run_cb(const char* f1, const char* f2, const char* f3, } -int DLLEXPORT swmm_getAPIError(int errorCode, char **errorMsg) +EXPORT_TOOLKIT int swmm_getAPIError(int errorCode, char **errorMsg) /// /// Input: errorCode = error code /// Output: errmessage String @@ -145,7 +146,7 @@ int DLLEXPORT swmm_getAPIError(int errorCode, char **errorMsg) } -int DLLEXPORT swmm_project_findObject(SM_ObjectType type, char *id, int *index) +EXPORT_TOOLKIT int swmm_project_findObject(SM_ObjectType type, char *id, int *index) { int error_code_index = 0; @@ -160,7 +161,7 @@ int DLLEXPORT swmm_project_findObject(SM_ObjectType type, char *id, int *index) return error_getCode(error_code_index); } -int DLLEXPORT swmm_getSimulationDateTime(SM_TimePropety type, int *year, int *month, int *day, +EXPORT_TOOLKIT int swmm_getSimulationDateTime(SM_TimePropety type, int *year, int *month, int *day, int *hour, int *minute, int *second) /// /// Input: type = time type to return @@ -201,7 +202,7 @@ int DLLEXPORT swmm_getSimulationDateTime(SM_TimePropety type, int *year, int *mo return error_getCode(error_code_index); } -int DLLEXPORT swmm_setSimulationDateTime(SM_TimePropety type, int year, int month, +EXPORT_TOOLKIT int swmm_setSimulationDateTime(SM_TimePropety type, int year, int month, int day, int hour, int minute, int second) /// @@ -263,7 +264,7 @@ int DLLEXPORT swmm_setSimulationDateTime(SM_TimePropety type, int year, int mont return error_getCode(error_code_index); } -int DLLEXPORT swmm_getSimulationUnit(SM_Units type, int *value) +EXPORT_TOOLKIT int swmm_getSimulationUnit(SM_Units type, int *value) /// /// Input: type = simulation unit type /// Output: enum representation of units @@ -295,7 +296,7 @@ int DLLEXPORT swmm_getSimulationUnit(SM_Units type, int *value) return error_getCode(error_code_index); } -int DLLEXPORT swmm_getSimulationAnalysisSetting(SM_SimOption type, int *value) +EXPORT_TOOLKIT int swmm_getSimulationAnalysisSetting(SM_SimOption type, int *value) /// /// Input: type = analysis type /// Output: setting True or False @@ -336,7 +337,7 @@ int DLLEXPORT swmm_getSimulationAnalysisSetting(SM_SimOption type, int *value) return error_getCode(error_code_index); } -int DLLEXPORT swmm_getSimulationParam(SM_SimSetting type, double *value) +EXPORT_TOOLKIT int swmm_getSimulationParam(SM_SimSetting type, double *value) /// /// Input: type = analysis type /// Output: Simulation Parameter @@ -408,7 +409,7 @@ int DLLEXPORT swmm_getSimulationParam(SM_SimSetting type, double *value) return error_getCode(error_code_index); } -int DLLEXPORT swmm_countObjects(SM_ObjectType type, int *count) +EXPORT_TOOLKIT int swmm_countObjects(SM_ObjectType type, int *count) /// /// Input: type = object type (Based on SM_ObjectType enum) /// Output: count = pointer to integer @@ -428,7 +429,7 @@ int DLLEXPORT swmm_countObjects(SM_ObjectType type, int *count) return error_getCode(error_code_index); } -int DLLEXPORT swmm_getObjectIndex(SM_ObjectType type, char *id, int *index) +EXPORT_TOOLKIT int swmm_getObjectIndex(SM_ObjectType type, char *id, int *index) /// /// Input: type = object type (Based on SM_ObjectType enum) /// char* = ID name @@ -447,7 +448,7 @@ int DLLEXPORT swmm_getObjectIndex(SM_ObjectType type, char *id, int *index) return error_getCode(error_code_index); } -int DLLEXPORT swmm_getObjectId(SM_ObjectType type, int index, char **id) +EXPORT_TOOLKIT int swmm_getObjectId(SM_ObjectType type, int index, char **id) /// /// Input: type = object type (Based on SM_ObjectType enum) /// index = Index of desired ID @@ -513,7 +514,7 @@ int DLLEXPORT swmm_getObjectId(SM_ObjectType type, int index, char **id) return error_getCode(error_code_index); } -int DLLEXPORT swmm_getNodeType(int index, SM_NodeType *Ntype) +EXPORT_TOOLKIT int swmm_getNodeType(int index, SM_NodeType *Ntype) /// /// Input: index = Index of desired ID /// Ntype = Node type (Based on enum SM_NodeType) @@ -537,7 +538,7 @@ int DLLEXPORT swmm_getNodeType(int index, SM_NodeType *Ntype) return error_getCode(error_code_index); } -int DLLEXPORT swmm_getLinkType(int index, SM_LinkType *Ltype) +EXPORT_TOOLKIT int swmm_getLinkType(int index, SM_LinkType *Ltype) /// /// Input: index = Index of desired ID /// Ltype = Link type (Based on enum SM_LinkType) @@ -561,7 +562,7 @@ int DLLEXPORT swmm_getLinkType(int index, SM_LinkType *Ltype) return error_getCode(error_code_index); } -int DLLEXPORT swmm_getLinkConnections(int index, int *Node1, int *Node2) +EXPORT_TOOLKIT int swmm_getLinkConnections(int index, int *Node1, int *Node2) /// /// Input: index = Index of desired ID /// Output: Node1 and Node2 indeces @@ -589,7 +590,7 @@ int DLLEXPORT swmm_getLinkConnections(int index, int *Node1, int *Node2) return error_getCode(error_code_index); } -int DLLEXPORT swmm_getLinkDirection(int index, signed char *value) +EXPORT_TOOLKIT int swmm_getLinkDirection(int index, signed char *value) /// /// Input: index = Index of desired ID /// Output: Link Direction (Only changes is slope < 0) @@ -615,7 +616,7 @@ int DLLEXPORT swmm_getLinkDirection(int index, signed char *value) return error_getCode(error_code_index); } -int DLLEXPORT swmm_getNodeParam(int index, SM_NodeProperty param, double *value) +EXPORT_TOOLKIT int swmm_getNodeParam(int index, SM_NodeProperty param, double *value) /// /// Input: index = Index of desired ID /// param = Parameter desired (Based on enum SM_NodeProperty) @@ -655,7 +656,7 @@ int DLLEXPORT swmm_getNodeParam(int index, SM_NodeProperty param, double *value) return error_getCode(error_code_index); } -int DLLEXPORT swmm_setNodeParam(int index, SM_NodeProperty param, double value) +EXPORT_TOOLKIT int swmm_setNodeParam(int index, SM_NodeProperty param, double value) /// /// Input: index = Index of desired ID /// param = Parameter desired (Based on enum SM_NodeProperty) @@ -701,7 +702,7 @@ int DLLEXPORT swmm_setNodeParam(int index, SM_NodeProperty param, double value) return error_getCode(error_code_index); } -int DLLEXPORT swmm_getLinkParam(int index, SM_LinkProperty param, double *value) +EXPORT_TOOLKIT int swmm_getLinkParam(int index, SM_LinkProperty param, double *value) /// /// Input: index = Index of desired ID /// param = Parameter desired (Based on enum SM_LinkProperty) @@ -745,7 +746,7 @@ int DLLEXPORT swmm_getLinkParam(int index, SM_LinkProperty param, double *value) return error_getCode(error_code_index); } -int DLLEXPORT swmm_setLinkParam(int index, SM_LinkProperty param, double value) +EXPORT_TOOLKIT int swmm_setLinkParam(int index, SM_LinkProperty param, double value) /// /// Input: index = Index of desired ID /// param = Parameter desired (Based on enum SM_LinkProperty) @@ -803,7 +804,7 @@ int DLLEXPORT swmm_setLinkParam(int index, SM_LinkProperty param, double value) } -int DLLEXPORT swmm_getSubcatchParam(int index, SM_SubcProperty param, double *value) +EXPORT_TOOLKIT int swmm_getSubcatchParam(int index, SM_SubcProperty param, double *value) /// /// Input: index = Index of desired ID /// param = Parameter desired (Based on enum SM_SubcProperty) @@ -843,7 +844,7 @@ int DLLEXPORT swmm_getSubcatchParam(int index, SM_SubcProperty param, double *va return error_getCode(error_code_index); } -int DLLEXPORT swmm_setSubcatchParam(int index, SM_SubcProperty param, double value) +EXPORT_TOOLKIT int swmm_setSubcatchParam(int index, SM_SubcProperty param, double value) /// /// Input: index = Index of desired ID /// param = Parameter desired (Based on enum SM_SubcProperty) @@ -894,7 +895,7 @@ int DLLEXPORT swmm_setSubcatchParam(int index, SM_SubcProperty param, double val return error_getCode(error_code_index); } -int DLLEXPORT swmm_getSubcatchOutConnection(int index, SM_ObjectType *type, int *out_index) +EXPORT_TOOLKIT int swmm_getSubcatchOutConnection(int index, SM_ObjectType *type, int *out_index) /// /// Input: index = Index of desired ID /// (Subcatchments can load to Node or another Subcatchment) @@ -938,7 +939,7 @@ int DLLEXPORT swmm_getSubcatchOutConnection(int index, SM_ObjectType *type, int } -int DLLEXPORT swmm_getLidUCount(int index, int *value) +EXPORT_TOOLKIT int swmm_getLidUCount(int index, int *value) // Input: index = Index of desired subcatchment // Output: int = number of lid units for subcatchment // Return: number of lid units for subcatchment @@ -965,7 +966,7 @@ int DLLEXPORT swmm_getLidUCount(int index, int *value) } -int DLLEXPORT swmm_getLidUParam(int index, int lidIndex, SM_LidUProperty param, double *value) +EXPORT_TOOLKIT int swmm_getLidUParam(int index, int lidIndex, SM_LidUProperty param, double *value) // // Input: index = Index of desired subcatchment // lidIndex = Index of desired lid unit (subcatchment allow for multiple lid units) @@ -1019,7 +1020,7 @@ int DLLEXPORT swmm_getLidUParam(int index, int lidIndex, SM_LidUProperty param, } -int DLLEXPORT swmm_setLidUParam(int index, int lidIndex, SM_LidUProperty param, double value) +EXPORT_TOOLKIT int swmm_setLidUParam(int index, int lidIndex, SM_LidUProperty param, double value) // // Input: index = Index of desired subcatchment // lidIndex = Index of desired lid unit (subcatchment allow for multiple lid units) @@ -1084,7 +1085,7 @@ int DLLEXPORT swmm_setLidUParam(int index, int lidIndex, SM_LidUProperty param, } -int DLLEXPORT swmm_getLidUOption(int index, int lidIndex, SM_LidUOptions param, int *value) +EXPORT_TOOLKIT int swmm_getLidUOption(int index, int lidIndex, SM_LidUOptions param, int *value) // // Input: index = Index of desired subcatchment // lidIndex = Index of desired lid unit (subcatchment allow for multiple lid units) @@ -1136,7 +1137,7 @@ int DLLEXPORT swmm_getLidUOption(int index, int lidIndex, SM_LidUOptions param, } -int DLLEXPORT swmm_setLidUOption(int index, int lidIndex, SM_LidUOptions param, int value) +EXPORT_TOOLKIT int swmm_setLidUOption(int index, int lidIndex, SM_LidUOptions param, int value) // // Input: index = Index of desired subcatchment // lidIndex = Index of desired lid unit (subcatchment allow for multiple lid units) @@ -1234,7 +1235,7 @@ int DLLEXPORT swmm_setLidUOption(int index, int lidIndex, SM_LidUOptions param, } -int DLLEXPORT swmm_getLidCOverflow(int lidControlIndex, int *condition) +EXPORT_TOOLKIT int swmm_getLidCOverflow(int lidControlIndex, int *condition) // // Input: lidControlIndex = Index of desired lid control // Output: condition = value to be output @@ -1266,7 +1267,7 @@ int DLLEXPORT swmm_getLidCOverflow(int lidControlIndex, int *condition) return error_getCode(error_code_index); } -int DLLEXPORT swmm_getLidCParam(int lidControlIndex, SM_LidLayer layerIndex, SM_LidLayerProperty param, double *value) +EXPORT_TOOLKIT int swmm_getLidCParam(int lidControlIndex, SM_LidLayer layerIndex, SM_LidLayerProperty param, double *value) // // Input: lidControlIndex = Index of desired lid control // layerIndex = Index of desired lid control layer (Based on enum SM_LidLayer) @@ -1449,7 +1450,7 @@ int DLLEXPORT swmm_getLidCParam(int lidControlIndex, SM_LidLayer layerIndex, SM_ } -int DLLEXPORT swmm_setLidCParam(int lidControlIndex, SM_LidLayer layerIndex, SM_LidLayerProperty param, double value) +EXPORT_TOOLKIT int swmm_setLidCParam(int lidControlIndex, SM_LidLayer layerIndex, SM_LidLayerProperty param, double value) // // Input: lidControlIndex = Index of desired lid control // layerIndex = Index of desired lid control layer (Based on enum SM_LidLayers) @@ -1794,7 +1795,7 @@ int DLLEXPORT swmm_setLidCParam(int lidControlIndex, SM_LidLayer layerIndex, SM_ // Active Simulation Results API //------------------------------- -int DLLEXPORT swmm_getCurrentDateTime(int *year, int *month, int *day, +EXPORT_TOOLKIT int swmm_getCurrentDateTime(int *year, int *month, int *day, int *hour, int *minute, int *second) /// /// Input: timetype = time type to return @@ -1829,7 +1830,7 @@ int DLLEXPORT swmm_getCurrentDateTime(int *year, int *month, int *day, } -int DLLEXPORT swmm_getNodeResult(int index, SM_NodeResult type, double *result) +EXPORT_TOOLKIT int swmm_getNodeResult(int index, SM_NodeResult type, double *result) /// /// Input: index = Index of desired ID /// type = Result Type (SM_NodeResult) @@ -1879,7 +1880,7 @@ int DLLEXPORT swmm_getNodeResult(int index, SM_NodeResult type, double *result) return error_getCode(error_code_index); } -int DLLEXPORT swmm_getNodePollut(int index, SM_NodePollut type, double **pollutArray, int *length) +EXPORT_TOOLKIT int swmm_getNodePollut(int index, SM_NodePollut type, double **pollutArray, int *length) /// /// Input: index = Index of desired ID /// type = Result Type (SM_NodePollut or SM_NODECIN or SM_NODEREACTORC) @@ -1939,7 +1940,7 @@ int DLLEXPORT swmm_getNodePollut(int index, SM_NodePollut type, double **pollutA return error_getCode(error_code_index); } -int DLLEXPORT swmm_setNodePollut(int index, int pollutant_index, double pollutant_value) +EXPORT_TOOLKIT int swmm_setNodePollut(int index, int pollutant_index, double pollutant_value) /// /// Input: index = Index of desired ID /// pollutant_index = Index of desired polluant @@ -1975,7 +1976,7 @@ int DLLEXPORT swmm_setNodePollut(int index, int pollutant_index, double pollutan } -int DLLEXPORT swmm_getLinkResult(int index, SM_LinkResult type, double *result) +EXPORT_TOOLKIT int swmm_getLinkResult(int index, SM_LinkResult type, double *result) /// /// Input: index = Index of desired ID /// type = Result Type (SM_LinkResult) @@ -2022,7 +2023,7 @@ int DLLEXPORT swmm_getLinkResult(int index, SM_LinkResult type, double *result) return error_getCode(error_code_index); } -int DLLEXPORT swmm_getLinkPollut(int index, SM_LinkPollut type, double **pollutArray, int *length) +EXPORT_TOOLKIT int swmm_getLinkPollut(int index, SM_LinkPollut type, double **pollutArray, int *length) /// /// Input: index = Index of desired ID /// type = Result Type (SM_LinkPollut) @@ -2082,7 +2083,7 @@ int DLLEXPORT swmm_getLinkPollut(int index, SM_LinkPollut type, double **pollutA } -int DLLEXPORT swmm_setLinkPollut(int index, int type, int pollutant_index, double pollutant_value) +EXPORT_TOOLKIT int swmm_setLinkPollut(int index, int type, int pollutant_index, double pollutant_value) /// /// Input: index = Index of the desired Link ID /// type = SM_LINKQUAL - Sets link's qual and allows accounting for loss and mixing calculation @@ -2125,7 +2126,7 @@ int DLLEXPORT swmm_setLinkPollut(int index, int type, int pollutant_index, doubl return error_getCode(error_code_index); } -int DLLEXPORT swmm_getSubcatchResult(int index, SM_SubcResult type, double* result) +EXPORT_TOOLKIT int swmm_getSubcatchResult(int index, SM_SubcResult type, double* result) /// /// Input: index = Index of desired ID /// type = Result Type (SM_SubcResult) @@ -2168,7 +2169,7 @@ int DLLEXPORT swmm_getSubcatchResult(int index, SM_SubcResult type, double* resu return error_getCode(error_code_index); } -int DLLEXPORT swmm_getSubcatchPollut(int index, SM_SubcPollut type, double **pollutArray, int *length) +EXPORT_TOOLKIT int swmm_getSubcatchPollut(int index, SM_SubcPollut type, double **pollutArray, int *length) /// /// Input: index = Index of desired ID /// type = Result Type (SM_SubcPollut) @@ -2249,7 +2250,7 @@ int DLLEXPORT swmm_getSubcatchPollut(int index, SM_SubcPollut type, double **pol return error_getCode(error_code_index); } -int DLLEXPORT swmm_getGagePrecip(int index, SM_GagePrecip type, double* result) +EXPORT_TOOLKIT int swmm_getGagePrecip(int index, SM_GagePrecip type, double* result) /// /// Input: index = Index of desired ID /// type = Result type @@ -2291,7 +2292,7 @@ int DLLEXPORT swmm_getGagePrecip(int index, SM_GagePrecip type, double* result) } -int DLLEXPORT swmm_getNodeStats(int index, SM_NodeStats *nodeStats) +EXPORT_TOOLKIT int swmm_getNodeStats(int index, SM_NodeStats *nodeStats) /// /// Output: Node Stats Structure (SM_NodeStats) /// Return: API Error @@ -2320,7 +2321,7 @@ int DLLEXPORT swmm_getNodeStats(int index, SM_NodeStats *nodeStats) return error_getCode(error_index); } -int DLLEXPORT swmm_getNodeTotalInflow(int index, double* value) +EXPORT_TOOLKIT int swmm_getNodeTotalInflow(int index, double* value) /// /// Input: Node Index /// Output: Node Total inflow Volume. @@ -2343,7 +2344,7 @@ int DLLEXPORT swmm_getNodeTotalInflow(int index, double* value) return error_getCode(error_index); } -int DLLEXPORT swmm_getStorageStats(int index, SM_StorageStats *storageStats) +EXPORT_TOOLKIT int swmm_getStorageStats(int index, SM_StorageStats *storageStats) /// /// Output: Storage Node Stats Structure (SM_StorageStats) /// Return: API Error @@ -2376,7 +2377,7 @@ int DLLEXPORT swmm_getStorageStats(int index, SM_StorageStats *storageStats) return error_getCode(error_index); } -int DLLEXPORT swmm_getOutfallStats(int index, SM_OutfallStats *outfallStats) +EXPORT_TOOLKIT int swmm_getOutfallStats(int index, SM_OutfallStats *outfallStats) { int error_index = 0; @@ -2406,7 +2407,7 @@ int DLLEXPORT swmm_getOutfallStats(int index, SM_OutfallStats *outfallStats) } -int DLLEXPORT swmm_getLinkStats(int index, SM_LinkStats *linkStats) +EXPORT_TOOLKIT int swmm_getLinkStats(int index, SM_LinkStats *linkStats) /// /// Output: Link Stats Structure (SM_LinkStats) /// Return: API Error @@ -2436,7 +2437,7 @@ int DLLEXPORT swmm_getLinkStats(int index, SM_LinkStats *linkStats) } -int DLLEXPORT swmm_getPumpStats(int index, SM_PumpStats *pumpStats) +EXPORT_TOOLKIT int swmm_getPumpStats(int index, SM_PumpStats *pumpStats) /// /// Output: Pump Link Stats Structure (SM_PumpStats) /// Return: API Error @@ -2470,7 +2471,7 @@ int DLLEXPORT swmm_getPumpStats(int index, SM_PumpStats *pumpStats) } -int DLLEXPORT swmm_getSubcatchStats(int index, SM_SubcatchStats *subcatchStats) +EXPORT_TOOLKIT int swmm_getSubcatchStats(int index, SM_SubcatchStats *subcatchStats) /// /// Output: Subcatchment Stats Structure (SM_SubcatchStats) /// Return: API Error @@ -2500,7 +2501,7 @@ int DLLEXPORT swmm_getSubcatchStats(int index, SM_SubcatchStats *subcatchStats) } -int DLLEXPORT swmm_getSystemRoutingTotals(SM_RoutingTotals *routingTotals) +EXPORT_TOOLKIT int swmm_getSystemRoutingTotals(SM_RoutingTotals *routingTotals) /// /// Output: System Routing Totals Structure (SM_RoutingTotals) /// Return: API Error @@ -2525,7 +2526,7 @@ int DLLEXPORT swmm_getSystemRoutingTotals(SM_RoutingTotals *routingTotals) return error_getCode(error_index); } -int DLLEXPORT swmm_getSystemRunoffTotals(SM_RunoffTotals *runoffTotals) +EXPORT_TOOLKIT int swmm_getSystemRunoffTotals(SM_RunoffTotals *runoffTotals) /// /// Output: System Runoff Totals Structure (SM_RunoffTotals) /// Return: API Error @@ -2550,7 +2551,7 @@ int DLLEXPORT swmm_getSystemRunoffTotals(SM_RunoffTotals *runoffTotals) return error_getCode(error_index); } -int DLLEXPORT swmm_getLidUFluxRates(int index, int lidIndex, SM_LidLayer layerIndex, double* result) +EXPORT_TOOLKIT int swmm_getLidUFluxRates(int index, int lidIndex, SM_LidLayer layerIndex, double* result) // // Input: index = Index of desired subcatchment // lidIndex = Index of desired lid control (subcatchment allow for multiple lids) @@ -2601,7 +2602,7 @@ int DLLEXPORT swmm_getLidUFluxRates(int index, int lidIndex, SM_LidLayer layerIn return error_getCode(error_code_index); } -int DLLEXPORT swmm_getLidGResult(int index, SM_LidResult type, double* result) +EXPORT_TOOLKIT int swmm_getLidGResult(int index, SM_LidResult type, double* result) // // Input: index = index of desired subcatchment // type = type of result data desired @@ -2650,7 +2651,7 @@ int DLLEXPORT swmm_getLidGResult(int index, SM_LidResult type, double* result) return error_getCode(error_code_index); } -int DLLEXPORT swmm_getLidUResult(int index, int lidIndex, SM_LidResult type, double* result) +EXPORT_TOOLKIT int swmm_getLidUResult(int index, int lidIndex, SM_LidResult type, double* result) // // Input: index = Index of desired subcatchment // lidIndex = Index of desired lid control (subcatchment allow for multiple lids) @@ -2753,7 +2754,7 @@ int DLLEXPORT swmm_getLidUResult(int index, int lidIndex, SM_LidResult type, dou // Setters API //------------------------------- -int DLLEXPORT swmm_setLinkSetting(int index, double setting) +EXPORT_TOOLKIT int swmm_setLinkSetting(int index, double setting) /// /// Input: index = Index of desired ID /// value = New Target Setting @@ -2796,7 +2797,7 @@ int DLLEXPORT swmm_setLinkSetting(int index, double setting) } -int DLLEXPORT swmm_setNodeInflow(int index, double flowrate) +EXPORT_TOOLKIT int swmm_setNodeInflow(int index, double flowrate) /// /// Input: index = Index of desired ID /// value = New Inflow Rate @@ -2857,7 +2858,7 @@ int DLLEXPORT swmm_setNodeInflow(int index, double flowrate) return error_getCode(error_code_index); } -int DLLEXPORT swmm_setOutfallStage(int index, double stage) +EXPORT_TOOLKIT int swmm_setOutfallStage(int index, double stage) /// /// Input: index = Index of desired outfall /// stage = New outfall stage (head) @@ -2892,7 +2893,7 @@ int DLLEXPORT swmm_setOutfallStage(int index, double stage) return error_getCode(error_code_index); } -int DLLEXPORT swmm_setGagePrecip(int index, double total_precip) +EXPORT_TOOLKIT int swmm_setGagePrecip(int index, double total_precip) /// /// Input: index = Index of desired ID /// total_precip = rainfall intensity to be set @@ -2930,6 +2931,15 @@ int DLLEXPORT swmm_setGagePrecip(int index, double total_precip) return error_getCode(error_code_index); } +EXPORT_TOOLKIT void swmm_freeMemory(void *memory) +// +// Purpose: Frees memory allocated by API calls +// +{ + free(memory); +} + + //------------------------------- // Utility Functions //------------------------------- @@ -2941,22 +2951,3 @@ double* newDoubleArray(int n) { return (double*) malloc((n)*sizeof(double)); } - - -//void DLLEXPORT freeArray(void** array) -/// -/// Helper function used to free array allocated memory by API. -/// -//{ -// FREE(*array); -// *array = NULL; -//} - - -void DLLEXPORT swmm_freeMemory(void *memory) -// -// Purpose: Frees memory allocated by API calls -// -{ - free(memory); -} From f9ef689cfeb8c10fe5907ef0ca70d83ce9c3d7e6 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 5 Nov 2021 09:32:02 -0400 Subject: [PATCH 166/190] Fussing --- CONTRIBUTORS | 3 ++- src/solver/include/toolkit_enums.h | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index ac4880d3d..d6ac5443e 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -92,7 +92,8 @@ D: Bug fix, testing N: Michael Tryby E: tryby.michael@epa.gov L: Public Domain -D: Mentor, output file API, testing framework, CLI, development operations, Python wrapper, review +D: Mentor, development operations, review +D: Output library, testing framework, CLI, versioning, Python wrapper N: Jia Xin Wu E: wuu.jennifer@gmail.com diff --git a/src/solver/include/toolkit_enums.h b/src/solver/include/toolkit_enums.h index 44c5e8f2c..9b54f7b2d 100644 --- a/src/solver/include/toolkit_enums.h +++ b/src/solver/include/toolkit_enums.h @@ -4,9 +4,7 @@ * Created on: November 13, 2020 * Updated on: * - * Author: Michael E. Tryby - * US EPA - ORD/CESER - * + * Author: See CONTRIBUTORS */ From 6d74d607553c3edc7004192b6e847fa79cbd117d Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 5 Nov 2021 15:12:30 -0400 Subject: [PATCH 167/190] Adding compiler information --- extern/version.h.in | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/extern/version.h.in b/extern/version.h.in index db3e47f7f..6fcc6a895 100644 --- a/extern/version.h.in +++ b/extern/version.h.in @@ -15,17 +15,19 @@ -#define ORGANIZATION "Open Water Analytics" -#define ORG "OWA" - -#define VERSION "@swmm-solver_VERSION@" -#define VERSION_MAJOR @swmm-solver_VERSION_MAJOR@ -#define VERSION_MINOR @swmm-solver_VERSION_MINOR@ -#define VERSION_PATCH @swmm-solver_VERSION_PATCH@ - -#define GIT_HASH "@GIT_HASH@" - -#define BUILD_ID "@BUILD_ID@" +#define PROJECT "SWMM" +#define ORGANIZATION "Open_Water_Analytics" + +#define VERSION "@swmm-solver_VERSION@" +#define VERSION_MAJOR @swmm-solver_VERSION_MAJOR@ +#define VERSION_MINOR @swmm-solver_VERSION_MINOR@ +#define VERSION_PATCH @swmm-solver_VERSION_PATCH@ +#define GIT_HASH "@GIT_HASH@" + +#define PLATFORM "@CMAKE_SYSTEM_NAME@" +#define COMPILER "@CMAKE_C_COMPILER_ID@" +#define COMPILER_VERSION "@CMAKE_C_COMPILER_VERSION@" +#define BUILD_ID "@BUILD_ID@" static inline int get_version_legacy() { \ From 1094aa9784dcc5bc6fccfb69bccae0b5c2b3996f Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 8 Nov 2021 11:55:46 -0500 Subject: [PATCH 168/190] Add const to function declarations --- src/solver/include/toolkit.h | 24 ++++++++++++------------ src/solver/toolkit.c | 24 ++++++++++++------------ 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/solver/include/toolkit.h b/src/solver/include/toolkit.h index 7a112df9b..989288f36 100644 --- a/src/solver/include/toolkit.h +++ b/src/solver/include/toolkit.h @@ -1,14 +1,14 @@ /* - * toolkit.h - OWA SWMM Toolkit API - * - * Created on: Aug 30, 2016 - * Updated on: - * - * Author: See CONTRIBUTORS - * - * Note: - * Originally developed by Bryant McDonnell - */ + * toolkit.h - OWA SWMM Toolkit API + * + * Created on: Aug 30, 2016 + * Updated on: + * + * Author: See CONTRIBUTORS + * + * Note: + * Originally developed by Bryant McDonnell + */ #ifndef TOOLKIT_H #define TOOLKIT_H @@ -32,14 +32,14 @@ extern "C" { @param[out] Pointer to version number string @returns error code */ -EXPORT_TOOLKIT char *swmm_getSemVersion(); +EXPORT_TOOLKIT const char *swmm_getSemVersion(); /** @brief Get Build Id @param[out] Pointer to build id string @returns error code */ -EXPORT_TOOLKIT char *swmm_getBuildId(); +EXPORT_TOOLKIT const char *swmm_getBuildId(); /** diff --git a/src/solver/toolkit.c b/src/solver/toolkit.c index 193d1287b..66539c6a7 100644 --- a/src/solver/toolkit.c +++ b/src/solver/toolkit.c @@ -1,14 +1,14 @@ /* - * toolkit.c - OWA SWMM Toolkit API - * - * Created on: Aug 30, 2016 - * Updated on: - * - * Author: See CONTRIBUTORS - * - * Note: - * Originally developed by Bryant McDonnell - */ + * toolkit.c - OWA SWMM Toolkit API + * + * Created on: Aug 30, 2016 + * Updated on: + * + * Author: See CONTRIBUTORS + * + * Note: + * Originally developed by Bryant McDonnell + */ #define _CRT_SECURE_NO_DEPRECATE @@ -42,7 +42,7 @@ double *newDoubleArray(int n); -EXPORT_TOOLKIT char *swmm_getSemVersion() +EXPORT_TOOLKIT const char *swmm_getSemVersion() // // Output: Returns Semantic Version // Purpose: retrieves the current semantic version @@ -51,7 +51,7 @@ EXPORT_TOOLKIT char *swmm_getSemVersion() return VERSION; } -EXPORT_TOOLKIT char *swmm_getBuildId() +EXPORT_TOOLKIT const char *swmm_getBuildId() // // Output: Returns Build Id // Purpose: retrieves the current build id From 76b8ae7e39f07a659b51284effafe668394d7f29 Mon Sep 17 00:00:00 2001 From: michaeltryby Date: Tue, 23 Nov 2021 19:10:39 -0500 Subject: [PATCH 169/190] Fix build error --- extern/boost.cmake | 3 +++ tests/solver/test_pollut.cpp | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/extern/boost.cmake b/extern/boost.cmake index 34ffb6be6..839df22ec 100644 --- a/extern/boost.cmake +++ b/extern/boost.cmake @@ -25,6 +25,9 @@ endif() if (DEFINED ENV{BOOST_ROOT_1_76_0}) set(BOOST_ROOT $ENV{BOOST_ROOT_1_76_0}) +elseif (DEFINED ENV{BOOST_ROOT_1_74_0}) + set(BOOST_ROOT $ENV{BOOST_ROOT_1_74_0}) + elseif (DEFINED ENV{BOOST_ROOT_1_72_0}) set(BOOST_ROOT $ENV{BOOST_ROOT_1_72_0}) diff --git a/tests/solver/test_pollut.cpp b/tests/solver/test_pollut.cpp index 55132d33b..9b879d977 100644 --- a/tests/solver/test_pollut.cpp +++ b/tests/solver/test_pollut.cpp @@ -357,7 +357,7 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values, FixtureBeforeStep_Po BOOST_REQUIRE(error == ERR_NONE); do{ - if (step > 1000 and step < 2000){ + if (step > 1000 && step < 2000){ // Set pollutant in link and check the pollutant in the node error = swmm_setLinkPollut(link_ind, SM_LINKQUAL, P1, 1.0); BOOST_REQUIRE(error == ERR_NONE); @@ -365,7 +365,7 @@ BOOST_FIXTURE_TEST_CASE(set_link_pollutant_stepwise_values, FixtureBeforeStep_Po // Route Model Forward error = swmm_step(&elapsedTime); BOOST_REQUIRE(error == ERR_NONE); - if (step > 1500 and step < 2000) // Wait for water to reach node + if (step > 1500 && step < 2000) // Wait for water to reach node { // Get infows concentration in node error = swmm_getNodePollut(node_ind, SM_NODEQUAL, &node_qual, &length); From 26e6a76c7a39dd9dfecfd49509dfca96625ff1a2 Mon Sep 17 00:00:00 2001 From: karosc Date: Sun, 16 Jan 2022 19:34:27 -0500 Subject: [PATCH 170/190] toolkit pollutant setter and getter fixes: more info at https://github.com/OpenWaterAnalytics/swmm-python/pull/94 - swmm_getNodeResult should return HRT from Storage[] instead of Node[] - swmm_setLinkPollut `type` argument as SM_LinkPollut instead of int - minor doc string fixes for swmm_setNodePollut and swmm_setLinkPollut --- src/solver/include/toolkit.h | 13 +++++++------ src/solver/toolkit.c | 6 +++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/solver/include/toolkit.h b/src/solver/include/toolkit.h index 989288f36..84d3ac083 100644 --- a/src/solver/include/toolkit.h +++ b/src/solver/include/toolkit.h @@ -397,20 +397,21 @@ EXPORT_TOOLKIT int swmm_getNodePollut(int index, SM_NodePollut type, double **po EXPORT_TOOLKIT int swmm_setNodePollut(int index, int pollutant_index, double pollutant_value); /** - @brief Sets pollutant values for a specified link. - @param index The index of a link + @brief Sets pollutant values for a specified node. + @param index The index of a node @param pollutant_index Pollutant index to set @param pollutant_value Pollutant value to set @return Error code */ -EXPORT_TOOLKIT int swmm_setLinkPollut(int index, int type, int pollutant_index, double pollutant_value); +EXPORT_TOOLKIT int swmm_setLinkPollut(int index, SM_LinkPollut type, int pollutant_index, double pollutant_value); /** - @brief Get a result value for specified link. + @brief Sets pollutant values for a specified link. @param index The index of a link - @param type The property type code (See @ref SM_LinkResult) - @param[out] result The result of the link's property + @param type The property type code (See @ref SM_LinkPollut) + @param pollutant_index Pollutant index to set + @param pollutant_value Pollutant value to set @return Error code */ EXPORT_TOOLKIT int swmm_getLinkResult(int index, SM_LinkResult type, double *result); diff --git a/src/solver/toolkit.c b/src/solver/toolkit.c index 66539c6a7..c5b8103c8 100644 --- a/src/solver/toolkit.c +++ b/src/solver/toolkit.c @@ -1872,8 +1872,8 @@ EXPORT_TOOLKIT int swmm_getNodeResult(int index, SM_NodeResult type, double *res + Node[index].invertElev) * UCF(LENGTH); break; case SM_LATINFLOW: *result = Node[index].newLatFlow * UCF(FLOW); break; - case SM_HRT: - *result = Node[index].hrt; break; + case SM_HRT: + *result = Storage[Node[index].subIndex].hrt; break; default: error_code_index = ERR_API_OUTBOUNDS; break; } } @@ -2083,7 +2083,7 @@ EXPORT_TOOLKIT int swmm_getLinkPollut(int index, SM_LinkPollut type, double **po } -EXPORT_TOOLKIT int swmm_setLinkPollut(int index, int type, int pollutant_index, double pollutant_value) +EXPORT_TOOLKIT int swmm_setLinkPollut(int index, SM_LinkPollut type, int pollutant_index, double pollutant_value) /// /// Input: index = Index of the desired Link ID /// type = SM_LINKQUAL - Sets link's qual and allows accounting for loss and mixing calculation From 41d3fb0b22d90d7914b06c2fb9e58aa4899f55f6 Mon Sep 17 00:00:00 2001 From: Jenn Date: Sun, 6 Feb 2022 12:28:35 -0500 Subject: [PATCH 171/190] Issue #365 - Update .rpt header to indicate source of build (#366) --- src/solver/report.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solver/report.c b/src/solver/report.c index 6052ec1fe..07598b98a 100644 --- a/src/solver/report.c +++ b/src/solver/report.c @@ -244,7 +244,7 @@ void report_writeLogo() // { sprintf(Msg, \ - "\n OWA STORM WATER MANAGEMENT MODEL - VERSION v%s (Build %.10s)", + "\n OWA STORM WATER MANAGEMENT MODEL - VERSION v%s (OWA %.10s)", VERSION, BUILD_ID); fprintf(Frpt.file, "%s", Msg); From dee7c1d8d14ad2fcc5cbd52a73064ead6a106d76 Mon Sep 17 00:00:00 2001 From: bemason Date: Thu, 24 Mar 2022 13:44:36 -0400 Subject: [PATCH 172/190] Modified pollutant getter functions 1. Fixed spacing for swmm_getNodePollut. 2. Added *length parameter to the SM_NODECIN and SM_NODEREACTORC cases in getNodePollut. 3. Added case for SM_LINKREACTORC in swmm_getLinkPollut. --- src/solver/toolkit.c | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/src/solver/toolkit.c b/src/solver/toolkit.c index c5b8103c8..35d89ad49 100644 --- a/src/solver/toolkit.c +++ b/src/solver/toolkit.c @@ -1920,20 +1920,24 @@ EXPORT_TOOLKIT int swmm_getNodePollut(int index, SM_NodePollut type, double **po *pollutArray = result; *length = Nobjects[POLLUT]; } break; - case SM_NODECIN: - { - for (p=0; p < Nobjects[POLLUT]; p++) - { - result[p] = Node[index].inQual[p]; - } *pollutArray = result; - } break; - case SM_NODEREACTORC: - { - for (p=0; p < Nobjects[POLLUT]; p++) - { - result[p] = Node[index].reactorQual[p]; - } *pollutArray = result; - } break; + case SM_NODECIN: + { + for (p=0; p < Nobjects[POLLUT]; p++) + { + result[p] = Node[index].inQual[p]; + } + *pollutArray = result; + *length = Nobjects[POLLUT]; + } break; + case SM_NODEREACTORC: + { + for (p=0; p < Nobjects[POLLUT]; p++) + { + result[p] = Node[index].reactorQual[p]; + } + *pollutArray = result; + *length = Nobjects[POLLUT]; + } break; default: error_code_index = ERR_API_OUTBOUNDS; break; } } @@ -2076,6 +2080,15 @@ EXPORT_TOOLKIT int swmm_getLinkPollut(int index, SM_LinkPollut type, double **po *pollutArray = result; *length = Nobjects[POLLUT]; } break; + case SM_LINKREACTORC: + { + for (p = 0; p < Nobjects[POLLUT]; p++) + { + result[p] = Link[index].reactorQual[p]; + } + *pollutArray = result; + *length = Nobjects[POLLUT]; + } break; default: error_code_index = ERR_API_OUTBOUNDS; break; } } From 659b0dea7d762e96d8e6977fad238dfaf1d84ee6 Mon Sep 17 00:00:00 2001 From: bemason Date: Mon, 28 Mar 2022 13:23:49 -0400 Subject: [PATCH 173/190] Updated test_pollut.cpp Have not run tests yet but committing to save changes. --- tests/solver/test_pollut.cpp | 54 ++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/tests/solver/test_pollut.cpp b/tests/solver/test_pollut.cpp index 9b879d977..da302f5c3 100644 --- a/tests/solver/test_pollut.cpp +++ b/tests/solver/test_pollut.cpp @@ -164,7 +164,10 @@ BOOST_FIXTURE_TEST_CASE(get_node_pollutant_values_cin, FixtureBeforeStep_Pollut_ step_ind = 0; do { - + // Check length is set correctly + BOOST_CHECK_EQUAL(length == 1) + + // Get inflow concentration error = swmm_getNodePollut(1, SM_NODECIN, &node_qual, &length); BOOST_REQUIRE(error == ERR_NONE); @@ -182,7 +185,7 @@ BOOST_FIXTURE_TEST_CASE(get_node_pollutant_values_cin, FixtureBeforeStep_Pollut_ swmm_end(); } -// Testing Reactor Concentration +// Testing Reactor Concentration in Node BOOST_FIXTURE_TEST_CASE(get_node_reactor_pollutant, FixtureBeforeStep_Pollut_Node){ int error, step_ind; @@ -198,7 +201,10 @@ BOOST_FIXTURE_TEST_CASE(get_node_reactor_pollutant, FixtureBeforeStep_Pollut_Nod step_ind = 0; do { - // Check for steady state after 1000 steps. + // Check length is set correctly + BOOST_CHECK_EQUAL(length == 1) + + // Check for steady state after 1000 steps. // 1000 is a aribitarly long time duration, it can be any value as long // the system reaches a steady state @@ -221,6 +227,48 @@ BOOST_FIXTURE_TEST_CASE(get_node_reactor_pollutant, FixtureBeforeStep_Pollut_Nod swmm_end(); } +// Testing Reactor Concentration in Link +BOOST_FIXTURE_TEST_CASE(get_link_reactor_pollutant, FixtureBeforeStep_Pollut_Link){ + + int error, step_ind; + double* old_qual; + double* new_qual; + double elapsedTime = 0.0; + double total_pollutant = 0.0; + int length; + + // Pollutant IDs + int P1 = 0; + + step_ind = 0; + do + { + // Check length is set correctly + BOOST_CHECK_EQUAL(length == 1) + + // Check for steady state after 1000 steps. + // 1000 is a aribitarly long time duration, it can be any value as long + // the system reaches a steady state + + // Get reactor concentration + error = swmm_getLinkPollut(1, SM_LINKREACTORC, &new_qual, &length); + BOOST_REQUIRE(error == ERR_NONE); + + if (step_ind > 1000) + { + BOOST_CHECK_CLOSE(old_qual[P1], new_qual[P1], 0.001); + } + + old_qual = new_qual; + + // Route Model Forward + error = swmm_step(&elapsedTime); + step_ind+=1; + }while (elapsedTime != 0 && !error); + BOOST_REQUIRE(error == ERR_NONE); + swmm_end(); +} + // Testing Pollutant Setter - Node - Cumulative and mass balance BOOST_FIXTURE_TEST_CASE(set_node_pollutant_cumulative_values, FixtureBeforeStep_Pollut_Node){ From b477688348e0d38c79b5e54dfd6eeea6b6ae2019 Mon Sep 17 00:00:00 2001 From: bemason Date: Tue, 29 Mar 2022 11:36:20 -0400 Subject: [PATCH 174/190] Update test_pollut.cc 1. Added a check to confirm length is getting pulled/set correctly. 2. Added a test for link reactorQual. --- tests/solver/test_pollut.cpp | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/tests/solver/test_pollut.cpp b/tests/solver/test_pollut.cpp index da302f5c3..61cd13a40 100644 --- a/tests/solver/test_pollut.cpp +++ b/tests/solver/test_pollut.cpp @@ -60,6 +60,8 @@ BOOST_FIXTURE_TEST_CASE(get_pollut_values, FixtureBeforeStep){ { // subcatchment buildup error = swmm_getSubcatchPollut(subc_ind, SM_BUILDUP, &buildup_array, &length); + // Check length is set correctly + BOOST_CHECK(length == 2); BOOST_REQUIRE(error == ERR_NONE); BOOST_CHECK_SMALL(buildup_array[TSS] - 31.906912, 0.0001); BOOST_CHECK_SMALL(buildup_array[Lead] - 0.0, 0.0001); @@ -164,11 +166,11 @@ BOOST_FIXTURE_TEST_CASE(get_node_pollutant_values_cin, FixtureBeforeStep_Pollut_ step_ind = 0; do { - // Check length is set correctly - BOOST_CHECK_EQUAL(length == 1) - + // Get inflow concentration error = swmm_getNodePollut(1, SM_NODECIN, &node_qual, &length); + // Check length is set correctly + BOOST_CHECK(length == 1); BOOST_REQUIRE(error == ERR_NONE); // Check for constant influent @@ -201,15 +203,14 @@ BOOST_FIXTURE_TEST_CASE(get_node_reactor_pollutant, FixtureBeforeStep_Pollut_Nod step_ind = 0; do { - // Check length is set correctly - BOOST_CHECK_EQUAL(length == 1) - // Check for steady state after 1000 steps. // 1000 is a aribitarly long time duration, it can be any value as long // the system reaches a steady state // Get reactor concentration error = swmm_getNodePollut(1, SM_NODEREACTORC, &new_qual, &length); + // Check length is set correctly + BOOST_CHECK(length == 1); BOOST_REQUIRE(error == ERR_NONE); if (step_ind > 1000) @@ -243,18 +244,17 @@ BOOST_FIXTURE_TEST_CASE(get_link_reactor_pollutant, FixtureBeforeStep_Pollut_Lin step_ind = 0; do { - // Check length is set correctly - BOOST_CHECK_EQUAL(length == 1) - // Check for steady state after 1000 steps. // 1000 is a aribitarly long time duration, it can be any value as long // the system reaches a steady state // Get reactor concentration error = swmm_getLinkPollut(1, SM_LINKREACTORC, &new_qual, &length); + // Check length is set correctly + BOOST_CHECK(length == 1); BOOST_REQUIRE(error == ERR_NONE); - if (step_ind > 1000) + if (step_ind > 1500) { BOOST_CHECK_CLOSE(old_qual[P1], new_qual[P1], 0.001); } @@ -269,7 +269,6 @@ BOOST_FIXTURE_TEST_CASE(get_link_reactor_pollutant, FixtureBeforeStep_Pollut_Lin swmm_end(); } - // Testing Pollutant Setter - Node - Cumulative and mass balance BOOST_FIXTURE_TEST_CASE(set_node_pollutant_cumulative_values, FixtureBeforeStep_Pollut_Node){ From fe450190484fd0a7e85a2fd819d3a4a5f78ec835 Mon Sep 17 00:00:00 2001 From: bemason Date: Tue, 5 Apr 2022 10:16:10 -0400 Subject: [PATCH 175/190] Changes for node pollutant setter 1. Wrote setNodePollut like setLinkPollut in toolkit.c and toolkit.h. Before it did not take the SM_NodePollut type so I added it. 2. Modified the tests in test_pollut.cpp to include this new parameter. Passed the tests. --- src/solver/include/toolkit.h | 3 ++- src/solver/toolkit.c | 19 ++++++++++++++----- tests/solver/test_pollut.cpp | 6 +++--- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/solver/include/toolkit.h b/src/solver/include/toolkit.h index 84d3ac083..7a0bdf250 100644 --- a/src/solver/include/toolkit.h +++ b/src/solver/include/toolkit.h @@ -394,11 +394,12 @@ EXPORT_TOOLKIT int swmm_getNodePollut(int index, SM_NodePollut type, double **po @param pollutant_value Pollutant value to set @return Error code */ -EXPORT_TOOLKIT int swmm_setNodePollut(int index, int pollutant_index, double pollutant_value); +EXPORT_TOOLKIT int swmm_setNodePollut(int index, SM_NodePollut type, int pollutant_index, double pollutant_value); /** @brief Sets pollutant values for a specified node. @param index The index of a node + @param type The property type code (See @ref SM_NodePollut) @param pollutant_index Pollutant index to set @param pollutant_value Pollutant value to set @return Error code diff --git a/src/solver/toolkit.c b/src/solver/toolkit.c index 35d89ad49..c76be3d88 100644 --- a/src/solver/toolkit.c +++ b/src/solver/toolkit.c @@ -1944,11 +1944,12 @@ EXPORT_TOOLKIT int swmm_getNodePollut(int index, SM_NodePollut type, double **po return error_getCode(error_code_index); } -EXPORT_TOOLKIT int swmm_setNodePollut(int index, int pollutant_index, double pollutant_value) +EXPORT_TOOLKIT int swmm_setNodePollut(int index, SM_NodePollut type, int pollutant_index, double pollutant_value) /// /// Input: index = Index of desired ID +/// type = SM_NODEQUAL - Set's node's qual and allows accounting for loss and mixing calculation /// pollutant_index = Index of desired polluant -// pollutant_value = Value of the pollutant +/// pollutant_value = Value of the pollutant /// Return: API Error /// Purpose: Set pollutant concentration in nodes at the current time step { @@ -1972,8 +1973,16 @@ EXPORT_TOOLKIT int swmm_setNodePollut(int index, int pollutant_index, double pol } if (pollutant_index <= Nobjects[POLLUT]) { - Node[index].extQual[pollutant_index] = pollutant_value; - Node[index].extPollutFlag[pollutant_index] = 1; + switch(type) + { + case SM_NODEQUAL: + { + Node[index].extQual[pollutant_index] = pollutant_value; + Node[index].extPollutFlag[pollutant_index] = 1; + } break; + default: error_code_index = ERR_API_OUTBOUNDS; break; + } + } } return error_getCode(error_code_index); @@ -2102,7 +2111,7 @@ EXPORT_TOOLKIT int swmm_setLinkPollut(int index, SM_LinkPollut type, int polluta /// type = SM_LINKQUAL - Sets link's qual and allows accounting for loss and mixing calculation /// pollutant_index = index of pollutant to set /// pollutant_value = concentration to set -/// Output: API error +/// Return: API error /// Purponse: Set pollutant concentration in links { int error_code_index = 0; diff --git a/tests/solver/test_pollut.cpp b/tests/solver/test_pollut.cpp index 61cd13a40..e1c978f95 100644 --- a/tests/solver/test_pollut.cpp +++ b/tests/solver/test_pollut.cpp @@ -285,7 +285,7 @@ BOOST_FIXTURE_TEST_CASE(set_node_pollutant_cumulative_values, FixtureBeforeStep_ do { // Set pollutant - error = swmm_setNodePollut(1, P1, 0); + error = swmm_setNodePollut(1, SM_NODEQUAL, P1, 0); BOOST_REQUIRE(error == ERR_NONE); // Route Model Forward error = swmm_step(&elapsedTime); @@ -320,7 +320,7 @@ BOOST_FIXTURE_TEST_CASE(set_node_pollutant_stepwise_values, FixtureBeforeStep_Po do { // Set pollutant - error = swmm_setNodePollut(1, P1, 1.234); + error = swmm_setNodePollut(1, SM_NODEQUAL, P1, 1.234); BOOST_REQUIRE(error == ERR_NONE); // Route Model Forward @@ -357,7 +357,7 @@ BOOST_FIXTURE_TEST_CASE(set_node_pollutant_stepwise_values_2, FixtureBeforeStep_ do { // Set pollutant - error = swmm_setNodePollut(1, P1, 50.0); + error = swmm_setNodePollut(1, SM_NODEQUAL, P1, 50.0); BOOST_REQUIRE(error == ERR_NONE); // Route Model Forward From 4f3aa95ec04c26943692d6b01f60b7f2fd3484af Mon Sep 17 00:00:00 2001 From: bemason Date: Wed, 6 Apr 2022 11:08:12 -0400 Subject: [PATCH 176/190] Update toolkit.c Moved length statement out of switch case for both node and link getters to make the code more concise. --- src/solver/toolkit.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/solver/toolkit.c b/src/solver/toolkit.c index c76be3d88..d073e5fb5 100644 --- a/src/solver/toolkit.c +++ b/src/solver/toolkit.c @@ -1909,6 +1909,7 @@ EXPORT_TOOLKIT int swmm_getNodePollut(int index, SM_NodePollut type, double **po else { + *length = Nobjects[POLLUT]; switch (type) { case SM_NODEQUAL: @@ -1918,7 +1919,6 @@ EXPORT_TOOLKIT int swmm_getNodePollut(int index, SM_NodePollut type, double **po result[p] = Node[index].newQual[p]; } *pollutArray = result; - *length = Nobjects[POLLUT]; } break; case SM_NODECIN: { @@ -1936,7 +1936,6 @@ EXPORT_TOOLKIT int swmm_getNodePollut(int index, SM_NodePollut type, double **po result[p] = Node[index].reactorQual[p]; } *pollutArray = result; - *length = Nobjects[POLLUT]; } break; default: error_code_index = ERR_API_OUTBOUNDS; break; } @@ -2065,6 +2064,8 @@ EXPORT_TOOLKIT int swmm_getLinkPollut(int index, SM_LinkPollut type, double **po else { + *length = Nobjects[POLLUT]; + switch (type) { case SM_LINKQUAL: @@ -2074,7 +2075,6 @@ EXPORT_TOOLKIT int swmm_getLinkPollut(int index, SM_LinkPollut type, double **po result[p] = Link[index].newQual[p]; } *pollutArray = result; - *length = Nobjects[POLLUT]; } break; case SM_TOTALLOAD: { @@ -2087,7 +2087,6 @@ EXPORT_TOOLKIT int swmm_getLinkPollut(int index, SM_LinkPollut type, double **po } } *pollutArray = result; - *length = Nobjects[POLLUT]; } break; case SM_LINKREACTORC: { @@ -2096,7 +2095,6 @@ EXPORT_TOOLKIT int swmm_getLinkPollut(int index, SM_LinkPollut type, double **po result[p] = Link[index].reactorQual[p]; } *pollutArray = result; - *length = Nobjects[POLLUT]; } break; default: error_code_index = ERR_API_OUTBOUNDS; break; } From 53dff53788c988abdc5ead543fda9872ec96e1e3 Mon Sep 17 00:00:00 2001 From: karosc Date: Thu, 7 Apr 2022 14:34:37 -0400 Subject: [PATCH 177/190] Add support for cmake option BUILD_SHARED_LIBS - link libraries statically using BUILD_SHARED_LIBS=OFF --- .github/workflows/build-test.yml | 23 +++++++++++------------ CMakeLists.txt | 2 +- src/outfile/CMakeLists.txt | 3 +-- src/solver/CMakeLists.txt | 6 ++---- 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index c9c5b5554..669287d37 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -20,15 +20,15 @@ jobs: strategy: fail-fast: false matrix: - os: [windows-2016, ubuntu-16.04, macos-10.15] + os: [windows-2022, ubuntu-20.04, macos-10.15] requirements: [requirements-swmm.txt] include: - - os: windows-2016 + - os: windows-2022 sys_pkgs: choco install boost-msvc-14.1 - build_unit_test: make.cmd -t -g "Visual Studio 15 2017 Win64" - build_reg_test: make.cmd -g "Visual Studio 15 2017 Win64" + build_unit_test: make.cmd -t -g "Visual Studio 17 2022" -A x64 + build_reg_test: make.cmd -g "Visual Studio 17 2022" -A x64 before_reg_test: before-nrtest.cmd - run_reg_test: run-nrtests.cmd + run_reg_test: run-nrtests.cmd build_id: "%GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER%" experimental: false artifacts_ext: zip @@ -38,12 +38,12 @@ jobs: shell: cmd working-directory: ./ci-tools/windows - - os: ubuntu-16.04 + - os: ubuntu-20.04 sys_pkgs: sudo apt install libboost-dev libboost-all-dev - build_unit_test: make.sh -t -g "Unix Makefiles" - build_reg_test: make.sh -g "Unix Makefiles" + build_unit_test: make.sh -s -t -g "Unix Makefiles" + build_reg_test: make.sh -s -g "Unix Makefiles" before_reg_test: before-nrtest.sh - run_reg_test: run-nrtests.sh + run_reg_test: run-nrtests.sh build_id: ${GITHUB_RUN_ID}_${GITHUB_RUN_NUMBER} experimental: true artifacts_ext: tar.gz @@ -56,9 +56,9 @@ jobs: - os: macos-10.15 sys_pkgs: brew install libomp boost build_unit_test: make.zsh -t -g "Xcode" - build_reg_test: make.zsh -g "Xcode" + build_reg_test: make.zsh -g "Xcode" before_reg_test: before-nrtest.zsh - run_reg_test: run-nrtests.zsh + run_reg_test: run-nrtests.zsh build_id: ${GITHUB_RUN_ID}_${GITHUB_RUN_NUMBER} experimental: true artifacts_ext: tar.gz @@ -80,7 +80,6 @@ jobs: BUILD_HOME: build TEST_HOME: nrtests - steps: - name: Checkout repo uses: actions/checkout@v2 diff --git a/CMakeLists.txt b/CMakeLists.txt index d3255fbac..816a48359 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,7 +48,7 @@ set(CONFIG_DIST "cmake") # Define build options option(BUILD_TESTS "Build component tests (requires Boost)" OFF) option(BUILD_DOCS "Build toolkit docs (requires Doxygen)" OFF) - +option(BUILD_SHARED_LIBS "Build using shared libraries" ON) # Add project subdirectories if(BUILD_DOCS) diff --git a/src/outfile/CMakeLists.txt b/src/outfile/CMakeLists.txt index 9b44c476c..11b7e5ce8 100644 --- a/src/outfile/CMakeLists.txt +++ b/src/outfile/CMakeLists.txt @@ -18,8 +18,7 @@ set(SWMM_OUT_PUBLIC_HEADERS # the binary output file API -add_library(swmm-output - SHARED +add_library(swmm-output swmm_output.c errormanager.c ) diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index db0d8636f..e10fa4a9a 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -38,8 +38,7 @@ if(BUILD_DEF) PROPERTIES_HEADER_FILE_ONLY TRUE ) - add_library(swmm5 - SHARED + add_library(swmm5 ${SWMM_SOURCES} ${PROJECT_SOURCE_DIR/bindings/swmm5.def} $ @@ -47,8 +46,7 @@ if(BUILD_DEF) else() # Performs standard library build - add_library(swmm5 - SHARED + add_library(swmm5 ${SWMM_SOURCES} $ ) From 235beb30a216a8645e237d5062f92280a4582141 Mon Sep 17 00:00:00 2001 From: bemason Date: Mon, 11 Apr 2022 15:04:42 -0400 Subject: [PATCH 178/190] Fixed error in swmm_setLinkPollut() in toolkit.c Checked object index but incorrectly called Nobjects[NODE] instead of Nobjects[LINK] --- src/solver/toolkit.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solver/toolkit.c b/src/solver/toolkit.c index d073e5fb5..032f385f5 100644 --- a/src/solver/toolkit.c +++ b/src/solver/toolkit.c @@ -2120,7 +2120,7 @@ EXPORT_TOOLKIT int swmm_setLinkPollut(int index, SM_LinkPollut type, int polluta error_code_index = ERR_API_INPUTNOTOPEN; } // Check if object index is within bounds - else if (index < 0 || index >= Nobjects[NODE]) + else if (index < 0 || index >= Nobjects[LINK]) { error_code_index = ERR_API_OBJECT_INDEX; } From 96292bd97f0ff7f10f4689c9ec818ffbc31aa5c6 Mon Sep 17 00:00:00 2001 From: karosc Date: Mon, 11 Apr 2022 23:39:19 -0400 Subject: [PATCH 179/190] fix windows build arguments --- .github/workflows/build-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 669287d37..2f379be56 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -25,8 +25,8 @@ jobs: include: - os: windows-2022 sys_pkgs: choco install boost-msvc-14.1 - build_unit_test: make.cmd -t -g "Visual Studio 17 2022" -A x64 - build_reg_test: make.cmd -g "Visual Studio 17 2022" -A x64 + build_unit_test: make.cmd /g "Visual Studio 17 2022" /A "x64" /t + build_reg_test: make.cmd /g "Visual Studio 17 2022" /A "x64" before_reg_test: before-nrtest.cmd run_reg_test: run-nrtests.cmd build_id: "%GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER%" From b77e8689dca20c13edc0b4a3507f3e898720310b Mon Sep 17 00:00:00 2001 From: bemason Date: Tue, 12 Apr 2022 13:28:59 -0400 Subject: [PATCH 180/190] add print statements for debugging --- CMakeFiles/cmake.check_cache | 1 + src/solver/toolkit.c | 12 +++++++++++- src/solver/treatmnt.c | 2 ++ 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 CMakeFiles/cmake.check_cache diff --git a/CMakeFiles/cmake.check_cache b/CMakeFiles/cmake.check_cache new file mode 100644 index 000000000..3dccd7317 --- /dev/null +++ b/CMakeFiles/cmake.check_cache @@ -0,0 +1 @@ +# This file is generated by cmake for dependency checking of the CMakeCache.txt file diff --git a/src/solver/toolkit.c b/src/solver/toolkit.c index 032f385f5..16f918018 100644 --- a/src/solver/toolkit.c +++ b/src/solver/toolkit.c @@ -15,6 +15,7 @@ #include #include +#include #include "headers.h" #include "version.h" @@ -1910,6 +1911,7 @@ EXPORT_TOOLKIT int swmm_getNodePollut(int index, SM_NodePollut type, double **po else { *length = Nobjects[POLLUT]; + printf("test_getNodePollut"); switch (type) { case SM_NODEQUAL: @@ -1917,6 +1919,7 @@ EXPORT_TOOLKIT int swmm_getNodePollut(int index, SM_NodePollut type, double **po for (p = 0; p < Nobjects[POLLUT]; p++) { result[p] = Node[index].newQual[p]; + printf("swmm_getNodePollut nodeQual = %f\n", result[p]); } *pollutArray = result; } break; @@ -1925,6 +1928,7 @@ EXPORT_TOOLKIT int swmm_getNodePollut(int index, SM_NodePollut type, double **po for (p=0; p < Nobjects[POLLUT]; p++) { result[p] = Node[index].inQual[p]; + printf("swmm_getNodePollut inflowQual = %f\n", result[p]); } *pollutArray = result; *length = Nobjects[POLLUT]; @@ -1934,6 +1938,7 @@ EXPORT_TOOLKIT int swmm_getNodePollut(int index, SM_NodePollut type, double **po for (p=0; p < Nobjects[POLLUT]; p++) { result[p] = Node[index].reactorQual[p]; + printf("swmm_getNodePollut reactorQual = %f\n", result[p]); } *pollutArray = result; } break; @@ -1978,6 +1983,7 @@ EXPORT_TOOLKIT int swmm_setNodePollut(int index, SM_NodePollut type, int polluta { Node[index].extQual[pollutant_index] = pollutant_value; Node[index].extPollutFlag[pollutant_index] = 1; + printf("swmm_setNodePollut value = %f\n", pollutant_value); } break; default: error_code_index = ERR_API_OUTBOUNDS; break; } @@ -2065,7 +2071,7 @@ EXPORT_TOOLKIT int swmm_getLinkPollut(int index, SM_LinkPollut type, double **po else { *length = Nobjects[POLLUT]; - + printf("test_print"); switch (type) { case SM_LINKQUAL: @@ -2073,6 +2079,7 @@ EXPORT_TOOLKIT int swmm_getLinkPollut(int index, SM_LinkPollut type, double **po for (p = 0; p < Nobjects[POLLUT]; p++) { result[p] = Link[index].newQual[p]; + printf("swmm_getLinkPollut linkQual = %f\n", result[p]); } *pollutArray = result; } break; @@ -2093,6 +2100,7 @@ EXPORT_TOOLKIT int swmm_getLinkPollut(int index, SM_LinkPollut type, double **po for (p = 0; p < Nobjects[POLLUT]; p++) { result[p] = Link[index].reactorQual[p]; + printf("swmm_getLinkPollut reactorQual = %f\n", result[p]); } *pollutArray = result; } break; @@ -2113,6 +2121,7 @@ EXPORT_TOOLKIT int swmm_setLinkPollut(int index, SM_LinkPollut type, int polluta /// Purponse: Set pollutant concentration in links { int error_code_index = 0; + printf("test_setLinkPollut"); // Check if Open if(swmm_IsOpenFlag() == FALSE) @@ -2138,6 +2147,7 @@ EXPORT_TOOLKIT int swmm_setLinkPollut(int index, SM_LinkPollut type, int polluta { Link[index].extQual[pollutant_index] = pollutant_value; Link[index].extPollutFlag[pollutant_index] = 1; + printf("swmm_setLinkPollut value = %f\n", pollutant_value); } break; default: error_code_index = ERR_API_OUTBOUNDS; break; } diff --git a/src/solver/treatmnt.c b/src/solver/treatmnt.c index 1b1491e49..da6c547d5 100644 --- a/src/solver/treatmnt.c +++ b/src/solver/treatmnt.c @@ -17,6 +17,7 @@ #include #include +#include #include "headers.h" //----------------------------------------------------------------------------- @@ -267,6 +268,7 @@ void treatmnt_treat(int j, double q, double v, double tStep) else if( Node[j].extPollutFlag[p] == 1) { cOut = Node[j].extQual[p]; + printf("treatmnt_treat value: %f\n", cOut); } // --- concentration-type equations get applied to nodal concentration From 9ddf3f98ec08ccac06e53ee15f9c10d17e3d88e8 Mon Sep 17 00:00:00 2001 From: bemason Date: Tue, 12 Apr 2022 13:56:33 -0400 Subject: [PATCH 181/190] remove print statements --- src/solver/toolkit.c | 16 +++------------- src/solver/treatmnt.c | 1 - 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/src/solver/toolkit.c b/src/solver/toolkit.c index 16f918018..47985c7df 100644 --- a/src/solver/toolkit.c +++ b/src/solver/toolkit.c @@ -1911,7 +1911,6 @@ EXPORT_TOOLKIT int swmm_getNodePollut(int index, SM_NodePollut type, double **po else { *length = Nobjects[POLLUT]; - printf("test_getNodePollut"); switch (type) { case SM_NODEQUAL: @@ -1919,8 +1918,7 @@ EXPORT_TOOLKIT int swmm_getNodePollut(int index, SM_NodePollut type, double **po for (p = 0; p < Nobjects[POLLUT]; p++) { result[p] = Node[index].newQual[p]; - printf("swmm_getNodePollut nodeQual = %f\n", result[p]); - } + } *pollutArray = result; } break; case SM_NODECIN: @@ -1928,7 +1926,6 @@ EXPORT_TOOLKIT int swmm_getNodePollut(int index, SM_NodePollut type, double **po for (p=0; p < Nobjects[POLLUT]; p++) { result[p] = Node[index].inQual[p]; - printf("swmm_getNodePollut inflowQual = %f\n", result[p]); } *pollutArray = result; *length = Nobjects[POLLUT]; @@ -1938,7 +1935,6 @@ EXPORT_TOOLKIT int swmm_getNodePollut(int index, SM_NodePollut type, double **po for (p=0; p < Nobjects[POLLUT]; p++) { result[p] = Node[index].reactorQual[p]; - printf("swmm_getNodePollut reactorQual = %f\n", result[p]); } *pollutArray = result; } break; @@ -1983,7 +1979,6 @@ EXPORT_TOOLKIT int swmm_setNodePollut(int index, SM_NodePollut type, int polluta { Node[index].extQual[pollutant_index] = pollutant_value; Node[index].extPollutFlag[pollutant_index] = 1; - printf("swmm_setNodePollut value = %f\n", pollutant_value); } break; default: error_code_index = ERR_API_OUTBOUNDS; break; } @@ -2071,7 +2066,6 @@ EXPORT_TOOLKIT int swmm_getLinkPollut(int index, SM_LinkPollut type, double **po else { *length = Nobjects[POLLUT]; - printf("test_print"); switch (type) { case SM_LINKQUAL: @@ -2079,8 +2073,7 @@ EXPORT_TOOLKIT int swmm_getLinkPollut(int index, SM_LinkPollut type, double **po for (p = 0; p < Nobjects[POLLUT]; p++) { result[p] = Link[index].newQual[p]; - printf("swmm_getLinkPollut linkQual = %f\n", result[p]); - } + } *pollutArray = result; } break; case SM_TOTALLOAD: @@ -2100,7 +2093,6 @@ EXPORT_TOOLKIT int swmm_getLinkPollut(int index, SM_LinkPollut type, double **po for (p = 0; p < Nobjects[POLLUT]; p++) { result[p] = Link[index].reactorQual[p]; - printf("swmm_getLinkPollut reactorQual = %f\n", result[p]); } *pollutArray = result; } break; @@ -2121,8 +2113,7 @@ EXPORT_TOOLKIT int swmm_setLinkPollut(int index, SM_LinkPollut type, int polluta /// Purponse: Set pollutant concentration in links { int error_code_index = 0; - printf("test_setLinkPollut"); - + // Check if Open if(swmm_IsOpenFlag() == FALSE) { @@ -2147,7 +2138,6 @@ EXPORT_TOOLKIT int swmm_setLinkPollut(int index, SM_LinkPollut type, int polluta { Link[index].extQual[pollutant_index] = pollutant_value; Link[index].extPollutFlag[pollutant_index] = 1; - printf("swmm_setLinkPollut value = %f\n", pollutant_value); } break; default: error_code_index = ERR_API_OUTBOUNDS; break; } diff --git a/src/solver/treatmnt.c b/src/solver/treatmnt.c index da6c547d5..aeaadcef7 100644 --- a/src/solver/treatmnt.c +++ b/src/solver/treatmnt.c @@ -268,7 +268,6 @@ void treatmnt_treat(int j, double q, double v, double tStep) else if( Node[j].extPollutFlag[p] == 1) { cOut = Node[j].extQual[p]; - printf("treatmnt_treat value: %f\n", cOut); } // --- concentration-type equations get applied to nodal concentration From 9867d925b6e1204933513e0fc9d75f2e80adee96 Mon Sep 17 00:00:00 2001 From: Constantine Karos <36245370+karosc@users.noreply.github.com> Date: Tue, 12 Apr 2022 14:20:29 -0400 Subject: [PATCH 182/190] Update nrtests benchmark tag to v2.0.1 --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 2f379be56..e52504728 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -113,7 +113,7 @@ jobs: - name: Before reg test env: NRTESTS_URL: https://github.com/OpenWaterAnalytics/swmm-nrtestsuite - BENCHMARK_TAG: v1.0.4-dev + BENCHMARK_TAG: v2.0.1 run: ./${{ matrix.before_reg_test }} ${{ env.BENCHMARK_TAG }} - name: Run reg test From 1e556b27a5245a15d29b2817d27973e17a32466b Mon Sep 17 00:00:00 2001 From: Constantine Karos <36245370+karosc@users.noreply.github.com> Date: Wed, 13 Apr 2022 10:49:31 -0400 Subject: [PATCH 183/190] Add comment cmake option addition --- .github/workflows/build-test.yml | 1 + CMakeLists.txt | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index e52504728..41e7c5020 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -40,6 +40,7 @@ jobs: - os: ubuntu-20.04 sys_pkgs: sudo apt install libboost-dev libboost-all-dev + # Statically link libraries with -s switch to address GitHub Ubuntu 20.04 symbol errors (issue #340) build_unit_test: make.sh -s -t -g "Unix Makefiles" build_reg_test: make.sh -s -g "Unix Makefiles" before_reg_test: before-nrtest.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 816a48359..087455567 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,6 +48,8 @@ set(CONFIG_DIST "cmake") # Define build options option(BUILD_TESTS "Build component tests (requires Boost)" OFF) option(BUILD_DOCS "Build toolkit docs (requires Doxygen)" OFF) + +# Added option to statically link libraries to address GitHub Ubuntu 20.04 symbol errors (issue #340) option(BUILD_SHARED_LIBS "Build using shared libraries" ON) # Add project subdirectories From 35ec2c7109ff4892dc1b658dc5a8e17f943d9aa1 Mon Sep 17 00:00:00 2001 From: bemason Date: Thu, 24 Mar 2022 13:44:36 -0400 Subject: [PATCH 184/190] Modified pollutant getter functions 1. Fixed spacing for swmm_getNodePollut. 2. Added *length parameter to the SM_NODECIN and SM_NODEREACTORC cases in getNodePollut. 3. Added case for SM_LINKREACTORC in swmm_getLinkPollut. --- src/solver/toolkit.c | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/src/solver/toolkit.c b/src/solver/toolkit.c index c5b8103c8..35d89ad49 100644 --- a/src/solver/toolkit.c +++ b/src/solver/toolkit.c @@ -1920,20 +1920,24 @@ EXPORT_TOOLKIT int swmm_getNodePollut(int index, SM_NodePollut type, double **po *pollutArray = result; *length = Nobjects[POLLUT]; } break; - case SM_NODECIN: - { - for (p=0; p < Nobjects[POLLUT]; p++) - { - result[p] = Node[index].inQual[p]; - } *pollutArray = result; - } break; - case SM_NODEREACTORC: - { - for (p=0; p < Nobjects[POLLUT]; p++) - { - result[p] = Node[index].reactorQual[p]; - } *pollutArray = result; - } break; + case SM_NODECIN: + { + for (p=0; p < Nobjects[POLLUT]; p++) + { + result[p] = Node[index].inQual[p]; + } + *pollutArray = result; + *length = Nobjects[POLLUT]; + } break; + case SM_NODEREACTORC: + { + for (p=0; p < Nobjects[POLLUT]; p++) + { + result[p] = Node[index].reactorQual[p]; + } + *pollutArray = result; + *length = Nobjects[POLLUT]; + } break; default: error_code_index = ERR_API_OUTBOUNDS; break; } } @@ -2076,6 +2080,15 @@ EXPORT_TOOLKIT int swmm_getLinkPollut(int index, SM_LinkPollut type, double **po *pollutArray = result; *length = Nobjects[POLLUT]; } break; + case SM_LINKREACTORC: + { + for (p = 0; p < Nobjects[POLLUT]; p++) + { + result[p] = Link[index].reactorQual[p]; + } + *pollutArray = result; + *length = Nobjects[POLLUT]; + } break; default: error_code_index = ERR_API_OUTBOUNDS; break; } } From b2529a0620aa95732fcfecb371677b47e54c89c2 Mon Sep 17 00:00:00 2001 From: bemason Date: Mon, 28 Mar 2022 13:23:49 -0400 Subject: [PATCH 185/190] Updated test_pollut.cpp Have not run tests yet but committing to save changes. --- tests/solver/test_pollut.cpp | 54 ++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/tests/solver/test_pollut.cpp b/tests/solver/test_pollut.cpp index 9b879d977..da302f5c3 100644 --- a/tests/solver/test_pollut.cpp +++ b/tests/solver/test_pollut.cpp @@ -164,7 +164,10 @@ BOOST_FIXTURE_TEST_CASE(get_node_pollutant_values_cin, FixtureBeforeStep_Pollut_ step_ind = 0; do { - + // Check length is set correctly + BOOST_CHECK_EQUAL(length == 1) + + // Get inflow concentration error = swmm_getNodePollut(1, SM_NODECIN, &node_qual, &length); BOOST_REQUIRE(error == ERR_NONE); @@ -182,7 +185,7 @@ BOOST_FIXTURE_TEST_CASE(get_node_pollutant_values_cin, FixtureBeforeStep_Pollut_ swmm_end(); } -// Testing Reactor Concentration +// Testing Reactor Concentration in Node BOOST_FIXTURE_TEST_CASE(get_node_reactor_pollutant, FixtureBeforeStep_Pollut_Node){ int error, step_ind; @@ -198,7 +201,10 @@ BOOST_FIXTURE_TEST_CASE(get_node_reactor_pollutant, FixtureBeforeStep_Pollut_Nod step_ind = 0; do { - // Check for steady state after 1000 steps. + // Check length is set correctly + BOOST_CHECK_EQUAL(length == 1) + + // Check for steady state after 1000 steps. // 1000 is a aribitarly long time duration, it can be any value as long // the system reaches a steady state @@ -221,6 +227,48 @@ BOOST_FIXTURE_TEST_CASE(get_node_reactor_pollutant, FixtureBeforeStep_Pollut_Nod swmm_end(); } +// Testing Reactor Concentration in Link +BOOST_FIXTURE_TEST_CASE(get_link_reactor_pollutant, FixtureBeforeStep_Pollut_Link){ + + int error, step_ind; + double* old_qual; + double* new_qual; + double elapsedTime = 0.0; + double total_pollutant = 0.0; + int length; + + // Pollutant IDs + int P1 = 0; + + step_ind = 0; + do + { + // Check length is set correctly + BOOST_CHECK_EQUAL(length == 1) + + // Check for steady state after 1000 steps. + // 1000 is a aribitarly long time duration, it can be any value as long + // the system reaches a steady state + + // Get reactor concentration + error = swmm_getLinkPollut(1, SM_LINKREACTORC, &new_qual, &length); + BOOST_REQUIRE(error == ERR_NONE); + + if (step_ind > 1000) + { + BOOST_CHECK_CLOSE(old_qual[P1], new_qual[P1], 0.001); + } + + old_qual = new_qual; + + // Route Model Forward + error = swmm_step(&elapsedTime); + step_ind+=1; + }while (elapsedTime != 0 && !error); + BOOST_REQUIRE(error == ERR_NONE); + swmm_end(); +} + // Testing Pollutant Setter - Node - Cumulative and mass balance BOOST_FIXTURE_TEST_CASE(set_node_pollutant_cumulative_values, FixtureBeforeStep_Pollut_Node){ From 4a0bbdcd6b37027828c28420dc2dd085d4a5e437 Mon Sep 17 00:00:00 2001 From: bemason Date: Tue, 29 Mar 2022 11:36:20 -0400 Subject: [PATCH 186/190] Update test_pollut.cc 1. Added a check to confirm length is getting pulled/set correctly. 2. Added a test for link reactorQual. --- tests/solver/test_pollut.cpp | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/tests/solver/test_pollut.cpp b/tests/solver/test_pollut.cpp index da302f5c3..61cd13a40 100644 --- a/tests/solver/test_pollut.cpp +++ b/tests/solver/test_pollut.cpp @@ -60,6 +60,8 @@ BOOST_FIXTURE_TEST_CASE(get_pollut_values, FixtureBeforeStep){ { // subcatchment buildup error = swmm_getSubcatchPollut(subc_ind, SM_BUILDUP, &buildup_array, &length); + // Check length is set correctly + BOOST_CHECK(length == 2); BOOST_REQUIRE(error == ERR_NONE); BOOST_CHECK_SMALL(buildup_array[TSS] - 31.906912, 0.0001); BOOST_CHECK_SMALL(buildup_array[Lead] - 0.0, 0.0001); @@ -164,11 +166,11 @@ BOOST_FIXTURE_TEST_CASE(get_node_pollutant_values_cin, FixtureBeforeStep_Pollut_ step_ind = 0; do { - // Check length is set correctly - BOOST_CHECK_EQUAL(length == 1) - + // Get inflow concentration error = swmm_getNodePollut(1, SM_NODECIN, &node_qual, &length); + // Check length is set correctly + BOOST_CHECK(length == 1); BOOST_REQUIRE(error == ERR_NONE); // Check for constant influent @@ -201,15 +203,14 @@ BOOST_FIXTURE_TEST_CASE(get_node_reactor_pollutant, FixtureBeforeStep_Pollut_Nod step_ind = 0; do { - // Check length is set correctly - BOOST_CHECK_EQUAL(length == 1) - // Check for steady state after 1000 steps. // 1000 is a aribitarly long time duration, it can be any value as long // the system reaches a steady state // Get reactor concentration error = swmm_getNodePollut(1, SM_NODEREACTORC, &new_qual, &length); + // Check length is set correctly + BOOST_CHECK(length == 1); BOOST_REQUIRE(error == ERR_NONE); if (step_ind > 1000) @@ -243,18 +244,17 @@ BOOST_FIXTURE_TEST_CASE(get_link_reactor_pollutant, FixtureBeforeStep_Pollut_Lin step_ind = 0; do { - // Check length is set correctly - BOOST_CHECK_EQUAL(length == 1) - // Check for steady state after 1000 steps. // 1000 is a aribitarly long time duration, it can be any value as long // the system reaches a steady state // Get reactor concentration error = swmm_getLinkPollut(1, SM_LINKREACTORC, &new_qual, &length); + // Check length is set correctly + BOOST_CHECK(length == 1); BOOST_REQUIRE(error == ERR_NONE); - if (step_ind > 1000) + if (step_ind > 1500) { BOOST_CHECK_CLOSE(old_qual[P1], new_qual[P1], 0.001); } @@ -269,7 +269,6 @@ BOOST_FIXTURE_TEST_CASE(get_link_reactor_pollutant, FixtureBeforeStep_Pollut_Lin swmm_end(); } - // Testing Pollutant Setter - Node - Cumulative and mass balance BOOST_FIXTURE_TEST_CASE(set_node_pollutant_cumulative_values, FixtureBeforeStep_Pollut_Node){ From 150d486bc0d250ec0c86c567001abd8336116076 Mon Sep 17 00:00:00 2001 From: bemason Date: Tue, 5 Apr 2022 10:16:10 -0400 Subject: [PATCH 187/190] Changes for node pollutant setter 1. Wrote setNodePollut like setLinkPollut in toolkit.c and toolkit.h. Before it did not take the SM_NodePollut type so I added it. 2. Modified the tests in test_pollut.cpp to include this new parameter. Passed the tests. --- src/solver/include/toolkit.h | 3 ++- src/solver/toolkit.c | 19 ++++++++++++++----- tests/solver/test_pollut.cpp | 6 +++--- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/solver/include/toolkit.h b/src/solver/include/toolkit.h index 84d3ac083..7a0bdf250 100644 --- a/src/solver/include/toolkit.h +++ b/src/solver/include/toolkit.h @@ -394,11 +394,12 @@ EXPORT_TOOLKIT int swmm_getNodePollut(int index, SM_NodePollut type, double **po @param pollutant_value Pollutant value to set @return Error code */ -EXPORT_TOOLKIT int swmm_setNodePollut(int index, int pollutant_index, double pollutant_value); +EXPORT_TOOLKIT int swmm_setNodePollut(int index, SM_NodePollut type, int pollutant_index, double pollutant_value); /** @brief Sets pollutant values for a specified node. @param index The index of a node + @param type The property type code (See @ref SM_NodePollut) @param pollutant_index Pollutant index to set @param pollutant_value Pollutant value to set @return Error code diff --git a/src/solver/toolkit.c b/src/solver/toolkit.c index 35d89ad49..c76be3d88 100644 --- a/src/solver/toolkit.c +++ b/src/solver/toolkit.c @@ -1944,11 +1944,12 @@ EXPORT_TOOLKIT int swmm_getNodePollut(int index, SM_NodePollut type, double **po return error_getCode(error_code_index); } -EXPORT_TOOLKIT int swmm_setNodePollut(int index, int pollutant_index, double pollutant_value) +EXPORT_TOOLKIT int swmm_setNodePollut(int index, SM_NodePollut type, int pollutant_index, double pollutant_value) /// /// Input: index = Index of desired ID +/// type = SM_NODEQUAL - Set's node's qual and allows accounting for loss and mixing calculation /// pollutant_index = Index of desired polluant -// pollutant_value = Value of the pollutant +/// pollutant_value = Value of the pollutant /// Return: API Error /// Purpose: Set pollutant concentration in nodes at the current time step { @@ -1972,8 +1973,16 @@ EXPORT_TOOLKIT int swmm_setNodePollut(int index, int pollutant_index, double pol } if (pollutant_index <= Nobjects[POLLUT]) { - Node[index].extQual[pollutant_index] = pollutant_value; - Node[index].extPollutFlag[pollutant_index] = 1; + switch(type) + { + case SM_NODEQUAL: + { + Node[index].extQual[pollutant_index] = pollutant_value; + Node[index].extPollutFlag[pollutant_index] = 1; + } break; + default: error_code_index = ERR_API_OUTBOUNDS; break; + } + } } return error_getCode(error_code_index); @@ -2102,7 +2111,7 @@ EXPORT_TOOLKIT int swmm_setLinkPollut(int index, SM_LinkPollut type, int polluta /// type = SM_LINKQUAL - Sets link's qual and allows accounting for loss and mixing calculation /// pollutant_index = index of pollutant to set /// pollutant_value = concentration to set -/// Output: API error +/// Return: API error /// Purponse: Set pollutant concentration in links { int error_code_index = 0; diff --git a/tests/solver/test_pollut.cpp b/tests/solver/test_pollut.cpp index 61cd13a40..e1c978f95 100644 --- a/tests/solver/test_pollut.cpp +++ b/tests/solver/test_pollut.cpp @@ -285,7 +285,7 @@ BOOST_FIXTURE_TEST_CASE(set_node_pollutant_cumulative_values, FixtureBeforeStep_ do { // Set pollutant - error = swmm_setNodePollut(1, P1, 0); + error = swmm_setNodePollut(1, SM_NODEQUAL, P1, 0); BOOST_REQUIRE(error == ERR_NONE); // Route Model Forward error = swmm_step(&elapsedTime); @@ -320,7 +320,7 @@ BOOST_FIXTURE_TEST_CASE(set_node_pollutant_stepwise_values, FixtureBeforeStep_Po do { // Set pollutant - error = swmm_setNodePollut(1, P1, 1.234); + error = swmm_setNodePollut(1, SM_NODEQUAL, P1, 1.234); BOOST_REQUIRE(error == ERR_NONE); // Route Model Forward @@ -357,7 +357,7 @@ BOOST_FIXTURE_TEST_CASE(set_node_pollutant_stepwise_values_2, FixtureBeforeStep_ do { // Set pollutant - error = swmm_setNodePollut(1, P1, 50.0); + error = swmm_setNodePollut(1, SM_NODEQUAL, P1, 50.0); BOOST_REQUIRE(error == ERR_NONE); // Route Model Forward From f57299e841b1fb4544a101df87fc719f5fea493a Mon Sep 17 00:00:00 2001 From: bemason Date: Wed, 6 Apr 2022 11:08:12 -0400 Subject: [PATCH 188/190] Update toolkit.c Moved length statement out of switch case for both node and link getters to make the code more concise. --- src/solver/toolkit.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/solver/toolkit.c b/src/solver/toolkit.c index c76be3d88..d073e5fb5 100644 --- a/src/solver/toolkit.c +++ b/src/solver/toolkit.c @@ -1909,6 +1909,7 @@ EXPORT_TOOLKIT int swmm_getNodePollut(int index, SM_NodePollut type, double **po else { + *length = Nobjects[POLLUT]; switch (type) { case SM_NODEQUAL: @@ -1918,7 +1919,6 @@ EXPORT_TOOLKIT int swmm_getNodePollut(int index, SM_NodePollut type, double **po result[p] = Node[index].newQual[p]; } *pollutArray = result; - *length = Nobjects[POLLUT]; } break; case SM_NODECIN: { @@ -1936,7 +1936,6 @@ EXPORT_TOOLKIT int swmm_getNodePollut(int index, SM_NodePollut type, double **po result[p] = Node[index].reactorQual[p]; } *pollutArray = result; - *length = Nobjects[POLLUT]; } break; default: error_code_index = ERR_API_OUTBOUNDS; break; } @@ -2065,6 +2064,8 @@ EXPORT_TOOLKIT int swmm_getLinkPollut(int index, SM_LinkPollut type, double **po else { + *length = Nobjects[POLLUT]; + switch (type) { case SM_LINKQUAL: @@ -2074,7 +2075,6 @@ EXPORT_TOOLKIT int swmm_getLinkPollut(int index, SM_LinkPollut type, double **po result[p] = Link[index].newQual[p]; } *pollutArray = result; - *length = Nobjects[POLLUT]; } break; case SM_TOTALLOAD: { @@ -2087,7 +2087,6 @@ EXPORT_TOOLKIT int swmm_getLinkPollut(int index, SM_LinkPollut type, double **po } } *pollutArray = result; - *length = Nobjects[POLLUT]; } break; case SM_LINKREACTORC: { @@ -2096,7 +2095,6 @@ EXPORT_TOOLKIT int swmm_getLinkPollut(int index, SM_LinkPollut type, double **po result[p] = Link[index].reactorQual[p]; } *pollutArray = result; - *length = Nobjects[POLLUT]; } break; default: error_code_index = ERR_API_OUTBOUNDS; break; } From 3aa785a614720f9de12b19af35e2813ca692967f Mon Sep 17 00:00:00 2001 From: bemason Date: Mon, 11 Apr 2022 15:04:42 -0400 Subject: [PATCH 189/190] Fixed error in swmm_setLinkPollut() in toolkit.c Checked object index but incorrectly called Nobjects[NODE] instead of Nobjects[LINK] --- src/solver/toolkit.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solver/toolkit.c b/src/solver/toolkit.c index d073e5fb5..032f385f5 100644 --- a/src/solver/toolkit.c +++ b/src/solver/toolkit.c @@ -2120,7 +2120,7 @@ EXPORT_TOOLKIT int swmm_setLinkPollut(int index, SM_LinkPollut type, int polluta error_code_index = ERR_API_INPUTNOTOPEN; } // Check if object index is within bounds - else if (index < 0 || index >= Nobjects[NODE]) + else if (index < 0 || index >= Nobjects[LINK]) { error_code_index = ERR_API_OBJECT_INDEX; } From e25a620c47a9925f67df0e652cf80bba4eb40611 Mon Sep 17 00:00:00 2001 From: bemason Date: Thu, 14 Apr 2022 08:44:35 -0400 Subject: [PATCH 190/190] removed stdio.h from treatmnt.c from debugging --- src/solver/treatmnt.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/solver/treatmnt.c b/src/solver/treatmnt.c index aeaadcef7..1b1491e49 100644 --- a/src/solver/treatmnt.c +++ b/src/solver/treatmnt.c @@ -17,7 +17,6 @@ #include #include -#include #include "headers.h" //-----------------------------------------------------------------------------