From 8ff5fffe7364a3512caf813eacc688e13af5ebdc Mon Sep 17 00:00:00 2001 From: Tryby Date: Wed, 19 Oct 2022 15:54:10 -0400 Subject: [PATCH 01/14] WIP use package --- .github/workflows/build-and-test.yml | 43 ++++++++++------------------ extern/boost.cmake | 23 +++++---------- 2 files changed, 22 insertions(+), 44 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 4414b2820..824cf4dc3 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -10,11 +10,14 @@ env: OMP_NUM_THREADS: 1 BUILD_HOME: build TEST_HOME: nrtests + PACKAGE_NAME: vcpkg-export-20220826-200052.1.0.0 + PKG_NAME: vcpkg-export-20220826-200052 jobs: unit_test: name: Build and unit test runs-on: windows-2019 + environment: testing defaults: run: shell: cmd @@ -30,37 +33,21 @@ jobs: repository: michaeltryby/ci-tools path: ci-tools - - name: Add tar.exe - if: ${{ runner.os == 'Windows' }} - shell: pwsh - run: | - "C:\Program Files\Git\usr\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 - - - - name: Cache boost - id: cache-boost - uses: actions/cache@v2 - env: - cache_paths: | - C:/ProgramData/chocolatey/lib/boost-* - C:/local/boost_* - pkg_file_hash: ${{ hashFiles('**/windows/packages.config') }} - with: - path: ${{ env.cache_paths }} - key: ${{ runner.os }}-cache-boost-${{ env.pkg_file_hash }} - restore-keys: | - ${{ runner.os }}-cache-boost- - ${{ runner.os }}-cache- - ${{ runner.os }}- - - - name: Install boost - if: steps.cache-boost.outputs.cache-hit != 'true' + - name: Install boost-test env: - pkg_cmnd: choco install -y packages.config - run: ${{ env.pkg_cmnd }} + REMOTE_STORE: "https://nuget.pkg.github.com/michaeltryby/index.json" + USERNAME: michaeltryby + run: | + nuget sources add -Name github -Source ${{ env.REMOTE_STORE }} -Username ${{ env.USERNAME }} -Password ${{ secrets.ACCESS_TOKEN }} + nuget install ${{env.PKG_NAME}} -Source github - name: Build and unit test - run: make.cmd /t /g "Visual Studio 16 2019" + env: + TOOL_CHAIN_PATH: \scripts\buildsystems\vcpkg.cmake + run: | + cd build + cmake .. -DBUILD-TESTS=ON -DCMAKE_TOOLCHAIN_FILE=.\${{env.PACKAGE_NAME_LONG}}\scripts\buildsystems\vcpkg.cmake + cmake --build . reg_test: diff --git a/extern/boost.cmake b/extern/boost.cmake index 84d29175a..28a5568a5 100644 --- a/extern/boost.cmake +++ b/extern/boost.cmake @@ -2,7 +2,7 @@ # CMakeLists.txt - CMake configuration file for swmm-solver/extern # # Created: March 16, 2020 -# Updated: May 21, 2020 +# Updated: Oct 19, 2022 # # Author: Michael E. Tryby # US EPA - ORD/CESER @@ -17,22 +17,13 @@ else() endif() -# Environment variable "BOOST_ROOT_X_XX_X" points to local install location -if(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}) - -elseif(DEFINED ENV{BOOST_ROOT_1_67_0}) - set(BOOST_ROOT $ENV{BOOST_ROOT_1_67_0}) - -endif() - - -find_package(Boost 1.67.0 +find_package( + Boost + REQUIRED COMPONENTS unit_test_framework ) -include_directories (${Boost_INCLUDE_DIRS}) +include_directories( + ${Boost_INCLUDE_DIRS} +) From c987969a84c296d7955ddcccdd07dad39c2d6d87 Mon Sep 17 00:00:00 2001 From: Tryby Date: Wed, 19 Oct 2022 16:03:34 -0400 Subject: [PATCH 02/14] Fix path issue --- .github/workflows/build-and-test.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 824cf4dc3..2a7e47fbe 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -21,18 +21,11 @@ jobs: defaults: run: shell: cmd - working-directory: ci-tools/windows steps: - name: Checkout repo uses: actions/checkout@v2 - - name: Checkout submodule - uses: actions/checkout@v2 - with: - repository: michaeltryby/ci-tools - path: ci-tools - - name: Install boost-test env: REMOTE_STORE: "https://nuget.pkg.github.com/michaeltryby/index.json" From 7ec29311efe6b20f4072fc8b33d00f876aa8ccf6 Mon Sep 17 00:00:00 2001 From: Tryby Date: Wed, 19 Oct 2022 16:18:35 -0400 Subject: [PATCH 03/14] Build path issue --- .github/workflows/build-and-test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 2a7e47fbe..fb727ec37 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -26,6 +26,7 @@ jobs: - name: Checkout repo uses: actions/checkout@v2 + - name: Install boost-test env: REMOTE_STORE: "https://nuget.pkg.github.com/michaeltryby/index.json" @@ -39,7 +40,7 @@ jobs: TOOL_CHAIN_PATH: \scripts\buildsystems\vcpkg.cmake run: | cd build - cmake .. -DBUILD-TESTS=ON -DCMAKE_TOOLCHAIN_FILE=.\${{env.PACKAGE_NAME_LONG}}\scripts\buildsystems\vcpkg.cmake + cmake -DBUILD-TESTS=ON -DCMAKE_TOOLCHAIN_FILE=..\${{env.PACKAGE_NAME_LONG}}${{env.TOOL_CHAIN_PATH}} .. cmake --build . From 450fdba82e3906417281bb45a9b37cbb75830082 Mon Sep 17 00:00:00 2001 From: Tryby Date: Wed, 19 Oct 2022 16:27:46 -0400 Subject: [PATCH 04/14] Fix build path issue --- .github/workflows/build-and-test.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index fb727ec37..ba45e7733 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -39,9 +39,8 @@ jobs: env: TOOL_CHAIN_PATH: \scripts\buildsystems\vcpkg.cmake run: | - cd build - cmake -DBUILD-TESTS=ON -DCMAKE_TOOLCHAIN_FILE=..\${{env.PACKAGE_NAME_LONG}}${{env.TOOL_CHAIN_PATH}} .. - cmake --build . + cmake -B.\build -DBUILD-TESTS=ON -DCMAKE_TOOLCHAIN_FILE=.\${{env.PACKAGE_NAME_LONG}}${{env.TOOL_CHAIN_PATH}} . + cmake --build .\build --config DEBUG reg_test: From cd00d6c092eade5058d02944fb3d4b988550fa28 Mon Sep 17 00:00:00 2001 From: Tryby Date: Wed, 19 Oct 2022 16:30:14 -0400 Subject: [PATCH 05/14] Fix path bug --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index ba45e7733..fb5fc3e1c 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -39,7 +39,7 @@ jobs: env: TOOL_CHAIN_PATH: \scripts\buildsystems\vcpkg.cmake run: | - cmake -B.\build -DBUILD-TESTS=ON -DCMAKE_TOOLCHAIN_FILE=.\${{env.PACKAGE_NAME_LONG}}${{env.TOOL_CHAIN_PATH}} . + cmake -B.\build -DBUILD-TESTS=ON -DCMAKE_TOOLCHAIN_FILE=.\${{env.PACKAGE_NAME}}${{env.TOOL_CHAIN_PATH}} . cmake --build .\build --config DEBUG From 91c408b6cffe223ec97f13e462ffc6c6912e0ed1 Mon Sep 17 00:00:00 2001 From: Tryby Date: Wed, 19 Oct 2022 16:57:23 -0400 Subject: [PATCH 06/14] Fix variable name --- .github/workflows/build-and-test.yml | 7 +++++-- extern/boost.cmake | 1 - 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index fb5fc3e1c..0da560ae4 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -35,13 +35,16 @@ jobs: nuget sources add -Name github -Source ${{ env.REMOTE_STORE }} -Username ${{ env.USERNAME }} -Password ${{ secrets.ACCESS_TOKEN }} nuget install ${{env.PKG_NAME}} -Source github - - name: Build and unit test + - name: Build env: TOOL_CHAIN_PATH: \scripts\buildsystems\vcpkg.cmake run: | - cmake -B.\build -DBUILD-TESTS=ON -DCMAKE_TOOLCHAIN_FILE=.\${{env.PACKAGE_NAME}}${{env.TOOL_CHAIN_PATH}} . + cmake -B.\build -DBUILD_TESTS=ON -DCMAKE_TOOLCHAIN_FILE=.\${{env.PACKAGE_NAME}}${{env.TOOL_CHAIN_PATH}} . cmake --build .\build --config DEBUG + -name: Unit Test + run: ctest -B.\build -C Debug --output-on-failure + reg_test: name: Build and reg test diff --git a/extern/boost.cmake b/extern/boost.cmake index 28a5568a5..c512fb559 100644 --- a/extern/boost.cmake +++ b/extern/boost.cmake @@ -19,7 +19,6 @@ endif() find_package( Boost - REQUIRED COMPONENTS unit_test_framework ) From 9d42aaf7f2364c0978489b85b5ee96be596e1375 Mon Sep 17 00:00:00 2001 From: Tryby Date: Wed, 19 Oct 2022 17:42:13 -0400 Subject: [PATCH 07/14] Fix yml syntax error --- .github/workflows/build-and-test.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 0da560ae4..f95800832 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -26,7 +26,6 @@ jobs: - name: Checkout repo uses: actions/checkout@v2 - - name: Install boost-test env: REMOTE_STORE: "https://nuget.pkg.github.com/michaeltryby/index.json" @@ -42,7 +41,7 @@ jobs: cmake -B.\build -DBUILD_TESTS=ON -DCMAKE_TOOLCHAIN_FILE=.\${{env.PACKAGE_NAME}}${{env.TOOL_CHAIN_PATH}} . cmake --build .\build --config DEBUG - -name: Unit Test + - name: Unit Test run: ctest -B.\build -C Debug --output-on-failure From ca1c302cbd0448c5b9353bb183a9f70a4cc11e9e Mon Sep 17 00:00:00 2001 From: Tryby Date: Wed, 19 Oct 2022 17:51:15 -0400 Subject: [PATCH 08/14] Config test runner --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index f95800832..9e2d4f1ff 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -42,7 +42,7 @@ jobs: cmake --build .\build --config DEBUG - name: Unit Test - run: ctest -B.\build -C Debug --output-on-failure + run: ctest --test-dir .\build -C Debug --output-on-failure reg_test: From c54b60f60641453f7f43b4b244a837d666f848b8 Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Mon, 21 Nov 2022 11:10:47 -0500 Subject: [PATCH 09/14] Release 5.2.2 --- Readme.txt | 4 +- src/climate.c | 1619 +++++++++++++++++++++++++++++ src/consts.h | 102 ++ src/controls.c | 1553 ++++++++++++++++++++++++++++ src/culvert.c | 411 ++++++++ src/datetime.c | 528 ++++++++++ src/datetime.h | 72 ++ src/dwflow.c | 684 +++++++++++++ src/dynwave.c | 908 ++++++++++++++++ src/enums.h | 500 +++++++++ src/error.c | 49 + src/error.h | 182 ++++ src/error.txt | 135 +++ src/exfil.c | 252 +++++ src/exfil.h | 35 + src/findroot.c | 138 +++ src/findroot.h | 18 + src/flowrout.c | 800 +++++++++++++++ src/forcmain.c | 157 +++ src/funcs.h | 547 ++++++++++ src/gage.c | 705 +++++++++++++ src/globals.h | 172 ++++ src/gwater.c | 872 ++++++++++++++++ src/hash.c | 117 +++ src/hash.h | 30 + src/headers.h | 20 + src/hotstart.c | 544 ++++++++++ src/iface.c | 648 ++++++++++++ src/infil.c | 1006 ++++++++++++++++++ src/infil.h | 112 ++ src/inflow.c | 484 +++++++++ src/inlet.c | 1955 +++++++++++++++++++++++++++++++++++ src/inlet.h | 30 + src/input.c | 930 +++++++++++++++++ src/inputrpt.c | 354 +++++++ src/keywords.c | 172 ++++ src/keywords.h | 73 ++ src/kinwave.c | 272 +++++ src/landuse.c | 723 +++++++++++++ src/lid.c | 2031 ++++++++++++++++++++++++++++++++++++ src/lid.h | 236 +++++ src/lidproc.c | 1592 ++++++++++++++++++++++++++++ src/link.c | 2679 ++++++++++++++++++++++++++++++++++++++++++++++++ src/macros.h | 45 + src/main.c | 104 ++ src/massbal.c | 1046 +++++++++++++++++++ src/mathexpr.c | 768 ++++++++++++++ src/mathexpr.h | 36 + src/mempool.c | 204 ++++ src/mempool.h | 26 + src/node.c | 1494 +++++++++++++++++++++++++++ src/objects.h | 1090 ++++++++++++++++++++ src/odesolve.c | 234 +++++ src/odesolve.h | 19 + src/output.c | 956 +++++++++++++++++ src/project.c | 1335 ++++++++++++++++++++++++ src/qualrout.c | 519 ++++++++++ src/rain.c | 1166 +++++++++++++++++++++ src/rdii.c | 1539 ++++++++++++++++++++++++++++ src/report.c | 1519 +++++++++++++++++++++++++++ src/roadway.c | 194 ++++ src/routing.c | 962 +++++++++++++++++ src/runoff.c | 527 ++++++++++ src/shape.c | 369 +++++++ src/snow.c | 865 ++++++++++++++++ src/stats.c | 865 ++++++++++++++++ src/statsrpt.c | 958 +++++++++++++++++ src/street.c | 162 +++ src/street.h | 21 + src/subcatch.c | 1159 +++++++++++++++++++++ src/surfqual.c | 478 +++++++++ src/swmm5.c | 1721 +++++++++++++++++++++++++++++++ src/swmm5.def | 23 + src/swmm5.h | 157 +++ src/table.c | 895 ++++++++++++++++ src/text.h | 462 +++++++++ src/toposort.c | 533 ++++++++++ src/transect.c | 678 ++++++++++++ src/treatmnt.c | 456 +++++++++ src/xsect.c | 2618 ++++++++++++++++++++++++++++++++++++++++++++++ src/xsect.dat | 491 +++++++++ 81 files changed, 51143 insertions(+), 2 deletions(-) create mode 100644 src/climate.c create mode 100644 src/consts.h create mode 100644 src/controls.c create mode 100644 src/culvert.c create mode 100644 src/datetime.c create mode 100644 src/datetime.h create mode 100644 src/dwflow.c create mode 100644 src/dynwave.c create mode 100644 src/enums.h create mode 100644 src/error.c create mode 100644 src/error.h create mode 100644 src/error.txt create mode 100644 src/exfil.c create mode 100644 src/exfil.h create mode 100644 src/findroot.c create mode 100644 src/findroot.h create mode 100644 src/flowrout.c create mode 100644 src/forcmain.c create mode 100644 src/funcs.h create mode 100644 src/gage.c create mode 100644 src/globals.h create mode 100644 src/gwater.c create mode 100644 src/hash.c create mode 100644 src/hash.h create mode 100644 src/headers.h create mode 100644 src/hotstart.c create mode 100644 src/iface.c create mode 100644 src/infil.c create mode 100644 src/infil.h create mode 100644 src/inflow.c create mode 100644 src/inlet.c create mode 100644 src/inlet.h create mode 100644 src/input.c create mode 100644 src/inputrpt.c create mode 100644 src/keywords.c create mode 100644 src/keywords.h create mode 100644 src/kinwave.c create mode 100644 src/landuse.c create mode 100644 src/lid.c create mode 100644 src/lid.h create mode 100644 src/lidproc.c create mode 100644 src/link.c create mode 100644 src/macros.h create mode 100644 src/main.c create mode 100644 src/massbal.c create mode 100644 src/mathexpr.c create mode 100644 src/mathexpr.h create mode 100644 src/mempool.c create mode 100644 src/mempool.h create mode 100644 src/node.c create mode 100644 src/objects.h create mode 100644 src/odesolve.c create mode 100644 src/odesolve.h create mode 100644 src/output.c create mode 100644 src/project.c create mode 100644 src/qualrout.c create mode 100644 src/rain.c create mode 100644 src/rdii.c create mode 100644 src/report.c create mode 100644 src/roadway.c create mode 100644 src/routing.c create mode 100644 src/runoff.c create mode 100644 src/shape.c create mode 100644 src/snow.c create mode 100644 src/stats.c create mode 100644 src/statsrpt.c create mode 100644 src/street.c create mode 100644 src/street.h create mode 100644 src/subcatch.c create mode 100644 src/surfqual.c create mode 100644 src/swmm5.c create mode 100644 src/swmm5.def create mode 100644 src/swmm5.h create mode 100644 src/table.c create mode 100644 src/text.h create mode 100644 src/toposort.c create mode 100644 src/transect.c create mode 100644 src/treatmnt.c create mode 100644 src/xsect.c create mode 100644 src/xsect.dat diff --git a/Readme.txt b/Readme.txt index f092f1de5..7bbb52e66 100644 --- a/Readme.txt +++ b/Readme.txt @@ -1,8 +1,8 @@ -CONTENTS OF SWMM520_ENGINE.ZIP +CONTENTS OF SWMM522_ENGINE.ZIP ============================== The 'src' folder of this archive contains the C source code for -version 5.2.0 of the Storm Water Management Model's computational +version 5.2.2 of the Storm Water Management Model's computational engine. Consult the included 'Roadmap.txt' file for an overview of the various code modules. The code can be compiled into both a shared object library and a command line executable. Under Windows, the diff --git a/src/climate.c b/src/climate.c new file mode 100644 index 000000000..336f0aaf0 --- /dev/null +++ b/src/climate.c @@ -0,0 +1,1619 @@ +//----------------------------------------------------------------------------- +// climate.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Climate related functions. +// +// Update History +// ============== +// Build 5.1.007: +// - NCDC GHCN climate file format added. +// - Monthly adjustments for temperature, evaporation & rainfall added. +// Build 5.1.008: +// - Monthly adjustments for hyd. conductivity added. +// - Time series evaporation rates can now vary within a day. +// - Evaporation rates are now properly updated when only flow routing +// is being simulated. +// Build 5.1.010: +// - Hargreaves evaporation now computed using 7-day average temperatures. +// Build 5.1.011: +// - Monthly adjustment for hyd. conductivity <= 0 is ignored. +// Build 5.1.013: +// - Reads names of monthly adjustment patterns for various parameters +// of a subcatchment from the [ADJUSTMENTS] section of input file. +// Build 5.2.0: +// - Reads temperature units for use with GHCND climate files. +// - Support added for relative file names. +///----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include +#include +#include "headers.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +enum ClimateFileFormats {UNKNOWN_FORMAT, + USER_PREPARED, // SWMM 5's own user format + GHCND, // NCDC GHCN Daily format + TD3200, // NCDC TD3200 format + DLY0204}; // Canadian DLY02 or DLY04 format +static const int MAXCLIMATEVARS = 4; +static const int MAXDAYSPERMONTH = 32; + +// These variables are used when processing climate files. +enum ClimateVarType {TMIN, TMAX, EVAP, WIND}; +enum WindSpeedType {WDMV, AWND}; +enum TempUnitsType {DEG_C10, DEG_C, DEG_F}; +static char* ClimateVarWords[] = {"TMIN", "TMAX", "EVAP", "WDMV", "AWND", + NULL}; +static char* TempUnitsWords[] = {"C10", "C", "F", NULL}; + +//----------------------------------------------------------------------------- +// Data Structures +//----------------------------------------------------------------------------- +typedef struct +{ + double tAve; // moving avg. for daily temperature (deg F) + double tRng; // moving avg. for daily temp. range (deg F) + double ta[7]; // data window for tAve + double tr[7]; // data window for tRng + int count; // length of moving average window + int maxCount; // maximum length of moving average window + int front; // index of front of moving average window +} TMovAve; + + +//----------------------------------------------------------------------------- +// Shared variables +//----------------------------------------------------------------------------- +// Temperature variables +static double Tmin; // min. daily temperature (deg F) +static double Tmax; // max. daily temperature (deg F) +static double Trng; // 1/2 range of daily temperatures +static double Trng1; // prev. max - current min. temp. +static double Tave; // average daily temperature (deg F) +static double Hrsr; // time of min. temp. (hrs) +static double Hrss; // time of max. temp (hrs) +static double Hrday; // avg. of min/max temp times +static double Dhrdy; // hrs. between min. & max. temp. times +static double Dydif; // hrs. between max. & min. temp. times +static DateTime LastDay; // date of last day with temp. data +static TMovAve Tma; // moving average of daily temperatures + +// Evaporation variables +static DateTime NextEvapDate; // next date when evap. rate changes +static double NextEvapRate; // next evaporation rate (user units) + +// Climate file variables +static int FileFormat; // file format (see ClimateFileFormats) +static int FileYear; // current year of file data +static int FileMonth; // current month of year of file data +static int FileDay; // current day of month of file data +static int FileLastDay; // last day of current month of file data +static int FileElapsedDays; // number of days read from file +static double FileValue[4]; // current day's values of climate data +static double FileData[4][32]; // month's worth of daily climate data +static char FileLine[MAXLINE+1]; // line from climate data file + +static int FileFieldPos[4]; // start of data fields for file record +static int FileDateFieldPos; // start of date field for file record +static int FileWindType; // wind speed type +static int FileTempUnits; // GHCND file temperature units (C10, C or F) + +//----------------------------------------------------------------------------- +// External functions (defined in funcs.h) +//----------------------------------------------------------------------------- +// climate_readParams // called by input_parseLine +// climate_readEvapParams // called by input_parseLine +// climate_validate // called by project_validate +// climate_openFile // called by runoff_open +// climate_initState // called by project_init +// climate_setState // called by runoff_execute +// climate_getNextEvapDate // called by runoff_getTimeStep + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static int getFileFormat(void); +static void readFileLine(int *year, int *month); +static void readUserFileLine(int *year, int *month); +static void readTD3200FileLine(int *year, int *month); +static void readDLY0204FileLine(int *year, int *month); +static void readFileValues(void); + +static void setNextEvapDate(DateTime thedate); +static void setEvap(DateTime theDate); +static void setTemp(DateTime theDate); +static void setWind(DateTime theDate); +static void updateTempTimes(int day); +static void updateTempMoveAve(double tmin, double tmax); +static double getTempEvap(int day, double ta, double tr); + +static void updateFileValues(DateTime theDate); +static void parseUserFileLine(void); +static void parseTD3200FileLine(void); +static void parseDLY0204FileLine(void); +static void setTD3200FileValues(int param); + +static int isGhcndFormat(char* line); +static void readGhcndFileLine(int *year, int *month); +static void parseGhcndFileLine(void); +static double convertGhcndValue(int var, double v); + +//============================================================================= + +int climate_readParams(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// Purpose: reads climate/temperature parameters from input line of data +// +// Format of data can be +// TIMESERIES name +// FILE name (start) (units) +// WINDSPEED MONTHLY v1 v2 ... v12 +// WINDSPEED FILE +// SNOWMELT v1 v2 ... v6 +// ADC IMPERV/PERV v1 v2 ... v10 +// +{ + int i, j, k; + double x[6], y; + DateTime aDate; + char fname[MAXFNAME + 1]; + + // --- identify keyword + k = findmatch(tok[0], TempKeyWords); + if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[0]); + switch (k) + { + case 0: // Time series name + // --- check that time series name exists + if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); + i = project_findObject(TSERIES, tok[1]); + if ( i < 0 ) return error_setInpError(ERR_NAME, tok[1]); + + // --- record the time series as being the data source for temperature + Temp.dataSource = TSERIES_TEMP; + Temp.tSeries = i; + Tseries[i].refersTo = TSERIES_TEMP; + break; + + case 1: // Climate file + // --- record file as being source of temperature data + if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); + Temp.dataSource = FILE_TEMP; + + // --- save name and usage mode of external climate file + Fclimate.mode = USE_FILE; + sstrncpy(fname, tok[1], MAXFNAME); + sstrncpy(Fclimate.name, addAbsolutePath(fname), MAXFNAME); + + // --- save starting date to read from file if one is provided + Temp.fileStartDate = NO_DATE; + if ( ntoks > 2 ) + { + if ( *tok[2] != '*') + { + if ( !datetime_strToDate(tok[2], &aDate) ) + return error_setInpError(ERR_DATETIME, tok[2]); + Temp.fileStartDate = aDate; + } + } + + // --- file temperature units + FileTempUnits = DEG_F; + if (UnitSystem == SI) + FileTempUnits = DEG_C; + if (ntoks > 3) + { + i = findmatch(tok[3], TempUnitsWords); + if (i < 0) + return error_setInpError(ERR_KEYWORD, tok[3]); + FileTempUnits = i; + } + break; + + case 2: // Wind speeds + // --- check if wind speeds will be supplied from climate file + if ( strcomp(tok[1], w_FILE) ) + { + Wind.type = FILE_WIND; + } + + // --- otherwise read 12 monthly avg. wind speed values + else + { + if ( ntoks < 14 ) return error_setInpError(ERR_ITEMS, ""); + Wind.type = MONTHLY_WIND; + for (i=0; i<12; i++) + { + if ( !getDouble(tok[i+2], &y) ) + return error_setInpError(ERR_NUMBER, tok[i+2]); + Wind.aws[i] = y; + } + } + break; + + case 3: // Snowmelt params + if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); + for (i=1; i<7; i++) + { + if ( !getDouble(tok[i], &x[i-1]) ) + return error_setInpError(ERR_NUMBER, tok[i]); + } + // --- convert deg. C to deg. F for snowfall temperature + if ( UnitSystem == SI ) x[0] = 9./5.*x[0] + 32.0; + Snow.snotmp = x[0]; + Snow.tipm = x[1]; + Snow.rnm = x[2]; + Temp.elev = x[3] / UCF(LENGTH); + Temp.anglat = x[4]; + Temp.dtlong = x[5] / 60.0; + break; + + case 4: // Areal Depletion Curve data + // --- check if data is for impervious or pervious areas + if ( ntoks < 12 ) return error_setInpError(ERR_ITEMS, ""); + if ( match(tok[1], w_IMPERV) ) i = 0; + else if ( match(tok[1], w_PERV) ) i = 1; + else return error_setInpError(ERR_KEYWORD, tok[1]); + + // --- read 10 fractional values + for (j=0; j<10; j++) + { + if ( !getDouble(tok[j+2], &y) || y < 0.0 || y > 1.0 ) + return error_setInpError(ERR_NUMBER, tok[j+2]); + Snow.adc[i][j] = y; + } + break; + } + return 0; +} + +//============================================================================= + +int climate_readEvapParams(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// Purpose: reads evaporation parameters from input line of data. +// +// Data formats are: +// CONSTANT value +// MONTHLY v1 ... v12 +// TIMESERIES name +// TEMPERATURE +// FILE (v1 ... v12) +// RECOVERY name +// DRY_ONLY YES/NO +// +{ + int i, k; + double x; + + // --- find keyword indicating what form the evaporation data is in + k = findmatch(tok[0], EvapTypeWords); + if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[0]); + + // --- check for RECOVERY pattern data + if ( k == RECOVERY ) + { + if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); + i = project_findObject(TIMEPATTERN, tok[1]); + if ( i < 0 ) return error_setInpError(ERR_NAME, tok[1]); + Evap.recoveryPattern = i; + return 0; + } + + // --- check for no evaporation in wet periods + if ( k == DRYONLY ) + { + if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); + if ( strcomp(tok[1], w_NO ) ) Evap.dryOnly = FALSE; + else if ( strcomp(tok[1], w_YES ) ) Evap.dryOnly = TRUE; + else return error_setInpError(ERR_KEYWORD, tok[1]); + return 0; + } + + // --- process data depending on its form + Evap.type = k; + if ( k != TEMPERATURE_EVAP && ntoks < 2 ) + return error_setInpError(ERR_ITEMS, ""); + switch ( k ) + { + case CONSTANT_EVAP: + // --- for constant evap., fill monthly avg. values with same number + if ( !getDouble(tok[1], &x) ) + return error_setInpError(ERR_NUMBER, tok[1]); + for (i=0; i<12; i++) Evap.monthlyEvap[i] = x; + break; + + case MONTHLY_EVAP: + // --- for monthly evap., read a value for each month of year + if ( ntoks < 13 ) return error_setInpError(ERR_ITEMS, ""); + for ( i=0; i<12; i++) + if ( !getDouble(tok[i+1], &Evap.monthlyEvap[i]) ) + return error_setInpError(ERR_NUMBER, tok[i+1]); + break; + + case TIMESERIES_EVAP: + // --- for time series evap., read name of time series + i = project_findObject(TSERIES, tok[1]); + if ( i < 0 ) return error_setInpError(ERR_NAME, tok[1]); + Evap.tSeries = i; + Tseries[i].refersTo = TIMESERIES_EVAP; + break; + + case FILE_EVAP: + // --- for evap. from climate file, read monthly pan coeffs. + // if they are provided (default values are 1.0) + if ( ntoks > 1 ) + { + if ( ntoks < 13 ) return error_setInpError(ERR_ITEMS, ""); + for (i=0; i<12; i++) + { + if ( !getDouble(tok[i+1], &Evap.panCoeff[i]) ) + return error_setInpError(ERR_NUMBER, tok[i+1]); + } + } + break; + } + return 0; +} + +//============================================================================= + +int climate_readAdjustments(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// Purpose: reads adjustments to monthly evaporation or rainfall +// from input line of data. +// +// Data formats are: +// TEMPERATURE v1 ... v12 +// EVAPORATION v1 ... v12 +// RAINFALL v1 ... v12 +// CONDUCTIVITY v1 ... v12 +// N-PERV subcatchID patternID +// DSTORE subcatchID patternID +// INFIL subcatchID patternID +{ + int i, j; + + if (ntoks == 1) return 0; + + if ( match(tok[0], "TEMP") ) + { + if ( ntoks < 13 ) return error_setInpError(ERR_ITEMS, ""); + for (i = 1; i < 13; i++) + { + if ( !getDouble(tok[i], &Adjust.temp[i-1]) ) + return error_setInpError(ERR_NUMBER, tok[i]); + } + return 0; + } + + if ( match(tok[0], "EVAP") ) + { + if ( ntoks < 13 ) return error_setInpError(ERR_ITEMS, ""); + for (i = 1; i < 13; i++) + { + if ( !getDouble(tok[i], &Adjust.evap[i-1]) ) + return error_setInpError(ERR_NUMBER, tok[i]); + } + return 0; + } + + if ( match(tok[0], "RAIN") ) + { + if ( ntoks < 13 ) return error_setInpError(ERR_ITEMS, ""); + for (i = 1; i < 13; i++) + { + if ( !getDouble(tok[i], &Adjust.rain[i-1]) ) + return error_setInpError(ERR_NUMBER, tok[i]); + } + return 0; + } + + if ( match(tok[0], "CONDUCT") ) + { + if ( ntoks < 13 ) return error_setInpError(ERR_ITEMS, ""); + for (i = 1; i < 13; i++) + { + if ( !getDouble(tok[i], &Adjust.hydcon[i-1]) ) + return error_setInpError(ERR_NUMBER, tok[i]); + if ( Adjust.hydcon[i-1] <= 0.0 ) Adjust.hydcon[i-1] = 1.0; + } + return 0; + } + + if ( match(tok[0], "N-PERV") ) + { + if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); + i = project_findObject(SUBCATCH, tok[1]); + if (i < 0) return error_setInpError(ERR_NAME, tok[1]); + j = project_findObject(TIMEPATTERN, tok[2]); + if (j < 0) return error_setInpError(ERR_NAME, tok[2]); + Subcatch[i].nPervPattern = j; + return 0; + } + + if ( match(tok[0], "DSTORE") ) + { + if (ntoks < 3) return error_setInpError(ERR_ITEMS, ""); + i = project_findObject(SUBCATCH, tok[1]); + if (i < 0) return error_setInpError(ERR_NAME, tok[1]); + j = project_findObject(TIMEPATTERN, tok[2]); + if (j < 0) return error_setInpError(ERR_NAME, tok[2]); + Subcatch[i].dStorePattern = j; + return 0; + } + + if (match(tok[0], "INFIL")) + { + if (ntoks < 3) return error_setInpError(ERR_ITEMS, ""); + i = project_findObject(SUBCATCH, tok[1]); + if (i < 0) return error_setInpError(ERR_NAME, tok[1]); + j = project_findObject(TIMEPATTERN, tok[2]); + if (j < 0) return error_setInpError(ERR_NAME, tok[2]); + Subcatch[i].infilPattern = j; + return 0; + } + return error_setInpError(ERR_KEYWORD, tok[0]); +} + +//============================================================================= + +void climate_validate() +// +// Input: none +// Output: none +// Purpose: validates climatological variables +// +{ + int i; + double a, z, pa; + + // --- check if climate data comes from external data file + if ( Wind.type == FILE_WIND || Evap.type == FILE_EVAP || + Evap.type == TEMPERATURE_EVAP ) + { + if ( Fclimate.mode == NO_FILE ) + { + report_writeErrorMsg(ERR_NO_CLIMATE_FILE, ""); + } + } + + // --- open the climate data file + if ( Fclimate.mode == USE_FILE ) climate_openFile(); + + // --- snow melt parameters tipm & rnm must be fractions + if ( Snow.tipm < 0.0 || + Snow.tipm > 1.0 || + Snow.rnm < 0.0 || + Snow.rnm > 1.0 ) report_writeErrorMsg(ERR_SNOWMELT_PARAMS, ""); + + // --- latitude should be between -90 & 90 degrees + a = Temp.anglat; + if ( a <= -89.99 || + a >= 89.99 ) report_writeErrorMsg(ERR_SNOWMELT_PARAMS, ""); + else Temp.tanAnglat = tan(a * PI / 180.0); + + // --- compute psychrometric constant + z = Temp.elev / 1000.0; + if ( z <= 0.0 ) pa = 29.9; + else pa = 29.9 - 1.02*z + 0.0032*pow(z, 2.4); // atmos. pressure + Temp.gamma = 0.000359 * pa; + + // --- convert units of monthly temperature & evap adjustments + for (i = 0; i < 12; i++) + { + if (UnitSystem == SI) Adjust.temp[i] *= 9.0/5.0; + Adjust.evap[i] /= UCF(EVAPRATE); + } +} + +//============================================================================= + +void climate_openFile() +// +// Input: none +// Output: none +// Purpose: opens a climate file and reads in first set of values. +// +{ + int i, m, y; + + // --- open the file + if ( (Fclimate.file = fopen(Fclimate.name, "rt")) == NULL ) + { + report_writeErrorMsg(ERR_CLIMATE_FILE_OPEN, Fclimate.name); + return; + } + + // --- initialize values of file's climate variables + // (Temp.ta was previously initialized in project.c) + FileValue[TMIN] = Temp.ta; + FileValue[TMAX] = Temp.ta; + FileValue[EVAP] = 0.0; + FileValue[WIND] = 0.0; + + // --- find climate file's format + FileFormat = getFileFormat(); + if ( FileFormat == UNKNOWN_FORMAT ) + { + report_writeErrorMsg(ERR_CLIMATE_FILE_READ, Fclimate.name); + return; + } + + // --- position file to begin reading climate file at either user-specified + // month/year or at start of simulation period. + rewind(Fclimate.file); + sstrncpy(FileLine, "", 0); + if ( Temp.fileStartDate == NO_DATE ) + datetime_decodeDate(StartDate, &FileYear, &FileMonth, &FileDay); + else + datetime_decodeDate(Temp.fileStartDate, &FileYear, &FileMonth, &FileDay); + while ( !feof(Fclimate.file) ) + { + sstrncpy(FileLine, "", 0); + readFileLine(&y, &m); + if ( y == FileYear && m == FileMonth ) break; + } + if ( feof(Fclimate.file) ) + { + report_writeErrorMsg(ERR_CLIMATE_END_OF_FILE, Fclimate.name); + return; + } + + // --- initialize file dates and current climate variable values + if ( !ErrorCode ) + { + FileElapsedDays = 0; + FileLastDay = datetime_daysPerMonth(FileYear, FileMonth); + readFileValues(); + for (i=TMIN; i<=WIND; i++) + { + if ( FileData[i][FileDay] == MISSING ) continue; + FileValue[i] = FileData[i][FileDay]; + } + } +} + +//============================================================================= + +void climate_initState() +// +// Input: none +// Output: none +// Purpose: initializes climate state variables. +// +{ + LastDay = NO_DATE; + Temp.tmax = MISSING; + Snow.removed = 0.0; + NextEvapDate = StartDate; + NextEvapRate = 0.0; + + // --- initialize variables for time series evaporation + if ( Evap.type == TIMESERIES_EVAP && Evap.tSeries >= 0 ) + { + // --- initialize NextEvapDate & NextEvapRate to first entry of + // time series whose date <= the simulation start date + table_getFirstEntry(&Tseries[Evap.tSeries], + &NextEvapDate, &NextEvapRate); + if ( NextEvapDate < StartDate ) + { + setNextEvapDate(StartDate); + } + Evap.rate = NextEvapRate / UCF(EVAPRATE); + + // --- find the next time evaporation rates change after this + setNextEvapDate(NextEvapDate); + } + + // --- initialize variables for temperature evaporation + if ( Evap.type == TEMPERATURE_EVAP ) + { + Tma.maxCount = sizeof(Tma.ta) / sizeof(double); + Tma.count = 0; + Tma.front = 0; + Tma.tAve = 0.0; + Tma.tRng = 0.0; + } +} + +//============================================================================= + +void climate_setState(DateTime theDate) +// +// Input: theDate = simulation date +// Output: none +// Purpose: sets climate variables for current date. +// +{ + if ( Fclimate.mode == USE_FILE ) updateFileValues(theDate); + if ( Temp.dataSource != NO_TEMP ) setTemp(theDate); + setEvap(theDate); + setWind(theDate); + Adjust.rainFactor = Adjust.rain[datetime_monthOfYear(theDate)-1]; + Adjust.hydconFactor = Adjust.hydcon[datetime_monthOfYear(theDate)-1]; + setNextEvapDate(theDate); +} + +//============================================================================= + +DateTime climate_getNextEvapDate() +// +// Input: none +// Output: returns the current value of NextEvapDate +// Purpose: gets the next date when evaporation rate changes. +// +{ + return NextEvapDate; +} + +//============================================================================= + +void setNextEvapDate(DateTime theDate) +// +// Input: theDate = current simulation date +// Output: sets a new value for NextEvapDate +// Purpose: finds date for next change in evaporation after the current date. +// +{ + int yr, mon, day, k; + double d, e; + + // --- do nothing if current date hasn't reached the current next date + if ( NextEvapDate > theDate ) return; + + switch ( Evap.type ) + { + // --- for constant evaporation, use a next date far in the future + case CONSTANT_EVAP: + NextEvapDate = theDate + 365.; + break; + + // --- for monthly evaporation, use the start of the next month + case MONTHLY_EVAP: + datetime_decodeDate(theDate, &yr, &mon, &day); + if ( mon == 12 ) + { + mon = 1; + yr++; + } + else mon++; + NextEvapDate = datetime_encodeDate(yr, mon, 1); + break; + + // --- for time series evaporation, find the next entry in the + // series on or after the current date + case TIMESERIES_EVAP: + k = Evap.tSeries; + if ( k >= 0 ) + { + NextEvapDate = theDate + 365.; + while ( table_getNextEntry(&Tseries[k], &d, &e) && + d <= EndDateTime ) + { + if ( d >= theDate ) + { + NextEvapDate = d; + NextEvapRate = e; + break; + } + } + } + break; + + // --- for climate file daily evaporation, use the next day + case FILE_EVAP: + NextEvapDate = floor(theDate) + 1.0; + break; + + default: NextEvapDate = theDate + 365.; + } +} + +//============================================================================= + +void updateFileValues(DateTime theDate) +// +// Input: theDate = current simulation date +// Output: none +// Purpose: updates daily climate variables for new day or reads in +// another month worth of values if a new month begins. +// +// NOTE: counters FileElapsedDays, FileDay, FileMonth, FileYear and +// FileLastDay were initialized in climate_openFile(). +// +{ + int i; + int deltaDays; + + // --- see if a new day has begun + deltaDays = (int)(floor(theDate) - floor(StartDateTime)); + if ( deltaDays > FileElapsedDays ) + { + // --- advance day counters + FileElapsedDays++; + FileDay++; + + // --- see if new month of data needs to be read from file + if ( FileDay > FileLastDay ) + { + FileMonth++; + if ( FileMonth > 12 ) + { + FileMonth = 1; + FileYear++; + } + readFileValues(); + FileDay = 1; + FileLastDay = datetime_daysPerMonth(FileYear, FileMonth); + } + + // --- set climate variables for new day + for (i=TMIN; i<=WIND; i++) + { + // --- no change in current value if its missing + if ( FileData[i][FileDay] == MISSING ) continue; + FileValue[i] = FileData[i][FileDay]; + } + } +} + +//============================================================================= + +void setTemp(DateTime theDate) +// +// Input: theDate = simulation date +// Output: none +// Purpose: updates temperatures for new simulation date. +// +{ + int j; // snow data object index + int k; // time series index + int mon; // month of year + int day; // day of year + DateTime theDay; // calendar day + double hour; // hour of day + double tmp; // temporary temperature + + // --- see if a new day has started + mon = datetime_monthOfYear(theDate); + theDay = floor(theDate); + if ( theDay > LastDay ) + { + // --- update min. & max. temps & their time of day + day = datetime_dayOfYear(theDate); + if ( Temp.dataSource == FILE_TEMP ) + { + Tmin = FileValue[TMIN] + Adjust.temp[mon-1]; + Tmax = FileValue[TMAX] + Adjust.temp[mon-1]; + if ( Tmin > Tmax ) + { + tmp = Tmin; + Tmin = Tmax; + Tmax = tmp; + } + updateTempTimes(day); + if ( Evap.type == TEMPERATURE_EVAP ) + { + updateTempMoveAve(Tmin, Tmax); + FileValue[EVAP] = getTempEvap(day, Tma.tAve, Tma.tRng); + } + } + + // --- compute snow melt coefficients based on day of year + Snow.season = sin(0.0172615*(day-81.0)); + for (j=0; j= Hrsr && hour <= Hrss ) + Temp.ta = Tave + Trng * sin(PI/Dhrdy * (Hrday - hour)); + else + Temp.ta = Tmax - Trng * sin(PI/Dydif * (hour - Hrss)); + } + + // --- for user-supplied temperature time series, + // get temperature value from time series + if ( Temp.dataSource == TSERIES_TEMP ) + { + k = Temp.tSeries; + if ( k >= 0) + { + Temp.ta = table_tseriesLookup(&Tseries[k], theDate, TRUE); + + // --- convert from deg. C to deg. F if need be + if ( UnitSystem == SI ) + { + Temp.ta = (9./5.) * Temp.ta + 32.0; + } + + // --- apply climate change adjustment factor + Temp.ta += Adjust.temp[mon-1]; + } + } + + // --- compute saturation vapor pressure + Temp.ea = 8.1175e6 * exp(-7701.544 / (Temp.ta + 405.0265) ); +} + +//============================================================================= + +void setEvap(DateTime theDate) +// +// Input: theDate = simulation date +// Output: none +// Purpose: sets evaporation rate (ft/sec) for a specified date. +// +{ + int k; + int mon = datetime_monthOfYear(theDate); + + switch ( Evap.type ) + { + case CONSTANT_EVAP: + Evap.rate = Evap.monthlyEvap[0] / UCF(EVAPRATE); + break; + + case MONTHLY_EVAP: + Evap.rate = Evap.monthlyEvap[mon-1] / UCF(EVAPRATE); + break; + + case TIMESERIES_EVAP: + if ( theDate >= NextEvapDate ) + Evap.rate = NextEvapRate / UCF(EVAPRATE); + break; + + case FILE_EVAP: + Evap.rate = FileValue[EVAP] / UCF(EVAPRATE); + Evap.rate *= Evap.panCoeff[mon-1]; + break; + + case TEMPERATURE_EVAP: + Evap.rate = FileValue[EVAP] / UCF(EVAPRATE); + break; + + default: Evap.rate = 0.0; + } + + // --- apply climate change adjustment + Evap.rate += Adjust.evap[mon-1]; + + // --- set soil recovery factor + Evap.recoveryFactor = 1.0; + k = Evap.recoveryPattern; + if ( k >= 0 && Pattern[k].type == MONTHLY_PATTERN ) + { + Evap.recoveryFactor = Pattern[k].factor[mon-1]; + } +} + +//============================================================================= + +void setWind(DateTime theDate) +// +// Input: theDate = simulation date +// Output: none +// Purpose: sets wind speed (mph) for a specified date. +// +{ + int yr, mon, day; + + switch ( Wind.type ) + { + case MONTHLY_WIND: + datetime_decodeDate(theDate, &yr, &mon, &day); + Wind.ws = Wind.aws[mon-1] / UCF(WINDSPEED); + break; + + case FILE_WIND: + Wind.ws = FileValue[WIND]; + break; + + default: Wind.ws = 0.0; + } +} + +//============================================================================= + +void updateTempTimes(int day) +// +// Input: day = day of year +// Output: none +// Purpose: computes time of day when min/max temperatures occur. +// (min. temp occurs at sunrise, max. temp. at 3 hrs. < sunset) +// +{ + double decl; // earth's declination + double hrang; // hour angle of sunrise/sunset + double arg; + + decl = 0.40928*cos(0.017202*(172.0-day)); + arg = -tan(decl)*Temp.tanAnglat; + if ( arg <= -1.0 ) arg = PI; + else if ( arg >= 1.0 ) arg = 0.0; + else arg = acos(arg); + hrang = 3.8197 * arg; + Hrsr = 12.0 - hrang + Temp.dtlong; + Hrss = 12.0 + hrang + Temp.dtlong - 3.0; + Dhrdy = Hrsr - Hrss; + Dydif = 24.0 + Hrsr - Hrss; + Hrday = (Hrsr + Hrss) / 2.0; + Tave = (Tmin + Tmax) / 2.0; + Trng = (Tmax - Tmin) / 2.0; + if ( Temp.tmax == MISSING ) Trng1 = Tmax - Tmin; + else Trng1 = Temp.tmax - Tmin; + Temp.tmax = Tmax; +} + +//============================================================================= + +double getTempEvap(int day, double tave, double trng) +// +// Input: day = day of year +// tave = 7-day average temperature (deg F) +// trng = 7-day average daily temperature range (deg F) +// Output: returns evaporation rate in user's units (US:in/day, SI:mm/day) +// Purpose: uses Hargreaves method to compute daily evaporation rate +// from daily average temperatures and Julian day. +// +{ + double a = 2.0*PI/365.0; + double ta = (tave - 32.0)*5.0/9.0; //average temperature (deg C) + double tr = trng*5.0/9.0; //temperature range (deg C) + double lamda = 2.50 - 0.002361 * ta; //latent heat of vaporization + double dr = 1.0 + 0.033*cos(a*day); //relative earth-sun distance + double phi = Temp.anglat*2.0*PI/360.0; //latitude angle (rad) + double del = 0.4093*sin(a*(284.+(double)day)); //solar declination angle (rad) + double omega = acos(-tan(phi)*tan(del)); //sunset hour angle (rad) + double ra = 37.6*dr* //extraterrestrial radiation + (omega*sin(phi)*sin(del) + + cos(phi)*cos(del)*sin(omega)); + double e = 0.0023*ra/lamda*sqrt(tr)*(ta+17.8); //evap. rate (mm/day) + if ( e < 0.0 ) e = 0.0; + if ( UnitSystem == US ) e /= MMperINCH; //evap rate (in/day) + return e; +} + +//============================================================================= + +int getFileFormat() +// +// Input: none +// Output: returns code number of climate file's format +// Purpose: determines what format the climate file is in. +// +{ + char recdType[4] = ""; + char elemType[4] = ""; + char filler[5] = ""; + char staID[80]; + char s[80]; + char line[MAXLINE]; + + int y, m, d, n; + + // --- read first line of file + if ( fgets(line, MAXLINE, Fclimate.file) == NULL ) return UNKNOWN_FORMAT; + + // --- check for TD3200 format + sstrncpy(recdType, line, 3); + sstrncpy(filler, &line[23], 4); + if ( strcmp(recdType, "DLY") == 0 && + strcmp(filler, "9999") == 0 ) return TD3200; + + // --- check for DLY0204 format + if ( strlen(line) >= 233 ) + { + sstrncpy(elemType, &line[13], 3); + n = atoi(elemType); + if ( n == 1 || n == 2 || n == 151 ) return DLY0204; + } + + // --- check for USER_PREPARED format + n = sscanf(line, "%s %d %d %d %s", staID, &y, &m, &d, s); + if ( n == 5 ) return USER_PREPARED; + + // --- check for GHCND format + if ( isGhcndFormat(line) ) return GHCND; + + return UNKNOWN_FORMAT; +} + +//============================================================================= + +void readFileLine(int *y, int *m) +// +// Input: none +// Output: y = year +// m = month +// Purpose: reads year & month from next line of climate file. +// +{ + // --- read next line from climate data file + while ( strlen(FileLine) == 0 ) + { + if ( fgets(FileLine, MAXLINE, Fclimate.file) == NULL ) return; + if ( FileLine[0] == '\n' ) FileLine[0] = '\0'; + } + + // --- parse year & month from line + switch (FileFormat) + { + case USER_PREPARED: readUserFileLine(y, m); break; + case TD3200: readTD3200FileLine(y,m); break; + case DLY0204: readDLY0204FileLine(y,m); break; + case GHCND: readGhcndFileLine(y,m); break; + } +} + +//============================================================================= + +void readUserFileLine(int* y, int* m) +// +// Input: none +// Output: y = year +// m = month +// Purpose: reads year & month from line of User-Prepared climate file. +// +{ + int n; + char staID[80]; + n = sscanf(FileLine, "%s %d %d", staID, y, m); + if ( n < 3 ) + { + report_writeErrorMsg(ERR_CLIMATE_FILE_READ, Fclimate.name); + } +} + +//============================================================================= + +void readTD3200FileLine(int* y, int* m) +// +// Input: none +// Output: y = year +// m = month +// Purpose: reads year & month from line of TD-3200 climate file. +// +{ + char recdType[4] = ""; + char year[5] = ""; + char month[3] = ""; + + // --- check for minimum number of characters + if ( strlen(FileLine) < 30 ) + { + report_writeErrorMsg(ERR_CLIMATE_FILE_READ, Fclimate.name); + return; + } + + // --- check for proper type of record + sstrncpy(recdType, FileLine, 3); + if ( strcmp(recdType, "DLY") != 0 ) + { + report_writeErrorMsg(ERR_CLIMATE_FILE_READ, Fclimate.name); + return; + } + + // --- get record's date + sstrncpy(year, &FileLine[17], 4); + sstrncpy(month, &FileLine[21], 2); + *y = atoi(year); + *m = atoi(month); +} + +//============================================================================= + +void readDLY0204FileLine(int* y, int* m) +// +// Input: none +// Output: y = year +// m = month +// Purpose: reads year & month from line of DLY02 or DLY04 climate file. +// +{ + char year[5] = ""; + char month[3] = ""; + + // --- check for minimum number of characters + if ( strlen(FileLine) < 16 ) + { + report_writeErrorMsg(ERR_CLIMATE_FILE_READ, Fclimate.name); + return; + } + + // --- get record's date + sstrncpy(year, &FileLine[7], 4); + sstrncpy(month, &FileLine[11], 2); + *y = atoi(year); + *m = atoi(month); +} + +//============================================================================= + +void readFileValues() +// +// Input: none +// Output: none +// Purpose: reads next month's worth of data from climate file. +// +{ + int i, j; + int y, m; + + // --- initialize FileData array to missing values + for ( i=0; i FileYear || m > FileMonth ) return; + + // --- parse climate values from file line + switch (FileFormat) + { + case USER_PREPARED: parseUserFileLine(); break; + case TD3200: parseTD3200FileLine(); break; + case DLY0204: parseDLY0204FileLine(); break; + case GHCND: parseGhcndFileLine(); break; + } + sstrncpy(FileLine, "", 0); + } +} + +//============================================================================= + +void parseUserFileLine() +// +// Input: none +// Output: none +// Purpose: parses climate variable values from a line of a user-prepared +// climate file. +// +{ + int n; + int y, m, d; + char staID[80]; + char s0[80] = ""; + char s1[80] = ""; + char s2[80] = ""; + char s3[80] = ""; + double x; + + // --- read day, Tmax, Tmin, Evap, & Wind from file line + n = sscanf(FileLine, "%s %d %d %d %s %s %s %s", + staID, &y, &m, &d, s0, s1, s2, s3); + if ( n < 4 ) return; + if ( d < 1 || d > 31 ) return; + + // --- process TMAX + if ( strlen(s0) > 0 && *s0 != '*' ) + { + x = atof(s0); + if ( UnitSystem == SI ) x = 9./5.*x + 32.0; + FileData[TMAX][d] = x; + } + + // --- process TMIN + if ( strlen(s1) > 0 && *s1 != '*' ) + { + x = atof(s1); + if ( UnitSystem == SI ) x = 9./5.*x + 32.0; + FileData[TMIN][d] = x; + } + + // --- process EVAP + if ( strlen(s2) > 0 && *s2 != '*' ) FileData[EVAP][d] = atof(s2); + + // --- process WIND + if ( strlen(s3) > 0 && *s3 != '*' ) FileData[WIND][d] = atof(s3); +} + +//============================================================================= + +void parseTD3200FileLine() +// +// Input: none +// Output: none +// Purpose: parses climate variable values from a line of a TD3200 file. +// +{ + int i; + char param[5] = ""; + + // --- parse parameter name + sstrncpy(param, &FileLine[11], 4); + + // --- see if parameter is temperature, evaporation or wind speed + for (i=0; i= 12*nValues + 30 ) + { + // --- for each day's value + for (j=0; j 0 + && d <= 31 ) + { + // --- convert from string value to numerical value + x = atof(value); + if ( sign[0] == '-' ) x = -x; + + // --- convert evaporation from hundreths of inches + if ( i == EVAP ) + { + x /= 100.0; + + // --- convert to mm if using SI units + if ( UnitSystem == SI ) x *= MMperINCH; + } + + // --- convert wind speed from miles/day to miles/hour + if ( i == WIND ) x /= 24.0; + + // --- store value + FileData[i][d] = x; + } + } + } +} + +//============================================================================= + +void parseDLY0204FileLine() +// +// Input: none +// Output: none +// Purpose: parses a month's worth of climate variable values from a line of +// a DLY02 or DLY04 climate file. +// +{ + int j, k, p; + char param[4] = ""; + char sign[2] = ""; + char value[6] = ""; + char code[2] = ""; + double x; + + // --- parse parameter name + sstrncpy(param, &FileLine[13], 3); + + // --- see if parameter is min or max temperature + p = atoi(param); + if ( p == 1 ) p = TMAX; + else if ( p == 2 ) p = TMIN; + else if ( p == 151 ) p = EVAP; + else return; + + // --- check for 233 characters on line + if ( strlen(FileLine) < 233 ) return; + + // --- for each of 31 days + k = 16; + for (j=1; j<=31; j++) + { + // --- parse value & flag from file line + sstrncpy(sign, &FileLine[k], 1); + sstrncpy(value, &FileLine[k+1], 5); + sstrncpy(code, &FileLine[k+6], 1); + k += 7; + + // --- if value is valid then store it in FileData array + + if ( strcmp(value, "99999") != 0 && strcmp(value, " ") != 0 ) + { + switch (p) + { + case TMAX: + case TMIN: + // --- convert from integer tenths of a degree C to degrees F + x = atof(value) / 10.0; + if ( sign[0] == '-' ) x = -x; + x = 9./5.*x + 32.0; + break; + case EVAP: + // --- convert from 0.1 mm to inches or mm + x = atof(value) / 10.0; + if ( UnitSystem == US ) x /= MMperINCH; + break; + default: return; + } + FileData[p][j] = x; + } + } +} + +//============================================================================= + +int isGhcndFormat(char* line) +// +// Input: line = first line of text from a climate file +// Output: returns TRUE if climate file is in NCDC GHCN Daily format. +// Purpose: Checks if a climate file is in the NCDC GHCN Daily format +// and determines the position of each climate variable field. +// +{ + int i; + char* ptr; + + // --- find starting position of the DATE field + ptr = strstr(line, "DATE"); + if ( ptr == NULL ) return FALSE; + FileDateFieldPos = (int)(ptr - line); + + // --- initialize starting position of each data field + for ( i = TMIN; i <= WIND; i++) FileFieldPos[i] = -1; + + // --- find starting position of each climate variable's data field + ptr = strstr(line, "TMIN"); + if ( ptr ) FileFieldPos[TMIN] = (int)(ptr - line); + ptr = strstr(line, "TMAX"); + if ( ptr ) FileFieldPos[TMAX] = (int)(ptr - line); + ptr = strstr(line, "EVAP"); + if ( ptr ) FileFieldPos[EVAP] = (int)(ptr - line); + + // --- WIND can either be daily movement or average speed + FileWindType = WDMV; + ptr = strstr(line, "WDMV"); + if ( ptr == NULL ) + { + FileWindType = AWND; + ptr = strstr(line, "AWND"); + } + if ( ptr ) FileFieldPos[WIND] = (int)(ptr - line); + + // --- check if at least one climate variable was found + for (i = TMIN; i <= WIND; i++) if (FileFieldPos[i] >= 0 ) return TRUE; + return FALSE; +} + +//============================================================================= + +void readGhcndFileLine(int* y, int* m) +// +// Input: none +// Output: y = year +// m = month +// Purpose: reads year & month from line of a NCDC GHCN Daily climate file. +// +{ + int n = sscanf(&FileLine[FileDateFieldPos], "%4d%2d", y, m); + if ( n != 2 ) + { + *y = -99999; + *m = -99999; + } +} + +//============================================================================= + +void parseGhcndFileLine() +// +// Input: none +// Output: none +// Purpose: parses a line of a NCDC GHCN Daily file for daily +// values of max/min temperature, pan evaporation and +// wind speed. +// +{ + int y, m, d, n, i; + double v; + + // --- parse day of month from date field + n = sscanf(&FileLine[FileDateFieldPos], "%4d%2d%2d", &y, &m, &d); + if ( n < 3 ) return; + if ( d < 1 || d > 31 ) return; + + // --- parse climate variables + for (i = TMIN; i <= WIND; i++) + { + if ( FileFieldPos[i] >= 0 ) + { + if ( sscanf(&FileLine[FileFieldPos[i]], "%8lf", &v) > 0 ) + { + if ( fabs(v) < 9999. ) + FileData[i][d] = convertGhcndValue(i, v); + } + } + } +} + +//============================================================================= + +double convertGhcndValue(int var, double v) +// +// Input: var = climate variable code +// v = climate variable value +// Output: climate variable value in SWMM's internal units +// Purpose: converts a climate variable value read from a NCDC GHCN Daily file +// to SWMM's internal units. +// +{ + switch (var) + { + case TMIN: + case TMAX: + switch (FileTempUnits) + { + case DEG_C10: // tenths deg. C ==> deg. F + return v / 10. * 9.0 / 5.0 + 32.0; + + case DEG_C: // deg. C ==> deg. F + return v * 9.0 / 5.0 + 32.0; + + default: // deg. F + return v; + } + case EVAP: + switch (FileTempUnits) + { + case DEG_C10: // tenths mm ==> inches or mm + v /= 10.; + if (UnitSystem == US) v /= MMperINCH; + return v; + + case DEG_C: // mm ==> inches or mm + if (UnitSystem == US) v /= MMperINCH; + return v; + + default: // inches ==> inches or mm + if (UnitSystem == SI) v *= MMperINCH; + return v; + } + case WIND: + switch (FileTempUnits) + { + case DEG_C10: + // km/day ==> miles/hr + if (FileWindType == WDMV) + return v * 0.62137 / 24.; + // tenths m/s ==> miles/hr + else + return v / 10. / 1000. * 0.62137 * 3600.; + case DEG_C: + // km/day ==> miles/hr + if (FileWindType == WDMV) + return v * 0.62137 / 24.; + // m/s ==> miles/hr + else + return v / 1000. * 0.62137 * 3600.; + + default: + // miles ==> miles/hr + if (FileWindType == WDMV) + return v / 24.; + // miles/hr + else + return v; + } + default: + return v; + } +} + +//============================================================================= + +void updateTempMoveAve(double tmin, double tmax) +// +// Input: tmin = minimum daily temperature (deg F) +// tmax = maximum daily temperature (deg F) +// Output: none +// Purpose: updates moving averages of average daily temperature +// and daily temperature range stored in structure Tma. +// +{ + double ta, // new day's average temperature (deg F) + tr; // new day's temperature range (deg F) + int kount = Tma.count; + double count = kount; + + // --- find ta and tr from new day's min and max temperature + ta = (tmin + tmax) / 2.0; + tr = fabs(tmax - tmin); + + // --- if the array used to store previous days' temperatures is full + if ( kount == Tma.maxCount ) + { + // --- update the moving averages with the new day's value + Tma.tAve = (Tma.tAve * count + ta - Tma.ta[Tma.front]) / count; + Tma.tRng = (Tma.tRng * count + tr - Tma.tr[Tma.front]) / count; + + // --- replace the values at the front of the moving average window + Tma.ta[Tma.front] = ta; + Tma.tr[Tma.front] = tr; + + // --- move the front one position forward + Tma.front++; + if ( Tma.front == count ) Tma.front = 0; + } + + // --- array of previous day's values not full (at start of simulation) + else + { + // --- find new moving averages by adding new values to previous ones + Tma.tAve = (Tma.tAve * count + ta) / (count + 1); + Tma.tRng = (Tma.tRng * count + tr) / (count + 1); + + // --- save new day's values + Tma.ta[Tma.front] = ta; + Tma.tr[Tma.front] = tr; + + // --- increment count and front of moving average window + Tma.count++; + Tma.front++; + if ( Tma.count == Tma.maxCount ) Tma.front = 0; + } +} diff --git a/src/consts.h b/src/consts.h new file mode 100644 index 000000000..f3deb70ef --- /dev/null +++ b/src/consts.h @@ -0,0 +1,102 @@ +//----------------------------------------------------------------------------- +// consts.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 10/18/22 (Build 5.2.2) +// Author: L. Rossman +// +// Various Constants +//----------------------------------------------------------------------------- + +#ifndef CONSTS_H +#define CONSTS_H + +//------------------ +// General Constants +//------------------ + +#define VERSION 52002 +#define MAGICNUMBER 516114522 +#define EOFMARK 0x1A // Use 0x04 for UNIX systems +#define MAXTITLE 3 // Max. # title lines +#define MAXMSG 1024 // Max. # characters in message text +#define MAXLINE 1024 // Max. # characters per input line +#define MAXFNAME 259 // Max. # characters in file name +#define MAXTOKS 40 // Max. items per line of input +#define MAXSTATES 10 // Max. # computed hyd. variables +#define MAXODES 4 // Max. # ODE's to be solved +#define NA -1 // NOT APPLICABLE code +#define TRUE 1 // Value for TRUE state +#define FALSE 0 // Value for FALSE state +#define BIG 1.E10 // Generic large value +#define TINY 1.E-6 // Generic small value +#define ZERO 1.E-10 // Effective zero value +#define MISSING -1.E10 // Missing value code +#define PI 3.141592654 // Value of pi +#define GRAVITY 32.2 // accel. of gravity in US units +#define SI_GRAVITY 9.81 // accel of gravity in SI units +/* DEPRECATED +#define MAXFILESIZE 2147483647L // largest file size in bytes +*/ + +//----------------------------- +// Units factor in Manning Eqn. +//----------------------------- +#define PHI 1.486 + +//---------------------------------------------- +// Definition of measureable runoff flow & depth +//---------------------------------------------- +#define MIN_RUNOFF_FLOW 0.001 // cfs +#define MIN_EXCESS_DEPTH 0.0001 // ft, = 0.03 mm +#define MIN_TOTAL_DEPTH 0.004167 // ft, = 0.05 inches +#define MIN_RUNOFF 2.31481e-8 // ft/sec = 0.001 in/hr + +//---------------------------------------------------------------------- +// Minimum flow, depth & volume used to evaluate steady state conditions +//---------------------------------------------------------------------- +#define FLOW_TOL 0.00001 // cfs +#define DEPTH_TOL 0.00001 // ft +#define VOLUME_TOL 0.01 // ft3 + +//--------------------------------------------------- +// Minimum depth for reporting non-zero water quality +//--------------------------------------------------- +//#define MIN_WQ_DEPTH 0.01 // ft (= 3 mm) +//#define MIN_WQ_FLOW 0.001 // cfs + +//----------------------------------------------------- +// Minimum flow depth and area for dynamic wave routing +//----------------------------------------------------- +#define FUDGE 0.0001 // ft or ft2 + +//--------------------------- +// Various conversion factors +//--------------------------- +#define GPMperCFS 448.831 +#define AFDperCFS 1.9837 +#define MGDperCFS 0.64632 +#define IMGDperCFS 0.5382 +#define LPSperCFS 28.317 +#define LPMperCFS 1699.0 +#define CMHperCFS 101.94 +#define CMDperCFS 2446.6 +#define MLDperCFS 2.4466 +#define M3perFT3 0.028317 +#define LperFT3 28.317 +#define MperFT 0.3048 +#define PSIperFT 0.4333 +#define KPAperPSI 6.895 +#define KWperHP 0.7457 +#define SECperDAY 86400 +#define MSECperDAY 8.64e7 +#define MMperINCH 25.40 + +//--------------------------- +// Token separator characters +//--------------------------- +#define SEPSTR " \t\n\r" + + +#endif //CONSTS_H diff --git a/src/controls.c b/src/controls.c new file mode 100644 index 000000000..b46054006 --- /dev/null +++ b/src/controls.c @@ -0,0 +1,1553 @@ +//----------------------------------------------------------------------------- +// controls.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 06/01/22 (Build 5.2.1) +// Author: L. Rossman +// +// Rule-based controls functions. +// +// Control rules have the format: +// RULE name +// IF +// AND / OR +// etc. +// THEN +// AND +// etc. +// ELSE +// AND +// etc. +// PRIORITY

+// +// consists of: +// value / +// where is +// E.g.: Node 123 Depth > 4.5 +// Node 456 Depth < Node 123 Depth +// +// consists of: +// = setting +// E.g.: Pump abc status = OFF +// Weir xyz setting = 0.5 +// +// Update History +// ============== +// Build 5.1.008: +// - Support added for r.h.s. variables in rule premises. +// - Node volume added as a premise variable. +// Build 5.1.009: +// - Fixed problem with parsing a RHS premise variable. +// Build 5.1.010: +// - Support added for link TIMEOPEN & TIMECLOSED premises. +// Build 5.1.011: +// - Support added for DAYOFYEAR attribute. +// - Modulated controls no longer included in reported control actions. +// Build 5.2.0: +// - Additional attributes added to condition clauses. +// - Support added for named variables in condition clauses. +// - Support added for math expressions in condition clauses. +// Build 5.2.1: +// - A refactoring bug from 5.2.0 causing duplicate actions to be added +// to the list of control actions to take was fixed. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include +#include "headers.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +enum RuleState {r_RULE, r_IF, r_AND, r_OR, r_THEN, r_ELSE, r_PRIORITY, + r_VARIABLE, r_EXPRESSION, r_ERROR}; +enum RuleObject {r_GAGE, r_NODE, r_LINK, r_CONDUIT, r_PUMP, r_ORIFICE, + r_WEIR, r_OUTLET, r_SIMULATION}; +enum RuleAttrib {r_DEPTH, r_MAXDEPTH, r_HEAD, r_VOLUME, r_INFLOW, + r_FLOW, r_FULLFLOW, r_FULLDEPTH, r_STATUS, r_SETTING, + r_LENGTH, r_SLOPE, r_VELOCITY, r_TIMEOPEN, r_TIMECLOSED, + r_TIME, r_DATE, r_CLOCKTIME, r_DAYOFYEAR, r_DAY, r_MONTH}; +enum RuleRelation {EQ, NE, LT, LE, GT, GE}; +enum RuleSetting {r_CURVE, r_TIMESERIES, r_PID, r_NUMERIC}; + +#define MAXVARNAME 32 + +static char* ObjectWords[] = + {"GAGE", "NODE", "LINK", "CONDUIT", "PUMP", "ORIFICE", "WEIR", "OUTLET", + "SIMULATION", NULL}; +static char* AttribWords[] = + {"DEPTH", "MAXDEPTH", "HEAD", "VOLUME", "INFLOW", + "FLOW", "FULLFLOW", "FULLDEPTH", "STATUS", "SETTING", + "LENGTH", "SLOPE", "VELOCITY", "TIMEOPEN", "TIMECLOSED", + "TIME", "DATE", "CLOCKTIME", "DAYOFYEAR", "DAY", "MONTH", NULL}; +static char* RelOpWords[] = {"=", "<>", "<", "<=", ">", ">=", NULL}; +static char* StatusWords[] = {"OFF", "ON", NULL}; +static char* ConduitWords[] = {"CLOSED", "OPEN", NULL}; +static char* SettingTypeWords[] = {"CURVE", "TIMESERIES", "PID", NULL}; +static char* IntensityWord = "INTENSITY"; + +//----------------------------------------------------------------------------- +// Data Structures +//----------------------------------------------------------------------------- +// Rule Premise Variable +struct TVariable +{ + int object; // type of object + int index; // index in object's array + int attribute; // object's attribute +}; + +// Named Variable +struct TNamedVariable +{ + struct TVariable variable; // a rule premise variable + char name[MAXVARNAME+1]; // name used in math expression +}; + +// Rule Premise Function +struct TExpression +{ + MathExpr* expression; // tokenized math expression + char name[MAXVARNAME+1]; // expression name +}; + +// Rule Premise Clause +struct TPremise +{ + int type; // clause type (IF/AND/OR) + int exprIndex; // expression index (-1 if N/A) + struct TVariable lhsVar; // left hand side variable + struct TVariable rhsVar; // right hand side variable + int relation; // relational operator (>, <, =, etc) + double value; // right hand side value + struct TPremise *next; // next premise clause of rule +}; + +// Rule Action Clause +struct TAction +{ + int rule; // index of rule that action belongs to + int link; // index of link being controlled + int attribute; // attribute of link being controlled + int curve; // index of curve for modulated control + int tseries; // index of time series for modulated control + double value; // control setting for link attribute + double kp, ki, kd; // coeffs. for PID modulated control + double e1, e2; // PID set point error from previous time steps + struct TAction *next; // next action clause of rule +}; + +// List of Control Actions +struct TActionList +{ + struct TAction* action; + struct TActionList* next; +}; + +// Control Rule +struct TRule +{ + char* ID; // rule ID + double priority; // priority level + struct TPremise* firstPremise; // pointer to first premise of rule + struct TPremise* lastPremise; // pointer to last premise of rule + struct TAction* thenActions; // linked list of actions if true + struct TAction* elseActions; // linked list of actions if false +}; + +//----------------------------------------------------------------------------- +// Shared variables +//----------------------------------------------------------------------------- +struct TRule* Rules; // array of control rules +struct TActionList* ActionList; // linked list of control actions +int InputState; // state of rule interpreter +int RuleCount; // total number of rules +double ControlValue; // value of controller variable +double SetPoint; // value of controller setpoint +DateTime CurrentDate; // current date in whole days +DateTime CurrentTime; // current time of day (decimal) + +int VariableCount; +int ExpressionCount; +int CurrentVariable; +int CurrentExpression; +struct TNamedVariable* NamedVariable; // array of named variables +struct TExpression* Expression; // array of math expressions + +//----------------------------------------------------------------------------- +// External functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// controls_create +// controls_delete +// controls_init +// controls_addToCount +// controls_addVariable +// controls_addExpression +// controls_addRuleClause +// controls_evaluate + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +int addPremise(int r, int type, char* Tok[], int nToks); +int getPremiseVariable(char* tok[], int nToks, int* k, struct TVariable* v); +int getPremiseValue(char* token, int attrib, double* value); +int addAction(int r, char* Tok[], int nToks); + +int evaluatePremise(struct TPremise* p, double tStep); +double getVariableValue(struct TVariable v); +int compareTimes(double lhsValue, int relation, double rhsValue, + double halfStep); +int compareValues(double lhsValue, int relation, double rhsValue); + +void updateActionList(struct TAction* a); +int executeActionList(DateTime currentTime); +void clearActionList(void); +void deleteActionList(void); +void deleteRules(void); + +int findExactMatch(char *s, char *keyword[]); +int setActionSetting(char* tok[], int nToks, int* curve, int* tseries, + int* attrib, double value[]); +void updateActionValue(struct TAction* a, DateTime currentTime, double dt); +double getPIDSetting(struct TAction* a, double dt); + +int getVariableIndex(char* varName); +double getNamedVariableValue(int varIndex); +int getExpressionIndex(char* exprName); +int getGageAttrib(char* token); +double getRainValue(struct TVariable v); + +//============================================================================= + +void controls_init() +// +// Input: none +// Output: none +// Purpose: initializes the control rule system. +// +{ + Rules = NULL; + NamedVariable = NULL; + Expression = NULL; + RuleCount = 0; + VariableCount = 0; + ExpressionCount = 0; +} + +//============================================================================= + +void controls_addToCount(char* s) +// +// Input: s = either VARIABLE or EXPRESSION +// Output: none +// Purpose: updates the number of named variables or math expressions used +// by control rules. +// +{ + if (match(s, w_VARIABLE)) VariableCount++; + else if (match(s, w_EXPRESSION)) ExpressionCount++; +} + +//============================================================================= + +int controls_create(int n) +// +// Input: n = total number of control rules +// Output: returns error code +// Purpose: creates an array of control rules. +// +{ + int r; + ActionList = NULL; + InputState = r_PRIORITY; + RuleCount = n; + if (RuleCount > 0) + { + Rules = (struct TRule *) calloc(RuleCount, sizeof(struct TRule)); + if (Rules == NULL) return ERR_MEMORY; + for ( r=0; r 0) + { + NamedVariable = (struct TNamedVariable *) calloc(VariableCount, + sizeof(struct TNamedVariable)); + if (NamedVariable == NULL) return ERR_MEMORY; + } + if (ExpressionCount > 0) + { + Expression = (struct TExpression *) calloc(ExpressionCount, + sizeof(struct TExpression)); + if (Expression == NULL) return ERR_MEMORY; + } + return 0; +} + +//============================================================================= + +void controls_delete(void) +// +// Input: none +// Output: none +// Purpose: deletes all control rules. +// +{ + int i; + + for (i = 0; i < ExpressionCount; i++) + { + mathexpr_delete(Expression[i].expression); + Expression[i].expression = NULL; + } + FREE(Expression); + FREE(NamedVariable); + + if ( RuleCount == 0 ) return; + deleteActionList(); + deleteRules(); +} + +//============================================================================= + +int controls_addVariable(char* tok[], int nToks) +// +// Input: tok = an array of string tokens +// n = the size of tok[] +// Output: returns error code +// Purpose: adds a named variable to the control rule system from a +// tokenized line of input with formats: +// VARIABLE name = Object id attribute +// VARIABLE name = SIMULATION attribute +// +{ + struct TVariable v1; + int k, err; + + CurrentVariable++; + if (nToks < 5) return ERR_ITEMS; + if (findExactMatch(tok[1], AttribWords) >= 0) + return error_setInpError(ERR_KEYWORD, tok[1]); + if (!match(tok[2], "=")) return error_setInpError(ERR_KEYWORD, tok[2]); + if (!match(tok[3], "SIMULATION") && nToks < 6) return ERR_ITEMS; + k = 3; + err = getPremiseVariable(tok, nToks, &k, &v1); + if (err > 0) return err; + k = CurrentVariable; + NamedVariable[k].variable = v1; + sstrncpy(NamedVariable[k].name, tok[1], MAXVARNAME); + return 0; +} + +//============================================================================= + +int controls_addExpression(char* tok[], int nToks) +// +// Input: tok = an array of string tokens +// n = number of tokens +// Output: returns error code +// Purpose: adds a math expression to the control rule system from a +// a tokenized line of input with format: +// EXPRESSION name = +// +{ + int i, k; + char s[MAXLINE + 1]; + MathExpr* expr; + + CurrentExpression++; + if (nToks < 4) return ERR_ITEMS; + k = CurrentExpression; + Expression[k].expression = NULL; + sstrncpy(Expression[k].name, tok[1], MAXVARNAME); + sstrncpy(s, tok[3], MAXLINE); + for (i = 4; i < nToks; i++) + { + sstrcat(s, " ", MAXLINE); + sstrcat(s, tok[i], MAXLINE); + } + + expr = mathexpr_create(s, getVariableIndex); + if (expr == NULL) + return error_setInpError(ERR_MATH_EXPR, ""); + + Expression[k].expression = expr; + return 0; +} + +//============================================================================= + +int getVariableIndex(char* varName) +// +// Input: varName = string containing a variable name +// Output: returns the index of the named variable or -1 if not found +// Purpose: finds the array index of a named variable. +// +{ + int i; + for (i = 0; i < VariableCount; i++) + { + if (match(varName, NamedVariable[i].name)) return i; + } + return -1; +} + +//============================================================================= + +double getNamedVariableValue(int varIndex) +// +// Input: varIndex = index of a named variable +// Output: returns the current value of the variable +// Purpose: finds the value of a named variable. +// +{ + return getVariableValue(NamedVariable[varIndex].variable); +} + +//============================================================================= + +int getExpressionIndex(char* exprName) +// +// Input: exprName = string containing an expression name +// Output: returns the index of the expression or -1 if not found +// Purpose: finds the array index of a math expression +// +{ + int i; + for (i = 0; i < ExpressionCount; i++) + { + if (match(exprName, Expression[i].name)) return i; + } + return -1; +} + +//============================================================================= + +int controls_addRuleClause(int r, int keyword, char* tok[], int nToks) +// +// Input: r = rule index +// keyword = the clause's keyword code (IF, THEN, etc.) +// tok = an array of string tokens that comprises the clause +// nToks = number of tokens +// Output: returns an error code +// Purpose: addd a new clause to a control rule. +// +{ + switch (keyword) + { + case r_RULE: + if ( Rules[r].ID == NULL ) + Rules[r].ID = project_findID(CONTROL, tok[1]); + InputState = r_RULE; + if ( nToks > 2 ) return ERR_RULE; + return 0; + + case r_IF: + if ( InputState != r_RULE ) return ERR_RULE; + InputState = r_IF; + return addPremise(r, r_AND, tok, nToks); + + case r_AND: + if ( InputState == r_IF ) return addPremise(r, r_AND, tok, nToks); + else if ( InputState == r_THEN || InputState == r_ELSE ) + return addAction(r, tok, nToks); + else return ERR_RULE; + + case r_OR: + if ( InputState != r_IF ) return ERR_RULE; + return addPremise(r, r_OR, tok, nToks); + + case r_THEN: + if ( InputState != r_IF ) return ERR_RULE; + InputState = r_THEN; + return addAction(r, tok, nToks); + + case r_ELSE: + if ( InputState != r_THEN ) return ERR_RULE; + InputState = r_ELSE; + return addAction(r, tok, nToks); + + case r_PRIORITY: + if ( InputState != r_THEN && InputState != r_ELSE ) return ERR_RULE; + InputState = r_PRIORITY; + if ( !getDouble(tok[1], &Rules[r].priority) ) return ERR_NUMBER; + if ( nToks > 2 ) return ERR_RULE; + return 0; + } + return 0; +} + +//============================================================================= + +int controls_evaluate(DateTime currentTime, DateTime elapsedTime, double tStep) +// +// Input: currentTime = current simulation date/time +// elapsedTime = decimal days since start of simulation +// tStep = simulation time step (days) +// Output: returns number of new actions taken +// Purpose: evaluates all control rules at current time of the simulation. +// +{ + int r; // control rule index + int result; // TRUE if rule premises satisfied + struct TPremise* p; // pointer to rule premise clause + struct TAction* a; // pointer to rule action clause + + // --- save date and time to shared variables + CurrentDate = floor(currentTime); + CurrentTime = currentTime - floor(currentTime); + ElapsedTime = elapsedTime; + + // --- evaluate each rule + if ( RuleCount == 0 ) return 0; + clearActionList(); + for (r=0; rtype == r_OR ) + { + if ( result == FALSE ) + result = evaluatePremise(p, tStep); + } + else + { + if ( result == FALSE ) break; + result = evaluatePremise(p, tStep); + } + p = p->next; + } + + // --- if premises true, add THEN clauses to action list + // else add ELSE clauses to action list + if ( result == TRUE ) a = Rules[r].thenActions; + else a = Rules[r].elseActions; + while (a) + { + updateActionValue(a, currentTime, tStep); + updateActionList(a); + a = a->next; + } + } + + // --- execute actions on action list + if ( ActionList ) return executeActionList(currentTime); + else return 0; +} + +//============================================================================= + +int addPremise(int r, int type, char* tok[], int nToks) +// +// Input: r = control rule index +// type = type of premise (IF, AND, OR) +// tok = array of string tokens containing premise statement +// nToks = number of string tokens +// Output: returns an error code +// Purpose: adds a new premise to a control rule. +// +{ + int relation, n, err = 0; + double value = MISSING; + struct TPremise* p; + struct TVariable v1; + struct TVariable v2; + int obj, exprIndex, varIndex = -1; + + // --- initialize LHS variable v1 + if (nToks < 4) return ERR_ITEMS; + v1.attribute = -1; + v1.object = -1; + v1.index = -1; + n = 1; + + // --- check if 2nd token is a math expression + exprIndex = getExpressionIndex(tok[1]); + + // --- if not then check if it's a named variable + if (exprIndex < 0) + { + varIndex = getVariableIndex(tok[n]); + if (varIndex >= 0) + { + v1 = NamedVariable[varIndex].variable; + } + + // otherwise parse object|index|attribute tokens + else + { + err = getPremiseVariable(tok, nToks, &n, &v1); + if ( err > 0 ) return err; + } + } + + // --- get relational operator + n++; + if ( n >= nToks ) return error_setInpError(ERR_ITEMS, ""); + relation = findExactMatch(tok[n], RelOpWords); + if ( relation < 0 ) return error_setInpError(ERR_KEYWORD, tok[n]); + + // --- initialize RHS variable v2 + v2.attribute = -1; + v2.object = -1; + v2.index = -1; + n++; + if (n >= nToks) return error_setInpError(ERR_ITEMS, ""); + + // --- check for named RHS variable + varIndex = getVariableIndex(tok[n]); + if (varIndex >= 0) + { + v2 = NamedVariable[varIndex].variable; + } + + // --- check for object|index|attribute variable + else + { + obj = findmatch(tok[n], ObjectWords); + if (obj >= 0) + { + err = getPremiseVariable(tok, nToks, &n, &v2); + if ( err > 0 ) return ERR_RULE; + if (exprIndex < 0 && v1.attribute != v2.attribute) + report_writeWarningMsg(WARN11, Rules[r].ID); + } + + // --- check for a single RHS value + else + { + err = getPremiseValue(tok[n], v1.attribute, &value); + if ( err > 0 ) return err; + } + } + + // --- make sure another clause is not on same line + n++; + if ( n < nToks && findmatch(tok[n], RuleKeyWords) >= 0 ) return ERR_RULE; + + // --- create the premise object + p = (struct TPremise *) malloc(sizeof(struct TPremise)); + if ( !p ) return ERR_MEMORY; + p->type = type; + p->exprIndex = exprIndex; + p->lhsVar = v1; + p->rhsVar = v2; + p->relation = relation; + p->value = value; + p->next = NULL; + if ( Rules[r].firstPremise == NULL ) + { + Rules[r].firstPremise = p; + } + else + { + Rules[r].lastPremise->next = p; + } + Rules[r].lastPremise = p; + return 0; +} + +//============================================================================= + +int getPremiseVariable(char* tok[], int nToks, int* k, struct TVariable* v) +// +// Input: tok = array of string tokens +// nToks = number of tokens +// k = index of current token +// Output: returns an error code; updates k to new current token and +// places identity of specified variable in v +// Purpose: parses a variable (e.g., Node 123 Depth) used in a control rule. +// +{ + int n = *k; + int object = -1; + int index = -1; + int obj, attrib; + + // --- get object type + obj = findmatch(tok[n], ObjectWords); + if ( obj < 0 ) return error_setInpError(ERR_KEYWORD, tok[n]); + + // --- get object index from its name + n++; + if (n >= nToks) return error_setInpError(ERR_ITEMS, ""); + switch (obj) + { + case r_GAGE: + index = project_findObject(GAGE, tok[n]); + if (index < 0) return error_setInpError(ERR_NAME, tok[n]); + object = r_GAGE; + break; + + case r_NODE: + index = project_findObject(NODE, tok[n]); + if ( index < 0 ) return error_setInpError(ERR_NAME, tok[n]); + object = r_NODE; + break; + + case r_LINK: + case r_CONDUIT: + case r_PUMP: + case r_ORIFICE: + case r_WEIR: + case r_OUTLET: + index = project_findObject(LINK, tok[n]); + if ( index < 0 ) return error_setInpError(ERR_NAME, tok[n]); + object = r_LINK; + break; + default: n--; + } + n++; + if (n >= nToks) return error_setInpError(ERR_ITEMS, ""); + + // --- get attribute index from its name + if (object == r_GAGE) + attrib = getGageAttrib(tok[n]); + else + attrib = findmatch(tok[n], AttribWords); + if ( attrib < 0 ) return error_setInpError(ERR_KEYWORD, tok[n]); + + // --- check that attribute belongs to object type + if (obj == r_GAGE) + { + + } + + else if ( obj == r_NODE ) switch (attrib) + { + case r_DEPTH: + case r_MAXDEPTH: + case r_HEAD: + case r_VOLUME: + case r_INFLOW: break; + default: return error_setInpError(ERR_KEYWORD, tok[n]); + } + + // --- check for link TIMEOPEN & TIMECLOSED attributes + else if ( object == r_LINK && index >= 0 && + ( (attrib == r_TIMEOPEN || attrib == r_TIMECLOSED) + )) + { + // nothing to do here + } + + else if ( obj == r_LINK || obj == r_CONDUIT ) switch (attrib) + { + case r_STATUS: + case r_DEPTH: + case r_FULLFLOW: + case r_FULLDEPTH: + case r_FLOW: + case r_LENGTH: + case r_SLOPE: + case r_VELOCITY: break; + default: return error_setInpError(ERR_KEYWORD, tok[n]); + } + else if ( obj == r_PUMP ) switch (attrib) + { + case r_FLOW: + case r_SETTING: + case r_STATUS: break; + default: return error_setInpError(ERR_KEYWORD, tok[n]); + } + else if ( obj == r_ORIFICE || obj == r_WEIR || + obj == r_OUTLET ) switch (attrib) + { + case r_FLOW: + case r_SETTING: break; + default: return error_setInpError(ERR_KEYWORD, tok[n]); + } + else switch (attrib) + { + case r_TIME: + case r_DATE: + case r_CLOCKTIME: + case r_DAY: + case r_MONTH: + case r_DAYOFYEAR: break; + default: return error_setInpError(ERR_KEYWORD, tok[n]); + } + + // --- populate variable structure + v->object = object; + v->index = index; + v->attribute = attrib; + *k = n; + return 0; +} + +//============================================================================= + +int getGageAttrib(char* token) +// +// Input: token = a string token +// Output: returns an attribute code or -1 if an error occurred +// Purpose: determines the atrribute code for a rain gage variable. +// Note: a valid token is INTENSITY for current rainfall intensity +// (attribute code = 0) or nHR_PRECIP for total rain depth +// over past n hours (attribute code = n). +// +{ + int attrib; + + // --- check if token is currrent rainfall intensity + if (match(token, IntensityWord)) + return 0; + + // --- token is past rain depth - read number of past hours + attrib = atoi(token); + + // --- check that number of hours is in allowable range + if (attrib < 1 || attrib > MAXPASTRAIN) + return -1; + return attrib; +} + +//============================================================================= + +int getPremiseValue(char* token, int attrib, double* value) +// +// Input: token = a string token +// attrib = index of a node/link attribute +// Output: value = attribute value; +// returns an error code; +// Purpose: parses the numerical value of a particular node/link attribute +// in the premise clause of a control rule. +// +{ + char strDate[25]; + switch (attrib) + { + case r_STATUS: + *value = findmatch(token, StatusWords); + if ( *value < 0.0 ) *value = findmatch(token, ConduitWords); + if ( *value < 0.0 ) return error_setInpError(ERR_KEYWORD, token); + break; + + case r_TIME: + case r_CLOCKTIME: + case r_TIMEOPEN: + case r_TIMECLOSED: + if ( !datetime_strToTime(token, value) ) + return error_setInpError(ERR_DATETIME, token); + break; + + case r_DATE: + if ( !datetime_strToDate(token, value) ) + return error_setInpError(ERR_DATETIME, token); + break; + + case r_DAY: + if ( !getDouble(token, value) ) + return error_setInpError(ERR_NUMBER, token); + if ( *value < 1.0 || *value > 7.0 ) + return error_setInpError(ERR_DATETIME, token); + break; + + case r_MONTH: + if ( !getDouble(token, value) ) + return error_setInpError(ERR_NUMBER, token); + if ( *value < 1.0 || *value > 12.0 ) + return error_setInpError(ERR_DATETIME, token); + break; + + case r_DAYOFYEAR: + sstrncpy(strDate, token, 6); + sstrcat(strDate, "/1947", 25); + if ( datetime_strToDate(strDate, value) ) + { + *value = datetime_dayOfYear(*value); + } + else if ( !getDouble(token, value) || *value < 1 || *value > 365 ) + return error_setInpError(ERR_DATETIME, token); + break; + + default: if ( !getDouble(token, value) ) + return error_setInpError(ERR_NUMBER, token); + } + return 0; +} + +//============================================================================= + +int addAction(int r, char* tok[], int nToks) +// +// Input: r = control rule index +// tok = array of string tokens containing action statement +// nToks = number of string tokens +// Output: returns an error code +// Purpose: adds a new action to a control rule. +// +{ + int obj, link, attrib; + int curve = -1, tseries = -1; + int n; + int err; + double values[] = {1.0, 0.0, 0.0}; + + struct TAction* a; + + // --- check for proper number of tokens + if ( nToks < 6 ) return error_setInpError(ERR_ITEMS, ""); + + // --- check for valid object type + obj = findmatch(tok[1], ObjectWords); + if ( obj != r_LINK && obj != r_CONDUIT && obj != r_PUMP && + obj != r_ORIFICE && obj != r_WEIR && obj != r_OUTLET ) + return error_setInpError(ERR_KEYWORD, tok[1]); + + // --- check that object name exists and is of correct type + link = project_findObject(LINK, tok[2]); + if ( link < 0 ) return error_setInpError(ERR_NAME, tok[2]); + switch (obj) + { + case r_CONDUIT: + if ( Link[link].type != CONDUIT ) + return error_setInpError(ERR_NAME, tok[2]); + break; + case r_PUMP: + if ( Link[link].type != PUMP ) + return error_setInpError(ERR_NAME, tok[2]); + break; + case r_ORIFICE: + if ( Link[link].type != ORIFICE ) + return error_setInpError(ERR_NAME, tok[2]); + break; + case r_WEIR: + if ( Link[link].type != WEIR ) + return error_setInpError(ERR_NAME, tok[2]); + break; + case r_OUTLET: + if ( Link[link].type != OUTLET ) + return error_setInpError(ERR_NAME, tok[2]); + break; + } + + // --- check for valid attribute name + attrib = findmatch(tok[3], AttribWords); + if ( attrib < 0 ) return error_setInpError(ERR_KEYWORD, tok[3]); + + // --- get control action setting + if ( obj == r_CONDUIT ) + { + if ( attrib == r_STATUS ) + { + values[0] = findmatch(tok[5], ConduitWords); + if ( values[0] < 0.0 ) + return error_setInpError(ERR_KEYWORD, tok[5]); + } + else return error_setInpError(ERR_KEYWORD, tok[3]); + } + + else if ( obj == r_PUMP ) + { + if ( attrib == r_STATUS ) + { + values[0] = findmatch(tok[5], StatusWords); + if ( values[0] < 0.0 ) + return error_setInpError(ERR_KEYWORD, tok[5]); + } + else if ( attrib == r_SETTING ) + { + err = setActionSetting(tok, nToks, &curve, &tseries, + &attrib, values); + if ( err > 0 ) return err; + } + else return error_setInpError(ERR_KEYWORD, tok[3]); + } + + else if ( obj == r_ORIFICE || obj == r_WEIR || obj == r_OUTLET ) + { + if ( attrib == r_SETTING ) + { + err = setActionSetting(tok, nToks, &curve, &tseries, + &attrib, values); + if ( err > 0 ) return err; + if ( attrib == r_SETTING + && (values[0] < 0.0 || values[0] > 1.0) ) + return error_setInpError(ERR_NUMBER, tok[5]); + } + else return error_setInpError(ERR_KEYWORD, tok[3]); + } + else return error_setInpError(ERR_KEYWORD, tok[1]); + + // --- check if another clause is on same line + n = 6; + if ( curve >= 0 || tseries >= 0 ) n = 7; + if ( attrib == r_PID ) n = 9; + if ( n < nToks && findmatch(tok[n], RuleKeyWords) >= 0 ) return ERR_RULE; + + // --- create the action object + a = (struct TAction *) malloc(sizeof(struct TAction)); + if ( !a ) return ERR_MEMORY; + a->rule = r; + a->link = link; + a->attribute = attrib; + a->curve = curve; + a->tseries = tseries; + a->value = values[0]; + if ( attrib == r_PID ) + { + a->kp = values[0]; + a->ki = values[1]; + a->kd = values[2]; + a->e1 = 0.0; + a->e2 = 0.0; + } + if ( InputState == r_THEN ) + { + a->next = Rules[r].thenActions; + Rules[r].thenActions = a; + } + else + { + a->next = Rules[r].elseActions; + Rules[r].elseActions = a; + } + return 0; +} + +//============================================================================= + +int setActionSetting(char* tok[], int nToks, int* curve, int* tseries, + int* attrib, double values[]) +// +// Input: tok = array of string tokens containing action statement +// nToks = number of string tokens +// Output: curve = index of controller curve +// tseries = index of controller time series +// attrib = r_PID if PID controller used +// values = values of control settings +// returns an error code +// Purpose: identifies how control actions settings are determined. +// +{ + int k, m; + + // --- see if control action is determined by a Curve or Time Series + if (nToks < 6) return error_setInpError(ERR_ITEMS, ""); + k = findmatch(tok[5], SettingTypeWords); + if ( k >= 0 && nToks < 7 ) return error_setInpError(ERR_ITEMS, ""); + switch (k) + { + + // --- control determined by a curve - find curve index + case r_CURVE: + m = project_findObject(CURVE, tok[6]); + if ( m < 0 ) return error_setInpError(ERR_NAME, tok[6]); + *curve = m; + break; + + // --- control determined by a time series - find time series index + case r_TIMESERIES: + m = project_findObject(TSERIES, tok[6]); + if ( m < 0 ) return error_setInpError(ERR_NAME, tok[6]); + *tseries = m; + Tseries[m].refersTo = CONTROL; + break; + + // --- control determined by PID controller + case r_PID: + if (nToks < 9) return error_setInpError(ERR_ITEMS, ""); + for (m=6; m<=8; m++) + { + if ( !getDouble(tok[m], &values[m-6]) ) + return error_setInpError(ERR_NUMBER, tok[m]); + } + *attrib = r_PID; + break; + + // --- direct numerical control is used + default: + if ( !getDouble(tok[5], &values[0]) ) + return error_setInpError(ERR_NUMBER, tok[5]); + } + return 0; +} + +//============================================================================= + +void updateActionValue(struct TAction* a, DateTime currentTime, double dt) +// +// Input: a = an action object +// currentTime = current simulation date/time (days) +// dt = time step (days) +// Output: none +// Purpose: updates value of actions found from Curves or Time Series. +// +{ + if ( a->curve >= 0 ) + { + a->value = table_lookup(&Curve[a->curve], ControlValue); + } + else if ( a->tseries >= 0 ) + { + a->value = table_tseriesLookup(&Tseries[a->tseries], currentTime, TRUE); + } + else if ( a->attribute == r_PID ) + { + a->value = getPIDSetting(a, dt); + } +} + +//============================================================================= + +double getPIDSetting(struct TAction* a, double dt) +// +// Input: a = an action object +// dt = current time step (days) +// Output: returns a new link setting +// Purpose: computes a new setting for a link subject to a PID controller. +// +// Note: a->kp = gain coefficient, +// a->ki = integral time (minutes) +// a->k2 = derivative time (minutes) +// a->e1 = error from previous time step +// a->e2 = error from two time steps ago +{ + double e0, setting; + double p, i, d, update; + double tolerance = 0.0001; + + // --- convert time step from days to minutes + dt *= 1440.0; + + // --- determine relative error in achieving controller set point + e0 = SetPoint - ControlValue; + if ( fabs(e0) > TINY ) + { + if ( SetPoint != 0.0 ) e0 = e0/SetPoint; + else e0 = e0/ControlValue; + } + + // --- reset previous errors to 0 if controller gets stuck + if (fabs(e0 - a->e1) < tolerance) + { + a->e2 = 0.0; + a->e1 = 0.0; + } + + // --- use the recursive form of the PID controller equation to + // determine the new setting for the controlled link + p = (e0 - a->e1); + if ( a->ki == 0.0 ) i = 0.0; + else i = e0 * dt / a->ki; + d = a->kd * (e0 - 2.0*a->e1 + a->e2) / dt; + update = a->kp * (p + i + d); + if ( fabs(update) < tolerance ) update = 0.0; + setting = Link[a->link].targetSetting + update; + + // --- update previous errors + a->e2 = a->e1; + a->e1 = e0; + + // --- check that new setting lies within feasible limits + if ( setting < 0.0 ) setting = 0.0; + if (Link[a->link].type != PUMP && setting > 1.0 ) setting = 1.0; + return setting; +} + +//============================================================================= + +void updateActionList(struct TAction* a) +// +// Input: a = an action object +// Output: none +// Purpose: adds a new action to the list of actions to be taken. +// +{ + struct TActionList* listItem; + struct TAction* a1; + double priority = Rules[a->rule].priority; + + // --- check if link referred to in action is already listed + listItem = ActionList; + while ( listItem ) + { + a1 = listItem->action; + if ( !a1 ) break; + if ( a1->link == a->link ) + { + // --- replace old action if new action has higher priority + if ( priority > Rules[a1->rule].priority ) listItem->action = a; + return; + } + listItem = listItem->next; + } + + // --- action not listed so add it to ActionList //5.2.1 + if ( !listItem ) + { + listItem = (struct TActionList *) malloc(sizeof(struct TActionList)); + listItem->next = ActionList; + ActionList = listItem; + } + listItem->action = a; +} + +//============================================================================= + +int executeActionList(DateTime currentTime) +// +// Input: currentTime = current date/time of the simulation +// Output: returns number of new actions taken +// Purpose: executes all actions required by fired control rules. +// +{ + struct TActionList* listItem; + struct TActionList* nextItem; + struct TAction* a1; + int count = 0; + + listItem = ActionList; + while ( listItem ) + { + a1 = listItem->action; + if ( !a1 ) break; + if ( a1->link >= 0 ) + { + if ( Link[a1->link].targetSetting != a1->value ) + { + Link[a1->link].targetSetting = a1->value; + if ( RptFlags.controls && a1->curve < 0 + && a1->tseries < 0 && a1->attribute != r_PID ) + report_writeControlAction(currentTime, Link[a1->link].ID, + a1->value, Rules[a1->rule].ID); + count++; + } + } + nextItem = listItem->next; + listItem = nextItem; + } + return count; +} + +//============================================================================= + +int evaluatePremise(struct TPremise* p, double tStep) +// +// Input: p = a control rule premise condition +// tStep = current time step (days) +// Output: returns TRUE if the condition is true or FALSE otherwise +// Purpose: evaluates the truth of a control rule premise condition. +// +{ + double lhsValue, rhsValue; + int result = FALSE; + + // --- check if left hand side (lhs) of premise is an expression + if (p->exprIndex >= 0) + lhsValue = mathexpr_eval(Expression[p->exprIndex].expression, + getNamedVariableValue); + + // --- otherwise get value of the lhs variable + else + lhsValue = getVariableValue(p->lhsVar); + + // --- if right hand side (rhs) of premise is a variable then get its value + if ( p->value == MISSING ) rhsValue = getVariableValue(p->rhsVar); + else rhsValue = p->value; + if ( lhsValue == MISSING || rhsValue == MISSING ) return FALSE; + + // --- compare the lhs of the premise to the rhs + switch (p->lhsVar.attribute) + { + case r_TIME: + case r_CLOCKTIME: + return compareTimes(lhsValue, p->relation, rhsValue, tStep/2.0); + case r_TIMEOPEN: + case r_TIMECLOSED: + result = compareTimes(lhsValue, p->relation, rhsValue, tStep/2.0); + ControlValue = lhsValue * 24.0; // convert time from days to hours + return result; + default: + return compareValues(lhsValue, p->relation, rhsValue); + } +} + +//============================================================================= + +double getVariableValue(struct TVariable v) +{ + int i = -1; // a node index + int j = -1; // a link index + + if (v.object == r_GAGE) + return getRainValue(v); + if (v.object == r_NODE) i = v.index; + if (v.object == r_LINK) j = v.index; + + switch ( v.attribute ) + { + case r_TIME: + return ElapsedTime; + + case r_DATE: + return CurrentDate; + + case r_CLOCKTIME: + return CurrentTime; + + case r_DAY: + return datetime_dayOfWeek(CurrentDate); + + case r_MONTH: + return datetime_monthOfYear(CurrentDate); + + case r_DAYOFYEAR: + return datetime_dayOfYear(CurrentDate); + + case r_STATUS: + if ( j < 0 || + (Link[j].type != CONDUIT && Link[j].type != PUMP) ) return MISSING; + else return Link[j].setting; + + case r_SETTING: + if ( j < 0 || (Link[j].type != PUMP && + Link[j].type != ORIFICE && + Link[j].type != WEIR) ) + return MISSING; + else return Link[j].setting; + + case r_FLOW: + if ( j < 0 ) return MISSING; + else return Link[j].direction*Link[j].newFlow*UCF(FLOW); + + case r_FULLFLOW: + case r_FULLDEPTH: + case r_VELOCITY: + case r_LENGTH: + case r_SLOPE: + if ( j < 0 ) return MISSING; + else if (Link[j].type != CONDUIT) return MISSING; + switch (v.attribute) + { + case r_FULLFLOW: return Link[j].qFull * UCF(FLOW); + case r_FULLDEPTH: return Link[j].xsect.yFull * UCF(LENGTH); + case r_VELOCITY: + return link_getVelocity(j, Link[j].newFlow, Link[j].newDepth) + * UCF(LENGTH); + case r_LENGTH: return Conduit[Link[j].subIndex].length * UCF(LENGTH); + case r_SLOPE: return Conduit[Link[j].subIndex].slope; + default: return MISSING; + } + case r_DEPTH: + if ( j >= 0 ) return Link[j].newDepth*UCF(LENGTH); + else if ( i >= 0 ) + return Node[i].newDepth*UCF(LENGTH); + else return MISSING; + + case r_MAXDEPTH: + if (i >= 0) return Node[i].fullDepth*UCF(LENGTH); + else return MISSING; + + case r_HEAD: + if ( i < 0 ) return MISSING; + return (Node[i].newDepth + Node[i].invertElev) * UCF(LENGTH); + + case r_VOLUME: + if ( i < 0 ) return MISSING; + return (Node[i].newVolume * UCF(VOLUME)); + + case r_INFLOW: + if ( i < 0 ) return MISSING; + else return Node[i].newLatFlow*UCF(FLOW); + + case r_TIMEOPEN: + if ( j < 0 ) return MISSING; + if ( Link[j].setting <= 0.0 ) return MISSING; + return CurrentDate + CurrentTime - Link[j].timeLastSet; + + case r_TIMECLOSED: + if ( j < 0 ) return MISSING; + if ( Link[j].setting > 0.0 ) return MISSING; + return CurrentDate + CurrentTime - Link[j].timeLastSet; + + default: return MISSING; + } +} + +//============================================================================= + +double getRainValue(struct TVariable v) +// +// Input: v = a rule premise variable for a rain gage +// Output: returns current or past rainfall amount +// Purpose: retrieves either the current rainfall intensity or the past +// rainfall total for a rain gage. +// +{ + if (v.index < 0) return MISSING; + else if (Gage[v.index].isUsed == FALSE) return 0.0; + else if (v.attribute == 0) + return Gage[v.index].rainfall; + else return gage_getPastRain(v.index, v.attribute); +} + +//============================================================================= + +int compareTimes(double lhsValue, int relation, double rhsValue, double halfStep) +// +// Input: lhsValue = date/time value on left hand side of relation +// relation = relational operator code (see RuleRelation enumeration) +// rhsValue = date/time value on right hand side of relation +// halfStep = 1/2 the current time step (days) +// Output: returns TRUE if time relation is satisfied +// Purpose: evaluates the truth of a relation between two date/times. +// +{ + if ( relation == EQ ) + { + if ( lhsValue >= rhsValue - halfStep + && lhsValue < rhsValue + halfStep ) return TRUE; + return FALSE; + } + else if ( relation == NE ) + { + if ( lhsValue < rhsValue - halfStep + || lhsValue >= rhsValue + halfStep ) return TRUE; + return FALSE; + } + else return compareValues(lhsValue, relation, rhsValue); +} + +//============================================================================= + +int compareValues(double lhsValue, int relation, double rhsValue) +// Input: lhsValue = value on left hand side of relation +// relation = relational operator code (see RuleRelation enumeration) +// rhsValue = value on right hand side of relation +// Output: returns TRUE if relation is satisfied +// Purpose: evaluates the truth of a relation between two values. +{ + SetPoint = rhsValue; + ControlValue = lhsValue; + switch (relation) + { + case EQ: if ( lhsValue == rhsValue ) return TRUE; break; + case NE: if ( lhsValue != rhsValue ) return TRUE; break; + case LT: if ( lhsValue < rhsValue ) return TRUE; break; + case LE: if ( lhsValue <= rhsValue ) return TRUE; break; + case GT: if ( lhsValue > rhsValue ) return TRUE; break; + case GE: if ( lhsValue >= rhsValue ) return TRUE; break; + } + return FALSE; +} + +//============================================================================= + +void clearActionList(void) +// +// Input: none +// Output: none +// Purpose: clears the list of actions to be executed. +// +{ + struct TActionList* listItem; + listItem = ActionList; + while ( listItem ) + { + listItem->action = NULL; + listItem = listItem->next; + } +} + +//============================================================================= + +void deleteActionList(void) +// +// Input: none +// Output: none +// Purpose: frees the memory used to hold the list of actions to be executed. +// +{ + struct TActionList* listItem; + struct TActionList* nextItem; + listItem = ActionList; + while ( listItem ) + { + nextItem = listItem->next; + free(listItem); + listItem = nextItem; + } + ActionList = NULL; +} + +//============================================================================= + +void deleteRules(void) +// +// Input: none +// Output: none +// Purpose: frees the memory used for all of the control rules. +// +{ + struct TPremise* p; + struct TPremise* pnext; + struct TAction* a; + struct TAction* anext; + int r; + for (r=0; rnext; + free(p); + p = pnext; + } + a = Rules[r].thenActions; + while (a ) + { + anext = a->next; + free(a); + a = anext; + } + a = Rules[r].elseActions; + while (a ) + { + anext = a->next; + free(a); + a = anext; + } + } + FREE(Rules); + RuleCount = 0; +} + +//============================================================================= + +int findExactMatch(char *s, char *keyword[]) +// +// Input: s = character string +// keyword = array of keyword strings +// Output: returns index of keyword which matches s or -1 if no match found +// Purpose: finds exact match between string and array of keyword strings. +// +{ + int i = 0; + while (keyword[i] != NULL) + { + if ( strcomp(s, keyword[i]) ) return(i); + i++; + } + return(-1); +} + +//============================================================================= diff --git a/src/culvert.c b/src/culvert.c new file mode 100644 index 000000000..5f62877b7 --- /dev/null +++ b/src/culvert.c @@ -0,0 +1,411 @@ +//----------------------------------------------------------------------------- +// culvert.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Culvert equations for SWMM5 +// +// Computes flow reduction in a culvert-type conduit due to +// inlet control using equations from the FHWA HEC-5 circular. +// +// Update History +// ============== +// Build 5.1.013: +// - C parameter corrected for Arch, Corrugated Metal, Mitered culvert. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include "findroot.h" +#include "headers.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +enum CulvertParam {FORM, K, M, C, Y}; +static const int MAX_CULVERT_CODE = 57; +static const double Params[58][5] = { + +// FORM K M C Y +//------------------------------------ + {0.0, 0.0, 0.0, 0.0, 0.00}, + + //Circular concrete + {1.0, 0.0098, 2.00, 0.0398, 0.67}, //Square edge w/headwall + {1.0, 0.0018, 2.00, 0.0292, 0.74}, //Groove end w/headwall + {1.0, 0.0045, 2.00, 0.0317, 0.69}, //Groove end projecting + + //Circular Corrugated Metal Pipe + {1.0, 0.0078, 2.00, 0.0379, 0.69}, //Headwall + {1.0, 0.0210, 1.33, 0.0463, 0.75}, //Mitered to slope + {1.0, 0.0340, 1.50, 0.0553, 0.54}, //Projecting + + //Circular Pipe, Beveled Ring Entrance + {1.0, 0.0018, 2.50, 0.0300, 0.74}, //Beveled ring, 45 deg bevels + {1.0, 0.0018, 2.50, 0.0243, 0.83}, //Beveled ring, 33.7 deg bevels + + //Rectangular Box with Flared Wingwalls + {1.0, 0.026, 1.0, 0.0347, 0.81}, //30-75 deg. wingwall flares + {1.0, 0.061, 0.75, 0.0400, 0.80}, //90 or 15 deg. wingwall flares + {1.0, 0.061, 0.75, 0.0423, 0.82}, //0 deg. wingwall flares (striaght sides) + + //Rectanglar Box with Flared Wingwalls & Top Edge Bevel + {2.0, 0.510, 0.667, 0.0309, 0.80}, //45 deg. flare; 0.43D top edge bevel + {2.0, 0.486, 0.667, 0.0249, 0.83}, //18-33.7 deg flare; 0.083D top edge bevel + + //Rectangular Box; 90-deg Headwall; Chamfered or Beveled Inlet Edges + {2.0, 0.515, 0.667, 0.0375, 0.79}, //chamfered 3/4-in + {2.0, 0.495, 0.667, 0.0314, 0.82}, //beveled 1/2-in/ft at 45 deg (1:1) + {2.0, 0.486, 0.667, 0.0252, 0.865}, //beveled 1-in/ft at 33.7 deg (1:1.5) + + //Rectangular Box; Skewed Headwall; Chamfered or Beveled Inlet Edges + {2.0, 0.545, 0.667, 0.04505,0.73}, //3/4" chamfered edge, 45 deg skewed headwall + {2.0, 0.533, 0.667, 0.0425, 0.705}, //3/4" chamfered edge, 30 deg skewed headwall + {2.0, 0.522, 0.667, 0.0402, 0.68}, //3/4" chamfered edge, 15 deg skewed headwall + {2.0, 0.498, 0.667, 0.0327, 0.75}, //45 deg beveled edge, 10-45 deg skewed headwall + + //Rectangular box, Non-offset Flared Wingwalls; 3/4" Chamfer at Top of Inlet + {2.0, 0.497, 0.667, 0.0339, 0.803}, //45 deg (1:1) wingwall flare + {2.0, 0.493, 0.667, 0.0361, 0.806}, //18.4 deg (3:1) wingwall flare + {2.0, 0.495, 0.667, 0.0386, 0.71}, //18.4 deg (3:1) wingwall flare, 30 deg inlet skew + + //Rectangular box, Offset Flared Wingwalls, Beveled Edge at Inlet Top + {2.0, 0.497, 0.667, 0.0302, 0.835}, //45 deg (1:1) flare, 0.042D top edge bevel + {2.0, 0.495, 0.667, 0.0252, 0.881}, //33.7 deg (1.5:1) flare, 0.083D top edge bevel + {2.0, 0.493, 0.667, 0.0227, 0.887}, //18.4 deg (3:1) flare, 0.083D top edge bevel + + // Corrugated Metal Box + {1.0, 0.0083, 2.00, 0.0379, 0.69}, //90 deg headwall + {1.0, 0.0145, 1.75, 0.0419, 0.64}, //Thick wall projecting + {1.0, 0.0340, 1.50, 0.0496, 0.57}, //Thin wall projecting + + // Horizontal Ellipse Concrete + {1.0, 0.0100, 2.00, 0.0398, 0.67}, //Square edge w/headwall + {1.0, 0.0018, 2.50, 0.0292, 0.74}, //Grooved end w/headwall + {1.0, 0.0045, 2.00, 0.0317, 0.69}, //Grooved end projecting + + // Vertical Ellipse Concrete + {1.0, 0.0100, 2.00, 0.0398, 0.67}, //Square edge w/headwall + {1.0, 0.0018, 2.50, 0.0292, 0.74}, //Grooved end w/headwall + {1.0, 0.0095, 2.00, 0.0317, 0.69}, //Grooved end projecting + + // Pipe Arch, 18" Corner Radius, Corrugated Metal + {1.0, 0.0083, 2.00, 0.0379, 0.69}, //90 deg headwall + {1.0, 0.0300, 1.00, 0.0463, 0.75}, //Mitered to slope + {1.0, 0.0340, 1.50, 0.0496, 0.57}, //Projecting + + // Pipe Arch, 18" Corner Radius, Corrugated Metal + {1.0, 0.0300, 1.50, 0.0496, 0.57}, //Projecting + {1.0, 0.0088, 2.00, 0.0368, 0.68}, //No bevels + {1.0, 0.0030, 2.00, 0.0269, 0.77}, //33.7 deg bevels + + // Pipe Arch, 31" Corner Radius, Corrugated Metal + {1.0, 0.0300, 1.50, 0.0496, 0.57}, //Projecting + {1.0, 0.0088, 2.00, 0.0368, 0.68}, //No bevels + {1.0, 0.0030, 2.00, 0.0269, 0.77}, //33.7 deg. bevels + + // Arch, Corrugated Metal + {1.0, 0.0083, 2.00, 0.0379, 0.69}, //90 deg headwall + {1.0, 0.0300, 1.00, 0.0473, 0.75}, //Mitered to slope + {1.0, 0.0340, 1.50, 0.0496, 0.57}, //Thin wall projecting + + // Circular Culvert + {2.0, 0.534, 0.555, 0.0196, 0.90}, //Smooth tapered inlet throat + {2.0, 0.519, 0.640, 0.0210, 0.90}, //Rough tapered inlet throat + + // Elliptical Inlet Face + {2.0, 0.536, 0.622, 0.0368, 0.83}, //Tapered inlet, beveled edges + {2.0, 0.5035,0.719, 0.0478, 0.80}, //Tapered inlet, square edges + {2.0, 0.547, 0.800, 0.0598, 0.75}, //Tapered inlet, thin edge projecting + + // Rectangular + {2.0, 0.475, 0.667, 0.0179, 0.97}, //Tapered inlet throat + + // Rectangular Concrete + {2.0, 0.560, 0.667, 0.0446, 0.85}, //Side tapered, less favorable edges + {2.0, 0.560, 0.667, 0.0378, 0.87}, //Side tapered, more favorable edges + + // Rectangular Concrete + {2.0, 0.500, 0.667, 0.0446, 0.65}, //Slope tapered, less favorable edges + {2.0, 0.500, 0.667, 0.0378, 0.71} //Slope tapered, more favorable edges + + }; + +//----------------------------------------------------------------------------- +// Culvert data structure +//----------------------------------------------------------------------------- +typedef struct +{ + double yFull; // full depth of culvert (ft) + double scf; // slope correction factor + double dQdH; // Derivative of flow w.r.t. head + double qc; // Unsubmerged critical flow + double kk; + double mm; // Coeffs. for unsubmerged flow + double ad; + double hPlus; // Intermediate terms + TXsect* xsect; // Pointer to culvert cross section +} TCulvert; + +//----------------------------------------------------------------------------- +// External functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// double culvert_getInflow + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static double getUnsubmergedFlow(int code, double h, TCulvert* culvert); +static double getSubmergedFlow(int code, double h, TCulvert* culvert); +static double getTransitionFlow(int code, double h, double h1, double h2, + TCulvert* culvert); +static double getForm1Flow(double h, TCulvert* culvert); +static double form1Eqn(double yc, void* p); +/* +static void report_CulvertControl(int j, double q0, double q, int condition, + double yRatio); //for debugging only +*/ + +//============================================================================= + +double culvert_getInflow(int j, double q0, double h) +// +// Input: j = link index +// q0 = unmodified flow rate (cfs) +// h = upstream head (ft) +// Output: returns modified flow rate through culvert (cfs) +// Purpose: uses FHWA HEC-5 equations to find flow through inlet +// controlled culverts +// +{ + int code, //culvert type code number + k, //conduit index + condition; //flow condition + double y, //current depth (ft) + y1, //unsubmerged depth limit (ft) + y2, //submerged depth limit (ft) + q; //inlet-controlled flow (cfs) + TCulvert culvert; //intermediate results + + // --- check that we have a culvert conduit + if ( Link[j].type != CONDUIT ) return q0; + culvert.xsect = &Link[j].xsect; + code = culvert.xsect->culvertCode; + if ( code <= 0 || code > MAX_CULVERT_CODE ) return q0; + + // --- compute often-used variables + k = Link[j].subIndex; + culvert.yFull = culvert.xsect->yFull; + culvert.ad = culvert.xsect->aFull * sqrt(culvert.yFull); + + // --- slope correction factor (-7 for mitered inlets, 0.5 for others) + switch (code) + { + case 5: + case 37: + case 46: culvert.scf = -7.0 * Conduit[k].slope; break; + default: culvert.scf = 0.5 * Conduit[k].slope; + } + + // --- find head relative to culvert's upstream invert + // (can be greater than yFull when inlet is submerged) + y = h - (Node[Link[j].node1].invertElev + Link[j].offset1); + + // --- check for submerged flow (based on FHWA criteria of Q/AD > 4) + y2 = culvert.yFull * (16.0 * Params[code][C] + Params[code][Y] - culvert.scf); + if ( y >= y2 ) + { + q = getSubmergedFlow(code, y, &culvert); + condition = 2; + } + else + { + // --- check for unsubmerged flow (based on arbitrary limit of 0.95 full) + y1 = 0.95 * culvert.yFull; + if ( y <= y1 ) + { + q = getUnsubmergedFlow(code, y, &culvert); + condition = 1; + } + // --- flow is in transition zone + else + { + q = getTransitionFlow(code, y, y1, y2, &culvert); + condition = 0; + } + } + + // --- check if inlet controls and replace conduit's value of dq/dh + if ( q < q0 ) + { + // --- for debugging only + //if ( RptFlags.controls ) report_CulvertControl(j, q0, q, condition, + // y / culvert.yFull); + + Link[j].inletControl = TRUE; + Link[j].dqdh = culvert.dQdH; + return q; + } + else return q0; +} + +//============================================================================= + +double getUnsubmergedFlow(int code, double h, TCulvert* culvert) +// +// Input: code = culvert type code number +// h = inlet water depth above culvert invert +// culvert = pointer to a culvert data structure +// Output: returns flow rate; +// computes value of variable Dqdh +// Purpose: computes flow rate and its derivative for unsubmerged +// culvert inlet. +// +{ + double arg; + double q; + + // --- assign shared variables + culvert->kk = Params[code][K]; + culvert->mm = Params[code][M]; + arg = h / culvert->yFull / culvert->kk; + + // --- evaluate correct equation form + if ( Params[code][FORM] == 1.0) + { + q = getForm1Flow(h, culvert); + } + else q = culvert->ad * pow(arg, 1.0/culvert->mm); + culvert->dQdH = q / h / culvert->mm; + return q; +} + +//============================================================================= + +double getSubmergedFlow(int code, double h, TCulvert* culvert) +// +// Input: code = culvert type code number +// h = inlet head (ft) +// culvert = pointer to a culvert data structure +// Output: returns flow rate; +// computes value of Dqdh +// Purpose: computes flow rate and its derivative for submerged +// culvert inlet. +// +{ + double cc = Params[code][C]; + double yy = Params[code][Y]; + double arg = (h/culvert->yFull - yy + culvert->scf) / cc ; + double q; + + if ( arg <= 0.0 ) + { + culvert->dQdH = 0.0; + return BIG; + } + q = sqrt(arg) * culvert->ad; + culvert->dQdH = 0.5 * q / arg / culvert->yFull / cc; + return q; +} + +//============================================================================= + +double getTransitionFlow(int code, double h, double h1, double h2, TCulvert* culvert) +// +// Input: code = culvert type code number +// h = inlet water depth above culvert invert (ft) +// h1 = head limit for unsubmerged condition (ft) +// h2 = head limit for submerged condition (ft) +// culvert = pointer to a culvert data structure +// Output: returns flow rate )cfs); +// computes value of Dqdh (cfs/ft) +// Purpose: computes flow rate and its derivative for inlet-controlled flow +// when inlet water depth lies in the transition range between +// submerged and unsubmerged conditions. +// +{ + double q1 = getUnsubmergedFlow(code, h1, culvert); + double q2 = getSubmergedFlow(code, h2, culvert); + double q = q1 + (q2 - q1) * (h - h1) / (h2 - h1); + culvert->dQdH = (q2 - q1) / (h2 - h1); + return q; +} + +//============================================================================= + +double getForm1Flow(double h, TCulvert* culvert) +// +// Input: h = inlet water depth above culvert invert +// culvert = pointer to a culvert data structure +// Output: returns inlet controlled flow rate +// Purpose: computes inlet-controlled flow rate for unsubmerged culvert +// using FHWA Equation Form1. +// +// See pages 195-196 of FHWA HEC-5 (2001) for details. +// +{ + // --- save re-used terms in culvert structure + culvert->hPlus = h / culvert->yFull + culvert->scf; + + // --- use Ridder's method to solve Equation Form 1 for critical depth + // between a range of 0.01h and h + findroot_Ridder(0.01*h, h, 0.001, form1Eqn, culvert); + + // --- return the flow value used in evaluating Equation Form 1 + return culvert->qc; +} + +//============================================================================= + +double form1Eqn(double yc, void* p) +// +// Input: yc = critical depth +// p = pointer to a TCulvert object +// Output: returns residual error +// Purpose: evaluates the error in satisfying FHWA culvert Equation Form1: +// +// h/yFull + 0.5*s = yc/yFull + yh/2/yFull + K[ac/aFull*sqrt(g*yh/yFull)]^M +// +// for a given value of critical depth yc where: +// h = inlet depth above culvert invert +// s = culvert slope +// yFull = full depth of culvert +// yh = hydraulic depth at critical depth +// ac = flow area at critical depth +// g = accel. of gravity +// K and M = coefficients +// +{ + double ac, wc, yh; + TCulvert* culvert = (TCulvert *)p; + + ac = xsect_getAofY(culvert->xsect, yc); + wc = xsect_getWofY(culvert->xsect, yc); + yh = ac/wc; + + culvert->qc = ac * sqrt(GRAVITY * yh); + return culvert->hPlus - yc/culvert->yFull - yh/2.0/culvert->yFull - + culvert->kk * pow(culvert->qc/culvert->ad, culvert->mm); +} + +//============================================================================= +/* +void report_CulvertControl(int j, double q0, double q, int condition, double yRatio) +// +// Used for debugging only +// +{ + static char* conditionTxt[] = {"transition", "unsubmerged", "submerged"}; + char theDate[12]; + char theTime[9]; + DateTime aDate = getDateTime(NewRoutingTime); + datetime_dateToStr(aDate, theDate); + datetime_timeToStr(aDate, theTime); + fprintf(Frpt.file, + "\n %11s: %8s Culvert %s flow reduced from %.3f to %.3f cfs for %s flow (%.2f).", + theDate, theTime, Link[j].ID, q0, q, conditionTxt[condition], yRatio); +} +*/ diff --git a/src/datetime.c b/src/datetime.c new file mode 100644 index 000000000..d96ef7f3b --- /dev/null +++ b/src/datetime.c @@ -0,0 +1,528 @@ +//----------------------------------------------------------------------------- +// datetime.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// DateTime functions. +// +// Update History +// ============== +// Build 5.1.011: +// - decodeTime() no longer rounds up. +// - New getTimeStamp function added. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include +#include +#include "datetime.h" + +// Macro to convert charcter x to upper case +#define UCHAR(x) (((x) >= 'a' && (x) <= 'z') ? ((x)&~32) : (x)) + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +static const char* MonthTxt[] = + {"JAN", "FEB", "MAR", "APR", + "MAY", "JUN", "JUL", "AUG", + "SEP", "OCT", "NOV", "DEC"}; +static const int DaysPerMonth[2][12] = // days per month + {{31, 28, 31, 30, 31, 30, // normal years + 31, 31, 30, 31, 30, 31}, + {31, 29, 31, 30, 31, 30, // leap years + 31, 31, 30, 31, 30, 31}}; +static const int DateDelta = 693594; // days since 01/01/00 +static const double SecsPerDay = 86400.; // seconds per day + +//----------------------------------------------------------------------------- +// Shared variables +//----------------------------------------------------------------------------- +static int DateFormat; + + +//============================================================================= + +void divMod(int n, int d, int* result, int* remainder) + +// Input: n = numerator +// d = denominator +// Output: result = integer part of n/d +// remainder = remainder of n/d +// Purpose: finds integer part and remainder of n/d. + +{ + if (d == 0) + { + *result = 0; + *remainder = 0; + } + else + { + *result = n/d; + *remainder = n - d*(*result); + } +} + +//============================================================================= + +int isLeapYear(int year) + +// Input: year = a year +// Output: returns 1 if year is a leap year, 0 if not +// Purpose: determines if year is a leap year. + +{ + if ((year % 4 == 0) + && ((year % 100 != 0) + || (year % 400 == 0))) return 1; + else return 0; +} + +//============================================================================= + +int datetime_findMonth(char* month) + +// Input: month = month of year as character string +// Output: returns: month of year as a number (1-12) +// Purpose: finds number (1-12) of month. + +{ + int i; + for (i = 0; i < 12; i++) + { + if (UCHAR(month[0]) == MonthTxt[i][0] + && UCHAR(month[1]) == MonthTxt[i][1] + && UCHAR(month[2]) == MonthTxt[i][2]) return i+1; + } + return 0; +} + +//============================================================================= + +DateTime datetime_encodeDate(int year, int month, int day) + +// Input: year = a year +// month = a month (1 to 12) +// day = a day of month +// Output: returns encoded value of year-month-day +// Purpose: encodes year-month-day to a DateTime value. + +{ + int i, j; + i = isLeapYear(year); + if ((year >= 1) + && (year <= 9999) + && (month >= 1) + && (month <= 12) + && (day >= 1) + && (day <= DaysPerMonth[i][month-1])) + { + for (j = 0; j < month-1; j++) day += DaysPerMonth[i][j]; + i = year - 1; + i = i*365 + i/4 - i/100 + i/400 + day - DateDelta; + return i; + } + else return -DateDelta; +} + +//============================================================================= + +DateTime datetime_encodeTime(int hour, int minute, int second) + +// Input: hour = hour of day (0-24) +// minute = minute of hour (0-60) +// second = seconds of minute (0-60) +// Output: returns time encoded as fractional part of a day +// Purpose: encodes hour:minute:second to a DateTime value + +{ + int s; + if ((hour >= 0) + && (minute >= 0) + && (second >= 0)) + { + s = (hour * 3600 + minute * 60 + second); + return (double)s/SecsPerDay; + } + else return 0.0; +} + +//============================================================================= + +void datetime_decodeDate(DateTime date, int* year, int* month, int* day) + +// Input: date = encoded date/time value +// Output: year = 4-digit year +// month = month of year (1-12) +// day = day of month +// Purpose: decodes DateTime value to year-month-day. + +{ + int D1, D4, D100, D400; + int y, m, d, i, k, t; + + D1 = 365; //365 + D4 = D1 * 4 + 1; //1461 + D100 = D4 * 25 - 1; //36524 + D400 = D100 * 4 + 1; //146097 + + t = (int)(floor (date)) + DateDelta; + if (t <= 0) + { + *year = 0; + *month = 1; + *day = 1; + } + else + { + t--; + y = 1; + while (t >= D400) + { + t -= D400; + y += 400; + } + divMod(t, D100, &i, &d); + if (i == 4) + { + i--; + d += D100; + } + y += i*100; + divMod(d, D4, &i, &d); + y += i*4; + divMod(d, D1, &i, &d); + if (i == 4) + { + i--; + d += D1; + } + y += i; + k = isLeapYear(y); + m = 1; + for (;;) + { + i = DaysPerMonth[k][m-1]; + if (d < i) break; + d -= i; + m++; + } + *year = y; + *month = m; + *day = d + 1; + } +} + +//============================================================================= + +void datetime_decodeTime(DateTime time, int* h, int* m, int* s) + +// Input: time = decimal fraction of a day +// Output: h = hour of day (0-23) +// m = minute of hour (0-59) +// s = second of minute (0-59) +// Purpose: decodes DateTime value to hour:minute:second. + +{ + int secs; + int mins; + double fracDay = (time - floor(time)) * SecsPerDay; + secs = (int)(floor(fracDay + 0.5)); + if ( secs >= 86400 ) secs = 86399; + divMod(secs, 60, &mins, s); + divMod(mins, 60, h, m); + if ( *h > 23 ) *h = 0; +} + +//============================================================================= + +void datetime_dateToStr(DateTime date, char* s) + +// Input: date = encoded date/time value +// Output: s = formatted date string +// Purpose: represents DateTime date value as a formatted string. + +{ + int y, m, d; + datetime_decodeDate(date, &y, &m, &d); + switch (DateFormat) + { + case Y_M_D: + snprintf(s, DATE_STR_SIZE, "%4d-%3s-%02d", y, MonthTxt[m-1], d); + break; + + case M_D_Y: + //sprintf(dateStr, "%3s-%02d-%4d", MonthTxt[m-1], d, y); + snprintf(s, DATE_STR_SIZE, "%02d/%02d/%04d", m, d, y); + break; + + default: + snprintf(s, DATE_STR_SIZE, "%02d-%3s-%4d", d, MonthTxt[m-1], y); + } +} + +void datetime_timeToStr(DateTime time, char* s) + +// Input: time = decimal fraction of a day +// Output: s = time in hr:min:sec format +// Purpose: represents DateTime time value as a formatted string. + +{ + int hr, min, sec; + datetime_decodeTime(time, &hr, &min, &sec); + snprintf(s, TIME_STR_SIZE, "%02d:%02d:%02d", hr, min, sec); +} + +//============================================================================= + +int datetime_strToDate(char* s, DateTime* d) + +// Input: s = date as string +// Output: d = encoded date; +// returns 1 if conversion successful, 0 if not +// Purpose: converts string date s to DateTime value. +// +{ + int yr = 0, mon = 0, day = 0, n; + char month[4]; + char sep1, sep2; + *d = -DateDelta; + if (strchr(s, '-') || strchr(s, '/')) + { + switch (DateFormat) + { + case Y_M_D: + n = sscanf(s, "%d%c%d%c%d", &yr, &sep1, &mon, &sep2, &day); + if ( n < 3 ) + { + mon = 0; + n = sscanf(s, "%d%c%3s%c%d", &yr, &sep1, month, &sep2, &day); + if ( n < 3 ) return 0; + } + break; + + case D_M_Y: + n = sscanf(s, "%d%c%d%c%d", &day, &sep1, &mon, &sep2, &yr); + if ( n < 3 ) + { + mon = 0; + n = sscanf(s, "%d%c%3s%c%d", &day, &sep1, month, &sep2, &yr); + if ( n < 3 ) return 0; + } + break; + + default: // M_D_Y + n = sscanf(s, "%d%c%d%c%d", &mon, &sep1, &day, &sep2, &yr); + if ( n < 3 ) + { + mon = 0; + n = sscanf(s, "%3s%c%d%c%d", month, &sep1, &day, &sep2, &yr); + if ( n < 3 ) return 0; + } + } + if (mon == 0) mon = datetime_findMonth(month); + *d = datetime_encodeDate(yr, mon, day); + } + if (*d == -DateDelta) return 0; + else return 1; +} + +//============================================================================= + +int datetime_strToTime(char* s, DateTime* t) + +// Input: s = time as string +// Output: t = encoded time, +// returns 1 if conversion successful, 0 if not +// Purpose: converts a string time to a DateTime value. +// Note: accepts time as hr:min:sec or as decimal hours. + +{ + int n, hr, min = 0, sec = 0; + char *endptr; + + // Attempt to read time as decimal hours + *t = strtod(s, &endptr); + if ( *endptr == 0 ) + { + *t /= 24.0; + return 1; + } + + // Read time in hr:min:sec format + *t = 0.0; + n = sscanf(s, "%d:%d:%d", &hr, &min, &sec); + if ( n == 0 ) return 0; + *t = datetime_encodeTime(hr, min, sec); + if ( (hr >= 0) && (min >= 0) && (sec >= 0) ) return 1; + else return 0; +} + +//============================================================================= + +void datetime_setDateFormat(int fmt) + +// Input: fmt = date format code +// Output: none +// Purpose: sets date format + +{ + if ( fmt >= Y_M_D && fmt <= M_D_Y) DateFormat = fmt; +} + +//============================================================================= + +DateTime datetime_addSeconds(DateTime date1, double seconds) + +// Input: date1 = an encoded date/time value +// seconds = number of seconds to add to date1 +// Output: returns updated value of date1 +// Purpose: adds a given number of seconds to a date/time. + +{ + double d = floor(date1); + int h, m, s; + datetime_decodeTime(date1, &h, &m, &s); + return d + (3600.0*h + 60.0*m + s + seconds)/SecsPerDay; +} + +//============================================================================= + +DateTime datetime_addDays(DateTime date1, DateTime date2) + +// Input: date1 = an encoded date/time value +// date2 = decimal days to be added to date1 +// Output: returns date1 + date2 +// Purpose: adds a given number of decimal days to a date/time. + +{ + double d1 = floor(date1); + double d2 = floor(date2); + int h1, m1, s1; + int h2, m2, s2; + datetime_decodeTime(date1, &h1, &m1, &s1); + datetime_decodeTime(date2, &h2, &m2, &s2); + return d1 + d2 + datetime_encodeTime(h1+h2, m1+m2, s1+s2); +} + +//============================================================================= + +long datetime_timeDiff(DateTime date1, DateTime date2) + +// Input: date1 = an encoded date/time value +// date2 = an encoded date/time value +// Output: returns date1 - date2 in seconds +// Purpose: finds number of seconds between two dates. + +{ + double d1 = floor(date1); + double d2 = floor(date2); + int h, m, s; + long s1, s2, secs; + datetime_decodeTime(date1, &h, &m, &s); + s1 = 3600*h + 60*m + s; + datetime_decodeTime(date2, &h, &m, &s); + s2 = 3600*h + 60*m + s; + secs = (int)(floor((d1 - d2)*SecsPerDay + 0.5)); + secs += (s1 - s2); + return secs; +} + +//============================================================================= + +int datetime_monthOfYear(DateTime date) + +// Input: date = an encoded date/time value +// Output: returns index of month of year (1..12) +// Purpose: finds month of year (Jan = 1 ...) for a given date. + +{ + int year, month, day; + datetime_decodeDate(date, &year, &month, &day); + return month; +} + +//============================================================================= + +int datetime_dayOfYear(DateTime date) + +// Input: date = an encoded date/time value +// Output: returns day of year (1..365) +// Purpose: finds day of year (Jan 1 = 1) for a given date. + +{ + int year, month, day; + DateTime startOfYear; + datetime_decodeDate(date, &year, &month, &day); + startOfYear = datetime_encodeDate(year, 1, 1); + return (int)(floor(date - startOfYear)) + 1; +} + +//============================================================================= + +int datetime_dayOfWeek(DateTime date) + +// Input: date = an encoded date/time value +// Output: returns index of day of week (1..7) +// Purpose: finds day of week (Sun = 1, ... Sat = 7) for a given date. + +{ + int t = (int)(floor(date)) + DateDelta; + return (t % 7) + 1; +} + +//============================================================================= + +int datetime_hourOfDay(DateTime date) + +// Input: date = an encoded date/time value +// Output: returns hour of day (0..23) +// Purpose: finds hour of day (0 = 12 AM, ..., 23 = 11 PM) for a given date. + +{ + int hour, min, sec; + datetime_decodeTime(date, &hour, &min, &sec); + return hour; +} + +//============================================================================= + +int datetime_daysPerMonth(int year, int month) + +// Input: year = year in which month falls +// month = month of year (1..12) +// Output: returns number of days in the month +// Purpose: finds number of days in a given month of a specified year. + +{ + if ( month < 1 || month > 12 ) return 0; + return DaysPerMonth[isLeapYear(year)][month-1]; +} + +//============================================================================= + +void datetime_getTimeStamp(int fmt, DateTime aDate, int stampSize, char* timeStamp) + +// Input: fmt = desired date format code +// aDate = a date/time value in decimal days +// stampSize = the number of bytes allocated for the time stamp +// Output: returns a time stamp string (e.g., Year-Month-Day Hr:Min:Sec) +// Purpose: Expresses a decimal day date by a time stamp. +{ + char dateStr[DATE_STR_SIZE]; + char timeStr[TIME_STR_SIZE]; + int oldDateFormat = DateFormat; + + if ( stampSize < TIME_STAMP_SIZE ) return; + datetime_setDateFormat(fmt); + datetime_dateToStr(aDate, dateStr); + DateFormat = oldDateFormat; + datetime_timeToStr(aDate, timeStr); + snprintf(timeStamp, stampSize, "%s %s", dateStr, timeStr); +} diff --git a/src/datetime.h b/src/datetime.h new file mode 100644 index 000000000..98b87b48e --- /dev/null +++ b/src/datetime.h @@ -0,0 +1,72 @@ +//----------------------------------------------------------------------------- +// datetime.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// The DateTime type is used to store date and time values. It is +// equivalent to a double floating point type. +// +// The integral part of a DateTime value is the number of days that have +// passed since 12/31/1899. The fractional part of a DateTime value is the +// fraction of a 24 hour day that has elapsed. +// +// Update History +// ============== +// Build 5.1.011: +// - New getTimeStamp function added. +//----------------------------------------------------------------------------- + +#ifndef DATETIME_H +#define DATETIME_H + + +typedef double DateTime; + +#define Y_M_D 0 +#define M_D_Y 1 +#define D_M_Y 2 +#define NO_DATE -693594 // 1/1/0001 +#define DATE_STR_SIZE 12 +#define TIME_STR_SIZE 9 +#define TIME_STAMP_SIZE 21 + +// Functions for encoding a date or time value to a DateTime value +DateTime datetime_encodeDate(int year, int month, int day); +DateTime datetime_encodeTime(int hour, int minute, int second); + +// Functions for decoding a DateTime value to a date and time +void datetime_decodeDate(DateTime date, int* y, int* m, int* d); +void datetime_decodeTime(DateTime time, int* h, int* m, int* s); + +// Function for finding day of week for a date (1 = Sunday) +// month of year, days per month, and hour of day +int datetime_monthOfYear(DateTime date); +int datetime_dayOfYear(DateTime date); +int datetime_dayOfWeek(DateTime date); +int datetime_hourOfDay(DateTime date); +int datetime_daysPerMonth(int year, int month); + +// Functions for converting a DateTime value to a string +void datetime_dateToStr(DateTime date, char* s); +void datetime_timeToStr(DateTime time, char* s); +void datetime_getTimeStamp(int fmt, DateTime aDate, int stampSize, + char* timeStamp); + +// Functions for converting a string date or time to a DateTime value +int datetime_findMonth(char* s); +int datetime_strToDate(char* s, DateTime* d); +int datetime_strToTime(char* s, DateTime* t); + +// Function for setting date format +void datetime_setDateFormat(int fmt); + +// Functions for adding and subtracting dates +DateTime datetime_addSeconds(DateTime date1, double seconds); +DateTime datetime_addDays(DateTime date1, DateTime date2); +long datetime_timeDiff(DateTime date1, DateTime date2); + + +#endif //DATETIME_H diff --git a/src/dwflow.c b/src/dwflow.c new file mode 100644 index 000000000..d6484fe57 --- /dev/null +++ b/src/dwflow.c @@ -0,0 +1,684 @@ +//----------------------------------------------------------------------------- +// dwflow.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 08/01/22 (Build 5.2.1) +// Author: L. Rossman +// M. Tryby (EPA) +// R. Dickinson (CDM) +// +// Solves the momentum equation for flow in a conduit under dynamic wave +// flow routing. +// +// Update History +// ============== +// Build 5.1.008: +// - Bug in finding if conduit was upstrm/dnstrm full was fixed. +// Build 5.1.012: +// - Modified uniform loss rate term of conduit momentum equation. +// 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. +// Build 5.2.1: +// - Implements the new option to skip checking for normal flow limitations. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include "headers.h" + +static const double MAXVELOCITY = 50.; // max. allowable velocity (ft/sec) + +static int getFlowClass(int link, double q, double h1, double h2, + double y1, double y2, double* criticalDepth, double* normalDepth, + double* fasnh); +static void findSurfArea(int link, double q, double length, double* h1, + double* h2, double* y1, double* y2); +static double findLocalLosses(int link, double a1, double a2, double aMid, + double q); + +static double getWidth(TXsect* xsect, double y); +static double getSlotWidth(TXsect* xsect, double y); +static double getArea(TXsect* xsect, double y, double wSlot); +static double getHydRad(TXsect* xsect, double y); + +static double checkNormalFlow(int j, double q, double y1, double y2, + double a1, double r1); + +//============================================================================= + +void dwflow_findConduitFlow(int j, int steps, double omega, double dt) +// +// Input: j = link index +// steps = number of iteration steps taken +// omega = under-relaxation parameter +// dt = time step (sec) +// Output: returns new flow value (cfs) +// Purpose: updates flow in conduit link by solving finite difference +// form of continuity and momentum equations. +// +{ + int k; // index of conduit + int n1, n2; // indexes of end nodes + double z1, z2; // upstream/downstream invert elev. (ft) + double h1, h2; // upstream/dounstream flow heads (ft) + double y1, y2; // upstream/downstream flow depths (ft) + double a1, a2; // upstream/downstream flow areas (ft2) + double r1; // upstream hyd. radius (ft) + double yMid, rMid, aMid; // mid-stream or avg. values of y, r, & a + double aWtd, rWtd; // upstream weighted area & hyd. radius + double qLast; // flow from previous iteration (cfs) + double qOld; // flow from previous time step (cfs) + double aOld; // area from previous time step (ft2) + double v; // velocity (ft/sec) + double rho; // upstream weighting factor + double sigma; // inertial damping factor + double length; // effective conduit length (ft) + double wSlot; // Preissmann slot width (ft) + double dq1, dq2, dq3, dq4, dq5, // terms in momentum eqn. + dq6; // term for evap and infil losses + double denom; // denominator of flow update formula + double q; // new flow value (cfs) + double barrels; // number of barrels in conduit + TXsect* xsect = &Link[j].xsect; // ptr. to conduit's cross section data + char isFull = FALSE; // TRUE if conduit flowing full + char isClosed = FALSE; // TRUE if conduit closed + + + + // --- adjust isClosed status by any control action + if ( Link[j].setting == 0 ) isClosed = TRUE; + + // --- get flow from last time step & previous iteration + k = Link[j].subIndex; + barrels = Conduit[k].barrels; + qOld = Link[j].oldFlow / barrels; + qLast = Conduit[k].q1; + Conduit[k].evapLossRate = 0.0; + Conduit[k].seepLossRate = 0.0; + + // --- get most current heads at upstream and downstream ends of conduit + n1 = Link[j].node1; + n2 = Link[j].node2; + z1 = Node[n1].invertElev + Link[j].offset1; + z2 = Node[n2].invertElev + Link[j].offset2; + h1 = Node[n1].newDepth + Node[n1].invertElev; + h2 = Node[n2].newDepth + Node[n2].invertElev; + h1 = MAX(h1, z1); + h2 = MAX(h2, z2); + + // --- get unadjusted upstream and downstream flow depths in conduit + // (flow depth = head in conduit - elev. of conduit invert) + y1 = h1 - z1; + y2 = h2 - z2; + y1 = MAX(y1, FUDGE); + y2 = MAX(y2, FUDGE); + + // --- flow depths can't exceed full depth of conduit if slot not used + if ( SurchargeMethod != SLOT ) + { + y1 = MIN(y1, xsect->yFull); + y2 = MIN(y2, xsect->yFull); + } + + // -- get area from solution at previous time step + aOld = Conduit[k].a2; + aOld = MAX(aOld, FUDGE); + + // --- use Courant-modified length instead of conduit's actual length + length = Conduit[k].modLength; + + // --- find surface area contributions to upstream and downstream nodes + // based on previous iteration's flow estimate + findSurfArea(j, qLast, length, &h1, &h2, &y1, &y2); + + // --- compute area at each end of conduit & hyd. radius at upstream end + wSlot = getSlotWidth(xsect, y1); + a1 = getArea(xsect, y1, wSlot); + r1 = getHydRad(xsect, y1); + wSlot = getSlotWidth(xsect, y2); + a2 = getArea(xsect, y2, wSlot); + + // --- compute area & hyd. radius at midpoint + yMid = 0.5 * (y1 + y2); + wSlot = getSlotWidth(xsect, yMid); + aMid = getArea(xsect, yMid, wSlot); + rMid = getHydRad(xsect, yMid); + + // --- alternate approach not currently used, but might produce better + // Bernoulli energy balance for steady flows + //aMid = (a1+a2)/2.0; + //rMid = (r1+getHydRad(xsect,y2))/2.0; + + // --- check if conduit is flowing full + if ( y1 >= xsect->yFull && + y2 >= xsect->yFull) isFull = TRUE; + + // --- set new flow to zero if conduit is dry or if flap gate is closed + if ( Link[j].flowClass == DRY || + Link[j].flowClass == UP_DRY || + Link[j].flowClass == DN_DRY || + isClosed || + aMid <= FUDGE ) + { + Conduit[k].a1 = 0.5 * (a1 + a2); + Conduit[k].q1 = 0.0;; + Conduit[k].q2 = 0.0; + Link[j].dqdh = GRAVITY * dt * aMid / length * barrels; + Link[j].froude = 0.0; + Link[j].newDepth = MIN(yMid, Link[j].xsect.yFull); + Link[j].newVolume = Conduit[k].a1 * link_getLength(j) * barrels; + Link[j].newFlow = 0.0; + return; + } + + // --- compute velocity from last flow estimate + v = qLast / aMid; + if ( fabs(v) > MAXVELOCITY ) v = MAXVELOCITY * SGN(qLast); + + // --- compute Froude No. + Link[j].froude = link_getFroude(j, v, yMid); + if ( Link[j].flowClass == SUBCRITICAL && + Link[j].froude > 1.0 ) Link[j].flowClass = SUPCRITICAL; + + // --- find inertial damping factor (sigma) + if ( Link[j].froude <= 0.5 ) sigma = 1.0; + else if ( Link[j].froude >= 1.0 ) sigma = 0.0; + else sigma = 2.0 * (1.0 - Link[j].froude); + + // --- get upstream-weighted area & hyd. radius based on damping factor + // (modified version of R. Dickinson's slope weighting) + rho = 1.0; + if ( !isFull && qLast > 0.0 && h1 >= h2 ) rho = sigma; + aWtd = a1 + (aMid - a1) * rho; + rWtd = r1 + (rMid - r1) * rho; + + // --- determine how much inertial damping to apply + if ( InertDamping == NO_DAMPING ) sigma = 1.0; + else if ( InertDamping == FULL_DAMPING ) sigma = 0.0; + + // --- use full inertial damping if closed conduit is surcharged + if ( isFull && !xsect_isOpen(xsect->type) ) sigma = 0.0; + + // --- compute terms of momentum eqn.: + // --- 1. friction slope term + if ( xsect->type == FORCE_MAIN && isFull ) + dq1 = dt * forcemain_getFricSlope(j, fabs(v), rMid); + else dq1 = dt * Conduit[k].roughFactor / pow(rWtd, 1.33333) * fabs(v); + + // --- 2. energy slope term + dq2 = dt * GRAVITY * aWtd * (h2 - h1) / length; + + // --- 3 & 4. inertial terms + dq3 = 0.0; + dq4 = 0.0; + if ( sigma > 0.0 ) + { + dq3 = 2.0 * v * (aMid - aOld) * sigma; + dq4 = dt * v * v * (a2 - a1) / length * sigma; + } + + // --- 5. local losses term + dq5 = 0.0; + if ( Conduit[k].hasLosses ) + { + dq5 = findLocalLosses(j, a1, a2, aMid, qLast) / 2.0 / length * dt; + } + + // --- 6. term for evap and seepage losses per unit length + dq6 = link_getLossRate(j, qLast) * 2.5 * dt * v / link_getLength(j); + + // --- combine terms to find new conduit flow + denom = 1.0 + dq1 + dq5; + q = (qOld - dq2 + dq3 + dq4 + dq6) / denom; + + // --- compute derivative of flow w.r.t. head + Link[j].dqdh = 1.0 / denom * GRAVITY * dt * aWtd / length * barrels; + + // --- check if any flow limitation applies + Link[j].inletControl = FALSE; + Link[j].normalFlow = FALSE; + if ( q > 0.0 ) + { + // --- check for inlet controlled culvert flow + if ( xsect->culvertCode > 0 && !isFull ) + q = culvert_getInflow(j, q, h1); + + // --- check for normal flow limitation based on surface slope & Fr + else if (NormalFlowLtd != NEITHER && y1 < Link[j].xsect.yFull && + ( Link[j].flowClass == SUBCRITICAL || + Link[j].flowClass == SUPCRITICAL )) + q = checkNormalFlow(j, q, y1, y2, a1, r1); + } + + // --- apply under-relaxation weighting between new & old flows; + // --- do not allow change in flow direction without first being zero + if ( steps > 0 ) + { + q = (1.0 - omega) * qLast + omega * q; + if ( q * qLast < 0.0 ) q = 0.001 * SGN(q); + } + + // --- check if user-supplied flow limit applies + if ( Link[j].qLimit > 0.0 ) + { + if ( fabs(q) > Link[j].qLimit ) q = SGN(q) * Link[j].qLimit; + } + + // --- check for reverse flow with closed flap gate + if ( link_setFlapGate(j, n1, n2, q) ) q = 0.0; + + // --- do not allow flow out of a dry node + // (as suggested by R. Dickinson) + if( q > FUDGE && Node[n1].newDepth <= FUDGE ) q = FUDGE; + if( q < -FUDGE && Node[n2].newDepth <= FUDGE ) q = -FUDGE; + + // --- save new values of area, flow, depth, & volume + Conduit[k].a1 = aMid; + Conduit[k].q1 = q; + Conduit[k].q2 = q; + Link[j].newDepth = MIN(yMid, xsect->yFull); + aMid = (a1 + a2) / 2.0; +// aMid = MIN(aMid, xsect->aFull); //Slot can have aMid > aFull + Conduit[k].fullState = link_getFullState(a1, a2, xsect->aFull); + Link[j].newVolume = aMid * link_getLength(j) * barrels; + Link[j].newFlow = q * barrels; +} + +//============================================================================= + +int getFlowClass(int j, double q, double h1, double h2, double y1, double y2, + double *yC, double *yN, double* fasnh) +// +// Input: j = conduit link index +// q = current conduit flow (cfs) +// h1 = head at upstream end of conduit (ft) +// h2 = head at downstream end of conduit (ft) +// y1 = upstream flow depth in conduit (ft) +// y2 = downstream flow depth in conduit (ft) +// yC = critical flow depth (ft) +// yN = normal flow depth (ft) +// fasnh = fraction between norm. & crit. depth +// Output: returns flow classification code +// Purpose: determines flow class for a conduit based on depths at each end. +// +{ + int n1, n2; // indexes of upstrm/downstrm nodes + int flowClass; // flow classification code + double ycMin, ycMax; // min/max critical depths (ft) + double z1, z2; // offsets of conduit inverts (ft) + + // --- get upstream & downstream node indexes + n1 = Link[j].node1; + n2 = Link[j].node2; + + // --- get upstream & downstream conduit invert offsets + z1 = Link[j].offset1; + z2 = Link[j].offset2; + + // --- base offset of an outfall conduit on outfall's depth + if ( Node[n1].type == OUTFALL ) z1 = MAX(0.0, (z1 - Node[n1].newDepth)); + if ( Node[n2].type == OUTFALL ) z2 = MAX(0.0, (z2 - Node[n2].newDepth)); + + // --- default class is SUBCRITICAL + flowClass = SUBCRITICAL; + *fasnh = 1.0; + + // --- case where both ends of conduit are wet + if ( y1 > FUDGE && y2 > FUDGE ) + { + if ( q < 0.0 ) + { + // --- upstream end at critical depth if flow depth is + // below conduit's critical depth and an upstream + // conduit offset exists + if ( z1 > 0.0 ) + { + *yN = link_getYnorm(j, fabs(q)); + *yC = link_getYcrit(j, fabs(q)); + ycMin = MIN(*yN, *yC); + if ( y1 < ycMin ) flowClass = UP_CRITICAL; + } + } + + // --- case of normal direction flow + else + { + // --- downstream end at smaller of critical and normal depth + // if downstream flow depth below this and a downstream + // conduit offset exists + if ( z2 > 0.0 ) + { + *yN = link_getYnorm(j, fabs(q)); + *yC = link_getYcrit(j, fabs(q)); + ycMin = MIN(*yN, *yC); + ycMax = MAX(*yN, *yC); + if ( y2 < ycMin ) flowClass = DN_CRITICAL; + else if ( y2 < ycMax ) + { + if ( ycMax - ycMin < FUDGE ) *fasnh = 0.0; + else *fasnh = (ycMax - y2) / (ycMax - ycMin); + } + } + } + } + + // --- case where no flow at either end of conduit + else if ( y1 <= FUDGE && y2 <= FUDGE ) flowClass = DRY; + + // --- case where downstream end of pipe is wet, upstream dry + else if ( y2 > FUDGE ) + { + // --- flow classification is UP_DRY if downstream head < + // invert of upstream end of conduit + if ( h2 < Node[n1].invertElev + Link[j].offset1 ) flowClass = UP_DRY; + + // --- otherwise, the downstream head will be >= upstream + // conduit invert creating a flow reversal and upstream end + // should be at critical depth, providing that an upstream + // offset exists (otherwise subcritical condition is maintained) + else if ( z1 > 0.0 ) + { + *yN = link_getYnorm(j, fabs(q)); + *yC = link_getYcrit(j, fabs(q)); + flowClass = UP_CRITICAL; + } + } + + // --- case where upstream end of pipe is wet, downstream dry + else + { + // --- flow classification is DN_DRY if upstream head < + // invert of downstream end of conduit + if ( h1 < Node[n2].invertElev + Link[j].offset2 ) flowClass = DN_DRY; + + // --- otherwise flow at downstream end should be at critical depth + // providing that a downstream offset exists (otherwise + // subcritical condition is maintained) + else if ( z2 > 0.0 ) + { + *yN = link_getYnorm(j, fabs(q)); + *yC = link_getYcrit(j, fabs(q)); + flowClass = DN_CRITICAL; + } + } + return flowClass; +} + +//============================================================================= + +void findSurfArea(int j, double q, double length, double* h1, double* h2, + double* y1, double* y2) +// +// Input: j = conduit link index +// q = current conduit flow (cfs) +// length = conduit length (ft) +// h1 = head at upstream end of conduit (ft) +// h2 = head at downstream end of conduit (ft) +// y1 = upstream flow depth (ft) +// y2 = downstream flow depth (ft) +// Output: updated values of h1, h2, y1, & y2; +// Purpose: assigns surface area of conduit to its up and downstream nodes. +// +{ + int n1, n2; // indexes of upstrm/downstrm nodes + double flowDepth1; // flow depth at upstrm end (ft) + double flowDepth2; // flow depth at downstrm end (ft) + double flowDepthMid; // flow depth at midpt. (ft) + double width1; // top width at upstrm end (ft) + double width2; // top width at downstrm end (ft) + double widthMid; // top width at midpt. (ft) + double surfArea1 = 0.0; // surface area at upstream node (ft2) + double surfArea2 = 0.0; // surface area st downstrm node (ft2) + double criticalDepth; // critical flow depth (ft) + double normalDepth; // normal flow depth (ft) + double fullDepth; // full depth (ft) + double fasnh = 1.0; // fraction between norm. & crit. depth + TXsect* xsect = &Link[j].xsect; // pointer to cross-section data + + // --- get node indexes & current flow depths + n1 = Link[j].node1; + n2 = Link[j].node2; + flowDepth1 = *y1; + flowDepth2 = *y2; + + normalDepth = (flowDepth1 + flowDepth2) / 2.0; + criticalDepth = normalDepth; + + // --- find conduit's flow classification + fullDepth = xsect->yFull; + if (flowDepth1 >= fullDepth && flowDepth2 >= fullDepth) + { + Link[j].flowClass = SUBCRITICAL; + } + else Link[j].flowClass = getFlowClass(j, q, *h1, *h2, *y1, *y2, + &criticalDepth, &normalDepth, &fasnh); + + // --- add conduit's surface area to its end nodes depending on flow class + switch ( Link[j].flowClass ) + { + case SUBCRITICAL: + flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); + if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; + width1 = getWidth(xsect, flowDepth1); + width2 = getWidth(xsect, flowDepth2); + widthMid = getWidth(xsect, flowDepthMid); + surfArea1 = (width1 + widthMid) * length / 4.; + surfArea2 = (widthMid + width2) * length / 4. * fasnh; + break; + + case UP_CRITICAL: + flowDepth1 = criticalDepth; + if ( normalDepth < criticalDepth ) flowDepth1 = normalDepth; + flowDepth1 = MAX(flowDepth1, FUDGE); + *h1 = Node[n1].invertElev + Link[j].offset1 + flowDepth1; + flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); + if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; + width2 = getWidth(xsect, flowDepth2); + widthMid = getWidth(xsect, flowDepthMid); + surfArea2 = (widthMid + width2) * length * 0.5; + break; + + case DN_CRITICAL: + flowDepth2 = criticalDepth; + if ( normalDepth < criticalDepth ) flowDepth2 = normalDepth; + flowDepth2 = MAX(flowDepth2, FUDGE); + *h2 = Node[n2].invertElev + Link[j].offset2 + flowDepth2; + width1 = getWidth(xsect, flowDepth1); + flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); + if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; + widthMid = getWidth(xsect, flowDepthMid); + surfArea1 = (width1 + widthMid) * length * 0.5; + break; + + case UP_DRY: + flowDepth1 = FUDGE; + flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); + if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; + width1 = getWidth(xsect, flowDepth1); + width2 = getWidth(xsect, flowDepth2); + widthMid = getWidth(xsect, flowDepthMid); + + // --- assign avg. surface area of downstream half of conduit + // to the downstream node + surfArea2 = (widthMid + width2) * length / 4.; + + // --- if there is no free-fall at upstream end, assign the + // upstream node the avg. surface area of the upstream half + if ( Link[j].offset1 <= 0.0 ) + { + surfArea1 = (width1 + widthMid) * length / 4.; + } + break; + + case DN_DRY: + flowDepth2 = FUDGE; + flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); + if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; + width1 = getWidth(xsect, flowDepth1); + width2 = getWidth(xsect, flowDepth2); + widthMid = getWidth(xsect, flowDepthMid); + + // --- assign avg. surface area of upstream half of conduit + // to the upstream node + surfArea1 = (widthMid + width1) * length / 4.; + + // --- if there is no free-fall at downstream end, assign the + // downstream node the avg. surface area of the downstream half + if ( Link[j].offset2 <= 0.0 ) + { + surfArea2 = (width2 + widthMid) * length / 4.; + } + break; + + case DRY: + surfArea1 = FUDGE * length / 2.0; + surfArea2 = surfArea1; + break; + } + Link[j].surfArea1 = surfArea1; + Link[j].surfArea2 = surfArea2; + *y1 = flowDepth1; + *y2 = flowDepth2; +} + +//============================================================================= + +double findLocalLosses(int j, double a1, double a2, double aMid, double q) +// +// Input: j = link index +// a1 = upstream area (ft2) +// a2 = downstream area (ft2) +// aMid = midpoint area (ft2) +// q = flow rate (cfs) +// Output: returns local losses (ft/sec) +// Purpose: computes local losses term of momentum equation. +// +{ + double losses = 0.0; + q = fabs(q); + if ( a1 > FUDGE ) losses += Link[j].cLossInlet * (q/a1); + if ( a2 > FUDGE ) losses += Link[j].cLossOutlet * (q/a2); + if ( aMid > FUDGE ) losses += Link[j].cLossAvg * (q/aMid); + return losses; +} + +//============================================================================= + +double getSlotWidth(TXsect* xsect, double y) +{ + double yNorm = y / xsect->yFull; + + // --- return 0.0 if slot surcharge method not used + if (SurchargeMethod != SLOT || xsect_isOpen(xsect->type) || + yNorm < CrownCutoff) return 0.0; + + // --- for depth > 1.78 * pipe depth, slot width = 1% of max. width + if (yNorm > 1.78) return 0.01 * xsect->wMax; + + // --- otherwise use the Sjoberg formula + return xsect->wMax * 0.5423 * exp(-pow(yNorm, 2.4)); +} + +//============================================================================= + +double getWidth(TXsect* xsect, double y) +// +// Input: xsect = ptr. to conduit cross section +// y = flow depth (ft) +// Output: returns top width (ft) +// Purpose: computes top width of flow surface in conduit. +// +{ + double wSlot = getSlotWidth(xsect, y); + if (wSlot > 0.0) return wSlot; + if (y / xsect->yFull >= CrownCutoff && !xsect_isOpen(xsect->type)) + y = CrownCutoff * xsect->yFull; + return xsect_getWofY(xsect, y); +} + +//============================================================================= + +double getArea(TXsect* xsect, double y, double wSlot) +// +// Input: xsect = ptr. to conduit cross section +// y = flow depth (ft) +// Output: returns flow area (ft2) +// Purpose: computes area of flow cross-section in a conduit. +// +{ + if ( y >= xsect->yFull ) return xsect->aFull + (y - xsect->yFull) * wSlot; + return xsect_getAofY(xsect, y); +} + +//============================================================================= + +double getHydRad(TXsect* xsect, double y) +// +// Input: xsect = ptr. to conduit cross section +// y = flow depth (ft) +// Output: returns hydraulic radius (ft) +// Purpose: computes hydraulic radius of flow cross-section in a conduit. +// +{ + if (y >= xsect->yFull) return xsect->rFull; + return xsect_getRofY(xsect, y); +} + +//============================================================================= + +double checkNormalFlow(int j, double q, double y1, double y2, double a1, + double r1) +// +// Input: j = link index +// q = link flow found from dynamic wave equations (cfs) +// y1 = flow depth at upstream end (ft) +// y2 = flow depth at downstream end (ft) +// a1 = flow area at upstream end (ft2) +// r1 = hyd. radius at upstream end (ft) +// Output: returns modifed flow in link (cfs) +// Purpose: checks if flow in link should be replaced by normal flow. +// +{ + int check = FALSE; + int k = Link[j].subIndex; + int n1 = Link[j].node1; + int n2 = Link[j].node2; + int hasOutfall = (Node[n1].type == OUTFALL || Node[n2].type == OUTFALL); + double qNorm; + double f1; + + // --- check if water surface slope < conduit slope + if ( NormalFlowLtd == SLOPE || NormalFlowLtd == BOTH || hasOutfall ) + { + if ( y1 < y2) check = TRUE; + } + + // --- check if Fr >= 1.0 at upstream end of conduit + if ( !check && (NormalFlowLtd == FROUDE || NormalFlowLtd == BOTH) && + !hasOutfall ) + { + if ( y1 > FUDGE && y2 > FUDGE ) + { + f1 = link_getFroude(j, q/a1, y1); + if ( f1 >= 1.0 ) check = TRUE; + } + } + + // --- check if normal flow < dynamic flow + if ( check ) + { + qNorm = Conduit[k].beta * a1 * pow(r1, 2./3.); + if ( qNorm < q ) + { + Link[j].normalFlow = TRUE; + return qNorm; + } + } + return q; +} diff --git a/src/dynwave.c b/src/dynwave.c new file mode 100644 index 000000000..ac302e849 --- /dev/null +++ b/src/dynwave.c @@ -0,0 +1,908 @@ +//----------------------------------------------------------------------------- +// dynwave.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// M. Tryby (EPA) +// R. Dickinson (CDM) +// +// Dynamic wave flow routing functions. +// +// This module solves the dynamic wave flow routing equations using +// Picard Iterations (i.e., a method of successive approximations) +// to solve the explicit form of the continuity and momentum equations +// for conduits. +// +// Update History +// ============== +// Build 5.1.002: +// - Only non-ponded nodal surface area is saved for use in +// surcharge algorithm. +// Build 5.1.007: +// - Node losses added to node outflow variable instead of treated +// as a separate item when computing change in node flow volume. +// Build 5.1.008: +// - Module-specific constants moved here from project.c. +// - Support added for user-specified minimum variable time step. +// - Node crown elevations found here instead of in flowrout.c module. +// - OpenMP use to parallelize findLinkFlows() & findNodeDepths(). +// - Bug in finding complete list of capacity limited links fixed. +// Build 5.1.011: +// - Added test for failed memory allocation. +// - Fixed illegal array index bug for Ideal Pumps. +// Build 5.1.013: +// - Include omp.h protected against lack of compiler support for OpenMP. +// - SurchargeMethod option used to decide how node surcharging is handled. +// - 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. +// Build 5.1.015: +// - Roll back the 5.1.014 change for conduit losses in updateNodeFlows(). +// Build 5.2.0: +// - Support added for reporting most frequent non-converging links. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include "headers.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +static const double MINTIMESTEP = 0.001; // min. time step (sec) +static const double OMEGA = 0.5; // under-relaxation parameter +static const double DEFAULT_SURFAREA = 12.566; // Min. nodal surface area (~4 ft diam.) +static const double DEFAULT_HEADTOL = 0.005; // Default head tolerance (ft) +static const double EXTRAN_CROWN_CUTOFF = 0.96; // crown cutoff for EXTRAN +static const double SLOT_CROWN_CUTOFF = 0.985257; // crown cutoff for SLOT +static const int DEFAULT_MAXTRIALS = 8; // Max. trials per time step + + +//----------------------------------------------------------------------------- +// Data Structures +//----------------------------------------------------------------------------- +typedef struct +{ + char converged; // TRUE if iterations for a node done + double newSurfArea; // current surface area (ft2) + double oldSurfArea; // previous surface area (ft2) + double sumdqdh; // sum of dqdh from adjoining links + double dYdT; // change in depth w.r.t. time (ft/sec) +} TXnode; + +//----------------------------------------------------------------------------- +// Shared Variables +//----------------------------------------------------------------------------- +static double VariableStep; // size of variable time step (sec) +static TXnode* Xnode; // extended nodal information + +static double Omega; // actual under-relaxation parameter +static int Steps; // number of Picard iterations + +//----------------------------------------------------------------------------- +// Function declarations +//----------------------------------------------------------------------------- +static void initRoutingStep(void); +static void initNodeStates(void); +static void findBypassedLinks(); +static void findLimitedLinks(); + +static void findLinkFlows(double dt); +static int isTrueConduit(int link); +static void findNonConduitFlow(int link, double dt); +static void findNonConduitSurfArea(int link); +static double getModPumpFlow(int link, double q, double dt); +static void updateNodeFlows(int link); +static void updateConvergenceStats(); + +static int findNodeDepths(double dt); +static void setNodeDepth(int node, double dt); +static double getFloodedDepth(int node, int canPond, double dV, double yNew, + double yMax, double dt); + +static double getVariableStep(double maxStep); +static double getLinkStep(double tMin, int *minLink); +static double getNodeStep(double tMin, int *minNode); + +//============================================================================= + +void dynwave_init() +// +// Input: none +// Output: none +// Purpose: initializes dynamic wave routing method. +// +{ + int i, j; + double z; + + VariableStep = 0.0; + Xnode = (TXnode *) calloc(Nobjects[NODE], sizeof(TXnode)); + if ( Xnode == NULL ) + { + report_writeErrorMsg(ERR_MEMORY, + " Not enough memory for dynamic wave routing."); + return; + } + + // --- initialize node surface areas & crown elev. + for (i = 0; i < Nobjects[NODE]; i++ ) + { + Xnode[i].newSurfArea = 0.0; + Xnode[i].oldSurfArea = 0.0; + Node[i].crownElev = Node[i].invertElev; + } + + // --- initialize links & update node crown elevations + for (i = 0; i < Nobjects[LINK]; i++) + { + j = Link[i].node1; + z = Node[j].invertElev + Link[i].offset1 + Link[i].xsect.yFull; + Node[j].crownElev = MAX(Node[j].crownElev, z); + j = Link[i].node2; + z = Node[j].invertElev + Link[i].offset2 + Link[i].xsect.yFull; + Node[j].crownElev = MAX(Node[j].crownElev, z); + Link[i].flowClass = DRY; + Link[i].dqdh = 0.0; + } + + // --- set crown cutoff for finding top width of closed conduits + if ( SurchargeMethod == SLOT ) CrownCutoff = SLOT_CROWN_CUTOFF; + else CrownCutoff = EXTRAN_CROWN_CUTOFF; +} + +//============================================================================= + +void dynwave_close() +// +// Input: none +// Output: none +// Purpose: frees memory allocated for dynamic wave routing method. +// +{ + FREE(Xnode); +} + +//============================================================================= + +void dynwave_validate() +// +// Input: none +// Output: none +// Purpose: adjusts dynamic wave routing options. +// +{ + if ( MinRouteStep > RouteStep ) MinRouteStep = RouteStep; + if ( MinRouteStep < MINTIMESTEP ) MinRouteStep = MINTIMESTEP; + if ( MinSurfArea == 0.0 ) MinSurfArea = DEFAULT_SURFAREA; + else MinSurfArea /= UCF(LENGTH) * UCF(LENGTH); + if ( HeadTol == 0.0 ) HeadTol = DEFAULT_HEADTOL; + else HeadTol /= UCF(LENGTH); + if ( MaxTrials == 0 ) MaxTrials = DEFAULT_MAXTRIALS; +} + +//============================================================================= + +double dynwave_getRoutingStep(double fixedStep) +// +// Input: fixedStep = user-supplied fixed time step (sec) +// Output: returns routing time step (sec) +// Purpose: computes variable routing time step if applicable. +// +{ + // --- use user-supplied fixed step if variable step option turned off + // or if its smaller than the min. allowable variable time step + if ( CourantFactor == 0.0 ) return fixedStep; + if ( fixedStep < MINTIMESTEP ) return fixedStep; + + // --- at start of simulation (when current variable step is zero) + // use the minimum allowable time step + if ( VariableStep == 0.0 ) + { + VariableStep = MinRouteStep; + } + + // --- otherwise compute variable step based on current flow solution + else VariableStep = getVariableStep(fixedStep); + + // --- adjust step to be a multiple of a millisecond + VariableStep = floor(1000.0 * VariableStep) / 1000.0; + return VariableStep; +} + +//============================================================================= + +int dynwave_execute(double tStep) +// +// Input: links = array of topo sorted links indexes +// tStep = time step (sec) +// Output: returns number of iterations used +// Purpose: routes flows through drainage network over current time step. +// +{ + int converged; + + // --- initialize + if ( ErrorCode ) return 0; + Steps = 0; + converged = FALSE; + Omega = OMEGA; + initRoutingStep(); + + // --- keep iterating until convergence + while ( Steps < MaxTrials ) + { + // --- execute a routing step & check for nodal convergence + initNodeStates(); + findLinkFlows(tStep); + converged = findNodeDepths(tStep); + Steps++; + if ( Steps > 1 ) + { + if ( converged ) break; + + // --- check if link calculations can be skipped in next step + findBypassedLinks(); + } + } + if ( !converged ) updateConvergenceStats(); + + // --- identify any capacity-limited conduits + findLimitedLinks(); + return Steps; +} + +//============================================================================= + +void updateConvergenceStats() +{ + int i; + NonConvergeCount++; + for (i = 0; i < Nobjects[NODE]; i++) + stats_updateConvergenceStats(i, Xnode[i].converged); +} + +//============================================================================= + +void initRoutingStep() +{ + int i; + for (i = 0; i < Nobjects[NODE]; i++) + { + Xnode[i].converged = FALSE; + Xnode[i].dYdT = 0.0; + } + for (i = 0; i < Nobjects[LINK]; i++) + { + Link[i].bypassed = FALSE; + Link[i].surfArea1 = 0.0; + Link[i].surfArea2 = 0.0; + } + + // --- a2 preserves conduit area from solution at last time step + for ( i = 0; i < Nlinks[CONDUIT]; i++) Conduit[i].a2 = Conduit[i].a1; +} + +//============================================================================= + +void initNodeStates() +// +// Input: none +// Output: none +// Purpose: initializes node's surface area, inflow & outflow +// +{ + int i; + + for (i = 0; i < Nobjects[NODE]; i++) + { + // --- initialize nodal surface area + if ( AllowPonding ) + { + Xnode[i].newSurfArea = node_getPondedArea(i, Node[i].newDepth); + } + else + { + Xnode[i].newSurfArea = node_getSurfArea(i, Node[i].newDepth); + } + + // --- initialize nodal inflow & outflow + Node[i].inflow = 0.0; + Node[i].outflow = Node[i].losses; + if ( Node[i].newLatFlow >= 0.0 ) + { + Node[i].inflow += Node[i].newLatFlow; + } + else + { + Node[i].outflow -= Node[i].newLatFlow; + } + Xnode[i].sumdqdh = 0.0; + } +} + +//============================================================================= + +void findBypassedLinks() +{ + int i; + for (i = 0; i < Nobjects[LINK]; i++) + { + if ( Xnode[Link[i].node1].converged && + Xnode[Link[i].node2].converged ) + Link[i].bypassed = TRUE; + else Link[i].bypassed = FALSE; + } +} + +//============================================================================= + +void findLimitedLinks() +// +// Input: none +// Output: none +// Purpose: determines if a conduit link is capacity limited. +// +{ + int j, n1, n2, k; + double h1, h2; + + for (j = 0; j < Nobjects[LINK]; j++) + { + // ---- check only non-dummy conduit links + if ( !isTrueConduit(j) ) continue; + + // --- check that upstream end is full + k = Link[j].subIndex; + Conduit[k].capacityLimited = FALSE; + if ( Conduit[k].a1 >= Link[j].xsect.aFull ) + { + // --- check if HGL slope > conduit slope + n1 = Link[j].node1; + n2 = Link[j].node2; + h1 = Node[n1].newDepth + Node[n1].invertElev; + h2 = Node[n2].newDepth + Node[n2].invertElev; + if ( (h1 - h2) > fabs(Conduit[k].slope) * Conduit[k].length ) + Conduit[k].capacityLimited = TRUE; + } + } +} + +//============================================================================= + +void findLinkFlows(double dt) +{ + int i; + + // --- find new flow in each non-dummy conduit +#pragma omp parallel num_threads(NumThreads) +{ + #pragma omp for + for ( i = 0; i < Nobjects[LINK]; i++) + { + if ( isTrueConduit(i) && !Link[i].bypassed ) + dwflow_findConduitFlow(i, Steps, Omega, dt); + } +} + + // --- update inflow/outflows for nodes attached to non-dummy conduits + for ( i = 0; i < Nobjects[LINK]; i++) + { + if ( isTrueConduit(i) ) updateNodeFlows(i); + } + + // --- find new flows for all dummy conduits, pumps & regulators + for ( i = 0; i < Nobjects[LINK]; i++) + { + if ( !isTrueConduit(i) ) + { + if ( !Link[i].bypassed ) findNonConduitFlow(i, dt); + updateNodeFlows(i); + } + } +} + +//============================================================================= + +int isTrueConduit(int j) +{ + return ( Link[j].type == CONDUIT && Link[j].xsect.type != DUMMY ); +} + +//============================================================================= + +void findNonConduitFlow(int i, double dt) +// +// Input: i = link index +// dt = time step (sec) +// Output: none +// Purpose: finds new flow in a non-conduit-type link +// +{ + double qLast; // previous link flow (cfs) + double qNew; // new link flow (cfs) + + // --- get link flow from last iteration + qLast = Link[i].newFlow; + Link[i].dqdh = 0.0; + + // --- get new inflow to link from its upstream node + // (link_getInflow returns 0 if flap gate closed or pump is offline) + qNew = link_getInflow(i); + if ( Link[i].type == PUMP ) qNew = getModPumpFlow(i, qNew, dt); + + // --- find surface area at each end of link + findNonConduitSurfArea(i); + + // --- apply under-relaxation with flow from previous iteration; + // --- do not allow flow to change direction without first being 0 + if ( Steps > 0 && Link[i].type != PUMP ) + { + qNew = (1.0 - Omega) * qLast + Omega * qNew; + if ( qNew * qLast < 0.0 ) qNew = 0.001 * SGN(qNew); + } + Link[i].newFlow = qNew; +} + +//============================================================================= + +double getModPumpFlow(int i, double q, double dt) +// +// Input: i = link index +// q = pump flow from pump curve (cfs) +// dt = time step (sec) +// Output: returns modified pump flow rate (cfs) +// Purpose: modifies pump curve pumping rate depending on amount of water +// available at pump's inlet node. +// +{ + int j = Link[i].node1; // pump's inlet node index + int k = Link[i].subIndex; // pump's index + double newNetInflow; // inflow - outflow rate (cfs) + double netFlowVolume; // inflow - outflow volume (ft3) + double y; // node depth (ft) + + if ( q == 0.0 ) return q; + + // --- case where inlet node is a storage node: + // prevent node volume from going negative + if ( Node[j].type == STORAGE ) return node_getMaxOutflow(j, q, dt); + + // --- case where inlet is a non-storage node + switch ( Pump[k].type ) + { + // --- for Type1 pump, a volume is computed for inlet node, + // so make sure it doesn't go negative + case TYPE1_PUMP: + return node_getMaxOutflow(j, q, dt); + + // --- for other types of pumps, if pumping rate would make depth + // at upstream node negative, then set pumping rate = inflow + case TYPE2_PUMP: + case TYPE4_PUMP: + case TYPE3_PUMP: + newNetInflow = Node[j].inflow - Node[j].outflow - q; + netFlowVolume = 0.5 * (Node[j].oldNetInflow + newNetInflow ) * dt; + y = Node[j].oldDepth + netFlowVolume / Xnode[j].newSurfArea; + if ( y <= 0.0 ) return Node[j].inflow; + } + return q; +} + +//============================================================================= + +void findNonConduitSurfArea(int i) +// +// Input: i = link index +// Output: none +// Purpose: finds the surface area contributed by a non-conduit +// link to its upstream and downstream nodes. +// +{ + if ( Link[i].type == ORIFICE ) + { + Link[i].surfArea1 = Orifice[Link[i].subIndex].surfArea / 2.; + } + + // --- no surface area for weirs to maintain SWMM 4 compatibility + else Link[i].surfArea1 = 0.0; + + Link[i].surfArea2 = Link[i].surfArea1; + if ( Link[i].flowClass == UP_CRITICAL || + Node[Link[i].node1].type == STORAGE ) Link[i].surfArea1 = 0.0; + if ( Link[i].flowClass == DN_CRITICAL || + Node[Link[i].node2].type == STORAGE ) Link[i].surfArea2 = 0.0; +} + +//============================================================================= + +void updateNodeFlows(int i) +// +// Input: i = link index +// q = link flow rate (cfs) +// Output: none +// Purpose: updates cumulative inflow & outflow at link's end nodes. +// +{ + int k; + int barrels = 1; + int n1 = Link[i].node1; + int n2 = Link[i].node2; + double q = Link[i].newFlow; + double uniformLossRate = 0.0; + + // --- compute any uniform seepage loss from a conduit + if ( Link[i].type == CONDUIT ) + { + k = Link[i].subIndex; + uniformLossRate = Conduit[k].evapLossRate + Conduit[k].seepLossRate; + barrels = Conduit[k].barrels; + uniformLossRate *= barrels; + } + + // --- update total inflow & outflow at upstream/downstream nodes + if ( q >= 0.0 ) + { + Node[n1].outflow += q + uniformLossRate; + Node[n2].inflow += q; + } + else + { + Node[n1].inflow -= q; + Node[n2].outflow -= q - uniformLossRate; + } + + // --- add surf. area contributions to upstream/downstream nodes + Xnode[Link[i].node1].newSurfArea += Link[i].surfArea1 * barrels; + Xnode[Link[i].node2].newSurfArea += Link[i].surfArea2 * barrels; + + // --- update summed value of dqdh at each end node + Xnode[Link[i].node1].sumdqdh += Link[i].dqdh; + if ( Link[i].type == PUMP ) + { + k = Link[i].subIndex; + if ( Pump[k].type != TYPE4_PUMP ) + { + Xnode[n2].sumdqdh += Link[i].dqdh; + } + } + else Xnode[n2].sumdqdh += Link[i].dqdh; +} + +//============================================================================= + +int findNodeDepths(double dt) +// +// Input: dt = time step (sec) +// Output: returns TRUE if depth change at all non-Outfall nodes is +// within the convergence tolerance and FALSE otherwise +// Purpose: finds new depth at all nodes and checks if convergence achieved. +// +{ + int i; + double yOld; // previous node depth (ft) + + // --- compute outfall depths based on flow in connecting link + for ( i = 0; i < Nobjects[LINK]; i++ ) link_setOutfallDepth(i); + + // --- compute new depth for all non-outfall nodes and determine if + // depth change from previous iteration is below tolerance +#pragma omp parallel num_threads(NumThreads) +{ + #pragma omp for private(yOld) + for ( i = 0; i < Nobjects[NODE]; i++ ) + { + if ( Node[i].type == OUTFALL ) continue; + yOld = Node[i].newDepth; + setNodeDepth(i, dt); + Xnode[i].converged = TRUE; + if ( fabs(yOld - Node[i].newDepth) > HeadTol ) + { + Xnode[i].converged = FALSE; + } + } +} + + // --- return FALSE if any non-Outfall node failed to converge + for (i = 0; i < Nobjects[NODE]; i++) + { + if ( Node[i].type == OUTFALL ) continue; + if (Xnode[i].converged == FALSE) return FALSE; + } + return TRUE; +} + +//============================================================================= + +void setNodeDepth(int i, double dt) +// +// Input: i = node index +// dt = time step (sec) +// Output: none +// Purpose: sets depth at non-outfall node after current time step. +// +{ + int canPond; // TRUE if node can pond overflows + int isPonded; // TRUE if node is currently ponded + int isSurcharged = FALSE; // TRUE if node is surcharged + double dQ; // inflow minus outflow at node (cfs) + double dV; // change in node volume (ft3) + double dy; // change in node depth (ft) + double yMax; // max. depth at node (ft) + double yOld; // node depth at previous time step (ft) + double yLast; // previous node depth (ft) + double yNew; // new node depth (ft) + double yCrown; // depth to node crown (ft) + double surfArea; // node surface area (ft2) + double denom; // denominator term + double corr; // correction factor + double f; // relative surcharge depth + + // --- see if node can pond water above it + canPond = (AllowPonding && Node[i].pondedArea > 0.0); + isPonded = (canPond && Node[i].newDepth > Node[i].fullDepth); + + // --- initialize values + yCrown = Node[i].crownElev - Node[i].invertElev; + yOld = Node[i].oldDepth; + yLast = Node[i].newDepth; + Node[i].overflow = 0.0; + surfArea = Xnode[i].newSurfArea; + surfArea = MAX(surfArea, MinSurfArea); + + // --- determine average net flow volume into node over the time step + dQ = Node[i].inflow - Node[i].outflow; + dV = 0.5 * (Node[i].oldNetInflow + dQ) * dt; + + + // --- determine if node is EXTRAN surcharged + if (SurchargeMethod == EXTRAN) + { + // --- ponded nodes don't surcharge + if (isPonded) isSurcharged = FALSE; + + // --- closed storage units that are full are in surcharge + else if (Node[i].type == STORAGE) + { + isSurcharged = (Node[i].surDepth > 0.0 && + yLast > Node[i].fullDepth); + } + + // --- surcharge occurs when node depth exceeds top of its highest link + else isSurcharged = (yCrown > 0.0 && yLast > yCrown); + } + + // --- if node not surcharged, base depth change on surface area + if (!isSurcharged) + { + dy = dV / surfArea; + yNew = yOld + dy; + + // --- save non-ponded surface area for use in surcharge algorithm + if ( !isPonded ) Xnode[i].oldSurfArea = surfArea; + + // --- apply under-relaxation to new depth estimate + if ( Steps > 0 ) + { + yNew = (1.0 - Omega) * yLast + Omega * yNew; + } + + // --- don't allow a ponded node to drop much below full depth + if ( isPonded && yNew < Node[i].fullDepth ) + yNew = Node[i].fullDepth - FUDGE; + } + + // --- if node surcharged, base depth change on dqdh + // NOTE: depth change is w.r.t depth from previous + // iteration; also, do not apply under-relaxation. + else + { + // --- apply correction factor for upstream terminal nodes + corr = 1.0; + if ( Node[i].degree < 0 ) corr = 0.6; + + // --- allow surface area from last non-surcharged condition + // to influence dqdh if depth close to crown depth + denom = Xnode[i].sumdqdh; + if ( yLast < 1.25 * yCrown ) + { + f = (yLast - yCrown) / yCrown; + denom += (Xnode[i].oldSurfArea/dt - + Xnode[i].sumdqdh) * exp(-15.0 * f); + } + + // --- compute new estimate of node depth + if ( denom == 0.0 ) dy = 0.0; + else dy = corr * dQ / denom; + yNew = yLast + dy; + if ( yNew < yCrown ) yNew = yCrown - FUDGE; + + // --- don't allow a newly ponded node to rise much above full depth + if ( canPond && yNew > Node[i].fullDepth ) + yNew = Node[i].fullDepth + FUDGE; + } + + // --- depth cannot be negative + if ( yNew < 0 ) yNew = 0.0; + + // --- determine max. non-flooded depth + yMax = Node[i].fullDepth; + if ( canPond == FALSE ) yMax += Node[i].surDepth; + + // --- find flooded depth & volume + if ( yNew > yMax ) + { + yNew = getFloodedDepth(i, canPond, dV, yNew, yMax, dt); + } + else Node[i].newVolume = node_getVolume(i, yNew); + + // --- compute change in depth w.r.t. time + Xnode[i].dYdT = fabs(yNew - yOld) / dt; + + // --- save new depth for node + Node[i].newDepth = yNew; +} + +//============================================================================= + +double getFloodedDepth(int i, int canPond, double dV, double yNew, + double yMax, double dt) +// +// Input: i = node index +// canPond = TRUE if water can pond over node +// isPonded = TRUE if water is currently ponded +// dV = change in volume over time step (ft3) +// yNew = current depth at node (ft) +// yMax = max. depth at node before ponding (ft) +// dt = time step (sec) +// Output: returns depth at node when flooded (ft) +// Purpose: computes depth, volume and overflow for a flooded node. +// +{ + if ( canPond == FALSE ) + { + Node[i].overflow = dV / dt; + Node[i].newVolume = Node[i].fullVolume; + yNew = yMax; + } + else + { + Node[i].newVolume = MAX((Node[i].oldVolume+dV), Node[i].fullVolume); + Node[i].overflow = (Node[i].newVolume - + MAX(Node[i].oldVolume, Node[i].fullVolume)) / dt; + } + if ( Node[i].overflow < FUDGE ) Node[i].overflow = 0.0; + return yNew; + +} + +//============================================================================= + +double getVariableStep(double maxStep) +// +// Input: maxStep = user-supplied max. time step (sec) +// Output: returns time step (sec) +// Purpose: finds time step that satisfies stability criterion but +// is no greater than the user-supplied max. time step. +// +{ + int minLink = -1; // index of link w/ min. time step + int minNode = -1; // index of node w/ min. time step + double tMin; // allowable time step (sec) + double tMinLink; // allowable time step for links (sec) + double tMinNode; // allowable time step for nodes (sec) + + // --- find stable time step for links & then nodes + tMin = maxStep; + tMinLink = getLinkStep(tMin, &minLink); + tMinNode = getNodeStep(tMinLink, &minNode); + + // --- use smaller of the link and node time step + tMin = tMinLink; + if ( tMinNode < tMin ) + { + tMin = tMinNode ; + minLink = -1; + } + + // --- update count of times the minimum node or link was critical + stats_updateCriticalTimeCount(minNode, minLink); + + // --- don't let time step go below an absolute minimum + if ( tMin < MinRouteStep ) tMin = MinRouteStep; + return tMin; +} + +//============================================================================= + +double getLinkStep(double tMin, int *minLink) +// +// Input: tMin = critical time step found so far (sec) +// Output: minLink = index of link with critical time step; +// returns critical time step (sec) +// Purpose: finds critical time step for conduits based on Courant criterion. +// +{ + int i; // link index + int k; // conduit index + double q; // conduit flow (cfs) + double t; // time step (sec) + double tLink = tMin; // critical link time step (sec) + + // --- examine each conduit link + for ( i = 0; i < Nobjects[LINK]; i++ ) + { + if ( Link[i].type == CONDUIT ) + { + // --- skip conduits with negligible flow, area or Fr + k = Link[i].subIndex; + q = fabs(Link[i].newFlow) / Conduit[k].barrels; + if ( q <= FUDGE + || Conduit[k].a1 <= FUDGE + || Link[i].froude <= 0.01 + ) continue; + + // --- compute time step to satisfy Courant condition + t = Link[i].newVolume / Conduit[k].barrels / q; + t = t * Conduit[k].modLength / link_getLength(i); + t = t * Link[i].froude / (1.0 + Link[i].froude) * CourantFactor; + + // --- update critical link time step + if ( t < tLink ) + { + tLink = t; + *minLink = i; + } + } + } + return tLink; +} + +//============================================================================= + +double getNodeStep(double tMin, int *minNode) +// +// Input: tMin = critical time step found so far (sec) +// Output: minNode = index of node with critical time step; +// returns critical time step (sec) +// Purpose: finds critical time step for nodes based on max. allowable +// projected change in depth. +// +{ + int i; // node index + double maxDepth; // max. depth allowed at node (ft) + double dYdT; // change in depth per unit time (ft/sec) + double t1; // time needed to reach depth limit (sec) + double tNode = tMin; // critical node time step (sec) + + // --- find smallest time so that estimated change in nodal depth + // does not exceed safety factor * maxdepth + for ( i = 0; i < Nobjects[NODE]; i++ ) + { + // --- see if node can be skipped + if ( Node[i].type == OUTFALL ) continue; + if ( Node[i].newDepth <= FUDGE) continue; + if ( Node[i].newDepth + FUDGE >= + Node[i].crownElev - Node[i].invertElev ) continue; + + // --- define max. allowable depth change using crown elevation + maxDepth = (Node[i].crownElev - Node[i].invertElev) * 0.25; + if ( maxDepth < FUDGE ) continue; + dYdT = Xnode[i].dYdT; + if (dYdT < FUDGE ) continue; + + // --- compute time to reach max. depth & compare with critical time + t1 = maxDepth / dYdT; + if ( t1 < tNode ) + { + tNode = t1; + *minNode = i; + } + } + return tNode; +} diff --git a/src/enums.h b/src/enums.h new file mode 100644 index 000000000..c260e4150 --- /dev/null +++ b/src/enums.h @@ -0,0 +1,500 @@ +//----------------------------------------------------------------------------- +// enums.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 06/01/22 (Build 5.2.1) +// Author: L. Rossman +// +// Enumerated constants +// +// Update History +// ============== +// Build 5.1.004: +// - IGNORE_RDII for the ignore RDII option added. +// Build 5.1.007: +// - s_GWF for [GWF] input file section added. +// - s_ADJUST for [ADJUSTMENTS] input file section added. +// Build 5.1.008: +// - Enumerations for fullness state of a conduit added. +// - NUM_THREADS added for number of parallel threads option. +// - Runoff flow categories added to represent mass balance components. +// Build 5.1.010: +// - New ROADWAY_WEIR type of weir added. +// - Potential evapotranspiration (PET) added as a system output variable. +// Build 5.1.011: +// - s_EVENT added to InputSectionType enumeration. +// Build 5.1.013: +// - SURCHARGE_METHOD and RULE_STEP options added. +// - WEIR_CURVE added as a curve type. +// Build 5.2.0: +// - Support added for Streets and Inlets. +// - Support added for variable speed pumps. +// - Support added for analytical storage shapes. +// Build 5.2.1: +// - Adds a NEITHER option to the NormalFlowType enumeration. +//----------------------------------------------------------------------------- + +#ifndef ENUMS_H +#define ENUMS_H + + +//------------------------------------- +// Names of major object types +//------------------------------------- + enum ObjectType { + GAGE, // rain gage + SUBCATCH, // subcatchment + NODE, // conveyance system node + LINK, // conveyance system link + POLLUT, // pollutant + LANDUSE, // land use category + TIMEPATTERN, // dry weather flow time pattern + CURVE, // generic table of values + TSERIES, // generic time series of values + CONTROL, // conveyance system control rules + TRANSECT, // irregular channel cross-section + AQUIFER, // groundwater aquifer + UNITHYD, // RDII unit hydrograph + SNOWMELT, // snowmelt parameter set + SHAPE, // custom conduit shape + LID, // LID treatment units + STREET, // street cross section + INLET, // street inlet design + MAX_OBJ_TYPES}; + +//------------------------------------- +// Names of Node sub-types +//------------------------------------- + #define MAX_NODE_TYPES 5 + enum NodeType { + JUNCTION, + OUTFALL, + STORAGE, + DIVIDER}; + +//------------------------------------- +// Names of Link sub-types +//------------------------------------- + #define MAX_LINK_TYPES 5 + enum LinkType { + CONDUIT, + PUMP, + ORIFICE, + WEIR, + OUTLET}; + +//------------------------------------- +// File types +//------------------------------------- + enum FileType { + RAINFALL_FILE, // rainfall file + RUNOFF_FILE, // runoff file + HOTSTART_FILE, // hotstart file + RDII_FILE, // RDII file + INFLOWS_FILE, // inflows interface file + OUTFLOWS_FILE}; // outflows interface file + +//------------------------------------- +// File usage types +//------------------------------------- + enum FileUsageType { + NO_FILE, // no file usage + SCRATCH_FILE, // use temporary scratch file + USE_FILE, // use previously saved file + SAVE_FILE}; // save file currently in use + +//------------------------------------- +// Rain gage data types +//------------------------------------- + enum GageDataType { + RAIN_TSERIES, // rainfall from user-supplied time series + RAIN_FILE}; // rainfall from external file + +//------------------------------------- +// Cross section shape types +//------------------------------------- + enum XsectType { + DUMMY, // 0 + CIRCULAR, // 1 closed + FILLED_CIRCULAR, // 2 closed + RECT_CLOSED, // 3 closed + RECT_OPEN, // 4 + TRAPEZOIDAL, // 5 + TRIANGULAR, // 6 + PARABOLIC, // 7 + POWERFUNC, // 8 + RECT_TRIANG, // 9 + RECT_ROUND, // 10 + MOD_BASKET, // 11 + HORIZ_ELLIPSE, // 12 closed + VERT_ELLIPSE, // 13 closed + ARCH, // 14 closed + EGGSHAPED, // 15 closed + HORSESHOE, // 16 closed + GOTHIC, // 17 closed + CATENARY, // 18 closed + SEMIELLIPTICAL, // 19 closed + BASKETHANDLE, // 20 closed + SEMICIRCULAR, // 21 closed + IRREGULAR, // 22 + CUSTOM, // 23 closed + FORCE_MAIN, // 24 closed + STREET_XSECT}; // 25 + +//------------------------------------- +// Measurement units types +//------------------------------------- + enum UnitsType { + US, // US units + SI}; // SI (metric) units + + enum FlowUnitsType { + CFS, // cubic feet per second + GPM, // gallons per minute + MGD, // million gallons per day + CMS, // cubic meters per second + LPS, // liters per second + MLD}; // million liters per day + + enum ConcUnitsType { + MG, // Milligrams / L + UG, // Micrograms / L + COUNT}; // Counts / L + +//-------------------------------------- +// Quantities requiring unit conversions +//-------------------------------------- + enum ConversionType { + RAINFALL, + RAINDEPTH, + EVAPRATE, + LENGTH, + LANDAREA, + VOLUME, + WINDSPEED, + TEMPERATURE, + MASS, + GWFLOW, + FLOW}; // Flow must always be listed last + +//------------------------------------- +// Computed subcatchment quantities +//------------------------------------- + #define MAX_SUBCATCH_RESULTS 9 + enum SubcatchResultType { + SUBCATCH_RAINFALL, // rainfall intensity + SUBCATCH_SNOWDEPTH, // snow depth + SUBCATCH_EVAP, // evap loss + SUBCATCH_INFIL, // infil loss + SUBCATCH_RUNOFF, // runoff flow rate + SUBCATCH_GW_FLOW, // groundwater flow rate to node + SUBCATCH_GW_ELEV, // elevation of saturated gw table + SUBCATCH_SOIL_MOIST, // soil moisture + SUBCATCH_WASHOFF}; // pollutant washoff concentration + +//------------------------------------- +// Computed node quantities +//------------------------------------- + #define MAX_NODE_RESULTS 7 + enum NodeResultType { + NODE_DEPTH, // water depth above invert + NODE_HEAD, // hydraulic head + NODE_VOLUME, // volume stored & ponded + NODE_LATFLOW, // lateral inflow rate + NODE_INFLOW, // total inflow rate + NODE_OVERFLOW, // overflow rate + NODE_QUAL}; // concentration of each pollutant + +//------------------------------------- +// Computed link quantities +//------------------------------------- + #define MAX_LINK_RESULTS 6 + enum LinkResultType { + LINK_FLOW, // flow rate + LINK_DEPTH, // flow depth + LINK_VELOCITY, // flow velocity + LINK_VOLUME, // link volume + LINK_CAPACITY, // ratio of area to full area + LINK_QUAL}; // concentration of each pollutant + +//------------------------------------- +// System-wide quantities +//------------------------------------- +#define MAX_SYS_RESULTS 15 +enum SysFlowType { + SYS_TEMPERATURE, // air temperature + SYS_RAINFALL, // rainfall intensity + SYS_SNOWDEPTH, // snow depth + SYS_INFIL, // infil + SYS_RUNOFF, // runoff flow + SYS_DWFLOW, // dry weather inflow + SYS_GWFLOW, // ground water inflow + SYS_IIFLOW, // RDII inflow + SYS_EXFLOW, // external inflow + SYS_INFLOW, // total lateral inflow + SYS_FLOODING, // flooding outflow + SYS_OUTFLOW, // outfall outflow + SYS_STORAGE, // storage volume + SYS_EVAP, // evaporation + SYS_PET}; // potential ET + +//------------------------------------- +// Conduit flow classifications +//------------------------------------- + enum FlowClassType { + DRY, // dry conduit + UP_DRY, // upstream end is dry + DN_DRY, // downstream end is dry + SUBCRITICAL, // sub-critical flow + SUPCRITICAL, // super-critical flow + UP_CRITICAL, // free-fall at upstream end + DN_CRITICAL, // free-fall at downstream end + MAX_FLOW_CLASSES, // number of distinct flow classes + UP_FULL, // upstream end is full + DN_FULL, // downstream end is full + ALL_FULL}; // completely full + +//------------------------ +// Runoff flow categories +//------------------------ +enum RunoffFlowType { + RUNOFF_RAINFALL, // rainfall + RUNOFF_EVAP, // evaporation + RUNOFF_INFIL, // infiltration + RUNOFF_RUNOFF, // runoff + RUNOFF_DRAINS, // LID drain flow + RUNOFF_RUNON}; // runon from outfalls + +//------------------------------------- +// Surface pollutant loading categories +//------------------------------------- + enum LoadingType { + BUILDUP_LOAD, // pollutant buildup load + DEPOSITION_LOAD, // rainfall deposition load + SWEEPING_LOAD, // load removed by sweeping + BMP_REMOVAL_LOAD, // load removed by BMPs + INFIL_LOAD, // runon load removed by infiltration + RUNOFF_LOAD, // load removed by runoff + FINAL_LOAD}; // load remaining on surface + +//------------------------------------- +// Input data options +//------------------------------------- + enum RainfallType { + RAINFALL_INTENSITY, // rainfall expressed as intensity + RAINFALL_VOLUME, // rainfall expressed as volume + CUMULATIVE_RAINFALL}; // rainfall expressed as cumulative volume + + enum TempType { + NO_TEMP, // no temperature data supplied + TSERIES_TEMP, // temperatures come from time series + FILE_TEMP}; // temperatures come from file + +enum WindType { + MONTHLY_WIND, // wind speed varies by month + FILE_WIND}; // wind speed comes from file + + enum EvapType { + CONSTANT_EVAP, // constant evaporation rate + MONTHLY_EVAP, // evaporation rate varies by month + TIMESERIES_EVAP, // evaporation supplied by time series + TEMPERATURE_EVAP, // evaporation from daily temperature + FILE_EVAP, // evaporation comes from file + RECOVERY, // soil recovery pattern + DRYONLY}; // evap. allowed only in dry periods + + enum NormalizerType { + PER_AREA, // buildup is per unit of area + PER_CURB}; // buildup is per unit of curb length + + enum BuildupType { + NO_BUILDUP, // no buildup + POWER_BUILDUP, // power function buildup equation + EXPON_BUILDUP, // exponential function buildup equation + SATUR_BUILDUP, // saturation function buildup equation + EXTERNAL_BUILDUP}; // external time series buildup + + enum WashoffType { + NO_WASHOFF, // no washoff + EXPON_WASHOFF, // exponential washoff equation + RATING_WASHOFF, // rating curve washoff equation + EMC_WASHOFF}; // event mean concentration washoff + +enum SubAreaType { + IMPERV0, // impervious w/o depression storage + IMPERV1, // impervious w/ depression storage + PERV}; // pervious + + enum RunoffRoutingType { + TO_OUTLET, // perv & imperv runoff goes to outlet + TO_IMPERV, // perv runoff goes to imperv area + TO_PERV}; // imperv runoff goes to perv subarea + + enum RouteModelType { + NO_ROUTING, // no routing + SF, // steady flow model + KW, // kinematic wave model + EKW, // extended kin. wave model + DW}; // dynamic wave model + + enum ForceMainType { + H_W, // Hazen-Williams eqn. + D_W}; // Darcy-Weisbach eqn. + + enum OffsetType { + DEPTH_OFFSET, // offset measured as depth + ELEV_OFFSET}; // offset measured as elevation + + enum KinWaveMethodType { + NORMAL, // normal method + MODIFIED}; // modified method + +enum CompatibilityType { + SWMM5, // SWMM 5 weighting for area & hyd. radius + SWMM3, // SWMM 3 weighting + SWMM4}; // SWMM 4 weighting + + enum NormalFlowType { + SLOPE, // based on slope only + FROUDE, // based on Fr only + BOTH, // based on slope & Fr + NEITHER}; + + enum InertialDampingType { + NO_DAMPING, // no inertial damping + PARTIAL_DAMPING, // partial damping + FULL_DAMPING}; // full damping + + 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 + WET_WEATHER_INFLOW, // computed runoff inflow + GROUNDWATER_INFLOW, // computed groundwater inflow + RDII_INFLOW, // computed I&I inflow + FLOW_INFLOW, // inflow parameter is flow + CONCEN_INFLOW, // inflow parameter is pollutant concen. + MASS_INFLOW}; // inflow parameter is pollutant mass + + enum PatternType { + MONTHLY_PATTERN, // DWF multipliers for each month + DAILY_PATTERN, // DWF multipliers for each day of week + HOURLY_PATTERN, // DWF multipliers for each hour of day + WEEKEND_PATTERN}; // hourly multipliers for week end days + + enum OutfallType { + FREE_OUTFALL, // critical depth outfall condition + NORMAL_OUTFALL, // normal flow depth outfall condition + FIXED_OUTFALL, // fixed depth outfall condition + TIDAL_OUTFALL, // variable tidal stage outfall condition + TIMESERIES_OUTFALL}; // variable time series outfall depth + + enum StorageType { + TABULAR, // area v. depth from table + FUNCTIONAL, // area v. depth from power function + CYLINDRICAL, // area v. depth from elliptical cylinder + CONICAL, // area v. depth from elliptical cone + PARABOLOID, // area v. depth from elliptical paraboloid + PYRAMIDAL}; // area v. depth from rectangular pyramid + + enum ReactorType { + CSTR, // completely mixed reactor + PLUG}; // plug flow reactor + + enum TreatmentType { + REMOVAL, // treatment stated as a removal + CONCEN}; // treatment stated as effluent concen. + + enum DividerType { + CUTOFF_DIVIDER, // diverted flow is excess of cutoff flow + TABULAR_DIVIDER, // table of diverted flow v. inflow + WEIR_DIVIDER, // diverted flow proportional to excess flow + OVERFLOW_DIVIDER}; // diverted flow is flow > full conduit flow + + enum PumpCurveType { + TYPE1_PUMP, // flow varies stepwise with wet well volume + TYPE2_PUMP, // flow varies stepwise with inlet depth + TYPE3_PUMP, // flow varies with head delivered + TYPE4_PUMP, // flow varies with inlet depth + TYPE5_PUMP, // variable speed version of TYPE3 pump + IDEAL_PUMP}; // outflow equals inflow + + enum OrificeType { + SIDE_ORIFICE, // side orifice + BOTTOM_ORIFICE}; // bottom orifice + + enum WeirType { + TRANSVERSE_WEIR, // transverse weir + SIDEFLOW_WEIR, // side flow weir + VNOTCH_WEIR, // V-notch (triangular) weir + TRAPEZOIDAL_WEIR, // trapezoidal weir + ROADWAY_WEIR}; // FHWA HDS-5 roadway weir + + enum CurveType { + STORAGE_CURVE, // surf. area v. depth for storage node + DIVERSION_CURVE, // diverted flow v. inflow for divider node + TIDAL_CURVE, // water elev. v. hour of day for outfall + RATING_CURVE, // flow rate v. head for outlet link + CONTROL_CURVE, // control setting v. controller variable + SHAPE_CURVE, // width v. depth for custom x-section + WEIR_CURVE, // discharge coeff. v. head for weir + PUMP1_CURVE, // flow v. wet well volume for pump + PUMP2_CURVE, // flow v. depth for pump (discrete) + PUMP3_CURVE, // flow v. head for pump (continuous) + PUMP4_CURVE, // flow v. depth for pump (continuous) + PUMP5_CURVE}; // variable speed version of TYPE3 pump + + enum NodeInletType { + NO_INLET, + BYPASS, + CAPTURE + }; + + enum InputSectionType { + s_TITLE, s_OPTION, s_FILE, s_RAINGAGE, + s_TEMP, s_EVAP, s_SUBCATCH, s_SUBAREA, + s_INFIL, s_AQUIFER, s_GROUNDWATER, s_SNOWMELT, + s_JUNCTION, s_OUTFALL, s_STORAGE, s_DIVIDER, + s_CONDUIT, s_PUMP, s_ORIFICE, s_WEIR, + s_OUTLET, s_XSECTION, s_TRANSECT, s_LOSSES, + s_CONTROL, s_POLLUTANT, s_LANDUSE, s_BUILDUP, + s_WASHOFF, s_COVERAGE, s_INFLOW, s_DWF, + s_PATTERN, s_RDII, s_UNITHYD, s_LOADING, + s_TREATMENT, s_CURVE, s_TIMESERIES, s_REPORT, + s_COORDINATE, s_VERTICES, s_POLYGON, s_LABEL, + s_SYMBOL, s_BACKDROP, s_TAG, s_PROFILE, + s_MAP, s_LID_CONTROL, s_LID_USAGE, s_GWF, + s_ADJUST, s_EVENT, s_STREET, s_INLET_USAGE, + s_INLET}; + + 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, + 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}; + +enum NoYesType { + NO, + YES}; + +enum NoneAllType { + NONE, + ALL, + SOME}; + + +#endif //ENUMS_H diff --git a/src/error.c b/src/error.c new file mode 100644 index 000000000..626950b6f --- /dev/null +++ b/src/error.c @@ -0,0 +1,49 @@ +//----------------------------------------------------------------------------- +// error.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Error messages +// +// Update History +// ============== +// Build 5.1.008: +// - Text of Error 217 for control rules modified. +// Build 5.1.010: +// - Text of Error 318 for rainfall data files modified. +// Build 5.1.015: +// - Added new Error 140 for storage nodes. +// Build 5.2.0: +// - Re-designed error message system. +// - Added new Error 235 for invalid infiltration parameters. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include "error.h" + +char ErrString[256]; + +char* error_getMsg(int errCode, char* msg) +{ + switch (errCode) + { + +#define ERR(code,string) case code: strcpy(msg, string); break; +#include "error.txt" +#undef ERR + + default: + strcpy(msg, ""); + } + return (msg); +}; + +int error_setInpError(int errcode, char* s) +{ + strcpy(ErrString, s); + return errcode; +} diff --git a/src/error.h b/src/error.h new file mode 100644 index 000000000..fedf0847e --- /dev/null +++ b/src/error.h @@ -0,0 +1,182 @@ +//----------------------------------------------------------------------------- +// error.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Error codes +// +//----------------------------------------------------------------------------- + +#ifndef ERROR_H +#define ERROR_H + +enum ErrorType { + +// ... Runtime Errors + ERR_NONE = 0, + ERR_MEMORY = 101, + ERR_KINWAVE = 103, + ERR_ODE_SOLVER = 105, + ERR_TIMESTEP = 107, + +// ... Subcatchment/Aquifer Errors + ERR_SUBCATCH_OUTLET = 108, + ERR_AQUIFER_PARAMS = 109, + ERR_GROUND_ELEV = 110, + +// ... Conduit/Pump Errors + ERR_LENGTH = 111, + ERR_ELEV_DROP = 112, + ERR_ROUGHNESS = 113, + ERR_BARRELS = 114, + ERR_SLOPE = 115, + ERR_NO_XSECT = 117, + ERR_XSECT = 119, + ERR_NO_CURVE = 121, + ERR_PUMP_LIMITS = 122, + +// ... Topology Errors + ERR_LOOP = 131, + ERR_MULTI_OUTLET = 133, + ERR_DUMMY_LINK = 134, + +// ... Node Errors + ERR_DIVIDER = 135, + ERR_DIVIDER_LINK = 136, + ERR_WEIR_DIVIDER = 137, + ERR_NODE_DEPTH = 138, + ERR_REGULATOR = 139, + ERR_STORAGE_VOLUME = 140, + ERR_OUTFALL = 141, + ERR_REGULATOR_SHAPE = 143, + ERR_NO_OUTLETS = 145, + +// ... RDII Errors + ERR_UNITHYD_TIMES = 151, + ERR_UNITHYD_RATIOS = 153, + ERR_RDII_AREA = 155, + +// ... Rain Gage Errors + ERR_RAIN_FILE_CONFLICT = 156, + ERR_RAIN_GAGE_FORMAT = 157, + ERR_RAIN_GAGE_TSERIES = 158, + ERR_RAIN_GAGE_INTERVAL = 159, + +// ... Treatment Function Error + ERR_CYCLIC_TREATMENT = 161, + +// ... Curve/Time Series Errors + ERR_CURVE_SEQUENCE = 171, + ERR_TIMESERIES_SEQUENCE = 173, + +// ... Snowmelt Errors + ERR_SNOWMELT_PARAMS = 181, + ERR_SNOWPACK_PARAMS = 182, + +// ... LID Errors + ERR_LID_TYPE = 183, + ERR_LID_LAYER = 184, + ERR_LID_PARAMS = 185, + ERR_LID_AREAS = 187, + ERR_LID_CAPTURE_AREA = 188, + +// ... Simulation Date/Time Errors + ERR_START_DATE = 191, + ERR_REPORT_DATE = 193, + ERR_REPORT_STEP = 195, + +// ... Input Parser Errors + ERR_INPUT = 200, + ERR_LINE_LENGTH = 201, + ERR_ITEMS = 203, + ERR_KEYWORD = 205, + ERR_DUP_NAME = 207, + ERR_NAME = 209, + ERR_NUMBER = 211, + ERR_DATETIME = 213, + ERR_RULE = 217, + ERR_TRANSECT_UNKNOWN = 219, + ERR_TRANSECT_SEQUENCE = 221, + ERR_TRANSECT_TOO_FEW = 223, + ERR_TRANSECT_TOO_MANY = 225, + ERR_TRANSECT_MANNING = 227, + ERR_TRANSECT_OVERBANK = 229, + ERR_TRANSECT_NO_DEPTH = 231, + ERR_MATH_EXPR = 233, + ERR_INFIL_PARAMS = 235, + +// ... File Name/Opening Errors + ERR_FILE_NAME = 301, + ERR_INP_FILE = 303, + ERR_RPT_FILE = 305, + ERR_OUT_FILE = 307, + ERR_OUT_SIZE = 308, + ERR_OUT_WRITE = 309, + ERR_OUT_READ = 311, + +// ... Rain File Errors + ERR_RAIN_FILE_SCRATCH = 313, + ERR_RAIN_FILE_OPEN = 315, + ERR_RAIN_FILE_DATA = 317, + ERR_RAIN_FILE_SEQUENCE = 318, + ERR_RAIN_FILE_FORMAT = 319, + ERR_RAIN_IFACE_FORMAT = 320, + ERR_RAIN_FILE_GAGE = 321, + +// ... Runoff File Errors + ERR_RUNOFF_FILE_OPEN = 323, + ERR_RUNOFF_FILE_FORMAT = 325, + ERR_RUNOFF_FILE_END = 327, + ERR_RUNOFF_FILE_READ = 329, + +// ... Hotstart File Errors + ERR_HOTSTART_FILE_OPEN = 331, + ERR_HOTSTART_FILE_FORMAT = 333, + ERR_HOTSTART_FILE_READ = 335, + +// ... Climate File Errors + ERR_NO_CLIMATE_FILE = 336, + ERR_CLIMATE_FILE_OPEN = 337, + ERR_CLIMATE_FILE_READ = 338, + ERR_CLIMATE_END_OF_FILE = 339, + +// ... RDII File Errors + ERR_RDII_FILE_SCRATCH = 341, + ERR_RDII_FILE_OPEN = 343, + ERR_RDII_FILE_FORMAT = 345, + +// ... Routing File Errors + ERR_ROUTING_FILE_OPEN = 351, + ERR_ROUTING_FILE_FORMAT = 353, + ERR_ROUTING_FILE_NOMATCH = 355, + ERR_ROUTING_FILE_NAMES = 357, + +// ... Time Series File Errors + ERR_TABLE_FILE_OPEN = 361, + ERR_TABLE_FILE_READ = 363, + +// ... Runtime Errors + ERR_SYSTEM = 500, + +// ... API Errors + ERR_API_NOT_OPEN = 501, + ERR_API_NOT_STARTED = 502, + ERR_API_NOT_ENDED = 503, + ERR_API_OBJECT_TYPE = 504, + ERR_API_OBJECT_INDEX = 505, + ERR_API_OBJECT_NAME = 506, + ERR_API_PROPERTY_TYPE = 507, + ERR_API_PROPERTY_VALUE = 508, + ERR_API_TIME_PERIOD = 509, + +// ... Additional Errors + MAXERRMSG = 1000 +}; + +char* error_getMsg(int i, char* msg); +int error_setInpError(int errcode, char* s); + +#endif //ERROR_H diff --git a/src/error.txt b/src/error.txt new file mode 100644 index 000000000..30d45a783 --- /dev/null +++ b/src/error.txt @@ -0,0 +1,135 @@ +// SWMM 5.2 Error Messages + +ERR(101,"\n ERROR 101: memory allocation error.") +ERR(103,"\n ERROR 103: cannot solve KW equations for Link %s.") +ERR(105,"\n ERROR 105: cannot open ODE solver.") +ERR(107,"\n ERROR 107: cannot compute a valid time step.") + +ERR(108,"\n ERROR 108: ambiguous outlet ID name for Subcatchment %s.") +ERR(109,"\n ERROR 109: invalid parameter values for Aquifer %s.") +ERR(110,"\n ERROR 110: ground elevation is below water table for Subcatchment %s.") + +ERR(111,"\n ERROR 111: invalid length for Conduit %s.") +ERR(112,"\n ERROR 112: elevation drop exceeds length for Conduit %s.") +ERR(113,"\n ERROR 113: invalid roughness for Conduit %s.") +ERR(114,"\n ERROR 114: invalid number of barrels for Conduit %s.") +ERR(115,"\n ERROR 115: adverse slope for Conduit %s.") +ERR(117,"\n ERROR 117: no cross section defined for Link %s.") +ERR(119,"\n ERROR 119: invalid cross section for Link %s.") +ERR(121,"\n ERROR 121: missing or invalid pump curve assigned to Pump %s.") +ERR(122,"\n ERROR 122: startup depth not higher than shutoff depth for Pump %s.") + +ERR(131,"\n ERROR 131: the following links form cyclic loops in the drainage system:") +ERR(133,"\n ERROR 133: Node %s has more than one outlet link.") +ERR(134,"\n ERROR 134: Node %s has illegal DUMMY link connections.") + +ERR(135,"\n ERROR 135: Divider %s does not have two outlet links.") +ERR(136,"\n ERROR 136: Divider %s has invalid diversion link.") +ERR(137,"\n ERROR 137: Weir Divider %s has invalid parameters.") +ERR(138,"\n ERROR 138: Node %s has initial depth greater than maximum depth.") +ERR(139,"\n ERROR 139: Regulator %s is the outlet of a non-storage node.") +ERR(140,"\n ERROR 140: Storage node %s has negative volume at full depth.") +ERR(141,"\n ERROR 141: Outfall %s has more than 1 inlet link or an outlet link.") +ERR(143,"\n ERROR 143: Regulator %s has invalid cross-section shape.") +ERR(145,"\n ERROR 145: Drainage system has no acceptable outlet nodes.") + +ERR(151,"\n ERROR 151: a Unit Hydrograph in set %s has invalid time base.") +ERR(153,"\n ERROR 153: a Unit Hydrograph in set %s has invalid response ratios.") +ERR(155,"\n ERROR 155: invalid sewer area for RDII at node %s.") + +ERR(156,"\n ERROR 156: ambiguous station ID for Rain Gage %s.") +ERR(157,"\n ERROR 157: inconsistent rainfall format for Rain Gage %s.") +ERR(158,"\n ERROR 158: time series for Rain Gage %s is also used by another object.") +ERR(159,"\n ERROR 159: recording interval greater than time series interval for Rain Gage %s.") + +ERR(161,"\n ERROR 161: cyclic dependency in treatment functions at node %s.") + +ERR(171,"\n ERROR 171: Curve %s has invalid or out of sequence data.") +ERR(173,"\n ERROR 173: Time Series %s has its data out of sequence.") + +ERR(181,"\n ERROR 181: invalid Snow Melt Climatology parameters.") +ERR(182,"\n ERROR 182: invalid parameters for Snow Pack %s.") + +ERR(183,"\n ERROR 183: no type specified for LID %s.") +ERR(184,"\n ERROR 184: missing layer for LID %s.") +ERR(185,"\n ERROR 185: invalid parameter value for LID %s.") +ERR(187,"\n ERROR 187: LID area exceeds total area for Subcatchment %s.") +ERR(188,"\n ERROR 188: LID capture area exceeds total impervious area for Subcatchment %s.") + +ERR(191,"\n ERROR 191: simulation start date comes after ending date.") +ERR(193,"\n ERROR 193: report start date comes after ending date.") +ERR(195,"\n ERROR 195: reporting time step or duration is less than routing time step.") + +ERR(200,"\n ERROR 200: one or more errors in input file.") +ERR(201,"\n ERROR 201: too many characters in input line ") +ERR(203,"\n ERROR 203: too few items ") +ERR(205,"\n ERROR 205: invalid keyword %s ") +ERR(207,"\n ERROR 207: duplicate ID name %s ") +ERR(209,"\n ERROR 209: undefined object %s ") +ERR(211,"\n ERROR 211: invalid number %s ") +ERR(213,"\n ERROR 213: invalid date/time %s ") +ERR(217,"\n ERROR 217: control rule clause invalid or out of sequence ") +ERR(219,"\n ERROR 219: data provided for unidentified transect ") +ERR(221,"\n ERROR 221: transect station out of sequence ") +ERR(223,"\n ERROR 223: Transect %s has too few stations.") +ERR(225,"\n ERROR 225: Transect %s has too many stations.") +ERR(227,"\n ERROR 227: Transect %s has no Manning's N.") +ERR(229,"\n ERROR 229: Transect %s has invalid overbank locations.") +ERR(231,"\n ERROR 231: Transect %s has no depth.") +ERR(233,"\n ERROR 233: invalid math expression ") +ERR(235,"\n ERROR 235: invalid infiltration parameters ") + +ERR(301,"\n ERROR 301: files share same names.") +ERR(303,"\n ERROR 303: cannot open input file.") +ERR(305,"\n ERROR 305: cannot open report file.") +ERR(307,"\n ERROR 307: cannot open binary results file.") +ERR(308,"\n ERROR 308: amount of output produced will exceed maximum file size.") + +ERR(309,"\n ERROR 309: error writing to binary results file.") +ERR(311,"\n ERROR 311: error reading from binary results file.") + +ERR(313,"\n ERROR 313: cannot open scratch rainfall interface file.") +ERR(315,"\n ERROR 315: cannot open rainfall interface file %s.") +ERR(317,"\n ERROR 317: cannot open rainfall data file %s.") +ERR(318,"\n ERROR 318: the following line is out of sequence in rainfall data file %s.") +ERR(319,"\n ERROR 319: unknown format for rainfall data file %s.") +ERR(320,"\n ERROR 320: invalid format for rainfall interface file.") +ERR(321,"\n ERROR 321: no data in rainfall interface file for gage %s.") + +ERR(323,"\n ERROR 323: cannot open runoff interface file %s.") +ERR(325,"\n ERROR 325: incompatible data found in runoff interface file.") +ERR(327,"\n ERROR 327: attempting to read beyond end of runoff interface file.") +ERR(329,"\n ERROR 329: error in reading from runoff interface file.") + +ERR(331,"\n ERROR 331: cannot open hot start interface file %s.") +ERR(333,"\n ERROR 333: incompatible data found in hot start interface file.") +ERR(335,"\n ERROR 335: error in reading from hot start interface file.") + +ERR(336,"\n ERROR 336: no climate file specified for evaporation and/or wind speed.") +ERR(337,"\n ERROR 337: cannot open climate file %s.") +ERR(338,"\n ERROR 338: error in reading from climate file %s.") +ERR(339,"\n ERROR 339: attempt to read beyond end of climate file %s.") + +ERR(341,"\n ERROR 341: cannot open scratch RDII interface file.") +ERR(343,"\n ERROR 343: cannot open RDII interface file %s.") +ERR(345,"\n ERROR 345: invalid format for RDII interface file.") + +ERR(351,"\n ERROR 351: cannot open routing interface file %s.") +ERR(353,"\n ERROR 353: invalid format for routing interface file %s.") +ERR(355,"\n ERROR 355: mis-matched names in routing interface file %s.") +ERR(357,"\n ERROR 357: inflows and outflows interface files have same name.") + +ERR(361,"\n ERROR 361: could not open external file used for Time Series %s.") +ERR(363,"\n ERROR 363: invalid data in external file used for Time Series %s.") + +// API Error Keys +ERR(500,"\n ERROR 500: System exception thrown.") +ERR(501,"\n API Error 501: project not opened.") +ERR(502,"\n API Error 502: simulation not started.") +ERR(503,"\n API Error 503: simulation not ended.") +ERR(504,"\n API Error 504: invalid object type.") +ERR(505,"\n API Error 505: invalid object index.") +ERR(506,"\n API Error 506: invalid object name.") +ERR(507,"\n API Error 507: invalid property type.") +ERR(508,"\n API Error 508: invalid property value.") +ERR(509,"\n API Error 509: invalid time period.") diff --git a/src/exfil.c b/src/exfil.c new file mode 100644 index 000000000..f1cb2adf6 --- /dev/null +++ b/src/exfil.c @@ -0,0 +1,252 @@ +//----------------------------------------------------------------------------- +// exfil.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Storage unit exfiltration functions. +// +// Update History +// ============== +// Build 5.1.008: +// - Monthly conductivity adjustment applied to exfiltration rate. +// Build 5.1.010: +// - New modified Green-Ampt infiltration option used. +// Build 5.1.011: +// - Fixed units conversion error for storage units with surface area curves. +// Build 5.2.0: +// - Support added for analytical storage shapes. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include "headers.h" +#include "infil.h" +#include "exfil.h" + +static int createStorageExfil(int k, double x[]); + +//============================================================================= + +int exfil_readStorageParams(int k, char* tok[], int ntoks, int n) +// +// Input: k = storage unit index +// tok[] = array of string tokens +// ntoks = number of tokens +// n = last token processed +// Output: returns an error code +// Purpose: reads a storage unit's exfiltration parameters from a +// tokenized line of input. +// +{ + int i; + double x[3]; //suction head, Ksat, IMDmax + + // --- read Ksat if it's the only remaining token + if ( ntoks == n+1 ) + { + if ( ! getDouble(tok[n], &x[1]) ) + return error_setInpError(ERR_NUMBER, tok[n]); + x[0] = 0.0; + x[2] = 0.0; + } + + // --- otherwise read Green-Ampt infiltration parameters from input tokens + else if ( ntoks < n + 3 ) return error_setInpError(ERR_ITEMS, ""); + else for (i = 0; i < 3; i++) + { + if ( ! getDouble(tok[n+i], &x[i]) ) + return error_setInpError(ERR_NUMBER, tok[n+i]); + } + + // --- no exfiltration if Ksat is 0 + if ( x[1] == 0.0 ) return 0; + + // --- create an exfiltration object + return createStorageExfil(k, x); +} + +//============================================================================= + +void exfil_initState(int k) +// +// Input: k = storage unit index +// Output: none +// Purpose: initializes the state of a storage unit's exfiltration object. +// +{ + int i; + double a, alast, d; + TTable* aCurve; + TExfil* exfil = Storage[k].exfil; + + // --- initialize exfiltration object + if ( exfil != NULL ) + { + // --- initialize the Green-Ampt infil. parameters + grnampt_initState(exfil->btmExfil); + grnampt_initState(exfil->bankExfil); + + switch (Storage[k].shape) + { + // --- shape given by a Storage Curve + case TABULAR: + i = Storage[k].aCurve; + exfil->btmArea = 0.0; + exfil->bankMinDepth = 0.0; + exfil->bankMaxDepth = 0.0; + exfil->bankMaxArea = 0.0; + if ( i >= 0 ) + { + // --- get bottom area + aCurve = &Curve[i]; + Storage[k].exfil->btmArea = table_lookupEx(aCurve, 0.0); + + // --- find min/max bank depths and max. bank area + table_getFirstEntry(aCurve, &d, &a); + alast = a; + while ( table_getNextEntry(aCurve, &d, &a) ) + { + if ( a < alast ) break; + else if ( a > alast ) + { + exfil->bankMaxArea = a; + exfil->bankMaxDepth = d; + } + else if ( exfil->bankMaxArea == 0.0 ) + exfil->bankMinDepth = d; + else break; + alast = a; + } + + // --- convert from user units to internal units + exfil->btmArea /= UCF(LENGTH) * UCF(LENGTH); + exfil->bankMaxArea /= UCF(LENGTH) * UCF(LENGTH); + exfil->bankMinDepth /= UCF(LENGTH); + exfil->bankMaxDepth /= UCF(LENGTH); + } + break; + + // --- functional storage shape curve + case FUNCTIONAL: + exfil->btmArea = Storage[k].a0; + if ( Storage[k].a2 == 0.0 ) + exfil->btmArea +=Storage[k].a1; + exfil->bankMinDepth = 0.0; + exfil->bankMaxDepth = BIG; + exfil->bankMaxArea = BIG; + break; + + // --- cylindrical, conical & prismatic shapes + case CYLINDRICAL: + case CONICAL: + case PYRAMIDAL: + exfil->btmArea = Storage[k].a0; + exfil->bankMinDepth = 0.0; + exfil->bankMaxDepth = BIG; + exfil->bankMaxArea = BIG; + break; + } + } +} + +//============================================================================= + +double exfil_getLoss(TExfil* exfil, double tStep, double depth, double area) +// +// Input: exfil = ptr. to a storage exfiltration object +// tStep = time step (sec) +// depth = water depth (ft) +// area = surface area (ft2) +// Output: returns exfiltration rate out of storage unit (cfs) +// Purpose: computes rate of water exfiltrated from a storage node into +// the soil beneath it. +// +{ + double exfilRate = 0.0; + + // --- find infiltration through bottom of unit + if ( exfil->btmExfil->IMDmax == 0.0 ) + { + exfilRate = exfil->btmExfil->Ks * Adjust.hydconFactor; + } + else exfilRate = grnampt_getInfil(exfil->btmExfil, tStep, 0.0, depth, + MOD_GREEN_AMPT); + exfilRate *= exfil->btmArea; + + // --- find infiltration through sloped banks + if ( depth > exfil->bankMinDepth ) + { + // --- get area of banks + area = MIN(area, exfil->bankMaxArea) - exfil->btmArea; + if ( area > 0.0 ) + { + // --- if infil. rate not a function of depth + if ( exfil->btmExfil->IMDmax == 0.0 ) + { + exfilRate += area * exfil->btmExfil->Ks * Adjust.hydconFactor; + } + + // --- infil. rate depends on depth above bank + else + { + // --- case where water depth is above the point where the + // storage curve no longer has increasing area with depth + if ( depth > exfil->bankMaxDepth ) + { + depth = depth - exfil->bankMaxDepth + + (exfil->bankMaxDepth - exfil->bankMinDepth) / 2.0; + } + + // --- case where water depth is below top of bank + else depth = (depth - exfil->bankMinDepth) / 2.0; + + // --- use Green-Ampt function for bank infiltration + exfilRate += area * grnampt_getInfil(exfil->bankExfil, + tStep, 0.0, depth, MOD_GREEN_AMPT); + } + } + } + return exfilRate; +} + +//============================================================================= + +int createStorageExfil(int k, double x[]) +// +// Input: k = index of storage unit node +// x = array of Green-Ampt infiltration parameters +// Output: returns an error code. +// Purpose: creates an exfiltration object for a storage node. +// +// Note: the exfiltration object is freed in project.c. +// +{ + TExfil* exfil; + + // --- create an exfiltration object for the storage node + exfil = Storage[k].exfil; + if ( exfil == NULL ) + { + exfil = (TExfil *) malloc(sizeof(TExfil)); + if ( exfil == NULL ) return error_setInpError(ERR_MEMORY, ""); + Storage[k].exfil = exfil; + + // --- create Green-Ampt infiltration objects for the bottom & banks + exfil->btmExfil = NULL; + exfil->bankExfil = NULL; + exfil->btmExfil = (TGrnAmpt *) malloc(sizeof(TGrnAmpt)); + if ( exfil->btmExfil == NULL ) return error_setInpError(ERR_MEMORY, ""); + exfil->bankExfil = (TGrnAmpt *) malloc(sizeof(TGrnAmpt)); + if ( exfil->bankExfil == NULL ) return error_setInpError(ERR_MEMORY, ""); + } + + // --- initialize the Green-Ampt parameters + if ( !grnampt_setParams(exfil->btmExfil, x) ) + return error_setInpError(ERR_NUMBER, ""); + grnampt_setParams(exfil->bankExfil, x); + return 0; +} diff --git a/src/exfil.h b/src/exfil.h new file mode 100644 index 000000000..8da4da302 --- /dev/null +++ b/src/exfil.h @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------------- +// exfil.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Public interface for exfiltration functions. +//----------------------------------------------------------------------------- + +#ifndef EXFIL_H +#define EXFIL_H + +//---------------------------- +// EXFILTRATION OBJECT +//---------------------------- +typedef struct +{ + TGrnAmpt* btmExfil; + TGrnAmpt* bankExfil; + double btmArea; + double bankMinDepth; + double bankMaxDepth; + double bankMaxArea; +} TExfil; + +//----------------------------------------------------------------------------- +// Exfiltration Methods +//----------------------------------------------------------------------------- +int exfil_readStorageParams(int k, char* tok[], int ntoks, int n); +void exfil_initState(int k); +double exfil_getLoss(TExfil* exfil, double tStep, double depth, double area); + +#endif diff --git a/src/findroot.c b/src/findroot.c new file mode 100644 index 000000000..d6056f658 --- /dev/null +++ b/src/findroot.c @@ -0,0 +1,138 @@ +//----------------------------------------------------------------------------- +// findroot.c +// +// Finds solution of func(x) = 0 using either the Newton-Raphson +// method or Ridder's Method. +// Based on code from Numerical Recipes in C (Cambridge University +// Press, 1992). +// +// Date: 11/19/13 +// Author: L. Rossman +//----------------------------------------------------------------------------- + +#include +#include "findroot.h" + +#define SIGN(a,b) ((b) >= 0.0 ? fabs(a) : -fabs(a)) +#define MAXIT 60 + + +int findroot_Newton(double x1, double x2, double* rts, double xacc, + void (*func) (double x, double* f, double* df, void* p), + void* p) +// +// Using a combination of Newton-Raphson and bisection, find the root of a +// function func bracketed between x1 and x2. The root, returned in rts, +// will be refined until its accuracy is known within +/-xacc. func is a +// user-supplied routine, that returns both the function value and the first +// derivative of the function. p is a pointer to any auxilary data structure +// that func may require. It can be NULL if not needed. The function returns +// the number of function evaluations used or 0 if the maximum allowed +// iterations were exceeded. +// +// NOTES: +// 1. The calling program must insure that the signs of func(x1) and func(x2) +// are not the same, otherwise x1 and x2 do not bracket the root. +// 2. If func(x1) > func(x2) then the order of x1 and x2 should be +// switched in the call to Newton. +// +{ + int j, n = 0; + double df, dx, dxold, f, x; + double temp, xhi, xlo; + + // Initialize the "stepsize before last" and the last step. + x = *rts; + xlo = x1; + xhi = x2; + dxold = fabs(x2-x1); + dx = dxold; + func(x, &f, &df, p); + n++; + + // Loop over allowed iterations. + for (j=1; j<=MAXIT; j++) + { + // Bisect if Newton out of range or not decreasing fast enough. + if ( ( ( (x-xhi)*df-f)*((x-xlo)*df-f) >= 0.0 + || (fabs(2.0*f) > fabs(dxold*df) ) ) ) + { + dxold = dx; + dx = 0.5*(xhi-xlo); + x = xlo + dx; + if ( xlo == x ) break; + } + + // Newton step acceptable. Take it. + else + { + dxold = dx; + dx = f/df; + temp = x; + x -= dx; + if ( temp == x ) break; + } + + // Convergence criterion. + if ( fabs(dx) < xacc ) break; + + // Evaluate function. Maintain bracket on the root. + func(x, &f, &df, p); + n++; + if ( f < 0.0 ) xlo = x; + else xhi = x; + } + *rts = x; + if ( n <= MAXIT) return n; + else return 0; +}; + + +double findroot_Ridder(double x1, double x2, double xacc, + double (*func)(double, void* p), void* p) +{ + int j; + double ans, fhi, flo, fm, fnew, s, xhi, xlo, xm, xnew; + + flo = func(x1, p); + fhi = func(x2, p); + if ( flo == 0.0 ) return x1; + if ( fhi == 0.0 ) return x2; + ans = 0.5*(x1+x2); + if ( (flo > 0.0 && fhi < 0.0) || (flo < 0.0 && fhi > 0.0) ) + { + xlo = x1; + xhi = x2; + for (j=1; j<=MAXIT; j++) { + xm = 0.5*(xlo + xhi); + fm = func(xm, p); + s = sqrt( fm*fm - flo*fhi ); + if (s == 0.0) return ans; + xnew = xm + (xm-xlo)*( (flo >= fhi ? 1.0 : -1.0)*fm/s ); + if ( fabs(xnew - ans) <= xacc ) break; + ans = xnew; + fnew = func(ans, p); + if ( SIGN(fm, fnew) != fm) + { + xlo = xm; + flo = fm; + xhi = ans; + fhi = fnew; + } + else if ( SIGN(flo, fnew) != flo ) + { + xhi = ans; + fhi = fnew; + } + else if ( SIGN(fhi, fnew) != fhi) + { + xlo = ans; + flo = fnew; + } + else return ans; + if ( fabs(xhi - xlo) <= xacc ) return ans; + } + return ans; + } + return -1.e20; +} diff --git a/src/findroot.h b/src/findroot.h new file mode 100644 index 000000000..3b54c6456 --- /dev/null +++ b/src/findroot.h @@ -0,0 +1,18 @@ +//----------------------------------------------------------------------------- +// findroot.h +// +// Header file for root finding method contained in findroot.c +// +// Last modified on 11/19/13. +//----------------------------------------------------------------------------- + +#ifndef FINDROOT_H +#define FINDROOT_H + +int findroot_Newton(double x1, double x2, double* rts, double xacc, + void (*func) (double x, double* f, double* df, void* p), + void* p); +double findroot_Ridder(double x1, double x2, double xacc, + double (*func)(double, void* p), void* p); + +#endif //FINDROOT_H diff --git a/src/flowrout.c b/src/flowrout.c new file mode 100644 index 000000000..f296c3fb0 --- /dev/null +++ b/src/flowrout.c @@ -0,0 +1,800 @@ +//----------------------------------------------------------------------------- +// flowrout.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 05/02/22 (Build 5.2.1) +// Author: L. Rossman +// M. Tryby (EPA) +// +// Flow routing functions. +// +// Update History +// ============== +// Build 5.1.007: +// - updateStorageState() modified in response to node outflow being +// initialized with current evap & seepage losses in routing_execute(). +// Build 5.1.008: +// - Determination of node crown elevations moved to dynwave.c. +// - Support added for new way of recording conduit's fullness state. +// Build 5.1.012: +// - Overflow computed in updateStorageState() must be non-negative. +// - Terminal storage nodes now updated corectly. +// Build 5.1.014: +// - Arguments to function link_getLossRate changed. +// Build 5.2.0: +// - Correction made to updating state of terminal storage nodes. +// Build 5.2.1: +// - For storage routing, after convergence the reported depth is now +// based on the last volume found rather than the next trial depth. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include "headers.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +static const double OMEGA = 0.55; // under-relaxation parameter +static const int MAXITER = 10; // max. iterations for storage updating +static const double STOPTOL = 0.005; // storage updating stopping tolerance + +//----------------------------------------------------------------------------- +// External functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// flowrout_init (called by routing_open) +// flowrout_close (called by routing_close) +// flowrout_getRoutingStep (called routing_getRoutingStep) +// flowrout_execute (called routing_execute) + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static void initLinkDepths(void); +static void initNodeDepths(void); +static void initNodes(void); +static void initLinks(int routingModel); +static void validateTreeLayout(void); +static void validateGeneralLayout(void); +static void updateStorageState(int i, int j, int links[], double dt); +static double getStorageOutflow(int node, int j, int links[], double dt); +static double getLinkInflow(int link, double dt); +static void setNewNodeState(int node, double dt); +static void setNewLinkState(int link); +static void updateNodeDepth(int node, double y); +static int steadyflow_execute(int link, double* qin, double* qout, + double tStep); + + +//============================================================================= + +void flowrout_init(int routingModel) +// +// Input: routingModel = routing model code +// Output: none +// Purpose: initializes flow routing system. +// +{ + // --- initialize for dynamic wave routing + if ( routingModel == DW ) + { + // --- check for valid conveyance network layout + validateGeneralLayout(); + dynwave_init(); + + // --- initialize node & link depths if not using a hotstart file + if ( Fhotstart1.mode == NO_FILE ) + { + initNodeDepths(); + initLinkDepths(); + } + } + + // --- validate network layout for kinematic wave routing + else validateTreeLayout(); + + // --- initialize node & link volumes + initNodes(); + initLinks(routingModel); +} + +//============================================================================= + +void flowrout_close(int routingModel) +// +// Input: routingModel = routing method code +// Output: none +// Purpose: closes down routing method used. +// +{ + if ( routingModel == DW ) dynwave_close(); +} + +//============================================================================= + +double flowrout_getRoutingStep(int routingModel, double fixedStep) +// +// Input: routingModel = type of routing method used +// fixedStep = user-assigned max. routing step (sec) +// Output: returns adjusted value of routing time step (sec) +// Purpose: finds variable time step for dynamic wave routing. +// +{ + if ( routingModel == DW ) + { + return dynwave_getRoutingStep(fixedStep); + } + return fixedStep; +} + +//============================================================================= + +int flowrout_execute(int links[], int routingModel, double tStep) +// +// Input: links = array of link indexes in topo-sorted order (per routing model) +// routingModel = type of routing method used +// tStep = routing time step (sec) +// Output: returns number of computational steps taken +// Purpose: routes flow through conveyance network over current time step. +// +{ + int i, j; + int n1; // upstream node of link + double qin; // link inflow (cfs) + double qout; // link outflow (cfs) + double steps; // computational step count + + // --- set overflows to drain any ponded water + if ( ErrorCode ) return 0; + for (j = 0; j < Nobjects[NODE]; j++) + { + Node[j].updated = FALSE; + Node[j].overflow = 0.0; + if ( Node[j].type != STORAGE + && Node[j].newVolume > Node[j].fullVolume ) + { + Node[j].overflow = (Node[j].newVolume - Node[j].fullVolume)/tStep; + } + } + + // --- execute dynamic wave routing if called for + if ( routingModel == DW ) + { + return dynwave_execute(tStep); + } + + // --- otherwise examine each link, moving from upstream to downstream + steps = 0.0; + for (i = 0; i < Nobjects[LINK]; i++) + { + // --- see if upstream node is a storage unit whose state needs updating + j = links[i]; + n1 = Link[j].node1; + if ( Node[n1].type == STORAGE ) updateStorageState(n1, i, links, tStep); + + // --- retrieve inflow at upstream end of link + qin = getLinkInflow(j, tStep); + + // --- route flow through link + if ( routingModel == SF ) + steps += steadyflow_execute(j, &qin, &qout, tStep); + else + steps += kinwave_execute(j, &qin, &qout, tStep); + Link[j].newFlow = qout; + + // adjust outflow at upstream node and inflow at downstream node + Node[ Link[j].node1 ].outflow += qin; + Node[ Link[j].node2 ].inflow += qout; + } + if ( Nobjects[LINK] > 0 ) steps /= Nobjects[LINK]; + + // --- update state of each non-updated node and link + for ( j=0; j 2 ) + { + report_writeErrorMsg(ERR_DIVIDER, Node[j].ID); + } + break; + + // --- outfalls cannot have any outlet links + case OUTFALL: + if ( Node[j].degree > 0 ) + { + report_writeErrorMsg(ERR_OUTFALL, Node[j].ID); + } + break; + + // --- storage nodes can have multiple outlets + case STORAGE: break; + + // --- all other nodes allowed only one outlet link + default: + if ( Node[j].degree > 1 ) + { + report_writeErrorMsg(ERR_MULTI_OUTLET, Node[j].ID); + } + } + } + + // --- check links + for (j=0; j 1 ) + { + report_writeErrorMsg(ERR_DUMMY_LINK, Node[i].ID); + } + } + } + + // --- check each node to see if it qualifies as an outlet node + // (meaning that degree = 0) + for ( i = 0; i < Nobjects[NODE]; i++ ) + { + // --- if node is of type Outfall, check that it has only 1 + // connecting link (which can either be an outflow or inflow link) + if ( Node[i].type == OUTFALL ) + { + if ( Node[i].degree + (int)Node[i].inflow > 1 ) + { + report_writeErrorMsg(ERR_OUTFALL, Node[i].ID); + } + else outletCount++; + } + } + if ( outletCount == 0 ) report_writeErrorMsg(ERR_NO_OUTLETS, ""); + + // --- reset node inflows back to zero + for ( i = 0; i < Nobjects[NODE]; i++ ) + { + if ( Node[i].inflow == 0.0 ) Node[i].degree = -Node[i].degree; + Node[i].inflow = 0.0; + } +} + +//============================================================================= + +void initNodeDepths(void) +// +// Input: none +// Output: none +// Purpose: sets initial depth at nodes for Dynamic Wave flow routing. +// +{ + int i; // link or node index + int n; // node index + double y; // node water depth (ft) + + // --- use Node[].inflow as a temporary accumulator for depth in + // connecting links and Node[].outflow as a temporary counter + // for the number of connecting links + for (i = 0; i < Nobjects[NODE]; i++) + { + Node[i].inflow = 0.0; + Node[i].outflow = 0.0; + } + + // --- total up flow depths in all connecting links into nodes + for (i = 0; i < Nobjects[LINK]; i++) + { + if ( Link[i].newDepth > FUDGE ) y = Link[i].newDepth + Link[i].offset1; + else y = 0.0; + n = Link[i].node1; + Node[n].inflow += y; + Node[n].outflow += 1.0; + n = Link[i].node2; + Node[n].inflow += y; + Node[n].outflow += 1.0; + } + + // --- if no user-supplied depth then set initial depth at non-storage/ + // non-outfall nodes to average of depths in connecting links + for ( i = 0; i < Nobjects[NODE]; i++ ) + { + if ( Node[i].type == OUTFALL ) continue; + if ( Node[i].type == STORAGE ) continue; + if ( Node[i].initDepth > 0.0 ) continue; + if ( Node[i].outflow > 0.0 ) + { + Node[i].newDepth = Node[i].inflow / Node[i].outflow; + } + } + + // --- compute initial depths at all outfall nodes + for ( i = 0; i < Nobjects[LINK]; i++ ) link_setOutfallDepth(i); +} + +//============================================================================= + +void initLinkDepths() +// +// Input: none +// Output: none +// Purpose: sets initial flow depths in conduits under Dyn. Wave routing. +// +{ + int i; // link index + double y, y1, y2; // depths (ft) + + // --- examine each link + for (i = 0; i < Nobjects[LINK]; i++) + { + // --- examine each conduit + if ( Link[i].type == CONDUIT ) + { + // --- skip conduits with user-assigned initial flows + // (their depths have already been set to normal depth) + if ( Link[i].q0 != 0.0 ) continue; + + // --- set depth to average of depths at end nodes + y1 = Node[Link[i].node1].newDepth - Link[i].offset1; + y1 = MAX(y1, 0.0); + y1 = MIN(y1, Link[i].xsect.yFull); + y2 = Node[Link[i].node2].newDepth - Link[i].offset2; + y2 = MAX(y2, 0.0); + y2 = MIN(y2, Link[i].xsect.yFull); + y = 0.5 * (y1 + y2); + y = MAX(y, FUDGE); + Link[i].newDepth = y; + } + } +} + +//============================================================================= + +void initNodes() +// +// Input: none +// Output: none +// Purpose: sets initial inflow/outflow and volume for each node +// +{ + int i; + + for ( i = 0; i < Nobjects[NODE]; i++ ) + { + // --- initialize node inflow and outflow + Node[i].inflow = Node[i].newLatFlow; + Node[i].outflow = 0.0; + + // --- initialize node volume + Node[i].newVolume = 0.0; + if ( AllowPonding && + Node[i].pondedArea > 0.0 && + Node[i].newDepth > Node[i].fullDepth ) + { + Node[i].newVolume = Node[i].fullVolume + + (Node[i].newDepth - Node[i].fullDepth) * + Node[i].pondedArea; + } + else Node[i].newVolume = node_getVolume(i, Node[i].newDepth); + } + + // --- update nodal inflow/outflow at ends of each link + // (needed for Steady Flow & Kin. Wave routing) + for ( i = 0; i < Nobjects[LINK]; i++ ) + { + if ( Link[i].newFlow >= 0.0 ) + { + Node[Link[i].node1].outflow += Link[i].newFlow; + Node[Link[i].node2].inflow += Link[i].newFlow; + } + else + { + Node[Link[i].node1].inflow -= Link[i].newFlow; + Node[Link[i].node2].outflow -= Link[i].newFlow; + } + } +} + +//============================================================================= + +void initLinks(int routingModel) +// +// Input: none +// Output: none +// Purpose: sets initial upstream/downstream conditions in links. +// +{ + int i; // link index + int k; // conduit or pump index + + // --- examine each link + for ( i = 0; i < Nobjects[LINK]; i++ ) + { + if ( routingModel == SF) Link[i].newFlow = 0.0; + + // --- otherwise if link is a conduit + else if ( Link[i].type == CONDUIT ) + { + // --- assign initial flow to both ends of conduit + k = Link[i].subIndex; + Conduit[k].q1 = Link[i].newFlow / Conduit[k].barrels; + Conduit[k].q2 = Conduit[k].q1; + + // --- find areas based on initial flow depth + Conduit[k].a1 = xsect_getAofY(&Link[i].xsect, Link[i].newDepth); + Conduit[k].a2 = Conduit[k].a1; + + // --- compute initial volume from area + { + Link[i].newVolume = Conduit[k].a1 * link_getLength(i) * + Conduit[k].barrels; + } + Link[i].oldVolume = Link[i].newVolume; + } + } +} + +//============================================================================= + +double getLinkInflow(int j, double dt) +// +// Input: j = link index +// dt = routing time step (sec) +// Output: returns link inflow (cfs) +// Purpose: finds flow into upstream end of link at current time step under +// Steady or Kin. Wave routing. +// +{ + int n1 = Link[j].node1; + double q; + if ( Link[j].type == CONDUIT || + Link[j].type == PUMP || + Node[n1].type == STORAGE ) q = link_getInflow(j); + else q = 0.0; + return node_getMaxOutflow(n1, q, dt); +} + +//============================================================================= + +void updateStorageState(int i, int j, int links[], double dt) +// +// Input: i = index of storage node +// j = current position in links array +// links = array of topo-sorted link indexes +// dt = routing time step (sec) +// Output: none +// Purpose: updates depth and volume of a storage node using successive +// approximation with under-relaxation for Steady or Kin. Wave +// routing. +// +{ + int iter; // iteration counter + int stopped; // TRUE when iterations stop + double vFixed; // fixed terms of flow balance eqn. + double v2; // new volume estimate (ft3) + double d1; // initial value of storage depth (ft) + double d2; // updated value of storage depth (ft) + + // --- see if storage node needs updating + if ( Node[i].type != STORAGE ) return; + if ( Node[i].updated ) return; + + // --- compute terms of flow balance eqn. + // v2 = v1 + (inflow - outflow)*dt + // that do not depend on storage depth at end of time step + vFixed = Node[i].oldVolume + + 0.5 * (Node[i].oldNetInflow + Node[i].inflow - + Node[i].outflow) * dt; + d1 = Node[i].newDepth; + + // --- iterate finding outflow (which depends on depth) and subsequent + // new volume and depth until negligible depth change occurs + iter = 1; + stopped = FALSE; + while ( iter < MAXITER && !stopped ) + { + // --- find new volume from flow balance eqn. + v2 = vFixed - 0.5 * getStorageOutflow(i, j, links, dt) * dt; + + // --- limit volume to full volume if no ponding + // and compute overflow rate + v2 = MAX(0.0, v2); + Node[i].overflow = 0.0; + if ( v2 > Node[i].fullVolume ) + { + Node[i].overflow = (v2 - MAX(Node[i].oldVolume, + Node[i].fullVolume)) / dt; + if ( Node[i].overflow < FUDGE ) Node[i].overflow = 0.0; + if ( !AllowPonding || Node[i].pondedArea == 0.0 ) + v2 = Node[i].fullVolume; + } + + // --- update node's volume & depth + Node[i].newVolume = v2; + d2 = node_getDepth(i, v2); + Node[i].newDepth = d2; + + // --- use under-relaxation to estimate new depth value + // and stop if close enough to previous value + d2 = (1.0 - OMEGA)*d1 + OMEGA*d2; + if ( fabs(d2 - d1) <= STOPTOL ) stopped = TRUE; + + // --- update old depth with new value and continue to iterate + d1 = d2; + iter++; + } + + // --- mark node as being updated + Node[i].updated = TRUE; +} + +//============================================================================= + +double getStorageOutflow(int i, int j, int links[], double dt) +// +// Input: i = index of storage node +// j = current position in links array +// links = array of topo-sorted link indexes +// dt = routing time step (sec) +// Output: returns total outflow from storage node (cfs) +// Purpose: computes total flow released from a storage node. +// +{ + int k, m; + double outflow = 0.0; + + for (k = j; k < Nobjects[LINK]; k++) + { + m = links[k]; + if ( Link[m].node1 != i ) break; + outflow += getLinkInflow(m, dt); + } + return outflow; +} + +//============================================================================= + +void setNewNodeState(int j, double dt) +// +// Input: j = node index +// dt = time step (sec) +// Output: none +// Purpose: updates state of node after current time step +// for Steady Flow or Kinematic Wave flow routing. +// +{ + int canPond; // TRUE if ponding can occur at node + double newNetInflow; // inflow - outflow at node (cfs) + + // --- update terminal storage nodes + if ( Node[j].type == STORAGE ) + { + if ( Node[j].updated == FALSE ) + updateStorageState(j, Nobjects[LINK], NULL, dt); + return; + } + + // --- update stored volume + newNetInflow = Node[j].inflow - Node[j].outflow - Node[j].losses; + Node[j].newVolume = Node[j].oldVolume + newNetInflow * dt; + if ( Node[j].newVolume < FUDGE ) Node[j].newVolume = 0.0; + + // --- determine any overflow lost from system + Node[j].overflow = 0.0; + canPond = (AllowPonding && Node[j].pondedArea > 0.0); + if ( Node[j].newVolume > Node[j].fullVolume ) + { + Node[j].overflow = (Node[j].newVolume - MAX(Node[j].oldVolume, + Node[j].fullVolume)) / dt; + if ( Node[j].overflow < FUDGE ) Node[j].overflow = 0.0; + if ( !canPond ) Node[j].newVolume = Node[j].fullVolume; + } + + // --- compute a depth from volume + // (depths at upstream nodes are subsequently adjusted in + // setNewLinkState to reflect depths in connected conduit) + Node[j].newDepth = node_getDepth(j, Node[j].newVolume); +} + +//============================================================================= + +void setNewLinkState(int j) +// +// Input: j = link index +// Output: none +// Purpose: updates state of link after current time step under +// Steady Flow or Kinematic Wave flow routing +// +{ + int k; + double a, y1, y2; + + Link[j].newDepth = 0.0; + Link[j].newVolume = 0.0; + + if ( Link[j].type == CONDUIT ) + { + // --- find avg. depth from entry/exit conditions + k = Link[j].subIndex; + a = 0.5 * (Conduit[k].a1 + Conduit[k].a2); + Link[j].newVolume = a * link_getLength(j) * Conduit[k].barrels; + y1 = xsect_getYofA(&Link[j].xsect, Conduit[k].a1); + y2 = xsect_getYofA(&Link[j].xsect, Conduit[k].a2); + Link[j].newDepth = 0.5 * (y1 + y2); + + // --- update depths at end nodes + updateNodeDepth(Link[j].node1, y1 + Link[j].offset1); + updateNodeDepth(Link[j].node2, y2 + Link[j].offset2); + + // --- check if capacity limited + if ( Conduit[k].a1 >= Link[j].xsect.aFull ) + { + Conduit[k].capacityLimited = TRUE; + Conduit[k].fullState = ALL_FULL; + } + else + { + Conduit[k].capacityLimited = FALSE; + Conduit[k].fullState = 0; + } + } +} + +//============================================================================= + +void updateNodeDepth(int i, double y) +// +// Input: i = node index +// y = flow depth (ft) +// Output: none +// Purpose: updates water depth at a node with a possibly higher value. +// +{ + // --- storage nodes were updated elsewhere + if ( Node[i].type == STORAGE ) return; + + // --- if non-outfall node is flooded, then use full depth + if ( Node[i].type != OUTFALL && Node[i].degree > 0 && + Node[i].overflow > 0.0 ) y = Node[i].fullDepth; + + // --- if current new depth below y + if ( Node[i].newDepth < y ) + { + // --- update new depth + Node[i].newDepth = y; + + // --- depth cannot exceed full depth (if value exists) + if ( Node[i].fullDepth > 0.0 && y > Node[i].fullDepth ) + { + Node[i].newDepth = Node[i].fullDepth; + } + } +} + +//============================================================================= + +int steadyflow_execute(int j, double* qin, double* qout, double tStep) +// +// Input: j = link index +// qin = inflow to link (cfs) +// tStep = time step (sec) +// Output: qin = adjusted inflow to link (limited by flow capacity) (cfs) +// qout = link's outflow (cfs) +// returns 1 if successful +// Purpose: performs steady flow routing through a single link. +// +{ + int k; + double s; + double q; + + // --- use Manning eqn. to compute flow area for conduits + if ( Link[j].type == CONDUIT ) + { + k = Link[j].subIndex; + q = (*qin) / Conduit[k].barrels; + if ( Link[j].xsect.type == DUMMY ) Conduit[k].a1 = 0.0; + else + { + // --- adjust flow for evap and infil losses + q -= link_getLossRate(j, q); + + // --- flow can't exceed full flow + if ( q > Link[j].qFull ) + { + q = Link[j].qFull; + Conduit[k].a1 = Link[j].xsect.aFull; + (*qin) = q * Conduit[k].barrels; + } + + // --- infer flow area from flow rate + else + { + s = q / Conduit[k].beta; + Conduit[k].a1 = xsect_getAofS(&Link[j].xsect, s); + } + } + Conduit[k].a2 = Conduit[k].a1; + + Conduit[k].q1Old = Conduit[k].q1; + Conduit[k].q2Old = Conduit[k].q2; + + Conduit[k].q1 = q; + Conduit[k].q2 = q; + (*qout) = q * Conduit[k].barrels; + } + else (*qout) = (*qin); + return 1; +} + +//============================================================================= diff --git a/src/forcmain.c b/src/forcmain.c new file mode 100644 index 000000000..a6314bbce --- /dev/null +++ b/src/forcmain.c @@ -0,0 +1,157 @@ +//----------------------------------------------------------------------------- +// forcemain.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Special Non-Manning Force Main functions +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include "headers.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +static const double VISCOS = 1.1E-5; // Kinematic viscosity of water + // @ 20 deg C (sq ft/sec) + +//----------------------------------------------------------------------------- +// External functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// forcemain_getEquivN +// forcemain_getRoughFactor +// forcemain_getFricSlope + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static double forcemain_getFricFactor(double e, double hrad, double re); +static double forcemain_getReynolds(double v, double hrad); + +//============================================================================= + +double forcemain_getEquivN(int j, int k) +// +// Input: j = link index +// k = conduit index +// Output: returns an equivalent Manning's n for a force main +// Purpose: computes a Mannng's n that results in the same normal flow +// value for a force main flowing full under fully turbulent +// conditions using either the Hazen-Williams or Dary-Weisbach +// flow equations. +// +{ + TXsect xsect = Link[j].xsect; + double f; + double d = xsect.yFull; + switch ( ForceMainEqn ) + { + case H_W: + return 1.067 / xsect.rBot * pow(d/Conduit[k].slope, 0.04); + case D_W: + f = forcemain_getFricFactor(xsect.rBot, d/4.0, 1.0e12); + return sqrt(f/185.0) * pow(d, (1./6.)); + } + return Conduit[k].roughness; +} + +//============================================================================= + +double forcemain_getRoughFactor(int j, double lengthFactor) +// +// Input: j = link index +// lengthFactor = factor by which a pipe will be artifically lengthened +// Output: returns a roughness adjustment factor for a force main +// Purpose: computes an adjustment factor for a force main that compensates for +// any artificial lengthening the pipe may have received. +// +{ + TXsect xsect = Link[j].xsect; + double r; + switch ( ForceMainEqn ) + { + case H_W: + r = 1.318*xsect.rBot*pow(lengthFactor, 0.54); + return GRAVITY / pow(r, 1.852); + case D_W: + return 1.0/8.0/lengthFactor; + } + return 0.0; +} + +//============================================================================= + +double forcemain_getFricSlope(int j, double v, double hrad) +// +// Input: j = link index +// v = flow velocity (ft/sec) +// hrad = hydraulic radius (ft) +// Output: returns a force main pipe's friction slope +// Purpose: computes the headloss per unit length used in dynamic wave +// flow routing for a pressurized force main using either the +// Hazen-Williams or Darcy-Weisbach flow equations. +// Note: the pipe's roughness factor was saved in xsect.sBot in +// conduit_validate() in LINK.C. +// +{ + double re, f; + TXsect xsect = Link[j].xsect; + switch ( ForceMainEqn ) + { + case H_W: + return xsect.sBot * pow(v, 0.852) / pow(hrad, 1.1667); + case D_W: + re = forcemain_getReynolds(v, hrad); + f = forcemain_getFricFactor(xsect.rBot, hrad, re); + return f * xsect.sBot * v / hrad; + } + return 0.0; +} + +//============================================================================= + +double forcemain_getReynolds(double v, double hrad) +// +// Input: v = flow velocity (ft/sec) +// hrad = hydraulic radius (ft) +// Output: returns a flow's Reynolds Number +// Purpose: computes a flow's Reynolds Number +// +{ + return 4.0 * hrad * v / VISCOS; +} + +//============================================================================= + +double forcemain_getFricFactor(double e, double hrad, double re) +// +// Input: e = roughness height (ft) +// hrad = hydraulic radius (ft) +// re = Reynolds number +// Output: returns a Darcy-Weisbach friction factor +// Purpose: computes the Darcy-Weisbach friction factor for a force main +// using the Swamee and Jain approximation to the Colebrook-White +// equation. +// +{ + double f; + if ( re < 10.0 ) re = 10.0; + if ( re <= 2000.0 ) f = 64.0 / re; + else if ( re < 4000.0 ) + { + f = forcemain_getFricFactor(e, hrad, 4000.0); + f = 0.032 + (f - 0.032) * ( re - 2000.0) / 2000.0; + } + else + { + f = e/3.7/(4.0*hrad); + if ( re < 1.0e10 ) f += 5.74/pow(re, 0.9); + f = log10(f); + f = 0.25 / f / f; + } + return f; +} diff --git a/src/funcs.h b/src/funcs.h new file mode 100644 index 000000000..6f9782723 --- /dev/null +++ b/src/funcs.h @@ -0,0 +1,547 @@ +//----------------------------------------------------------------------------- +// funcs.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// M. Tryby (EPA) +// +// Global interfacing functions. +// +// Update History +// ============== +// Build 5.1.007: +// - climate_readAdjustments() added. +// Build 5.1.008: +// - Function list was re-ordered and blank lines added for readability. +// - Pollutant buildup/washoff functions for the new surfqual.c module added. +// - Several other functions added, re-named or have modified arguments. +// Build 5.1.010: +// - New roadway_getInflow() function added. +// Build 5.1.013: +// - Additional arguments added to function stats_updateSubcatchStats. +// Build 5.1.014: +// - Arguments to link_getLossRate function changed. +// Build 5.2.0: +// - Support added for Streets and Inlets. +// - Support added for reporting most frequent non-converging links. +// - Support added for named variables & math expressions in control rules. +// - Support added for tracking a gage's prior n-hour rainfall total. +// - Refactored external inflow code. +//----------------------------------------------------------------------------- + +#ifndef FUNCS_H +#define FUNCS_H + +//----------------------------------------------------------------------------- +// Project Methods +//----------------------------------------------------------------------------- +void project_open(const char *f1, const char *f2, const char *f3); +void project_close(void); + +void project_readInput(void); +int project_readOption(char* s1, char* s2); +void project_validate(void); +int project_init(void); + +int project_addObject(int type, char* id, int n); +int project_findObject(int type, const char* id); +char* project_findID(int type, char* id); + +double** project_createMatrix(int nrows, int ncols); +void project_freeMatrix(double** m); + +//----------------------------------------------------------------------------- +// Input Reader Methods +//----------------------------------------------------------------------------- +int input_countObjects(void); +int input_readData(void); + +//----------------------------------------------------------------------------- +// Report Writer Methods +//----------------------------------------------------------------------------- +int report_readOptions(char* tok[], int ntoks); + +void report_writeLine(const char* line); +void report_writeSysTime(void); +void report_writeLogo(void); +void report_writeTitle(void); +void report_writeOptions(void); +void report_writeReport(void); + +void report_writeRainStats(int gage, TRainStats* rainStats); +void report_writeRdiiStats(double totalRain, double totalRdii); + +void report_writeControlActionsHeading(void); +void report_writeControlAction(DateTime aDate, char* linkID, double value, + char* ruleID); + +void report_writeRunoffError(TRunoffTotals* totals, double area); +void report_writeLoadingError(TLoadingTotals* totals); +void report_writeGwaterError(TGwaterTotals* totals, double area); +void report_writeFlowError(TRoutingTotals* totals); +void report_writeQualError(TRoutingTotals* totals); + +void report_writeMaxStats(TMaxStats massBalErrs[], TMaxStats CourantCrit[], + int nMaxStats); +void report_writeMaxFlowTurns(TMaxStats flowTurns[], int nMaxStats); +void report_writeNonconvergedStats(TMaxStats maxNonconverged[], + int nMaxStats); +void report_writeTimeStepStats(TTimeStepStats* timeStepStats); + +void report_writeErrorMsg(int code, char* msg); +void report_writeErrorCode(void); +void report_writeInputErrorMsg(int k, int sect, char* line, long lineCount); +void report_writeWarningMsg(char* msg, char* id); +void report_writeTseriesErrorMsg(int code, TTable *tseries); + +void inputrpt_writeInput(void); +void statsrpt_writeReport(void); + +//----------------------------------------------------------------------------- +// Temperature/Evaporation Methods +//----------------------------------------------------------------------------- +int climate_readParams(char* tok[], int ntoks); +int climate_readEvapParams(char* tok[], int ntoks); +int climate_readAdjustments(char* tok[], int ntoks); +void climate_validate(void); +void climate_openFile(void); +void climate_initState(void); +void climate_setState(DateTime aDate); +DateTime climate_getNextEvapDate(void); + +//----------------------------------------------------------------------------- +// Rainfall Processing Methods +//----------------------------------------------------------------------------- +void rain_open(void); +void rain_close(void); + +//----------------------------------------------------------------------------- +// Snowmelt Processing Methods +//----------------------------------------------------------------------------- +int snow_readMeltParams(char* tok[], int ntoks); +int snow_createSnowpack(int subcacth, int snowIndex); + +void snow_validateSnowmelt(int snowIndex); +void snow_initSnowpack(int subcatch); +void snow_initSnowmelt(int snowIndex); + +void snow_getState(int subcatch, int subArea, double x[]); +void snow_setState(int subcatch, int subArea, double x[]); + +void snow_setMeltCoeffs(int snowIndex, double season); +void snow_plowSnow(int subcatch, double tStep); +double snow_getSnowMelt(int subcatch, double rainfall, double snowfall, + double tStep, double netPrecip[]); +double snow_getSnowCover(int subcatch); + +//----------------------------------------------------------------------------- +// Runoff Analyzer Methods +//----------------------------------------------------------------------------- +int runoff_open(void); +void runoff_execute(void); +void runoff_close(void); + +//----------------------------------------------------------------------------- +// Conveyance System Routing Methods +//----------------------------------------------------------------------------- +int routing_open(void); +double routing_getRoutingStep(int routingModel, double fixedStep); +void routing_execute(int routingModel, double routingStep); +void routing_close(int routingModel); + +//----------------------------------------------------------------------------- +// Output Filer Methods +//----------------------------------------------------------------------------- +int output_open(void); +void output_end(void); +void output_close(void); +void output_saveResults(double reportTime); +void output_updateAvgResults(void); +void output_readDateTime(long period, DateTime *aDate); +void output_readSubcatchResults(long period, int index); +void output_readNodeResults(int long, int index); +void output_readLinkResults(int long, int index); + +//----------------------------------------------------------------------------- +// Groundwater Methods +//----------------------------------------------------------------------------- +int gwater_readAquiferParams(int aquifer, char* tok[], int ntoks); +int gwater_readGroundwaterParams(char* tok[], int ntoks); +int gwater_readFlowExpression(char* tok[], int ntoks); +void gwater_deleteFlowExpression(int subcatch); + +void gwater_validateAquifer(int aquifer); +void gwater_validate(int subcatch); + +void gwater_initState(int subcatch); +void gwater_getState(int subcatch, double x[]); +void gwater_setState(int subcatch, double x[]); + +void gwater_getGroundwater(int subcatch, double evap, double infil, + double tStep); +double gwater_getVolume(int subcatch); + +//----------------------------------------------------------------------------- +// RDII Methods +//----------------------------------------------------------------------------- +int rdii_readRdiiInflow(char* tok[], int ntoks); +void rdii_deleteRdiiInflow(int node); +void rdii_initUnitHyd(int unitHyd); +int rdii_readUnitHydParams(char* tok[], int ntoks); +void rdii_openRdii(void); +void rdii_closeRdii(void); +int rdii_getNumRdiiFlows(DateTime aDate); +void rdii_getRdiiFlow(int index, int* node, double* q); + +//----------------------------------------------------------------------------- +// Landuse Methods +//----------------------------------------------------------------------------- +int landuse_readParams(int landuse, char* tok[], int ntoks); +int landuse_readPollutParams(int pollut, char* tok[], int ntoks); +int landuse_readBuildupParams(char* tok[], int ntoks); +int landuse_readWashoffParams(char* tok[], int ntoks); + +void landuse_getInitBuildup(TLandFactor* landFactor, double* initBuildup, + double area, double curb); +double landuse_getBuildup(int landuse, int pollut, double area, double curb, + double buildup, double tStep); + +double landuse_getWashoffLoad(int landuse, int p, double area, + TLandFactor landFactor[], double runoff, double vOutflow); +double landuse_getAvgBmpEffic(int j, int p); +double landuse_getCoPollutLoad(int p, double washoff[]); + +//----------------------------------------------------------------------------- +// Flow/Quality Routing Methods +//----------------------------------------------------------------------------- +void flowrout_init(int routingModel); +void flowrout_close(int routingModel); +double flowrout_getRoutingStep(int routingModel, double fixedStep); +int flowrout_execute(int links[], int routingModel, double tStep); + +void toposort_sortLinks(int links[]); +int kinwave_execute(int link, double* qin, double* qout, double tStep); + +void dynwave_validate(void); +void dynwave_init(void); +void dynwave_close(void); +double dynwave_getRoutingStep(double fixedStep); +int dynwave_execute(double tStep); +void dwflow_findConduitFlow(int j, int steps, double omega, double dt); + +void qualrout_init(void); +void qualrout_execute(double tStep); + +//----------------------------------------------------------------------------- +// Treatment Methods +//----------------------------------------------------------------------------- +int treatmnt_open(void); +void treatmnt_close(void); +int treatmnt_readExpression(char* tok[], int ntoks); +void treatmnt_delete(int node); +void treatmnt_treat(int node, double q, double v, double tStep); +void treatmnt_setInflow(double qIn, double wIn[]); + +//----------------------------------------------------------------------------- +// Mass Balance Methods +//----------------------------------------------------------------------------- +int massbal_open(void); +void massbal_close(void); +void massbal_report(void); + +void massbal_updateRunoffTotals(int type, double v); +void massbal_updateLoadingTotals(int type, int pollut, double w); +void massbal_updateGwaterTotals(double vInfil, double vUpperEvap, + double vLowerEvap, double vLowerPerc, double vGwater); +void massbal_updateRoutingTotals(double tStep); + + +void massbal_initTimeStepTotals(void); +void massbal_addInflowFlow(int type, double q); +void massbal_addInflowQual(int type, int pollut, double w); +void massbal_addOutflowFlow(double q, int isFlooded); +void massbal_addOutflowQual(int pollut, double mass, int isFlooded); +void massbal_addNodeLosses(double evapLoss, double infilLoss); +void massbal_addLinkLosses(double evapLoss, double infilLoss); +void massbal_addReactedMass(int pollut, double mass); +void massbal_addSeepageLoss(int pollut, double seepLoss); +void massbal_addToFinalStorage(int pollut, double mass); +double massbal_getStepFlowError(void); +double massbal_getRunoffError(void); +double massbal_getFlowError(void); + +//----------------------------------------------------------------------------- +// Simulation Statistics Methods +//----------------------------------------------------------------------------- +int stats_open(void); +void stats_close(void); +void stats_report(void); + +void stats_updateCriticalTimeCount(int node, int link); +void stats_updateFlowStats(double tStep, DateTime aDate); +void stats_updateTimeStepStats(double tStep, int trialsCount, int steadyState); + +void stats_updateSubcatchStats(int subcatch, double rainVol, + double runonVol, double evapVol, double infilVol, + double impervVol, double pervVol, double runoffVol, double runoff); +void stats_updateGwaterStats(int j, double infil, double evap, + double latFlow, double deepFlow, double theta, double waterTable, + double tStep); +void stats_updateMaxRunoff(void); +void stats_updateMaxNodeDepth(int node, double depth); +void stats_updateConvergenceStats(int node, int converged); + + +//----------------------------------------------------------------------------- +// Raingage Methods +//----------------------------------------------------------------------------- +int gage_readParams(int gage, char* tok[], int ntoks); +void gage_validate(int gage); +void gage_initState(int gage); +void gage_setState(int gage, DateTime aDate); +double gage_getPrecip(int gage, double *rainfall, double *snowfall); +void gage_setReportRainfall(int gage, DateTime aDate); +DateTime gage_getNextRainDate(int gage, DateTime aDate); +void gage_updatePastRain(int j, int tStep); +double gage_getPastRain(int gage, int hrs); + +//----------------------------------------------------------------------------- +// Subcatchment Methods +//----------------------------------------------------------------------------- +int subcatch_readParams(int subcatch, char* tok[], int ntoks); +int subcatch_readSubareaParams(char* tok[], int ntoks); +int subcatch_readLanduseParams(char* tok[], int ntoks); +int subcatch_readInitBuildup(char* tok[], int ntoks); + +void subcatch_validate(int subcatch); +void subcatch_initState(int subcatch); +void subcatch_setOldState(int subcatch); + +double subcatch_getFracPerv(int subcatch); +double subcatch_getStorage(int subcatch); +double subcatch_getDepth(int subcatch); + +void subcatch_getRunon(int subcatch); +void subcatch_addRunonFlow(int subcatch, double flow); +double subcatch_getRunoff(int subcatch, double tStep); + +double subcatch_getWtdOutflow(int subcatch, double wt); +void subcatch_getResults(int subcatch, double wt, float x[]); + +//----------------------------------------------------------------------------- +// Surface Pollutant Buildup/Washoff Methods +//----------------------------------------------------------------------------- +void surfqual_initState(int subcatch); +void surfqual_getWashoff(int subcatch, double runoff, double tStep); +void surfqual_getBuildup(int subcatch, double tStep); +void surfqual_sweepBuildup(int subcatch, DateTime aDate); +double surfqual_getWtdWashoff(int subcatch, int pollut, double wt); + +//----------------------------------------------------------------------------- +// Conveyance System Node Methods +//----------------------------------------------------------------------------- +int node_readParams(int node, int type, int subIndex, char* tok[], int ntoks); +void node_validate(int node); + +void node_initState(int node); +void node_initFlows(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); + +double node_getSurfArea(int node, double depth); +double node_getDepth(int node, double volume); +double node_getVolume(int node, double depth); +double node_getPondedArea(int node, double depth); + +double node_getOutflow(int node, int link); +double node_getLosses(int node, double tStep); +double node_getMaxOutflow(int node, double q, double tStep); +double node_getSystemOutflow(int node, int *isFlooded); +void node_getResults(int node, double wt, float x[]); + +//----------------------------------------------------------------------------- +// Conveyance System Inflow Methods +//----------------------------------------------------------------------------- +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); + +void inflow_initDwfInflow(TDwfInflow* inflow); +void inflow_initDwfPattern(int pattern); + +double inflow_getExtInflow(TExtInflow* inflow, DateTime aDate); +double inflow_getDwfInflow(TDwfInflow* inflow, int m, int d, int h); + +void inflow_deleteExtInflows(int node); +void inflow_deleteDwfInflows(int node); + +//----------------------------------------------------------------------------- +// Routing Interface File Methods +//----------------------------------------------------------------------------- +int iface_readFileParams(char* tok[], int ntoks); +void iface_openRoutingFiles(void); +void iface_closeRoutingFiles(void); +int iface_getNumIfaceNodes(DateTime aDate); +int iface_getIfaceNode(int index); +double iface_getIfaceFlow(int index); +double iface_getIfaceQual(int index, int pollut); +void iface_saveOutletResults(DateTime reportDate, FILE* file); + +//----------------------------------------------------------------------------- +// Hot Start File Methods +//----------------------------------------------------------------------------- +int hotstart_open(void); +void hotstart_close(void); + +//----------------------------------------------------------------------------- +// Conveyance System Link Methods +//----------------------------------------------------------------------------- +int link_readParams(int link, int type, int subIndex, char* tok[], int ntoks); +int link_readXsectParams(char* tok[], int ntoks); +int link_readLossParams(char* tok[], int ntoks); + +void link_validate(int link); +void link_initState(int link); +void link_setOldHydState(int link); +void link_setOldQualState(int link); + +void link_setTargetSetting(int j); +void link_setSetting(int j, double tstep); +int link_setFlapGate(int link, int n1, int n2, double q); + +double link_getInflow(int link); +void link_setOutfallDepth(int link); +double link_getLength(int link); +double link_getYcrit(int link, double q); +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); +char link_getFullState(double a1, double a2, double aFull); + +void link_getResults(int link, double wt, float x[]); + +//----------------------------------------------------------------------------- +// Link Cross-Section Methods +//----------------------------------------------------------------------------- +int xsect_isOpen(int type); +int xsect_setParams(TXsect *xsect, int type, double p[], double ucf); +void xsect_setIrregXsectParams(TXsect *xsect); +void xsect_setCustomXsectParams(TXsect *xsect); +void xsect_setStreetXsectParams(TXsect *xsect); +double xsect_getAmax(TXsect* xsect); + +double xsect_getSofA(TXsect* xsect, double area); +double xsect_getYofA(TXsect* xsect, double area); +double xsect_getRofA(TXsect* xsect, double area); +double xsect_getAofS(TXsect* xsect, double sFactor); +double xsect_getdSdA(TXsect* xsect, double area); +double xsect_getAofY(TXsect* xsect, double y); +double xsect_getRofY(TXsect* xsect, double y); +double xsect_getWofY(TXsect* xsect, double y); +double xsect_getYcrit(TXsect* xsect, double q); + +//----------------------------------------------------------------------------- +// Culvert/Roadway Methods +//----------------------------------------------------------------------------- +double culvert_getInflow(int link, double q, double h); +double roadway_getInflow(int link, double dir, double hcrest, double h1, + double h2); + +//----------------------------------------------------------------------------- +// Force Main Methods +//----------------------------------------------------------------------------- +double forcemain_getEquivN(int j, int k); +double forcemain_getRoughFactor(int j, double lengthFactor); +double forcemain_getFricSlope(int j, double v, double hrad); + +//----------------------------------------------------------------------------- +// Cross-Section Transect Methods +//----------------------------------------------------------------------------- +int transect_create(int n); +void transect_delete(void); +int transect_readParams(int* count, char* tok[], int ntoks); +void transect_validate(int j); +void transect_createStreetTransect(TStreet* street); + +//----------------------------------------------------------------------------- +// Street Cross-Section Methods +//----------------------------------------------------------------------------- +int street_create(int nStreets); +void street_delete(); +int street_readParams(char* tok[], int ntoks); +double street_getExtentFilled(int link); + +//----------------------------------------------------------------------------- +// Custom Shape Cross-Section Methods +//----------------------------------------------------------------------------- +int shape_validate(TShape *shape, TTable *curve); + +//----------------------------------------------------------------------------- +// Control Rule Methods +//----------------------------------------------------------------------------- +int controls_create(int n); +void controls_delete(void); +void controls_init(void); +void controls_addToCount(char* s); +int controls_addVariable(char* tok[], int ntoks); +int controls_addExpression(char* tok[], int ntoks); +int controls_addRuleClause(int rule, int keyword, char* Tok[], int nTokens); +int controls_evaluate(DateTime currentTime, DateTime elapsedTime, + double tStep); + +//----------------------------------------------------------------------------- +// Table & Time Series Methods +//----------------------------------------------------------------------------- +int table_readCurve(char* tok[], int ntoks); +int table_readTimeseries(char* tok[], int ntoks); + +int table_addEntry(TTable* table, double x, double y); +int table_getFirstEntry(TTable* table, double* x, double* y); +int table_getNextEntry(TTable* table, double* x, double* y); +void table_deleteEntries(TTable* table); + +void table_init(TTable* table); +int table_validate(TTable* table); + +double table_lookup(TTable* table, double x); +double table_lookupEx(TTable* table, double x); +double table_intervalLookup(TTable* table, double x); +double table_inverseLookup(TTable* table, double y); + +double table_getSlope(TTable *table, double x); +double table_getMaxY(TTable *table, double x); +double table_getStorageVolume(TTable* table, double x); +double table_getStorageDepth(TTable* table, double v); + +void table_tseriesInit(TTable *table); +double table_tseriesLookup(TTable* table, double t, char extend); + +//----------------------------------------------------------------------------- +// Utility Methods +//----------------------------------------------------------------------------- +double UCF(int quantity); // units conversion factor +int getInt(char *s, int *y); // get integer from string +int getFloat(char *s, float *y); // get float from string +int getDouble(char *s, double *y); // get double from string +char* getTempFileName(char *s); // get temporary file name +int findmatch(char *s, char *keyword[]); // search for matching keyword +int match(char *str, char *substr); // true if substr matches part of str +int strcomp(const char *s1, const char *s2); // case insensitive string compare +size_t sstrncpy(char *dest, const char *src, + size_t n); // safe string copy +size_t sstrcat(char* dest, const char* src, + size_t destsize); // safe string concatenation +void writecon(const 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); +char* addAbsolutePath(char *fname); // add full path to a file name + +#endif //FUNCS_H diff --git a/src/gage.c b/src/gage.c new file mode 100644 index 000000000..031cc017c --- /dev/null +++ b/src/gage.c @@ -0,0 +1,705 @@ +//----------------------------------------------------------------------------- +// gage.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Rain gage functions. +// +// Update History +// ============== +// Build 5.1.007: +// - Support for monthly rainfall adjustments added. +// Build 5.1.013: +// - Validation no longer performed on unused gages. +// Build 5.2.0: +// - Support added for tracking a gage's prior n-hour rainfall total. +// - Support added for relative file names. +// - Support added for setting rainfall through API call. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include "headers.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +const double OneSecond = 1.1574074e-5; + +//----------------------------------------------------------------------------- +// External functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// gage_readParams (called by input_readLine) +// gage_validate (called by project_validate) +// gage_initState (called by project_init) +// gage_setState (called by runoff_execute & getRainfall in rdii.c) +// gage_getPrecip (called by subcatch_getRunoff) +// gage_getNextRainDate (called by runoff_getTimeStep) +// gage_updatePastRain (called by runoff_execute) +// gage_getPastRain (called by getRainValue in controls.c) +// gage_setReportRainfall (called by output_saveSubcatchResults) + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static int readGageSeriesFormat(char* tok[], int ntoks, double x[]); +static int readGageFileFormat(char* tok[], int ntoks, double x[]); +static int getFirstRainfall(int gage); +static int getNextRainfall(int gage); +static double convertRainfall(int gage, double rain); +static void initPastRain(int gage); + +//============================================================================= + +int gage_readParams(int j, char* tok[], int ntoks) +// +// Input: j = rain gage index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads rain gage parameters from a line of input data +// +// Data formats are: +// Name RainType RecdFreq SCF TIMESERIES SeriesName +// Name RainType RecdFreq SCF FILE FileName Station Units StartDate +// +{ + int k, err; + char *id; + char fname[MAXFNAME+1]; + char staID[MAXMSG+1]; + double x[7]; + + // --- check that gage exists + if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); + id = project_findID(GAGE, tok[0]); + if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); + + // --- assign default parameter values + x[0] = -1.0; // No time series index + x[1] = 1.0; // Rain type is volume + x[2] = 3600.0; // Recording freq. is 3600 sec + x[3] = 1.0; // Snow catch deficiency factor + x[4] = NO_DATE; // Default is no start/end date + x[5] = NO_DATE; + x[6] = 0.0; // US units + fname[0] = '\0'; + staID[0] = '\0'; + + if ( ntoks < 5 ) return error_setInpError(ERR_ITEMS, ""); + k = findmatch(tok[4], GageDataWords); + if ( k == RAIN_TSERIES ) + { + err = readGageSeriesFormat(tok, ntoks, x); + } + else if ( k == RAIN_FILE ) + { + if ( ntoks < 8 ) return error_setInpError(ERR_ITEMS, ""); + sstrncpy(fname, tok[5], MAXFNAME); + sstrncpy(staID, tok[6], MAXMSG); + err = readGageFileFormat(tok, ntoks, x); + } + else return error_setInpError(ERR_KEYWORD, tok[4]); + + // --- save parameters to rain gage object + if ( err > 0 ) return err; + Gage[j].ID = id; + Gage[j].tSeries = (int)x[0]; + Gage[j].rainType = (int)x[1]; + Gage[j].rainInterval = (int)x[2]; + Gage[j].snowFactor = x[3]; + Gage[j].rainUnits = (int)x[6]; + if ( Gage[j].tSeries >= 0 ) Gage[j].dataSource = RAIN_TSERIES; + else Gage[j].dataSource = RAIN_FILE; + if ( Gage[j].dataSource == RAIN_FILE ) + { + sstrncpy(Gage[j].fname, addAbsolutePath(fname), MAXFNAME); + sstrncpy(Gage[j].staID, staID, MAXMSG); + Gage[j].startFileDate = x[4]; + Gage[j].endFileDate = x[5]; + } + Gage[j].unitsFactor = 1.0; + Gage[j].coGage = -1; + Gage[j].isUsed = FALSE; + return 0; +} + +//============================================================================= + +int readGageSeriesFormat(char* tok[], int ntoks, double x[]) +{ + int m, ts; + DateTime aTime; + + if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); + + // --- determine type of rain data + m = findmatch(tok[1], RainTypeWords); + if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); + x[1] = (double)m; + + // --- get data time interval & convert to seconds + if ( getDouble(tok[2], &x[2]) ) x[2] = floor(x[2]*3600 + 0.5); + else if ( datetime_strToTime(tok[2], &aTime) ) + { + x[2] = floor(aTime*SECperDAY + 0.5); + } + else return error_setInpError(ERR_DATETIME, tok[2]); + if ( x[2] <= 0.0 ) return error_setInpError(ERR_DATETIME, tok[2]); + + // --- get snow catch deficiency factor + if ( !getDouble(tok[3], &x[3]) ) + return error_setInpError(ERR_DATETIME, tok[3]);; + + // --- get time series index + ts = project_findObject(TSERIES, tok[5]); + if ( ts < 0 ) return error_setInpError(ERR_NAME, tok[5]); + x[0] = (double)ts; + sstrncpy(tok[2], "", 0); + return 0; +} + +//============================================================================= + +int readGageFileFormat(char* tok[], int ntoks, double x[]) +{ + int m, u; + DateTime aDate; + DateTime aTime; + + // --- determine type of rain data + m = findmatch(tok[1], RainTypeWords); + if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); + x[1] = (double)m; + + // --- get data time interval & convert to seconds + if ( getDouble(tok[2], &x[2]) ) x[2] *= 3600; + else if ( datetime_strToTime(tok[2], &aTime) ) + { + x[2] = floor(aTime*SECperDAY + 0.5); + } + else return error_setInpError(ERR_DATETIME, tok[2]); + if ( x[2] <= 0.0 ) return error_setInpError(ERR_DATETIME, tok[2]); + + // --- get snow catch deficiency factor + if ( !getDouble(tok[3], &x[3]) ) + return error_setInpError(ERR_NUMBER, tok[3]); + + // --- get rain depth units + u = findmatch(tok[7], RainUnitsWords); + if ( u < 0 ) return error_setInpError(ERR_KEYWORD, tok[7]); + x[6] = (double)u; + + // --- get start date (if present) + if ( ntoks > 8 && *tok[8] != '*') + { + if ( !datetime_strToDate(tok[8], &aDate) ) + return error_setInpError(ERR_DATETIME, tok[8]); + x[4] = (float) aDate; + } + return 0; +} + +//============================================================================= + +void gage_validate(int j) +// +// Input: j = rain gage index +// Output: none +// Purpose: checks for valid rain gage parameters +// +// NOTE: assumes that any time series used by a rain gage has been +// previously validated. +// +{ + int i, k; + int gageInterval; + + // --- for gage with time series data: + if ( Gage[j].dataSource == RAIN_TSERIES ) + { + // --- no validation for an unused gage + if ( !Gage[j].isUsed ) return; + + // --- see if gage uses same time series as another gage + k = Gage[j].tSeries; + for (i=0; i= 0 ) + { + report_writeErrorMsg(ERR_RAIN_GAGE_TSERIES, Gage[j].ID); + } + gageInterval = (int)(floor(Tseries[k].dxMin*SECperDAY + 0.5)); + if ( gageInterval > 0 && Gage[j].rainInterval > gageInterval ) + { + report_writeErrorMsg(ERR_RAIN_GAGE_INTERVAL, Gage[j].ID); + } + if ( Gage[j].rainInterval < gageInterval ) + { + report_writeWarningMsg(WARN09, Gage[j].ID); + } + if ( Gage[j].rainInterval < WetStep ) + { + report_writeWarningMsg(WARN01, Gage[j].ID); + WetStep = Gage[j].rainInterval; + } + } +} + +//============================================================================= + +void gage_initState(int j) +// +// Input: j = rain gage index +// Output: none +// Purpose: initializes state of rain gage. +// +{ + // --- initialize actual and reported rainfall + Gage[j].rainfall = 0.0; + Gage[j].apiRainfall = MISSING; + Gage[j].reportRainfall = 0.0; + if ( IgnoreRainfall ) return; + + // --- for gage with file data: + if ( Gage[j].dataSource == RAIN_FILE ) + { + // --- set current file position to start of period of record + Gage[j].currentFilePos = Gage[j].startFilePos; + + // --- assign units conversion factor + // (rain depths on interface file are in inches) + if ( UnitSystem == SI ) Gage[j].unitsFactor = MMperINCH; + } + + // --- get first & next rainfall values + if ( getFirstRainfall(j) ) + { + // --- find date at end of starting rain interval + Gage[j].endDate = datetime_addSeconds( + Gage[j].startDate, Gage[j].rainInterval); + + // --- if rainfall record begins after start of simulation, + if ( Gage[j].startDate > StartDateTime ) + { + // --- make next rainfall date the start of the rain record + Gage[j].nextDate = Gage[j].startDate; + Gage[j].nextRainfall = Gage[j].rainfall; + + // --- make start of current rain interval the simulation start + Gage[j].startDate = StartDateTime; + Gage[j].endDate = Gage[j].nextDate; + Gage[j].rainfall = 0.0; + } + + // --- otherwise find next recorded rainfall + else if ( !getNextRainfall(j) ) Gage[j].nextDate = NO_DATE; + } + else Gage[j].startDate = NO_DATE; + initPastRain(j); +} + +//============================================================================= + +void gage_setState(int j, DateTime t) +// +// Input: j = rain gage index +// t = a calendar date/time +// Output: none +// Purpose: updates state of rain gage for specified date. +// +{ + // --- return if gage not used by any subcatchment + if ( Gage[j].isUsed == FALSE ) return; + + // --- set rainfall to zero if disabled + if ( IgnoreRainfall ) + { + Gage[j].rainfall = 0.0; + return; + } + + // --- use rainfall from co-gage (gage with lower index that uses + // same rainfall time series or file) if it exists + if ( Gage[j].coGage >= 0) + { + Gage[j].rainfall = Gage[Gage[j].coGage].rainfall; + return; + } + + // --- use rainfall supplied by API function call + // (where constant ZERO (1.e-10) is used for 0 rainfall) + if (Gage[j].apiRainfall != MISSING) + { + Gage[j].rainfall = Gage[j].apiRainfall; + return; + } + + // --- otherwise march through rainfall record until date t is bracketed + t += OneSecond; + for (;;) + { + // --- no rainfall if no interval start date + if ( Gage[j].startDate == NO_DATE ) + { + Gage[j].rainfall = 0.0; + return; + } + + // --- no rainfall if time is before interval start date + if ( t < Gage[j].startDate ) + { + Gage[j].rainfall = 0.0; + return; + } + + // --- use current rainfall if time is before interval end date + if ( t < Gage[j].endDate ) + { + return; + } + + // --- no rainfall if t >= interval end date & no next interval exists + if ( Gage[j].nextDate == NO_DATE ) + { + Gage[j].rainfall = 0.0; + return; + } + + // --- no rainfall if t > interval end date & < next interval date + if ( t < Gage[j].nextDate ) + { + Gage[j].rainfall = 0.0; + return; + } + + // --- otherwise update next rainfall interval date + Gage[j].startDate = Gage[j].nextDate; + Gage[j].endDate = datetime_addSeconds(Gage[j].startDate, + Gage[j].rainInterval); + Gage[j].rainfall = Gage[j].nextRainfall; + if ( !getNextRainfall(j) ) Gage[j].nextDate = NO_DATE; + } +} + +//============================================================================= + +void initPastRain(int j) +{ + // --- initialize past hourly rain accumulation + int i; + for (i = 0; i <= MAXPASTRAIN; i++) + Gage[j].pastRain[i] = 0.0; + Gage[j].pastInterval = 0; +} + +//============================================================================= + +void gage_updatePastRain(int j, int tStep) +// +// Input: j = rain gage index +// tStep = current runoff time step (sec) +// Output: none +// Purpose: updates past MAXPASTRAIN hourly rain totals. +// +// Note: pastRain[0] is past rain volume prior to 1 hour, +// pastRain[n] is past rain volume after n hours, +// pastInterval is time since last hour was reached. +{ + int i, t; + double r; + + // --- current rainfall intensity (in/sec or mm/sec) + r = Gage[j].rainfall / 3600.; + + // --- process each hourly interval of current time step + while (tStep > 0) + { + // --- time for most recent rainfall interval to reach 1 hr + t = 3600 - Gage[j].pastInterval; + + // --- remaining time step is greater than this time + if (tStep > t) + { + // --- add current rain to most recent interval + Gage[j].pastRain[0] += t * r; + + // --- shift all prior hourly rain amounts by 1 hour + for (i = MAXPASTRAIN; i > 0; i-- ) + Gage[j].pastRain[i] = Gage[j].pastRain[i-1]; + + // --- begin a new most recent interval + Gage[j].pastInterval = 0; + Gage[j].pastRain[0] = 0.0; + tStep -= t; + } + // --- time to reach 1 hr in most recent interval is greater + // than remaining time step so update most recent interval + else + { + Gage[j].pastRain[0] += tStep * r; + Gage[j].pastInterval += tStep; + tStep = 0; + } + } +} + +//============================================================================= + +double gage_getPastRain(int j, int n) +// +// Input: j = rain gage index +// n = number of hours prior to current date +// Output: cumulative rain volume (inches or mm) in last n hours +// Purpose: retrieves rainfall total over some previous number of hours. +// +{ + int i; + double result = 0.0; + if (n < 1 || n > MAXPASTRAIN) return 0.0; + for (i = 1; i <= n; i++) + result += Gage[j].pastRain[i]; + return result; +} + +//============================================================================= + +DateTime gage_getNextRainDate(int j, DateTime aDate) +// +// Input: j = rain gage index +// aDate = calendar date/time +// Output: next date with rainfall occurring +// Purpose: finds the next date from specified date when rainfall occurs. +// +{ + if ( Gage[j].isUsed == FALSE ) return aDate; + aDate += OneSecond; + if ( aDate < Gage[j].startDate ) return Gage[j].startDate; + if ( aDate < Gage[j].endDate ) return Gage[j].endDate; + return Gage[j].nextDate; +} + +//============================================================================= + +double gage_getPrecip(int j, double *rainfall, double *snowfall) +// +// Input: j = rain gage index +// Output: rainfall = rainfall rate (ft/sec) +// snowfall = snow fall rate (ft/sec) +// returns total precipitation (ft/sec) +// Purpose: determines whether gage's recorded rainfall is rain or snow. +// +{ + *rainfall = 0.0; + *snowfall = 0.0; + if ( !IgnoreSnowmelt && Temp.ta <= Snow.snotmp ) + { + *snowfall = Gage[j].rainfall * Gage[j].snowFactor / UCF(RAINFALL); + } + else *rainfall = Gage[j].rainfall / UCF(RAINFALL); + return (*rainfall) + (*snowfall); +} + +//============================================================================= + +void gage_setReportRainfall(int j, DateTime reportDate) +// +// Input: j = rain gage index +// reportDate = date/time value of current reporting time +// Output: none +// Purpose: sets the rainfall value reported at the current reporting time. +// +{ + double result; + + // --- use value from co-gage if it exists + if ( Gage[j].coGage >= 0) + { + Gage[j].reportRainfall = Gage[Gage[j].coGage].reportRainfall; + return; + } + + // --- rainfall set by API call + if (Gage[j].apiRainfall != MISSING) + { + Gage[j].reportRainfall = Gage[j].apiRainfall; + return; + } + + // --- otherwise increase reporting time by 1 second to avoid + // roundoff problems + reportDate += OneSecond; + + // --- use current rainfall if report date/time is before end + // of current rain interval + if ( reportDate < Gage[j].endDate ) result = Gage[j].rainfall; + + // --- use 0.0 if report date/time is before start of next rain interval + else if ( reportDate < Gage[j].nextDate ) result = 0.0; + + // --- otherwise report date/time falls right on end of current rain + // interval and start of next interval so use next interval's rainfall + else result = Gage[j].nextRainfall; + Gage[j].reportRainfall = result; +} + +//============================================================================= + +int getFirstRainfall(int j) +// +// Input: j = rain gage index +// Output: returns TRUE if successful +// Purpose: positions rainfall record to date with first rainfall. +// +{ + int k; // time series index + float vFirst; // first rain volume (ft or m) + double rFirst; // first rain intensity (in/hr or mm/hr) + + // --- assign default values to date & rainfall + Gage[j].startDate = NO_DATE; + Gage[j].rainfall = 0.0; + + // --- initialize internal cumulative rainfall value + Gage[j].rainAccum = 0; + + // --- use rain interface file if applicable + if ( Gage[j].dataSource == RAIN_FILE ) + { + if ( Frain.file && Gage[j].endFilePos > Gage[j].startFilePos ) + { + // --- retrieve 1st date & rainfall volume from file + fseek(Frain.file, Gage[j].startFilePos, SEEK_SET); + fread(&Gage[j].startDate, sizeof(DateTime), 1, Frain.file); + fread(&vFirst, sizeof(float), 1, Frain.file); + Gage[j].currentFilePos = ftell(Frain.file); + + // --- convert rainfall to intensity + Gage[j].rainfall = convertRainfall(j, (double)vFirst); + return 1; + } + return 0; + } + + // --- otherwise access user-supplied rainfall time series + else + { + k = Gage[j].tSeries; + if ( k >= 0 ) + { + // --- retrieve first rainfall value from time series + if ( table_getFirstEntry(&Tseries[k], &Gage[j].startDate, + &rFirst) ) + { + // --- convert rainfall to intensity + Gage[j].rainfall = convertRainfall(j, rFirst); + return 1; + } + } + return 0; + } +} + +//============================================================================= + +int getNextRainfall(int j) +// +// Input: j = rain gage index +// Output: returns 1 if successful; 0 if not +// Purpose: positions rainfall record to date with next non-zero rainfall +// while updating the gage's next rain intensity value. +// +// Note: zero rainfall values explicitly entered into a rain file or +// time series are skipped over so that a proper accounting of +// wet and dry periods can be maintained. +// +{ + int k; // time series index + float vNext; // next rain volume (ft or m) + double rNext; // next rain intensity (in/hr or mm/hr) + + Gage[j].nextRainfall = 0.0; + do + { + if ( Gage[j].dataSource == RAIN_FILE ) + { + if ( Frain.file && Gage[j].currentFilePos < Gage[j].endFilePos ) + { + fseek(Frain.file, Gage[j].currentFilePos, SEEK_SET); + fread(&Gage[j].nextDate, sizeof(DateTime), 1, Frain.file); + fread(&vNext, sizeof(float), 1, Frain.file); + Gage[j].currentFilePos = ftell(Frain.file); + rNext = convertRainfall(j, (double)vNext); + } + else return 0; + } + + else + { + k = Gage[j].tSeries; + if ( k >= 0 ) + { + if ( !table_getNextEntry(&Tseries[k], + &Gage[j].nextDate, &rNext) ) return 0; + rNext = convertRainfall(j, rNext); + } + else return 0; + } + } while (rNext == 0.0); + Gage[j].nextRainfall = rNext; + return 1; +} + +//============================================================================= + +double convertRainfall(int j, double r) +// +// Input: j = rain gage index +// r = rainfall value (user units) +// Output: returns rainfall intensity (user units) +// Purpose: converts rainfall value to an intensity (depth per hour). +// +{ + double r1; + switch ( Gage[j].rainType ) + { + case RAINFALL_INTENSITY: + r1 = r; + break; + + case RAINFALL_VOLUME: + r1 = r / Gage[j].rainInterval * 3600.0; + break; + + case CUMULATIVE_RAINFALL: + if ( r < Gage[j].rainAccum ) + r1 = r / Gage[j].rainInterval * 3600.0; + else r1 = (r - Gage[j].rainAccum) / Gage[j].rainInterval * 3600.0; + Gage[j].rainAccum = r; + break; + + default: r1 = r; + } + return r1 * Gage[j].unitsFactor * Adjust.rainFactor; +} + +//============================================================================= diff --git a/src/globals.h b/src/globals.h new file mode 100644 index 000000000..70f2431aa --- /dev/null +++ b/src/globals.h @@ -0,0 +1,172 @@ +//----------------------------------------------------------------------------- +// globals.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Global Variables +// +// Update History +// ============== +// Build 5.1.004: +// - Ignore RDII option added. +// Build 5.1.007: +// - Monthly climate variable adjustments added. +// Build 5.1.008: +// - Number of parallel threads for dynamic wave routing added. +// - Minimum dynamic wave routing variable time step added. +// Build 5.1.011: +// - Changed WarningCode to Warnings (# warnings issued) +// - Added error message text as a variable. +// - Added elapsed simulation time (in decimal days) variable. +// - Added variables associated with detailed routing events. +// Build 5.1.012: +// - InSteadyState variable made local to routing_execute in routing.c. +// Build 5.1.013: +// - CrownCutoff and RuleStep added as analysis option variables. +// Build 5.1.015: +// - Fixes bug in summary statistics when Report Start date > Start Date. +// Build 5.2.0: +// - Support for relative file names added. +//----------------------------------------------------------------------------- + +#ifndef GLOBALS_H +#define GLOBALS_H + + +EXTERN TFile + Finp, // Input file + Fout, // Output file + Frpt, // Report file + Fclimate, // Climate file + Frain, // Rainfall file + Frunoff, // Runoff file + Frdii, // RDII inflow file + Fhotstart1, // Hot start input file + Fhotstart2, // Hot start output file + Finflows, // Inflows routing file + Foutflows; // Outflows routing file + +EXTERN long + Nperiods, // Number of reporting periods + TotalStepCount, // Total routing steps used + ReportStepCount, // Reporting routing steps used + NonConvergeCount; // Number of non-converging steps + +EXTERN char + Msg[MAXMSG+1], // Text of output message + ErrorMsg[MAXMSG+1], // Text of error message + Title[MAXTITLE][MAXMSG+1],// Project title + TempDir[MAXFNAME+1], // Temporary file directory + InpDir[MAXFNAME+1]; // Input file directory + +EXTERN TRptFlags + RptFlags; // Reporting options + +EXTERN int + Nobjects[MAX_OBJ_TYPES], // Number of each object type + Nnodes[MAX_NODE_TYPES], // Number of each node sub-type + Nlinks[MAX_LINK_TYPES], // Number of each link sub-type + UnitSystem, // Unit system + FlowUnits, // Flow units + InfilModel, // Infiltration method + RouteModel, // Flow routing method + ForceMainEqn, // Flow equation for force mains + LinkOffsets, // Link offset convention + SurchargeMethod, // EXTRAN or SLOT method + AllowPonding, // Allow water to pond at nodes + InertDamping, // Degree of inertial damping + NormalFlowLtd, // Normal flow limited + SlopeWeighting, // Use slope weighting + Compatibility, // SWMM 5/3/4 compatibility + SkipSteadyState, // Skip over steady state periods + IgnoreRainfall, // Ignore rainfall/runoff + IgnoreRDII, // Ignore RDII + IgnoreSnowmelt, // Ignore snowmelt + IgnoreGwater, // Ignore groundwater + IgnoreRouting, // Ignore flow routing + IgnoreQuality, // Ignore water quality + ErrorCode, // Error code number + Warnings, // Number of warning messages + WetStep, // Runoff wet time step (sec) + DryStep, // Runoff dry time step (sec) + ReportStep, // Reporting time step (sec) + RuleStep, // Rule evaluation time step (sec) + SweepStart, // Day of year when sweeping starts + SweepEnd, // Day of year when sweeping ends + MaxTrials, // Max. trials for DW routing + NumThreads, // Number of parallel threads used + NumEvents; // Number of detailed events + +EXTERN double + RouteStep, // Routing time step (sec) + MinRouteStep, // Minimum variable time step (sec) + LengtheningStep, // Time step for lengthening (sec) + StartDryDays, // Antecedent dry days + CourantFactor, // Courant time step factor + MinSurfArea, // Minimum nodal surface area + MinSlope, // Minimum conduit slope + RunoffError, // Runoff continuity error + GwaterError, // Groundwater continuity error + FlowError, // Flow routing error + QualError, // Quality routing error + HeadTol, // DW routing head tolerance (ft) + SysFlowTol, // Tolerance for steady system flow + LatFlowTol, // Tolerance for steady nodal inflow + CrownCutoff; // Fractional pipe crown cutoff + +EXTERN DateTime + StartDate, // Starting date + StartTime, // Starting time + StartDateTime, // Starting Date+Time + EndDate, // Ending date + EndTime, // Ending time + EndDateTime, // Ending Date+Time + ReportStartDate, // Report start date + ReportStartTime, // Report start time + ReportStart; // Report start Date+Time + +EXTERN double + ReportTime, // Current reporting time (msec) + OldRunoffTime, // Previous runoff time (msec) + NewRunoffTime, // Current runoff time (msec) + OldRoutingTime, // Previous routing time (msec) + NewRoutingTime, // Current routing time (msec) + TotalDuration, // Simulation duration (msec) + ElapsedTime; // Current elapsed time (days) + +EXTERN TTemp Temp; // Temperature data +EXTERN TEvap Evap; // Evaporation data +EXTERN TWind Wind; // Wind speed data +EXTERN TSnow Snow; // Snow melt data +EXTERN TAdjust Adjust; // Climate adjustments + +EXTERN TSnowmelt* Snowmelt; // Array of snow melt objects +EXTERN TGage* Gage; // Array of rain gages +EXTERN TSubcatch* Subcatch; // Array of subcatchments +EXTERN TAquifer* Aquifer; // Array of groundwater aquifers +EXTERN TUnitHyd* UnitHyd; // Array of unit hydrographs +EXTERN TNode* Node; // Array of nodes +EXTERN TOutfall* Outfall; // Array of outfall nodes +EXTERN TDivider* Divider; // Array of divider nodes +EXTERN TStorage* Storage; // Array of storage nodes +EXTERN TLink* Link; // Array of links +EXTERN TConduit* Conduit; // Array of conduit links +EXTERN TPump* Pump; // Array of pump links +EXTERN TOrifice* Orifice; // Array of orifice links +EXTERN TWeir* Weir; // Array of weir links +EXTERN TOutlet* Outlet; // Array of outlet device links +EXTERN TPollut* Pollut; // Array of pollutants +EXTERN TLanduse* Landuse; // Array of landuses +EXTERN TPattern* Pattern; // Array of time patterns +EXTERN TTable* Curve; // Array of curve tables +EXTERN TTable* Tseries; // Array of time series tables +EXTERN TTransect* Transect; // Array of transect data +EXTERN TStreet* Street; // Array of defined Street cross-sections +EXTERN TShape* Shape; // Array of custom conduit shapes +EXTERN TEvent* Event; // Array of routing events + + +#endif //GLOBALS_H diff --git a/src/gwater.c b/src/gwater.c new file mode 100644 index 000000000..48db9de17 --- /dev/null +++ b/src/gwater.c @@ -0,0 +1,872 @@ +//----------------------------------------------------------------------------- +// gwater.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Groundwater functions. +// +// Update History +// ============== +// Build 5.1.007: +// - User-supplied function for deep GW seepage flow added. +// - New variable names for use in user-supplied GW flow equations added. +// Build 5.1.008: +// - More variable names for user-supplied GW flow equations added. +// - Subcatchment area made into a shared variable. +// - Evaporation loss initialized to 0. +// - Support for collecting GW statistics added. +// Build 5.1.010: +// - Unsaturated hydraulic conductivity added to GW flow equation variables. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include +#include "headers.h" +#include "odesolve.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +static const double GWTOL = 0.0001; // ODE solver tolerance +static const double XTOL = 0.001; // tolerance for moisture & depth + +enum GWstates {THETA, // moisture content of upper GW zone + LOWERDEPTH}; // depth of lower saturated GW zone + +enum GWvariables { + gwvHGW, // water table height (ft) + gwvHSW, // surface water height (ft) + gwvHCB, // channel bottom height (ft) + gwvHGS, // ground surface height (ft) + gwvKS, // sat. hyd. condutivity (ft/s) + gwvK, // unsat. hyd. conductivity (ft/s) + gwvTHETA, // upper zone moisture content + gwvPHI, // soil porosity + gwvFI, // surface infiltration (ft/s) + gwvFU, // uper zone percolation rate (ft/s) + gwvA, // subcatchment area (ft2) + gwvMAX}; + +// Names of GW variables that can be used in GW outflow expression +static char* GWVarWords[] = {"HGW", "HSW", "HCB", "HGS", "KS", "K", + "THETA", "PHI", "FI", "FU", "A", NULL}; + +//----------------------------------------------------------------------------- +// Shared variables +//----------------------------------------------------------------------------- +// NOTE: all flux rates are in ft/sec, all depths are in ft. +static double Area; // subcatchment area (ft2) +static double Infil; // infiltration rate from surface +static double MaxEvap; // max. evaporation rate +static double AvailEvap; // available evaporation rate +static double UpperEvap; // evaporation rate from upper GW zone +static double LowerEvap; // evaporation rate from lower GW zone +static double UpperPerc; // percolation rate from upper to lower zone +static double LowerLoss; // loss rate from lower GW zone +static double GWFlow; // flow rate from lower zone to conveyance node +static double MaxUpperPerc; // upper limit on UpperPerc +static double MaxGWFlowPos; // upper limit on GWFlow when its positve +static double MaxGWFlowNeg; // upper limit on GWFlow when its negative +static double FracPerv; // fraction of surface that is pervious +static double TotalDepth; // total depth of GW aquifer +static double Theta; // moisture content of upper zone +static double HydCon; // unsaturated hydraulic conductivity (ft/s) +static double Hgw; // ht. of saturated zone +static double Hstar; // ht. from aquifer bottom to node invert +static double Hsw; // ht. from aquifer bottom to water surface +static double Tstep; // current time step (sec) +static TAquifer A; // aquifer being analyzed +static TGroundwater* GW; // groundwater object being analyzed +static MathExpr* LatFlowExpr; // user-supplied lateral GW flow expression +static MathExpr* DeepFlowExpr; // user-supplied deep GW flow expression + +//----------------------------------------------------------------------------- +// External Functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// gwater_readAquiferParams (called by input_readLine) +// gwater_readGroundwaterParams (called by input_readLine) +// gwater_readFlowExpression (called by input_readLine) +// gwater_deleteFlowExpression (called by deleteObjects in project.c) +// gwater_validateAquifer (called by swmm_open) +// gwater_validate (called by subcatch_validate) +// gwater_initState (called by subcatch_initState) +// gwater_getVolume (called by massbal_open & massbal_getGwaterError) +// gwater_getGroundwater (called by getSubareaRunoff in subcatch.c) +// gwater_getState (called by saveRunoff in hotstart.c) +// gwater_setState (called by readRunoff in hotstart.c) + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static void getDxDt(double t, double* x, double* dxdt); +static void getFluxes(double upperVolume, double lowerDepth); +static void getEvapRates(double theta, double upperDepth); +static double getUpperPerc(double theta, double upperDepth); +static double getGWFlow(double lowerDepth); +static void updateMassBal(double area, double tStep); + +// Used to process custom GW outflow equations +static int getVariableIndex(char* s); +static double getVariableValue(int varIndex); + +//============================================================================= + +int gwater_readAquiferParams(int j, char* tok[], int ntoks) +// +// Input: j = aquifer index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns error message +// Purpose: reads aquifer parameter values from line of input data +// +// Data line contains following parameters: +// ID, porosity, wiltingPoint, fieldCapacity, conductivity, +// conductSlope, tensionSlope, upperEvapFraction, lowerEvapDepth, +// gwRecession, bottomElev, waterTableElev, upperMoisture +// (evapPattern) +// +{ + int i, p; + double x[12]; + char *id; + + // --- check that aquifer exists + if ( ntoks < 13 ) return error_setInpError(ERR_ITEMS, ""); + id = project_findID(AQUIFER, tok[0]); + if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); + + // --- read remaining tokens as numbers + for (i = 0; i < 11; i++) x[i] = 0.0; + for (i = 1; i < 13; i++) + { + if ( ! getDouble(tok[i], &x[i-1]) ) + return error_setInpError(ERR_NUMBER, tok[i]); + } + + // --- read upper evap pattern if present + p = -1; + if ( ntoks > 13 ) + { + p = project_findObject(TIMEPATTERN, tok[13]); + if ( p < 0 ) return error_setInpError(ERR_NAME, tok[13]); + } + + // --- assign parameters to aquifer object + Aquifer[j].ID = id; + Aquifer[j].porosity = x[0]; + Aquifer[j].wiltingPoint = x[1]; + Aquifer[j].fieldCapacity = x[2]; + Aquifer[j].conductivity = x[3] / UCF(RAINFALL); + Aquifer[j].conductSlope = x[4]; + Aquifer[j].tensionSlope = x[5] / UCF(LENGTH); + Aquifer[j].upperEvapFrac = x[6]; + Aquifer[j].lowerEvapDepth = x[7] / UCF(LENGTH); + Aquifer[j].lowerLossCoeff = x[8] / UCF(RAINFALL); + Aquifer[j].bottomElev = x[9] / UCF(LENGTH); + Aquifer[j].waterTableElev = x[10] / UCF(LENGTH); + Aquifer[j].upperMoisture = x[11]; + Aquifer[j].upperEvapPat = p; + return 0; +} + +//============================================================================= + +int gwater_readGroundwaterParams(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// Purpose: reads groundwater inflow parameters for a subcatchment from +// a line of input data. +// +// Data format is: +// subcatch aquifer node surfElev a1 b1 a2 b2 a3 fixedDepth + +// (nodeElev bottomElev waterTableElev upperMoisture ) +// +{ + int i, j, k, m, n; + double x[11]; + TGroundwater* gw; + + // --- check that specified subcatchment, aquifer & node exist + if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); + j = project_findObject(SUBCATCH, tok[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); + + // --- check for enough tokens + if ( ntoks < 11 ) return error_setInpError(ERR_ITEMS, ""); + + // --- check that specified aquifer and node exists + k = project_findObject(AQUIFER, tok[1]); + if ( k < 0 ) return error_setInpError(ERR_NAME, tok[1]); + n = project_findObject(NODE, tok[2]); + if ( n < 0 ) return error_setInpError(ERR_NAME, tok[2]); + + // -- read in the flow parameters + for ( i = 0; i < 7; i++ ) + { + if ( ! getDouble(tok[i+3], &x[i]) ) + return error_setInpError(ERR_NUMBER, tok[i+3]); + } + + // --- read in optional depth parameters + for ( i = 7; i < 11; i++) + { + x[i] = MISSING; + m = i + 3; + if ( ntoks > m && *tok[m] != '*' ) + { + if (! getDouble(tok[m], &x[i]) ) + return error_setInpError(ERR_NUMBER, tok[m]); + if ( i < 10 ) x[i] /= UCF(LENGTH); + } + } + + // --- create a groundwater flow object + if ( !Subcatch[j].groundwater ) + { + gw = (TGroundwater *) malloc(sizeof(TGroundwater)); + if ( !gw ) return error_setInpError(ERR_MEMORY, ""); + Subcatch[j].groundwater = gw; + } + else gw = Subcatch[j].groundwater; + + // --- populate the groundwater flow object with its parameters + gw->aquifer = k; + gw->node = n; + gw->surfElev = x[0] / UCF(LENGTH); + gw->a1 = x[1]; + gw->b1 = x[2]; + gw->a2 = x[3]; + gw->b2 = x[4]; + gw->a3 = x[5]; + gw->fixedDepth = x[6] / UCF(LENGTH); + gw->nodeElev = x[7]; //already converted to ft. + gw->bottomElev = x[8]; + gw->waterTableElev = x[9]; + gw->upperMoisture = x[10]; + return 0; +} + +//============================================================================= + +int gwater_readFlowExpression(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// Purpose: reads mathematical expression for lateral or deep groundwater +// flow for a subcatchment from a line of input data. +// +// Format is: subcatch LATERAL/DEEP +// where subcatch is the ID of the subcatchment, LATERAL is for lateral +// GW flow, DEEP is for deep GW flow and is any well-formed math +// expression. +// +{ + int i, j, k; + char exprStr[MAXLINE+1]; + MathExpr* expr; + + // --- return if too few tokens + if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); + + // --- check that subcatchment exists + j = project_findObject(SUBCATCH, tok[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); + + // --- check if expression is for lateral or deep GW flow + k = 1; + if ( match(tok[1], "LAT") ) k = 1; + else if ( match(tok[1], "DEEP") ) k = 2; + else return error_setInpError(ERR_KEYWORD, tok[1]); + + // --- concatenate remaining tokens into a single string + sstrncpy(exprStr, tok[2], MAXLINE); + for ( i = 3; i < ntoks; i++) + { + sstrcat(exprStr, " ", MAXLINE+1); + sstrcat(exprStr, tok[i], MAXLINE+1); + } + + // --- delete any previous flow eqn. + if ( k == 1 ) mathexpr_delete(Subcatch[j].gwLatFlowExpr); + else mathexpr_delete(Subcatch[j].gwDeepFlowExpr); + + // --- create a parsed expression tree from the string expr + // (getVariableIndex is the function that converts a GW + // variable's name into an index number) + expr = mathexpr_create(exprStr, getVariableIndex); + if ( expr == NULL ) return error_setInpError(ERR_MATH_EXPR, ""); + + // --- save expression tree with the subcatchment + if ( k == 1 ) Subcatch[j].gwLatFlowExpr = expr; + else Subcatch[j].gwDeepFlowExpr = expr; + return 0; +} + +//============================================================================= + +void gwater_deleteFlowExpression(int j) +// +// Input: j = subcatchment index +// Output: none +// Purpose: deletes a subcatchment's custom groundwater flow expressions. +// +{ + mathexpr_delete(Subcatch[j].gwLatFlowExpr); + mathexpr_delete(Subcatch[j].gwDeepFlowExpr); +} + +//============================================================================= + +void gwater_validateAquifer(int j) +// +// Input: j = aquifer index +// Output: none +// Purpose: validates groundwater aquifer properties . +// +{ + int p; + + if ( Aquifer[j].porosity <= 0.0 + || Aquifer[j].fieldCapacity >= Aquifer[j].porosity + || Aquifer[j].wiltingPoint >= Aquifer[j].fieldCapacity + || Aquifer[j].conductivity <= 0.0 + || Aquifer[j].conductSlope < 0.0 + || Aquifer[j].tensionSlope < 0.0 + || Aquifer[j].upperEvapFrac < 0.0 + || Aquifer[j].lowerEvapDepth < 0.0 + || Aquifer[j].waterTableElev < Aquifer[j].bottomElev + || Aquifer[j].upperMoisture > Aquifer[j].porosity + || Aquifer[j].upperMoisture < Aquifer[j].wiltingPoint ) + report_writeErrorMsg(ERR_AQUIFER_PARAMS, Aquifer[j].ID); + + p = Aquifer[j].upperEvapPat; + if ( p >= 0 && Pattern[p].type != MONTHLY_PATTERN ) + { + report_writeErrorMsg(ERR_AQUIFER_PARAMS, Aquifer[j].ID); + } +} + +//============================================================================= + +void gwater_validate(int j) +{ + TAquifer a; // Aquifer data structure + TGroundwater* gw; // Groundwater data structure + + gw = Subcatch[j].groundwater; + if ( gw ) + { + a = Aquifer[gw->aquifer]; + + // ... use aquifer values for missing groundwater parameters + if ( gw->bottomElev == MISSING ) gw->bottomElev = a.bottomElev; + if ( gw->waterTableElev == MISSING ) gw->waterTableElev = a.waterTableElev; + if ( gw->upperMoisture == MISSING ) gw->upperMoisture = a.upperMoisture; + + // ... ground elevation can't be below water table elevation + if ( gw->surfElev < gw->waterTableElev ) + report_writeErrorMsg(ERR_GROUND_ELEV, Subcatch[j].ID); + } +} + +//============================================================================= + +void gwater_initState(int j) +// +// Input: j = subcatchment index +// Output: none +// Purpose: initializes state of subcatchment's groundwater. +// +{ + TAquifer a; // Aquifer data structure + TGroundwater* gw; // Groundwater data structure + + gw = Subcatch[j].groundwater; + if ( gw ) + { + a = Aquifer[gw->aquifer]; + + // ... initial moisture content + gw->theta = gw->upperMoisture; + if ( gw->theta >= a.porosity ) + { + gw->theta = a.porosity - XTOL; + } + + // ... initial depth of lower (saturated) zone + gw->lowerDepth = gw->waterTableElev - gw->bottomElev; + if ( gw->lowerDepth >= gw->surfElev - gw->bottomElev ) + { + gw->lowerDepth = gw->surfElev - gw->bottomElev - XTOL; + } + + // ... initial lateral groundwater outflow + gw->oldFlow = 0.0; + gw->newFlow = 0.0; + gw->evapLoss = 0.0; + + // ... initial available infiltration volume into upper zone + gw->maxInfilVol = (gw->surfElev - gw->waterTableElev) * + (a.porosity - gw->theta) / + subcatch_getFracPerv(j); + } +} + +//============================================================================= + +void gwater_getState(int j, double x[]) +// +// Input: j = subcatchment index +// Output: x[] = array of groundwater state variables +// Purpose: retrieves state of subcatchment's groundwater. +// +{ + TGroundwater* gw = Subcatch[j].groundwater; + x[0] = gw->theta; + x[1] = gw->bottomElev + gw->lowerDepth; + x[2] = gw->newFlow; + x[3] = gw->maxInfilVol; +} + +//============================================================================= + +void gwater_setState(int j, double x[]) +// +// Input: j = subcatchment index +// x[] = array of groundwater state variables +// Purpose: assigns values to a subcatchment's groundwater state. +// +{ + TGroundwater* gw = Subcatch[j].groundwater; + if ( gw == NULL ) return; + gw->theta = x[0]; + gw->lowerDepth = x[1] - gw->bottomElev; + gw->oldFlow = x[2]; + if ( x[3] != MISSING ) gw->maxInfilVol = x[3]; +} + +//============================================================================= + +double gwater_getVolume(int j) +// +// Input: j = subcatchment index +// Output: returns total volume of groundwater in ft/ft2 +// Purpose: finds volume of groundwater stored in upper & lower zones +// +{ + TAquifer a; + TGroundwater* gw; + double upperDepth; + gw = Subcatch[j].groundwater; + if ( gw == NULL ) return 0.0; + a = Aquifer[gw->aquifer]; + upperDepth = gw->surfElev - gw->bottomElev - gw->lowerDepth; + return (upperDepth * gw->theta) + (gw->lowerDepth * a.porosity); +} + +//============================================================================= + +void gwater_getGroundwater(int j, double evap, double infil, double tStep) +// +// Purpose: computes groundwater flow from subcatchment during current time step. +// Input: j = subcatchment index +// evap = pervious surface evaporation volume consumed (ft3) +// infil = surface infiltration volume (ft3) +// tStep = time step (sec) +// Output: none +// +{ + int n; // node exchanging groundwater + double x[2]; // upper moisture content & lower depth + double vUpper; // upper vol. available for percolation + double nodeFlow; // max. possible GW flow from node + + // --- save subcatchment's groundwater and aquifer objects to + // shared variables + GW = Subcatch[j].groundwater; + if ( GW == NULL ) return; + LatFlowExpr = Subcatch[j].gwLatFlowExpr; + DeepFlowExpr = Subcatch[j].gwDeepFlowExpr; + A = Aquifer[GW->aquifer]; + + // --- get fraction of total area that is pervious + FracPerv = subcatch_getFracPerv(j); + if ( FracPerv <= 0.0 ) return; + Area = Subcatch[j].area; + + // --- convert infiltration volume (ft3) to equivalent rate + // over entire GW (subcatchment) area + infil = infil / Area / tStep; + Infil = infil; + Tstep = tStep; + + // --- convert pervious surface evaporation already exerted (ft3) + // to equivalent rate over entire GW (subcatchment) area + evap = evap / Area / tStep; + + // --- convert max. surface evap rate (ft/sec) to a rate + // that applies to GW evap (GW evap can only occur + // through the pervious land surface area) + MaxEvap = Evap.rate * FracPerv; + + // --- available subsurface evaporation is difference between max. + // rate and pervious surface evap already exerted + AvailEvap = MAX((MaxEvap - evap), 0.0); + + // --- save total depth & outlet node properties to shared variables + TotalDepth = GW->surfElev - GW->bottomElev; + if ( TotalDepth <= 0.0 ) return; + n = GW->node; + + // --- establish min. water table height above aquifer bottom at which + // GW flow can occur (override node's invert if a value was provided + // in the GW object) + if ( GW->nodeElev != MISSING ) Hstar = GW->nodeElev - GW->bottomElev; + else Hstar = Node[n].invertElev - GW->bottomElev; + + // --- establish surface water height (relative to aquifer bottom) + // for drainage system node connected to the GW aquifer + if ( GW->fixedDepth > 0.0 ) + { + Hsw = GW->fixedDepth + Node[n].invertElev - GW->bottomElev; + } + else Hsw = Node[n].newDepth + Node[n].invertElev - GW->bottomElev; + + // --- store state variables (upper zone moisture content, lower zone + // depth) in work vector x + x[THETA] = GW->theta; + x[LOWERDEPTH] = GW->lowerDepth; + + // --- set limit on percolation rate from upper to lower GW zone + vUpper = (TotalDepth - x[LOWERDEPTH]) * (x[THETA] - A.fieldCapacity); + vUpper = MAX(0.0, vUpper); + MaxUpperPerc = vUpper / tStep; + + // --- set limit on GW flow out of aquifer based on volume of lower zone + MaxGWFlowPos = x[LOWERDEPTH]*A.porosity / tStep; + + // --- set limit on GW flow into aquifer from drainage system node + // based on min. of capacity of upper zone and drainage system + // inflow to the node + MaxGWFlowNeg = (TotalDepth - x[LOWERDEPTH]) * (A.porosity - x[THETA]) + / tStep; + nodeFlow = (Node[n].inflow + Node[n].newVolume/tStep) / Area; + MaxGWFlowNeg = -MIN(MaxGWFlowNeg, nodeFlow); + + // --- integrate eqns. for d(Theta)/dt and d(LowerDepth)/dt + // NOTE: ODE solver must have been initialized previously + odesolve_integrate(x, 2, 0, tStep, GWTOL, tStep, getDxDt); + + // --- keep state variables within allowable bounds + x[THETA] = MAX(x[THETA], A.wiltingPoint); + if ( x[THETA] >= A.porosity ) + { + x[THETA] = A.porosity - XTOL; + x[LOWERDEPTH] = TotalDepth - XTOL; + } + x[LOWERDEPTH] = MAX(x[LOWERDEPTH], 0.0); + if ( x[LOWERDEPTH] >= TotalDepth ) + { + x[LOWERDEPTH] = TotalDepth - XTOL; + } + + // --- save new values of state values + GW->theta = x[THETA]; + GW->lowerDepth = x[LOWERDEPTH]; + getFluxes(GW->theta, GW->lowerDepth); + GW->oldFlow = GW->newFlow; + GW->newFlow = GWFlow; + GW->evapLoss = UpperEvap + LowerEvap; + + //--- find max. infiltration volume (as depth over + // the pervious portion of the subcatchment) + // that upper zone can support in next time step + GW->maxInfilVol = (TotalDepth - x[LOWERDEPTH]) * + (A.porosity - x[THETA]) / FracPerv; + + // --- update GW mass balance + updateMassBal(Area, tStep); + + // --- update GW statistics + stats_updateGwaterStats(j, infil, GW->evapLoss, GWFlow, LowerLoss, + GW->theta, GW->lowerDepth + GW->bottomElev, tStep); +} + +//============================================================================= + +void updateMassBal(double area, double tStep) +// +// Input: area = subcatchment area (ft2) +// tStep = time step (sec) +// Output: none +// Purpose: updates GW mass balance with volumes of water fluxes. +// +{ + double vInfil; // infiltration volume + double vUpperEvap; // upper zone evap. volume + double vLowerEvap; // lower zone evap. volume + double vLowerPerc; // lower zone deep perc. volume + double vGwater; // volume of exchanged groundwater + double ft2sec = area * tStep; + + vInfil = Infil * ft2sec; + vUpperEvap = UpperEvap * ft2sec; + vLowerEvap = LowerEvap * ft2sec; + vLowerPerc = LowerLoss * ft2sec; + vGwater = 0.5 * (GW->oldFlow + GW->newFlow) * ft2sec; + massbal_updateGwaterTotals(vInfil, vUpperEvap, vLowerEvap, vLowerPerc, + vGwater); +} + +//============================================================================= + +void getFluxes(double theta, double lowerDepth) +// +// Input: upperVolume = vol. depth of upper zone (ft) +// upperDepth = depth of upper zone (ft) +// Output: none +// Purpose: computes water fluxes into/out of upper/lower GW zones. +// +{ + double upperDepth; + + // --- find upper zone depth + lowerDepth = MAX(lowerDepth, 0.0); + lowerDepth = MIN(lowerDepth, TotalDepth); + upperDepth = TotalDepth - lowerDepth; + + // --- save lower depth and theta to global variables + Hgw = lowerDepth; + Theta = theta; + + // --- find evaporation rate from both zones + getEvapRates(theta, upperDepth); + + // --- find percolation rate from upper to lower zone + UpperPerc = getUpperPerc(theta, upperDepth); + UpperPerc = MIN(UpperPerc, MaxUpperPerc); + + // --- find loss rate to deep GW + if ( DeepFlowExpr != NULL ) + LowerLoss = mathexpr_eval(DeepFlowExpr, getVariableValue) / + UCF(RAINFALL); + else + LowerLoss = A.lowerLossCoeff * lowerDepth / TotalDepth; + LowerLoss = MIN(LowerLoss, lowerDepth/Tstep); + + // --- find GW flow rate from lower zone to drainage system node + GWFlow = getGWFlow(lowerDepth); + if ( LatFlowExpr != NULL ) + { + GWFlow += mathexpr_eval(LatFlowExpr, getVariableValue) / UCF(GWFLOW); + } + if ( GWFlow >= 0.0 ) GWFlow = MIN(GWFlow, MaxGWFlowPos); + else GWFlow = MAX(GWFlow, MaxGWFlowNeg); +} + +//============================================================================= + +void getDxDt(double t, double* x, double* dxdt) +// +// Input: t = current time (not used) +// x = array of state variables +// Output: dxdt = array of time derivatives of state variables +// Purpose: computes time derivatives of upper moisture content +// and lower depth. +// +{ + double qUpper; // inflow - outflow for upper zone (ft/sec) + double qLower; // inflow - outflow for lower zone (ft/sec) + double denom; + + getFluxes(x[THETA], x[LOWERDEPTH]); + qUpper = Infil - UpperEvap - UpperPerc; + qLower = UpperPerc - LowerLoss - LowerEvap - GWFlow; + + // --- d(upper zone moisture)/dt = (net upper zone flow) / + // (upper zone depth) + denom = TotalDepth - x[LOWERDEPTH]; + if (denom > 0.0) + dxdt[THETA] = qUpper / denom; + else + dxdt[THETA] = 0.0; + + // --- d(lower zone depth)/dt = (net lower zone flow) / + // (upper zone moisture deficit) + denom = A.porosity - x[THETA]; + if (denom > 0.0) + dxdt[LOWERDEPTH] = qLower / denom; + else + dxdt[LOWERDEPTH] = 0.0; +} + +//============================================================================= + +void getEvapRates(double theta, double upperDepth) +// +// Input: theta = moisture content of upper zone +// upperDepth = depth of upper zone (ft) +// Output: none +// Purpose: computes evapotranspiration out of upper & lower zones. +// +{ + int p, month; + double f; + double lowerFrac, upperFrac; + + // --- no GW evaporation when infiltration is occurring + UpperEvap = 0.0; + LowerEvap = 0.0; + if ( Infil > 0.0 ) return; + + // --- get monthly-adjusted upper zone evap fraction + upperFrac = A.upperEvapFrac; + f = 1.0; + p = A.upperEvapPat; + if ( p >= 0 ) + { + month = datetime_monthOfYear(getDateTime(NewRunoffTime)); + f = Pattern[p].factor[month-1]; + } + upperFrac *= f; + + // --- upper zone evaporation requires that soil moisture + // be above the wilting point + if ( theta > A.wiltingPoint ) + { + // --- actual evap is upper zone fraction applied to max. potential + // rate, limited by the available rate after any surface evap + UpperEvap = upperFrac * MaxEvap; + UpperEvap = MIN(UpperEvap, AvailEvap); + } + + // --- check if lower zone evaporation is possible + if ( A.lowerEvapDepth > 0.0 ) + { + // --- find the fraction of the lower evaporation depth that + // extends into the saturated lower zone + lowerFrac = (A.lowerEvapDepth - upperDepth) / A.lowerEvapDepth; + lowerFrac = MAX(0.0, lowerFrac); + lowerFrac = MIN(lowerFrac, 1.0); + + // --- make the lower zone evap rate proportional to this fraction + // and the evap not used in the upper zone + LowerEvap = lowerFrac * (1.0 - upperFrac) * MaxEvap; + LowerEvap = MIN(LowerEvap, (AvailEvap - UpperEvap)); + } +} + +//============================================================================= + +double getUpperPerc(double theta, double upperDepth) +// +// Input: theta = moisture content of upper zone +// upperDepth = depth of upper zone (ft) +// Output: returns percolation rate (ft/sec) +// Purpose: finds percolation rate from upper to lower zone. +// +{ + double delta; // unfilled water content of upper zone + double dhdz; // avg. change in head with depth + double hydcon; // unsaturated hydraulic conductivity + + // --- no perc. from upper zone if no depth or moisture content too low + if ( upperDepth <= 0.0 || theta <= A.fieldCapacity ) return 0.0; + + // --- compute hyd. conductivity as function of moisture content + delta = theta - A.porosity; + hydcon = A.conductivity * exp(delta * A.conductSlope); + + // --- compute integral of dh/dz term + delta = theta - A.fieldCapacity; + dhdz = 1.0 + A.tensionSlope * 2.0 * delta / upperDepth; + + // --- compute upper zone percolation rate + HydCon = hydcon; + return hydcon * dhdz; +} + +//============================================================================= + +double getGWFlow(double lowerDepth) +// +// Input: lowerDepth = depth of lower zone (ft) +// Output: returns groundwater flow rate (ft/sec) +// Purpose: finds groundwater outflow from lower saturated zone. +// +{ + double q, t1, t2, t3; + + // --- water table must be above Hstar for flow to occur + if ( lowerDepth <= Hstar ) return 0.0; + + // --- compute groundwater component of flow + if ( GW->b1 == 0.0 ) t1 = GW->a1; + else t1 = GW->a1 * pow( (lowerDepth - Hstar)*UCF(LENGTH), GW->b1); + + // --- compute surface water component of flow + if ( GW->b2 == 0.0 ) t2 = GW->a2; + else if (Hsw > Hstar) + { + t2 = GW->a2 * pow( (Hsw - Hstar)*UCF(LENGTH), GW->b2); + } + else t2 = 0.0; + + // --- compute groundwater/surface water interaction term + t3 = GW->a3 * lowerDepth * Hsw * UCF(LENGTH) * UCF(LENGTH); + + // --- compute total groundwater flow + q = (t1 - t2 + t3) / UCF(GWFLOW); + if ( q < 0.0 && GW->a3 != 0.0 ) q = 0.0; + return q; +} + +//============================================================================= + +int getVariableIndex(char* s) +// +// Input: s = name of a groundwater variable +// Output: returns index of groundwater variable +// Purpose: finds position of GW variable in list of GW variable names. +// +{ + int k; + + k = findmatch(s, GWVarWords); + if ( k >= 0 ) return k; + return -1; +} + +//============================================================================= + +double getVariableValue(int varIndex) +// +// Input: varIndex = index of a GW variable +// Output: returns current value of GW variable +// Purpose: finds current value of a GW variable. +// +{ + switch (varIndex) + { + case gwvHGW: return Hgw * UCF(LENGTH); + case gwvHSW: return Hsw * UCF(LENGTH); + case gwvHCB: return Hstar * UCF(LENGTH); + case gwvHGS: return TotalDepth * UCF(LENGTH); + case gwvKS: return A.conductivity * UCF(RAINFALL); + case gwvK: return HydCon * UCF(RAINFALL); + case gwvTHETA:return Theta; + case gwvPHI: return A.porosity; + case gwvFI: return Infil * UCF(RAINFALL); + case gwvFU: return UpperPerc * UCF(RAINFALL); + case gwvA: return Area * UCF(LANDAREA); + default: return 0.0; + } +} diff --git a/src/hash.c b/src/hash.c new file mode 100644 index 000000000..4a73e6ccd --- /dev/null +++ b/src/hash.c @@ -0,0 +1,117 @@ +//----------------------------------------------------------------------------- +// hash.c +// +// Implementation of a simple Hash Table for string storage & retrieval +// CASE INSENSITIVE +// +// Written by L. Rossman +// Last Updated on 6/19/03 +// +// The hash table data structure (HTable) is defined in "hash.h". +// Interface Functions: +// HTcreate() - creates a hash table +// HTinsert() - inserts a string & its index value into a hash table +// HTfind() - retrieves the index value of a string from a table +// HTfree() - frees a hash table +//----------------------------------------------------------------------------- + +#include +#include +#include "hash.h" +#define UCHAR(x) (((x) >= 'a' && (x) <= 'z') ? ((x)&~32) : (x)) + +/* Case-insensitive comparison of strings s1 and s2 */ +int samestr(const char *s1, const char *s2) +{ + int i; + for (i=0; UCHAR(s1[i]) == UCHAR(s2[i]); i++) + if (!s1[i+1] && !s2[i+1]) return(1); + return(0); +} /* End of samestr */ + +/* Use Fletcher's checksum to compute 2-byte hash of string */ +unsigned int hash(const char *str) +{ + unsigned int sum1= 0, check1; + unsigned long sum2= 0L; + while( '\0' != *str ) + { + sum1 += UCHAR(*str); + str++; + if ( 255 <= sum1 ) sum1 -= 255; + sum2 += sum1; + } + check1= sum2; + check1 %= 255; + check1= 255 - (sum1+check1) % 255; + sum1= 255 - (sum1+check1) % 255; + return( ( ( check1 << 8 ) | sum1 ) % HTMAXSIZE); +} + +HTtable *HTcreate() +{ + int i; + HTtable *ht = (HTtable *) calloc(HTMAXSIZE, sizeof(HTtable)); + if (ht != NULL) for (i=0; i= HTMAXSIZE ) return(0); + entry = (struct HTentry *) malloc(sizeof(struct HTentry)); + if (entry == NULL) return(0); + entry->key = key; + entry->data = data; + entry->next = ht[i]; + ht[i] = entry; + return(1); +} + +int HTfind(HTtable *ht, const char *key) +{ + unsigned int i = hash(key); + struct HTentry *entry; + if ( i >= HTMAXSIZE ) return(NOTFOUND); + entry = ht[i]; + while (entry != NULL) + { + if ( samestr(entry->key,key) ) return(entry->data); + entry = entry->next; + } + return(NOTFOUND); +} + +char *HTfindKey(HTtable *ht, const char *key) +{ + unsigned int i = hash(key); + struct HTentry *entry; + if ( i >= HTMAXSIZE ) return(NULL); + entry = ht[i]; + while (entry != NULL) + { + if ( samestr(entry->key,key) ) return(entry->key); + entry = entry->next; + } + return(NULL); +} + +void HTfree(HTtable *ht) +{ + struct HTentry *entry, + *nextentry; + int i; + for (i=0; inext; + free(entry); + entry = nextentry; + } + } + free(ht); +} diff --git a/src/hash.h b/src/hash.h new file mode 100644 index 000000000..ba42be00e --- /dev/null +++ b/src/hash.h @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------------- +// hash.h +// +// Header file for Hash Table module hash.c. +//----------------------------------------------------------------------------- + +#ifndef HASH_H +#define HASH_H + + +#define HTMAXSIZE 1999 +#define NOTFOUND -1 + +struct HTentry +{ + char *key; + int data; + struct HTentry *next; +}; + +typedef struct HTentry *HTtable; + +HTtable* HTcreate(void); +int HTinsert(HTtable *, char *, int); +int HTfind(HTtable *, const char *); +char* HTfindKey(HTtable *, const char *); +void HTfree(HTtable *); + + +#endif //HASH_H diff --git a/src/headers.h b/src/headers.h new file mode 100644 index 000000000..87139b060 --- /dev/null +++ b/src/headers.h @@ -0,0 +1,20 @@ +//----------------------------------------------------------------------------- +// headers.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Header files included in most SWMM5 modules. +// +// DO NOT CHANGE THE ORDER OF THE #INCLUDE STATEMENTS +//----------------------------------------------------------------------------- +#include "macros.h" +#include "objects.h" +#define EXTERN extern +#include "globals.h" +#include "funcs.h" +#include "error.h" +#include "text.h" +#include "keywords.h" diff --git a/src/hotstart.c b/src/hotstart.c new file mode 100644 index 000000000..69f7abcc9 --- /dev/null +++ b/src/hotstart.c @@ -0,0 +1,544 @@ +//----------------------------------------------------------------------------- +// hotstart.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Hot Start file functions. +// +// A SWMM hot start file contains the state of a SWMM project after +// a simulation has been run, allowing it to be used to initialize +// a subsequent simulation that picks up where the previous run ended. +// +// An abridged version of the hot start file (version 2) is available +// that contains only variables that appear in the binary output file +// (groundwater upper moisture and water table elevation, node depth, +// lateral inflow, and quality, and link flow, depth, setting and quality). +// +// When reading a previously saved hot start file checks are made to +// insure the the current SWMM project has the same number of major +// components (subcatchments, land uses, nodes, links, and pollutants) +// and unit system as does the hot start file. No test is made to +// insure that these components are of the same sub-type and maintain +// the same order as when the hot start file was created. +// +// Update History +// ============== +// Build 5.1.008: +// - Storage node hydraulic residence time (HRT) was added to the file. +// - Link control settings are now applied when reading a hot start file. +// - Runoff read from file assigned to newRunoff property instead of oldRunoff. +// - Array indexing bug when reading snowpack state from file fixed. +// Build 5.1.011: +// - Link control setting bug when reading a hot start file fixed. +// Build 5.1.015: +// - Support added for multiple infiltration methods within a project. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include +#include +#include "headers.h" + +//----------------------------------------------------------------------------- +// Local Variables +//----------------------------------------------------------------------------- +static int fileVersion; + +//----------------------------------------------------------------------------- +// External functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// hotstart_open (called by swmm_start in swmm5.c) +// hotstart_close (called by swmm_end in swmm5.c) + +//----------------------------------------------------------------------------- +// Function declarations +//----------------------------------------------------------------------------- +static int openHotstartFile1(void); +static int openHotstartFile2(void); +static void readRunoff(void); +static void saveRunoff(void); +static void readRouting(void); +static void saveRouting(void); +static int readFloat(float *x, FILE* f); +static int readDouble(double* x, FILE* f); + +//============================================================================= + +int hotstart_open() +{ + // --- open hot start files + if ( !openHotstartFile1() ) return FALSE; //input hot start file + if ( !openHotstartFile2() ) return FALSE; //output hot start file + return TRUE; +} + +//============================================================================= + +void hotstart_close() +{ + if ( Fhotstart2.file ) + { + saveRunoff(); + saveRouting(); + fclose(Fhotstart2.file); + } +} + +//============================================================================= + +int openHotstartFile1() +// +// Input: none +// Output: none +// Purpose: opens a previously saved routing hotstart file. +// +{ + int nSubcatch; + int nNodes; + int nLinks; + int nPollut; + int nLandUses; + int flowUnits; + char fStamp[] = "SWMM5-HOTSTART"; + char fileStamp[] = "SWMM5-HOTSTART"; + char fStampx[] = "SWMM5-HOTSTARTx"; + char fileStamp2[] = "SWMM5-HOTSTART2"; + char fileStamp3[] = "SWMM5-HOTSTART3"; + char fileStamp4[] = "SWMM5-HOTSTART4"; + + // --- try to open the file + if ( Fhotstart1.mode != USE_FILE ) return TRUE; + if ( (Fhotstart1.file = fopen(Fhotstart1.name, "r+b")) == NULL) + { + report_writeErrorMsg(ERR_HOTSTART_FILE_OPEN, Fhotstart1.name); + return FALSE; + } + + // --- check that file contains proper header records + fread(fStampx, sizeof(char), strlen(fileStamp2), Fhotstart1.file); + if ( strcmp(fStampx, fileStamp4) == 0 ) fileVersion = 4; + else if ( strcmp(fStampx, fileStamp3) == 0 ) fileVersion = 3; + else if ( strcmp(fStampx, fileStamp2) == 0 ) fileVersion = 2; + else + { + rewind(Fhotstart1.file); + fread(fStamp, sizeof(char), strlen(fileStamp), Fhotstart1.file); + if ( strcmp(fStamp, fileStamp) != 0 ) + { + report_writeErrorMsg(ERR_HOTSTART_FILE_FORMAT, ""); + return FALSE; + } + fileVersion = 1; + } + + nSubcatch = -1; + nNodes = -1; + nLinks = -1; + nPollut = -1; + nLandUses = -1; + flowUnits = -1; + if ( fileVersion >= 2 ) + { + fread(&nSubcatch, sizeof(int), 1, Fhotstart1.file); + } + else nSubcatch = Nobjects[SUBCATCH]; + if ( fileVersion >= 3 ) + { + fread(&nLandUses, sizeof(int), 1, Fhotstart1.file); + } + else nLandUses = Nobjects[LANDUSE]; + fread(&nNodes, sizeof(int), 1, Fhotstart1.file); + fread(&nLinks, sizeof(int), 1, Fhotstart1.file); + fread(&nPollut, sizeof(int), 1, Fhotstart1.file); + fread(&flowUnits, sizeof(int), 1, Fhotstart1.file); + if ( nSubcatch != Nobjects[SUBCATCH] + || nLandUses != Nobjects[LANDUSE] + || nNodes != Nobjects[NODE] + || nLinks != Nobjects[LINK] + || nPollut != Nobjects[POLLUT] + || flowUnits != FlowUnits ) + { + report_writeErrorMsg(ERR_HOTSTART_FILE_FORMAT, ""); + return FALSE; + } + + // --- read contents of the file and close it + if ( fileVersion >= 3 ) readRunoff(); + readRouting(); + fclose(Fhotstart1.file); + if ( ErrorCode ) return FALSE; + else return TRUE; +} + +//============================================================================= + +int openHotstartFile2() +// +// Input: none +// Output: none +// Purpose: opens a new routing hotstart file to save results to. +// +{ + int nSubcatch; + int nLandUses; + int nNodes; + int nLinks; + int nPollut; + int flowUnits; + char fileStamp[] = "SWMM5-HOTSTART4"; + + // --- try to open file + if ( Fhotstart2.mode != SAVE_FILE ) return TRUE; + if ( (Fhotstart2.file = fopen(Fhotstart2.name, "w+b")) == NULL) + { + report_writeErrorMsg(ERR_HOTSTART_FILE_OPEN, Fhotstart2.name); + return FALSE; + } + + // --- write file stamp & number of objects to file + nSubcatch = Nobjects[SUBCATCH]; + nLandUses = Nobjects[LANDUSE]; + nNodes = Nobjects[NODE]; + nLinks = Nobjects[LINK]; + nPollut = Nobjects[POLLUT]; + flowUnits = FlowUnits; + fwrite(fileStamp, sizeof(char), strlen(fileStamp), Fhotstart2.file); + fwrite(&nSubcatch, sizeof(int), 1, Fhotstart2.file); + fwrite(&nLandUses, sizeof(int), 1, Fhotstart2.file); + fwrite(&nNodes, sizeof(int), 1, Fhotstart2.file); + fwrite(&nLinks, sizeof(int), 1, Fhotstart2.file); + fwrite(&nPollut, sizeof(int), 1, Fhotstart2.file); + fwrite(&flowUnits, sizeof(int), 1, Fhotstart2.file); + return TRUE; +} + +//============================================================================= + +void saveRouting() +// +// Input: none +// Output: none +// Purpose: saves current state of all nodes and links to hotstart file. +// +{ + int i, j; + float x[3]; + + for (i = 0; i < Nobjects[NODE]; i++) + { + x[0] = (float)Node[i].newDepth; + x[1] = (float)Node[i].newLatFlow; + fwrite(x, sizeof(float), 2, Fhotstart2.file); + + if ( Node[i].type == STORAGE ) + { + j = Node[i].subIndex; + x[0] = (float)Storage[j].hrt; + fwrite(&x[0], sizeof(float), 1, Fhotstart2.file); + } + + for (j = 0; j < Nobjects[POLLUT]; j++) + { + x[0] = (float)Node[i].newQual[j]; + fwrite(&x[0], sizeof(float), 1, Fhotstart2.file); + } + } + for (i = 0; i < Nobjects[LINK]; i++) + { + x[0] = (float)Link[i].newFlow; + x[1] = (float)Link[i].newDepth; + x[2] = (float)Link[i].setting; + fwrite(x, sizeof(float), 3, Fhotstart2.file); + for (j = 0; j < Nobjects[POLLUT]; j++) + { + x[0] = (float)Link[i].newQual[j]; + fwrite(&x[0], sizeof(float), 1, Fhotstart2.file); + } + } +} + +//============================================================================= + +void readRouting() +// +// Input: none +// Output: none +// Purpose: reads initial state of all nodes, links and groundwater objects +// from hotstart file. +// +{ + int i, j; + float x; + double xgw[4]; + FILE* f = Fhotstart1.file; + + // --- for file format 2, assign GW moisture content and lower depth + if ( fileVersion == 2 ) + { + // --- flow and available upper zone volume not used + xgw[2] = 0.0; + xgw[3] = MISSING; + for (i = 0; i < Nobjects[SUBCATCH]; i++) + { + // --- read moisture content and water table elevation as floats + if ( !readFloat(&x, f) ) return; + xgw[0] = x; + if ( !readFloat(&x, f) ) return; + xgw[1] = x; + + // --- set GW state + if ( Subcatch[i].groundwater != NULL ) gwater_setState(i, xgw); + } + } + + // --- read node states + for (i = 0; i < Nobjects[NODE]; i++) + { + if ( !readFloat(&x, f) ) return; + Node[i].newDepth = x; + if ( !readFloat(&x, f) ) return; + Node[i].newLatFlow = x; + + if ( fileVersion >= 4 && Node[i].type == STORAGE ) + { + if ( !readFloat(&x, f) ) return; + j = Node[i].subIndex; + Storage[j].hrt = x; + } + + for (j = 0; j < Nobjects[POLLUT]; j++) + { + if ( !readFloat(&x, f) ) return; + Node[i].newQual[j] = x; + } + + // --- read in zeros here for backwards compatibility + if ( fileVersion <= 2 ) + { + for (j = 0; j < Nobjects[POLLUT]; j++) + { + if ( !readFloat(&x, f) ) return; + } + } + } + + // --- read link states + for (i = 0; i < Nobjects[LINK]; i++) + { + if ( !readFloat(&x, f) ) return; + Link[i].newFlow = x; + if ( !readFloat(&x, f) ) return; + Link[i].newDepth = x; + if ( !readFloat(&x, f) ) return; + Link[i].setting = x; + + // --- set link's target setting to saved setting + Link[i].targetSetting = x; + link_setTargetSetting(i); + link_setSetting(i, 0.0); + + for (j = 0; j < Nobjects[POLLUT]; j++) + { + if ( !readFloat(&x, f) ) return; + Link[i].newQual[j] = x; + } + + } +} + +//============================================================================= + +void saveRunoff(void) +// +// Input: none +// Output: none +// Purpose: saves current state of all subcatchments to hotstart file. +// +{ + int i, j, k; + double x[6]; + FILE* f = Fhotstart2.file; + + for (i = 0; i < Nobjects[SUBCATCH]; i++) + { + // Ponded depths for each sub-area & total runoff (4 elements) + for (j = 0; j < 3; j++) x[j] = Subcatch[i].subArea[j].depth; + x[3] = Subcatch[i].newRunoff; + fwrite(x, sizeof(double), 4, f); + + // Infiltration state (max. of 6 elements) + for (j=0; j<6; j++) x[j] = 0.0; + infil_getState(i, x); + fwrite(x, sizeof(double), 6, f); + + // Groundwater state (4 elements) + if ( Subcatch[i].groundwater != NULL ) + { + gwater_getState(i, x); + fwrite(x, sizeof(double), 4, f); + } + + // Snowpack state (5 elements for each of 3 snow surfaces) + if ( Subcatch[i].snowpack != NULL ) + { + for (j=0; j<3; j++) + { + snow_getState(i, j, x); + fwrite(x, sizeof(double), 5, f); + } + } + + // Water quality + if ( Nobjects[POLLUT] > 0 ) + { + // Runoff quality + for (j=0; j 0 ) + { + // Runoff quality + for (j=0; j +#include +#include "headers.h" + +//----------------------------------------------------------------------------- +// Imported variables +//----------------------------------------------------------------------------- +extern double Qcf[]; // flow units conversion factors + // (see swmm5.c) + +//----------------------------------------------------------------------------- +// Shared variables +//----------------------------------------------------------------------------- +static int IfaceFlowUnits; // flow units for routing interface file +static int IfaceStep; // interface file time step (sec) +static int NumIfacePolluts; // number of pollutants in interface file +static int* IfacePolluts; // indexes of interface file pollutants +static int NumIfaceNodes; // number of nodes on interface file +static int* IfaceNodes; // indexes of nodes on interface file +static double** OldIfaceValues; // interface flows & WQ at previous time +static double** NewIfaceValues; // interface flows & WQ at next time +static double IfaceFrac; // fraction of interface file time step +static DateTime OldIfaceDate; // previous date of interface values +static DateTime NewIfaceDate; // next date of interface values + +//----------------------------------------------------------------------------- +// External Functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// iface_readFileParams (called by input_readLine) +// iface_openRoutingFiles (called by routing_open) +// iface_closeRoutingFiles (called by routing_close) +// iface_getNumIfaceNodes (called by addIfaceInflows in routing.c) +// iface_getIfaceNode (called by addIfaceInflows in routing.c) +// iface_getIfaceFlow (called by addIfaceInflows in routing.c) +// iface_getIfaceQual (called by addIfaceInflows in routing.c) +// iface_saveOutletResults (called by output_saveResults) + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static void openFileForOutput(void); +static void openFileForInput(void); +static int getIfaceFilePolluts(void); +static int getIfaceFileNodes(void); +static void setOldIfaceValues(void); +static void readNewIfaceValues(void); +static int isOutletNode(int node); + +//============================================================================= + +int iface_readFileParams(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads interface file information from a line of input data. +// +// Data format is: +// USE/SAVE FileType FileName +// +{ + char k; + int j; + char fname[MAXFNAME+1]; + + // --- determine file disposition and type + if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); + k = (char)findmatch(tok[0], FileModeWords); + if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[0]); + j = findmatch(tok[1], FileTypeWords); + if ( j < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); + if ( ntoks < 3 ) return 0; + sstrncpy(fname, tok[2], MAXFNAME); + + // --- process file name + switch ( j ) + { + case RAINFALL_FILE: + Frain.mode = k; + sstrncpy(Frain.name, addAbsolutePath(fname), MAXFNAME); + break; + + case RUNOFF_FILE: + Frunoff.mode = k; + sstrncpy(Frunoff.name, addAbsolutePath(fname), MAXFNAME); + break; + + case HOTSTART_FILE: + if ( k == USE_FILE ) + { + Fhotstart1.mode = k; + sstrncpy(Fhotstart1.name, addAbsolutePath(fname), MAXFNAME); + } + else if ( k == SAVE_FILE ) + { + Fhotstart2.mode = k; + sstrncpy(Fhotstart2.name, addAbsolutePath(fname), MAXFNAME); + } + break; + + case RDII_FILE: + Frdii.mode = k; + sstrncpy(Frdii.name, fname, MAXFNAME); + break; + + case INFLOWS_FILE: + if ( k != USE_FILE ) return error_setInpError(ERR_ITEMS, ""); + Finflows.mode = k; + sstrncpy(Finflows.name, addAbsolutePath(fname), MAXFNAME); + break; + + case OUTFLOWS_FILE: + if ( k != SAVE_FILE ) return error_setInpError(ERR_ITEMS, ""); + Foutflows.mode = k; + sstrncpy(Foutflows.name, addAbsolutePath(fname), MAXFNAME); + break; + } + return 0; +} + +//============================================================================= + +void iface_openRoutingFiles() +// +// Input: none +// Output: none +// Purpose: opens routing interface files. +// +{ + // --- initialize shared variables + NumIfacePolluts = 0; + IfacePolluts = NULL; + NumIfaceNodes = 0; + IfaceNodes = NULL; + OldIfaceValues = NULL; + NewIfaceValues = NULL; + + // --- check that inflows & outflows files are not the same + if ( Foutflows.mode != NO_FILE && Finflows.mode != NO_FILE ) + { + if ( strcomp(Foutflows.name, Finflows.name) ) + { + report_writeErrorMsg(ERR_ROUTING_FILE_NAMES, ""); + return; + } + } + + // --- open the file for reading or writing + if ( Foutflows.mode == SAVE_FILE ) openFileForOutput(); + if ( Finflows.mode == USE_FILE ) openFileForInput(); +} + +//============================================================================= + +void iface_closeRoutingFiles() +// +// Input: none +// Output: none +// Purpose: closes routing interface files. +// +{ + FREE(IfacePolluts); + FREE(IfaceNodes); + if ( OldIfaceValues != NULL ) project_freeMatrix(OldIfaceValues); + if ( NewIfaceValues != NULL ) project_freeMatrix(NewIfaceValues); + if ( Finflows.file ) fclose(Finflows.file); + if ( Foutflows.file ) fclose(Foutflows.file); +} + +//============================================================================= + +int iface_getNumIfaceNodes(DateTime currentDate) +// +// Input: currentDate = current date/time +// Output: returns number of interface nodes if data exists or +// 0 otherwise +// Purpose: reads inflow data from interface file at current date. +// +{ + // --- return 0 if file begins after current date + if ( OldIfaceDate > currentDate ) return 0; + + // --- keep updating new interface values until current date bracketed + while ( NewIfaceDate < currentDate && NewIfaceDate != NO_DATE ) + { + setOldIfaceValues(); + readNewIfaceValues(); + } + + // --- return 0 if no data available + if ( NewIfaceDate == NO_DATE ) return 0; + + // --- find fraction current date is bewteen old & new interface dates + IfaceFrac = (currentDate - OldIfaceDate) / (NewIfaceDate - OldIfaceDate); + IfaceFrac = MAX(0.0, IfaceFrac); + IfaceFrac = MIN(IfaceFrac, 1.0); + + // --- return number of interface nodes + return NumIfaceNodes; +} + +//============================================================================= + +int iface_getIfaceNode(int index) +// +// Input: index = interface file node index +// Output: returns project node index +// Purpose: finds index of project node associated with interface node index +// +{ + if ( index >= 0 && index < NumIfaceNodes ) return IfaceNodes[index]; + else return -1; +} + +//============================================================================= + +double iface_getIfaceFlow(int index) +// +// Input: index = interface file node index +// Output: returns inflow to node +// Purpose: finds interface flow for particular node index. +// +{ + double q1, q2; + + if ( index >= 0 && index < NumIfaceNodes ) + { + // --- interpolate flow between old and new values + q1 = OldIfaceValues[index][0]; + q2 = NewIfaceValues[index][0]; + return (1.0 - IfaceFrac)*q1 + IfaceFrac*q2; + } + else return 0.0; +} + +//============================================================================= + +double iface_getIfaceQual(int index, int pollut) +// +// Input: index = index of node on interface file +// pollut = index of pollutant on interface file +// Output: returns inflow pollutant concentration +// Purpose: finds interface concentration for particular node index & pollutant. +// +{ + int j; + double c1, c2; + + if ( index >= 0 && index < NumIfaceNodes ) + { + // --- find index of pollut on interface file + j = IfacePolluts[pollut]; + if ( j < 0 ) return 0.0; + + // --- interpolate flow between old and new values + // (remember that 1st col. of values matrix is for flow) + c1 = OldIfaceValues[index][j+1]; + c2 = NewIfaceValues[index][j+1]; + return (1.0 - IfaceFrac)*c1 + IfaceFrac*c2; + } + else return 0.0; +} + +//============================================================================= + +void iface_saveOutletResults(DateTime reportDate, FILE* file) +// +// Input: reportDate = reporting date/time +// file = ptr. to interface file +// Output: none +// Purpose: saves system outflows to routing interface file. +// +{ + int i, p, yr, mon, day, hr, min, sec; + char theDate[26]; + datetime_decodeDate(reportDate, &yr, &mon, &day); + datetime_decodeTime(reportDate, &hr, &min, &sec); + snprintf(theDate, 26, " %04d %02d %02d %02d %02d %02d ", + yr, mon, day, hr, min, sec); + for (i=0; i 0 ) + { + report_writeErrorMsg(err, Finflows.name); + return; + } + + // --- match nodes in file with those in project + err = getIfaceFileNodes(); + if ( err > 0 ) + { + report_writeErrorMsg(err, Finflows.name); + return; + } + + // --- create matrices for old & new interface flows & WQ values + OldIfaceValues = project_createMatrix(NumIfaceNodes, + 1+NumIfacePolluts); + NewIfaceValues = project_createMatrix(NumIfaceNodes, + 1+NumIfacePolluts); + if ( OldIfaceValues == NULL || NewIfaceValues == NULL ) + { + report_writeErrorMsg(ERR_MEMORY, ""); + return; + } + + // --- read in new interface flows & WQ values + readNewIfaceValues(); + OldIfaceDate = NewIfaceDate; +} + +//============================================================================= + +int getIfaceFilePolluts() +// +// Input: none +// Output: returns an error code +// Purpose: reads names of pollutants saved on the inflows interface file. +// +{ + int i, j; + char line[MAXLINE+1]; // line from inflows interface file + char s1[MAXLINE+1]; // general string variable + char s2[MAXLINE+1]; + + // --- read number of pollutants (minus FLOW) + fgets(line, MAXLINE, Finflows.file); + NumIfacePolluts = -1; + if (sscanf(line, "%d", &NumIfacePolluts)) + NumIfacePolluts--; + if ( NumIfacePolluts < 0 ) return ERR_ROUTING_FILE_FORMAT; + + // --- read flow units + fgets(line, MAXLINE, Finflows.file); + if (sscanf(line, "%s %s", s1, s2) < 2) + return ERR_ROUTING_FILE_FORMAT; + if ( !strcomp(s1, "FLOW") ) + return ERR_ROUTING_FILE_FORMAT; + IfaceFlowUnits = findmatch(s2, FlowUnitWords); + if ( IfaceFlowUnits < 0 ) + return ERR_ROUTING_FILE_FORMAT; + + // --- allocate memory for pollutant index array + if ( Nobjects[POLLUT] > 0 ) + { + IfacePolluts = (int *) calloc(Nobjects[POLLUT], sizeof(int)); + if ( !IfacePolluts ) return ERR_MEMORY; + for (i=0; i 0 && Nobjects[POLLUT] > 0 ) + { + // --- check each pollutant name on file with project's pollutants + for (i=0; i +#include +#include "headers.h" +#include "infil.h" + +//----------------------------------------------------------------------------- +// Local Variables +//----------------------------------------------------------------------------- +typedef union TInfil { + THorton horton; + TGrnAmpt grnAmpt; + TCurveNum curveNum; +} TInfil; +TInfil *Infil; + +static double Fumax; // saturated water volume in upper soil zone (ft) +static double InfilFactor; + +//----------------------------------------------------------------------------- +// External Functions (declared in infil.h) +//----------------------------------------------------------------------------- +// infil_create (called by createObjects in project.c) +// infil_delete (called by deleteObjects in project.c) +// infil_readParams (called by input_readLine) +// infil_initState (called by subcatch_initState) +// infil_getState (called by writeRunoffFile in hotstart.c) +// infil_setState (called by readRunoffFile in hotstart.c) +// infil_getInfil (called by getSubareaRunoff in subcatch.c) + +// Called locally and by storage node methods in node.c +// grnampt_setParams +// grnampt_initState +// grnampt_getInfil + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static int horton_setParams(THorton *infil, double p[]); +static void horton_initState(THorton *infil); +static void horton_getState(THorton *infil, double x[]); +static void horton_setState(THorton *infil, double x[]); +static double horton_getInfil(THorton *infil, double tstep, double irate, + double depth); +static double modHorton_getInfil(THorton *infil, double tstep, double irate, + double depth); + +static void grnampt_getState(TGrnAmpt *infil, double x[]); +static void grnampt_setState(TGrnAmpt *infil, double x[]); +static double grnampt_getUnsatInfil(TGrnAmpt *infil, double tstep, + double irate, double depth, int modelType); +static double grnampt_getSatInfil(TGrnAmpt *infil, double tstep, + double irate, double depth); +static double grnampt_getF2(double f1, double c1, double ks, double ts); + +static int curvenum_setParams(TCurveNum *infil, double p[]); +static void curvenum_initState(TCurveNum *infil); +static void curvenum_getState(TCurveNum *infil, double x[]); +static void curvenum_setState(TCurveNum *infil, double x[]); +static double curvenum_getInfil(TCurveNum *infil, double tstep, double irate, + double depth); + +//============================================================================= + +void infil_create(int n) +// +// Purpose: creates an array of infiltration objects. +// Input: n = number of subcatchments +// Output: none +// +{ + Infil = (TInfil *) calloc(n, sizeof(TInfil)); + if (Infil == NULL) ErrorCode = ERR_MEMORY; + InfilFactor = 1.0; + return; +} + +//============================================================================= + +void infil_delete() +// +// Purpose: deletes infiltration objects associated with subcatchments +// Input: none +// Output: none +// +{ + FREE(Infil); +} + +//============================================================================= + +int infil_readParams(int m, char* tok[], int ntoks) +// +// Input: m = default infiltration model +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: sets infiltration parameters from a line of input data. +// +// Format of data line is: +// subcatch p1 p2 ... (infilMethod) +{ + int i, j, n, status; + double x[5]; + + // --- check that subcatchment exists + j = project_findObject(SUBCATCH, tok[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); + + // --- check for infiltration method keyword is last token + i = findmatch(tok[ntoks-1], InfilModelWords); + if ( i >= 0 ) + { + m = i; + --ntoks; + } + + // --- number of input tokens depends on infiltration model m + if ( m == HORTON ) n = 5; + else if ( m == MOD_HORTON ) n = 5; + else if ( m == GREEN_AMPT ) n = 4; + else if ( m == MOD_GREEN_AMPT ) n = 4; + else if ( m == CURVE_NUMBER ) n = 4; + else return 0; + + if ( ntoks < n ) return error_setInpError(ERR_ITEMS, ""); + + // --- parse numerical values from tokens + for (i = 0; i < 5; i++) x[i] = 0.0; + for (i = 1; i < n; i++) + { + if (!getDouble(tok[i], &x[i - 1])) + return error_setInpError(ERR_NUMBER, tok[i]); + } + + // --- special case for Horton infil. - last parameter is optional + if ( (m == HORTON || m == MOD_HORTON) && ntoks > n ) + { + if ( ! getDouble(tok[n], &x[n-1]) ) + return error_setInpError(ERR_NUMBER, tok[n]); + } + + // --- assign parameter values to infil, infilModel object + Subcatch[j].infil = j; + Subcatch[j].infilModel = m; + switch (m) + { + case HORTON: + case MOD_HORTON: status = horton_setParams(&Infil[j].horton, x); + break; + case GREEN_AMPT: + case MOD_GREEN_AMPT: + status = grnampt_setParams(&Infil[j].grnAmpt, x); + break; + case CURVE_NUMBER: status = curvenum_setParams(&Infil[j].curveNum, x); + break; + default: status = TRUE; + } + if ( !status ) return error_setInpError(ERR_INFIL_PARAMS, ""); + return 0; +} + +//============================================================================= + +void infil_initState(int j) +// +// Input: j = subcatchment index +// Output: none +// Purpose: initializes state of infiltration for a subcatchment. +// +{ + switch (Subcatch[j].infilModel) + { + case HORTON: + case MOD_HORTON: horton_initState(&Infil[j].horton); break; + case GREEN_AMPT: + case MOD_GREEN_AMPT: + grnampt_initState(&Infil[j].grnAmpt); break; + case CURVE_NUMBER: curvenum_initState(&Infil[j].curveNum); break; + } +} + +//============================================================================= + +void infil_getState(int j, double x[]) +// +// Input: j = subcatchment index +// Output: x = subcatchment's infiltration state +// Purpose: retrieves the current infiltration state for a subcatchment. +// +{ + switch (Subcatch[j].infilModel) + { + case HORTON: + case MOD_HORTON: horton_getState(&Infil[j].horton, x); break; + case GREEN_AMPT: + case MOD_GREEN_AMPT: + grnampt_getState(&Infil[j].grnAmpt, x); break; + case CURVE_NUMBER: curvenum_getState(&Infil[j].curveNum, x); break; + } +} + +//============================================================================= + +void infil_setState(int j, double x[]) +// +// Input: j = subcatchment index +// m = infiltration method code +// Output: none +// Purpose: sets the current infiltration state for a subcatchment. +// +{ + switch (Subcatch[j].infilModel) + { + case HORTON: + case MOD_HORTON: horton_setState(&Infil[j].horton, x); break; + case GREEN_AMPT: + case MOD_GREEN_AMPT: + grnampt_setState(&Infil[j].grnAmpt, x); break; + case CURVE_NUMBER: curvenum_setState(&Infil[j].curveNum, x); break; + } +} + +//============================================================================= + +void infil_setInfilFactor(int j) +// +// Input: j = subcatchment index +// Output: none +// Purpose: assigns a value to the infiltration adjustment factor. +{ + int m; + int p; + + // ... set factor to the global conductivity adjustment factor + InfilFactor = Adjust.hydconFactor; + + // ... override global factor with subcatchment's adjustment if assigned + if (j >= 0) + { + p = Subcatch[j].infilPattern; + if (p >= 0 && Pattern[p].type == MONTHLY_PATTERN) + { + m = datetime_monthOfYear(getDateTime(OldRunoffTime)) - 1; + InfilFactor = Pattern[p].factor[m]; + } + } +} + +//============================================================================= + +double infil_getInfil(int j, double tstep, double rainfall, + double runon, double depth) +// +// Input: j = subcatchment index +// tstep = runoff time step (sec) +// rainfall = rainfall rate (ft/sec) +// runon = runon rate from other sub-areas or subcatchments (ft/sec) +// depth = depth of surface water on subcatchment (ft) +// Output: returns infiltration rate (ft/sec) +// Purpose: computes infiltration rate depending on infiltration method. +// +{ + switch (Subcatch[j].infilModel) + { + case HORTON: + return horton_getInfil(&Infil[j].horton, tstep, rainfall+runon, depth); + + case MOD_HORTON: + return modHorton_getInfil(&Infil[j].horton, tstep, rainfall+runon, + depth); + + case GREEN_AMPT: + case MOD_GREEN_AMPT: + return grnampt_getInfil(&Infil[j].grnAmpt, tstep, rainfall+runon, depth, + Subcatch[j].infilModel); + + case CURVE_NUMBER: + depth += runon * tstep; + return curvenum_getInfil(&Infil[j].curveNum, tstep, rainfall, depth); + + default: + return 0.0; + } +} + +//============================================================================= + +int horton_setParams(THorton *infil, double p[]) +// +// Input: infil = ptr. to Horton infiltration object +// p[] = array of parameter values +// Output: returns TRUE if parameters are valid, FALSE otherwise +// Purpose: assigns Horton infiltration parameters to a subcatchment. +// +{ + int k; + for (k = 0; k < 5; k++) if ( p[k] < 0.0 ) return FALSE; + + // --- max. & min. infil rates (ft/sec) + infil->f0 = p[0] / UCF(RAINFALL); + infil->fmin = p[1] / UCF(RAINFALL); + + // --- convert decay const. to 1/sec + infil->decay = p[2] / 3600.; + + // --- convert drying time (days) to a regeneration const. (1/sec) + // assuming that former is time to reach 98% dry along an + // exponential drying curve + if (p[3] == 0.0 ) p[3] = TINY; + infil->regen = -log(1.0-0.98) / p[3] / SECperDAY; + + // --- optional max. infil. capacity (ft) (p[4] = 0 if no value supplied) + infil->Fmax = p[4] / UCF(RAINDEPTH); + if ( infil->f0 < infil->fmin ) return FALSE; + return TRUE; +} + +//============================================================================= + +void horton_initState(THorton *infil) +// +// Input: infil = ptr. to Horton infiltration object +// Output: none +// Purpose: initializes time on Horton infiltration curve for a subcatchment. +// +{ + infil->tp = 0.0; + infil->Fe = 0.0; +} + +//============================================================================= + +void horton_getState(THorton *infil, double x[]) +{ + x[0] = infil->tp; + x[1] = infil->Fe; +} + +void horton_setState(THorton *infil, double x[]) +{ + infil->tp = x[0]; + infil->Fe = x[1]; +} + +//============================================================================= + +double horton_getInfil(THorton *infil, double tstep, double irate, double depth) +// +// Input: infil = ptr. to Horton infiltration object +// tstep = runoff time step (sec), +// irate = net "rainfall" rate (ft/sec), +// = rainfall + snowmelt + runon - evaporation +// depth = depth of ponded water (ft). +// Output: returns infiltration rate (ft/sec) +// Purpose: computes Horton infiltration for a subcatchment. +// +{ + // --- assign local variables + int iter; + double fa, fp = 0.0; + double Fp, F1, t1, tlim, ex, kt; + double FF, FF1, r; + double f0 = infil->f0 * InfilFactor; + double fmin = infil->fmin * InfilFactor; + double Fmax = infil->Fmax; + double tp = infil->tp; + double df = f0 - fmin; + double kd = infil->decay; + double kr = infil->regen * Evap.recoveryFactor; + + // --- special cases of no infil. or constant infil + if ( df < 0.0 || kd < 0.0 || kr < 0.0 ) return 0.0; + if ( df == 0.0 || kd == 0.0 ) + { + fp = f0; + fa = irate + depth / tstep; + if ( fp > fa ) fp = fa; + return MAX(0.0, fp); + } + + // --- compute water available for infiltration + fa = irate + depth / tstep; + + // --- case where there is water to infiltrate + if ( fa > ZERO ) + { + // --- compute average infil. rate over time step + t1 = tp + tstep; // future cumul. time + tlim = 16.0 / kd; // for tp >= tlim, f = fmin + if ( tp >= tlim ) + { + Fp = fmin * tp + df / kd; + F1 = Fp + fmin * tstep; + } + else + { + Fp = fmin * tp + df / kd * (1.0 - exp(-kd * tp)); + F1 = fmin * t1 + df / kd * (1.0 - exp(-kd * t1)); + } + fp = (F1 - Fp) / tstep; + fp = MAX(fp, fmin); + + // --- limit infil rate to available infil + if ( fp > fa ) fp = fa; + + // --- if fp on flat portion of curve then increase tp by tstep + if ( t1 > tlim ) tp = t1; + + // --- if infil < available capacity then increase tp by tstep + else if ( fp < fa ) tp = t1; + + // --- if infil limited by available capacity then + // solve F(tp) - F1 = 0 using Newton-Raphson method + else + { + F1 = Fp + fp * tstep; + tp = tp + tstep / 2.0; + for ( iter=1; iter<=20; iter++ ) + { + kt = MIN( 60.0, kd*tp ); + ex = exp(-kt); + FF = fmin * tp + df / kd * (1.0 - ex) - F1; + FF1 = fmin + df * ex; + r = FF / FF1; + tp = tp - r; + if ( fabs(r) <= 0.001 * tstep ) break; + } + } + + // --- limit cumulative infiltration to Fmax + if ( Fmax > 0.0 ) + { + if ( infil->Fe + fp * tstep > Fmax ) + fp = (Fmax - infil->Fe) / tstep; + fp = MAX(fp, 0.0); + infil->Fe += fp * tstep; + } + } + + // --- case where infil. capacity is regenerating; update tp. + else if (kr > 0.0) + { + r = exp(-kr * tstep); + tp = 1.0 - exp(-kd * tp); + tp = -log(1.0 - r*tp) / kd; + + // reduction in cumulative infiltration + if ( Fmax > 0.0 ) + { + infil->Fe = fmin*tp + (df/kd)*(1.0 - exp(-kd*tp)); + } + } + infil->tp = tp; + return fp; +} + +//============================================================================= + +double modHorton_getInfil(THorton *infil, double tstep, double irate, + double depth) +// +// Input: infil = ptr. to Horton infiltration object +// tstep = runoff time step (sec), +// irate = net "rainfall" rate (ft/sec), +// = rainfall + snowmelt + runon +// depth = depth of ponded water (ft). +// Output: returns infiltration rate (ft/sec) +// Purpose: computes modified Horton infiltration for a subcatchment. +// +{ + // --- assign local variables + double f = 0.0; + double fp, fa; + double f0 = infil->f0 * InfilFactor; + double fmin = infil->fmin * InfilFactor; + double df = f0 - fmin; + double kd = infil->decay; + double kr = infil->regen * Evap.recoveryFactor; + + // --- special cases of no or constant infiltration + if ( df < 0.0 || kd < 0.0 || kr < 0.0 ) return 0.0; + if ( df == 0.0 || kd == 0.0 ) + { + fp = f0; + fa = irate + depth / tstep; + if ( fp > fa ) fp = fa; + return MAX(0.0, fp); + } + + // --- compute water available for infiltration + fa = irate + depth / tstep; + + // --- case where there is water to infiltrate + if ( fa > ZERO ) + { + // --- saturated condition + if ( infil->Fmax > 0.0 && infil->Fe >= infil->Fmax ) return 0.0; + + // --- potential infiltration + fp = f0 - kd * infil->Fe; + fp = MAX(fp, fmin); + + // --- actual infiltration + f = MIN(fa, fp); + + // --- new cumulative infiltration minus seepage + infil->Fe += MAX((f - fmin), 0.0) * tstep; + if ( infil->Fmax > 0.0 ) infil->Fe = MAX(infil->Fe, infil->Fmax); + } + + // --- reduce cumulative infiltration for dry condition + else if (kr > 0.0) + { + infil->Fe *= exp(-kr * tstep); + infil->Fe = MAX(infil->Fe, 0.0); + } + return f; +} + +//============================================================================= + +void grnampt_getParams(int j, double p[]) +// +// Input: j = subcatchment index +// p[] = array of parameter values +// Output: none +// Purpose: retrieves Green-Ampt infiltration parameters for a subcatchment. +// +{ + p[0] = Infil[j].grnAmpt.S * UCF(RAINDEPTH); // Capillary suction head (ft) + p[1] = Infil[j].grnAmpt.Ks * UCF(RAINFALL); // Sat. hyd. conductivity (ft/sec) + p[2] = Infil[j].grnAmpt.IMDmax; // Max. init. moisture deficit +} + +//============================================================================= + +int grnampt_setParams(TGrnAmpt *infil, double p[]) +// +// Input: infil = ptr. to Green-Ampt infiltration object +// p[] = array of parameter values +// Output: returns TRUE if parameters are valid, FALSE otherwise +// Purpose: assigns Green-Ampt infiltration parameters to a subcatchment. +// +{ + double ksat; // sat. hyd. conductivity in in/hr + + if ( p[0] < 0.0 || p[1] <= 0.0 || p[2] < 0.0 || p[2] > 1.0) return FALSE; + infil->S = p[0] / UCF(RAINDEPTH); // Capillary suction head (ft) + infil->Ks = p[1] / UCF(RAINFALL); // Sat. hyd. conductivity (ft/sec) + infil->IMDmax = p[2]; // Max. init. moisture deficit + + // --- find depth of upper soil zone (ft) using Mein's eqn. + ksat = infil->Ks * 12. * 3600.; + infil->Lu = 4.0 * sqrt(ksat) / 12.; + return TRUE; +} + +//============================================================================= + +void grnampt_initState(TGrnAmpt *infil) +// +// Input: infil = ptr. to Green-Ampt infiltration object +// Output: none +// Purpose: initializes state of Green-Ampt infiltration for a subcatchment. +// +{ + if (infil == NULL) return; + infil->IMD = infil->IMDmax; + infil->Fu = 0.0; + infil->F = 0.0; + infil->Sat = FALSE; + infil->T = 0.0; +} + +void grnampt_getState(TGrnAmpt *infil, double x[]) +{ + x[0] = infil->IMD; + x[1] = infil->F; + x[2] = infil->Fu; + x[3] = infil->Sat; + x[4] = infil->T; +} + +void grnampt_setState(TGrnAmpt *infil, double x[]) +{ + infil->IMD = x[0]; + infil->F = x[1]; + infil->Fu = x[2]; + infil->Sat = (char)x[3]; + infil->T = x[4]; +} + +//============================================================================= + +double grnampt_getInfil(TGrnAmpt *infil, double tstep, double irate, + double depth, int modelType) +// +// Input: infil = ptr. to Green-Ampt infiltration object +// tstep = time step (sec), +// irate = net "rainfall" rate to upper zone (ft/sec); +// = rainfall + snowmelt + runon, +// does not include ponded water (added on below) +// depth = depth of ponded water (ft) +// modelType = either GREEN_AMPT or MOD_GREEN_AMPT +// Output: returns infiltration rate (ft/sec) +// Purpose: computes Green-Ampt infiltration for a subcatchment +// or a storage node. +// +{ + // --- find saturated upper soil zone water volume + Fumax = infil->IMDmax * infil->Lu * sqrt(InfilFactor); + + // --- reduce time until next event + infil->T -= tstep; + + // --- use different procedures depending on upper soil zone saturation + if ( infil->Sat ) return grnampt_getSatInfil(infil, tstep, irate, depth); + else return grnampt_getUnsatInfil(infil, tstep, irate, depth, modelType); +} + +//============================================================================= + +double grnampt_getUnsatInfil(TGrnAmpt *infil, double tstep, double irate, + double depth, int modelType) +// +// Input: infil = ptr. to Green-Ampt infiltration object +// tstep = runoff time step (sec), +// irate = net "rainfall" rate to upper zone (ft/sec); +// = rainfall + snowmelt + runon, +// does not include ponded water (added on below) +// depth = depth of ponded water (ft) +// modelType = either GREEN_AMPT or MOD_GREEN_AMPT +// Output: returns infiltration rate (ft/sec) +// Purpose: computes Green-Ampt infiltration when upper soil zone is +// unsaturated. +// +{ + double ia, c1, F2, dF, Fs, kr, ts; + double ks = infil->Ks * InfilFactor; + double lu = infil->Lu * sqrt(InfilFactor); + + // --- get available infiltration rate (rainfall + ponded water) + ia = irate + depth / tstep; + if ( ia < ZERO ) ia = 0.0; + + // --- no rainfall so recover upper zone moisture + if ( ia == 0.0 ) + { + if ( infil->Fu <= 0.0 ) return 0.0; + kr = lu / 90000.0 * Evap.recoveryFactor; + dF = kr * Fumax * tstep; + infil->F -= dF; + infil->Fu -= dF; + if ( infil->Fu <= 0.0 ) + { + infil->Fu = 0.0; + infil->F = 0.0; + infil->IMD = infil->IMDmax; + return 0.0; + } + + // --- if new wet event begins then reset IMD & F + if ( infil->T <= 0.0 ) + { + infil->IMD = (Fumax - infil->Fu) / lu; + infil->F = 0.0; + } + return 0.0; + } + + // --- rainfall does not exceed Ksat + if ( ia <= ks ) + { + dF = ia * tstep; + infil->F += dF; + infil->Fu += dF; + infil->Fu = MIN(infil->Fu, Fumax); + if ( modelType == GREEN_AMPT && infil->T <= 0.0 ) + { + infil->IMD = (Fumax - infil->Fu) / lu; + infil->F = 0.0; + } + return ia; + } + + // --- rainfall exceeds Ksat; renew time to drain upper zone + infil->T = 5400.0 / lu / Evap.recoveryFactor; + + // --- find volume needed to saturate surface layer + Fs = ks * (infil->S + depth) * infil->IMD / (ia - ks); + + // --- surface layer already saturated + if ( infil->F > Fs ) + { + infil->Sat = TRUE; + return grnampt_getSatInfil(infil, tstep, irate, depth); + } + + // --- surface layer remains unsaturated + if ( infil->F + ia*tstep < Fs ) + { + dF = ia * tstep; + infil->F += dF; + infil->Fu += dF; + infil->Fu = MIN(infil->Fu, Fumax); + return ia; + } + + // --- surface layer becomes saturated during time step; + // --- compute portion of tstep when saturated + ts = tstep - (Fs - infil->F) / ia; + if ( ts <= 0.0 ) ts = 0.0; + + // --- compute new total volume infiltrated + c1 = (infil->S + depth) * infil->IMD; + F2 = grnampt_getF2(Fs, c1, ks, ts); + if ( F2 > Fs + ia*ts ) F2 = Fs + ia*ts; + + // --- compute infiltration rate + dF = F2 - infil->F; + infil->F = F2; + infil->Fu += dF; + infil->Fu = MIN(infil->Fu, Fumax); + infil->Sat = TRUE; + return dF / tstep; +} + +//============================================================================= + +double grnampt_getSatInfil(TGrnAmpt *infil, double tstep, double irate, + double depth) +// +// Input: infil = ptr. to Green-Ampt infiltration object +// tstep = runoff time step (sec), +// irate = net "rainfall" rate to upper zone (ft/sec); +// = rainfall + snowmelt + runon, +// does not include ponded water (added on below) +// depth = depth of ponded water (ft). +// Output: returns infiltration rate (ft/sec) +// Purpose: computes Green-Ampt infiltration when upper soil zone is +// saturated. +// +{ + double ia, c1, dF, F2; + double ks = infil->Ks * InfilFactor; + double lu = infil->Lu * sqrt(InfilFactor); + + // --- get available infiltration rate (rainfall + ponded water) + ia = irate + depth / tstep; + if ( ia < ZERO ) return 0.0; + + // --- re-set new event recovery time + infil->T = 5400.0 / lu / Evap.recoveryFactor; + + // --- solve G-A equation for new cumulative infiltration volume (F2) + c1 = (infil->S + depth) * infil->IMD; + F2 = grnampt_getF2(infil->F, c1, ks, tstep); + dF = F2 - infil->F; + + // --- all available water infiltrates -- set saturated state to false + if ( dF > ia * tstep ) + { + dF = ia * tstep; + infil->Sat = FALSE; + } + + // --- update total infiltration and upper zone moisture deficit + infil->F += dF; + infil->Fu += dF; + infil->Fu = MIN(infil->Fu, Fumax); + return dF / tstep; +} + +//============================================================================= + +double grnampt_getF2(double f1, double c1, double ks, double ts) +// +// Input: f1 = old infiltration volume (ft) +// c1 = head * moisture deficit (ft) +// ks = sat. hyd. conductivity (ft/sec) +// ts = time step (sec) +// Output: returns infiltration volume at end of time step (ft) +// Purpose: computes new infiltration volume over a time step +// using Green-Ampt formula for saturated upper soil zone. +// +{ + int i; + double f2 = f1; + double f2min; + double df2; + double c2; + + // --- find min. infil. volume + f2min = f1 + ks * ts; + + // --- use min. infil. volume for 0 moisture deficit + if ( c1 == 0.0 ) return f2min; + + // --- use direct form of G-A equation for small time steps + // and c1/f1 < 100 + if ( ts < 10.0 && f1 > 0.01 * c1 ) + { + f2 = f1 + ks * (1.0 + c1/f1) * ts; + return MAX(f2, f2min); + } + + // --- use Newton-Raphson method to solve integrated G-A equation + // (convergence limit reduced from that used in previous releases) + c2 = c1 * log(f1 + c1) - ks * ts; + for ( i = 1; i <= 20; i++ ) + { + df2 = (f2 - f1 - c1 * log(f2 + c1) + c2) / (1.0 - c1 / (f2 + c1) ); + if ( fabs(df2) < 0.00001 ) + { + return MAX(f2, f2min); + } + f2 -= df2; + } + return f2min; +} + +//============================================================================= + +int curvenum_setParams(TCurveNum *infil, double p[]) +// +// Input: infil = ptr. to Curve Number infiltration object +// p[] = array of parameter values +// Output: returns TRUE if parameters are valid, FALSE otherwise +// Purpose: assigns Curve Number infiltration parameters to a subcatchment. +// +{ + + // --- convert Curve Number to max. infil. capacity + if ( p[0] < 10.0 ) p[0] = 10.0; + if ( p[0] > 99.0 ) p[0] = 99.0; + infil->Smax = (1000.0 / p[0] - 10.0) / 12.0; + if ( infil->Smax < 0.0 ) return FALSE; + + // --- convert drying time (days) to a regeneration const. (1/sec) + if ( p[2] > 0.0 ) infil->regen = 1.0 / (p[2] * SECperDAY); + else return FALSE; + + // --- compute inter-event time from regeneration const. as in Green-Ampt + infil->Tmax = 0.06 / infil->regen; + + return TRUE; +} + +//============================================================================= + +void curvenum_initState(TCurveNum *infil) +// +// Input: infil = ptr. to Curve Number infiltration object +// Output: none +// Purpose: initializes state of Curve Number infiltration for a subcatchment. +// +{ + infil->S = infil->Smax; + infil->P = 0.0; + infil->F = 0.0; + infil->T = 0.0; + infil->Se = infil->Smax; + infil->f = 0.0; +} + +void curvenum_getState(TCurveNum *infil, double x[]) +{ + x[0] = infil->S; + x[1] = infil->P; + x[2] = infil->F; + x[3] = infil->T; + x[4] = infil->Se; + x[5] = infil->f; +} + +void curvenum_setState(TCurveNum *infil, double x[]) +{ + infil->S = x[0]; + infil->P = x[1]; + infil->F = x[2]; + infil->T = x[3]; + infil->Se = x[4]; + infil->f = x[5]; +} + +//============================================================================= + +double curvenum_getInfil(TCurveNum *infil, double tstep, double irate, + double depth) +// +// Input: infil = ptr. to Curve Number infiltration object +// tstep = runoff time step (sec), +// irate = rainfall rate (ft/sec); +// depth = depth of runon + ponded water (ft) +// Output: returns infiltration rate (ft/sec) +// Purpose: computes infiltration rate using the Curve Number method. +// Note: this function treats runon from other subcatchments as part +// of the ponded depth and not as an effective rainfall rate. +{ + double F1; // new cumulative infiltration (ft) + double f1 = 0.0; // new infiltration rate (ft/sec) + double fa = irate + depth/tstep; // max. available infil. rate (ft/sec) + + // --- case where there is rainfall + if ( irate > ZERO ) + { + // --- check if new rain event + if ( infil->T >= infil->Tmax ) + { + infil->P = 0.0; + infil->F = 0.0; + infil->f = 0.0; + infil->Se = infil->S; + } + infil->T = 0.0; + + // --- update cumulative precip. + infil->P += irate * tstep; + + // --- find potential new cumulative infiltration + F1 = infil->P * (1.0 - infil->P / (infil->P + infil->Se)); + + // --- compute potential infiltration rate + f1 = (F1 - infil->F) / tstep; + if ( f1 < 0.0 || infil->S <= 0.0 ) f1 = 0.0; + + } + + // --- case of no rainfall + else + { + // --- if there is ponded water then use previous infil. rate + if ( depth > MIN_TOTAL_DEPTH && infil->S > 0.0 ) + { + f1 = infil->f; + if ( f1*tstep > infil->S ) f1 = infil->S / tstep; + } + + // --- otherwise update inter-event time + else infil->T += tstep; + } + + // --- if there is some infiltration + if ( f1 > 0.0 ) + { + // --- limit infil. rate to max. available rate + f1 = MIN(f1, fa); + f1 = MAX(f1, 0.0); + + // --- update actual cumulative infiltration + infil->F += f1 * tstep; + + // --- reduce infil. capacity if a regen. constant was supplied + if ( infil->regen > 0.0 ) + { + infil->S -= f1 * tstep; + if ( infil->S < 0.0 ) infil->S = 0.0; + } + } + + // --- otherwise regenerate infil. capacity + else + { + infil->S += infil->regen * infil->Smax * tstep * Evap.recoveryFactor; + if ( infil->S > infil->Smax ) infil->S = infil->Smax; + } + infil->f = f1; + return f1; +} diff --git a/src/infil.h b/src/infil.h new file mode 100644 index 000000000..f5ddf31aa --- /dev/null +++ b/src/infil.h @@ -0,0 +1,112 @@ +//----------------------------------------------------------------------------- +// infil.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Public interface for infiltration functions. +// +// Update History +// ============== +// Build 5.1.010: +// - New Modified Green Ampt infiltration option added. +// Build 5.1.013: +// - New function infil_setInfilFactor() added. +// Build 5.1.015: +// - Support added for multiple infiltration methods within a project. +//----------------------------------------------------------------------------- + +#ifndef INFIL_H +#define INFIL_H + +//--------------------- +// Enumerated Constants +//--------------------- +enum InfilType { + HORTON, // Horton infiltration + MOD_HORTON, // Modified Horton infiltration + GREEN_AMPT, // Green-Ampt infiltration + MOD_GREEN_AMPT, // Modified Green-Ampt infiltration + CURVE_NUMBER}; // SCS Curve Number infiltration + +//--------------------- +// Horton Infiltration +//--------------------- +typedef struct +{ + double f0; // initial infil. rate (ft/sec) + double fmin; // minimum infil. rate (ft/sec) + double Fmax; // maximum total infiltration (ft); + double decay; // decay coeff. of infil. rate (1/sec) + double regen; // regeneration coeff. of infil. rate (1/sec) + //----------------------------- + double tp; // present time on infiltration curve (sec) + double Fe; // cumulative infiltration (ft) +} THorton; + + +//------------------------- +// Green-Ampt Infiltration +//------------------------- +typedef struct +{ + double S; // avg. capillary suction (ft) + double Ks; // saturated conductivity (ft/sec) + double IMDmax; // max. soil moisture deficit (ft/ft) + //----------------------------- + double IMD; // current initial soil moisture deficit + double F; // current cumulative infiltrated volume (ft) + double Fu; // current upper zone infiltrated volume (ft) + double Lu; // depth of upper soil zone (ft) + double T; // time until start of next rain event (sec) + char Sat; // saturation flag +} TGrnAmpt; + + +//-------------------------- +// Curve Number Infiltration +//-------------------------- +typedef struct +{ + double Smax; // max. infiltration capacity (ft) + double regen; // infil. capacity regeneration constant (1/sec) + double Tmax; // maximum inter-event time (sec) + //----------------------------- + double S; // current infiltration capacity (ft) + double F; // current cumulative infiltration (ft) + double P; // current cumulative precipitation (ft) + double T; // current inter-event time (sec) + double Se; // current event infiltration capacity (ft) + double f; // previous infiltration rate (ft/sec) + +} TCurveNum; + +//----------------------------------------------------------------------------- +// Exported Variables +//----------------------------------------------------------------------------- +extern THorton* HortInfil; +extern TGrnAmpt* GAInfil; +extern TCurveNum* CNInfil; + +//----------------------------------------------------------------------------- +// Infiltration Methods +//----------------------------------------------------------------------------- +void infil_create(int n); +void infil_delete(void); +int infil_readParams(int m, char* tok[], int ntoks); +void infil_initState(int j); +void infil_getState(int j, double x[]); +void infil_setState(int j, double x[]); +void infil_setInfilFactor(int j); +double infil_getInfil(int area, double tstep, double rainfall, double runon, + double depth); + +void grnampt_getParams(int j, double p[]); +int grnampt_setParams(TGrnAmpt *infil, double p[]); +void grnampt_initState(TGrnAmpt *infil); +double grnampt_getInfil(TGrnAmpt *infil, double tstep, double irate, + double depth, int modelType); + +#endif diff --git a/src/inflow.c b/src/inflow.c new file mode 100644 index 000000000..7cb5e250f --- /dev/null +++ b/src/inflow.c @@ -0,0 +1,484 @@ +//----------------------------------------------------------------------------- +// inflow.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Manages any Direct External or Dry Weather Flow inflows +// that have been assigned to nodes of the drainage system. +// +// Update History +// ============== +// Build 5.2.0: +// - Removed references to unused extIfaceInflow member of ExtInflow struct. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include "headers.h" + +//----------------------------------------------------------------------------- +// External Functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// inflow_initDwfPattern (called createObjects in project.c) +// inflow_readExtInflow (called by input_readLine) +// inflow_readDwfInflow (called by input_readLine) +// inflow_deleteExtInflows (called by deleteObjects in project.c) +// inflow_deleteDwfInflows (called by deleteObjects in project.c) +// inflow_getExtInflow (called by addExternalInflows in routing.c) +// inflow_setExtInflow (called by setNodeInflow in swmm5.c) +// inflow_getDwfInflow (called by addDryWeatherInflows in routing.c) + +//----------------------------------------------------------------------------- +// Local Functions +//----------------------------------------------------------------------------- +double getPatternFactor(int p, int month, int day, int hour); + + +int inflow_readExtInflow(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error message +// Purpose: reads parameters of a direct external inflow from a line of input. +// +// Formats of data line are: +// nodeID FLOW tSeriesID (FLOW 1.0 scaleFactor baseline basePat) +// nodeID pollutID tSeriesID (CONCEN/MASS unitsFactor scaleFactor baseline basePat) +// +{ + int j; // object index + int param; // FLOW (-1) or pollutant index + int type = CONCEN_INFLOW; // FLOW, CONCEN or MASS inflow + int tseries = -1; // time series index + int basePat = -1; // baseline pattern + double cf = 1.0; // units conversion factor + double sf = 1.0; // scaling factor + double baseline = 0.0; // baseline value + + // --- find index of node receiving the inflow + if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); + j = project_findObject(NODE, tok[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); + + // --- find index of inflow pollutant or use -1 for FLOW + param = project_findObject(POLLUT, tok[1]); + if ( param < 0 ) + { + if ( match(tok[1], w_FLOW) ) param = -1; + else return error_setInpError(ERR_NAME, tok[1]); + } + + // --- find index of inflow time series (if supplied) in data base + if ( strlen(tok[2]) > 0 ) + { + tseries = project_findObject(TSERIES, tok[2]); + if ( tseries < 0 ) return error_setInpError(ERR_NAME, tok[2]); + Tseries[tseries].refersTo = EXTERNAL_INFLOW; + } + + // --- assign type & cf values for a FLOW inflow + if (param == -1) + { + type = FLOW_INFLOW; + cf = 1.0/UCF(FLOW); + } + + // --- do the same for a pollutant inflow + if ( ntoks >= 4 && param > -1) + { + if ( match(tok[3], w_CONCEN) ) type = CONCEN_INFLOW; + else if ( match(tok[3], w_MASS) ) type = MASS_INFLOW; + else return error_setInpError(ERR_KEYWORD, tok[3]); + if ( ntoks >= 5 && type == MASS_INFLOW ) + { + if ( ! getDouble(tok[4], &cf) ) + { + return error_setInpError(ERR_NUMBER, tok[4]); + } + if ( cf <= 0.0 ) return error_setInpError(ERR_NUMBER, tok[4]); + } + } + + // --- get sf and baseline values + if ( ntoks >= 6 ) + { + if ( ! getDouble(tok[5], &sf) ) + { + return error_setInpError(ERR_NUMBER, tok[5]); + } + } + if ( ntoks >= 7 ) + { + if ( ! getDouble(tok[6], &baseline) ) + { + return error_setInpError(ERR_NUMBER, tok[6]); + } + } + + // --- get baseline time pattern + if ( ntoks >= 8 ) + { + basePat = project_findObject(TIMEPATTERN, tok[7]); + if ( basePat < 0 ) return error_setInpError(ERR_NAME, tok[7]); + } + + // --- include LperFT3 term in conversion factor for MASS_INFLOW + if ( type == MASS_INFLOW ) cf /= LperFT3; + + return(inflow_setExtInflow(j, param, type, tseries, basePat, + cf, baseline, sf)); +} + +//============================================================================= + +int inflow_setExtInflow(int j, int param, int type, int tseries, int basePat, + double cf, double baseline, double sf) +// Purpose: This function assigns property values to the inflow object +// Inputs: j = Node index +// param = FLOW (-1) or pollutant index +// type = FLOW, CONCEN or MASS inflow +// tSeries = time series index +// basePat = baseline pattern +// cf = units conversion factor +// baseline = baseline inflow value +// sf = scaling factor +// Return: returns Error Code + +{ + TExtInflow* inflow; // external inflow object + + // --- check if an external inflow object for this constituent already exists + inflow = Node[j].extInflow; + while ( inflow ) + { + if ( inflow->param == param ) break; + inflow = inflow->next; + } + + // --- if it doesn't exist, then create it + if ( inflow == NULL ) + { + inflow = (TExtInflow *) malloc(sizeof(TExtInflow)); + if ( inflow == NULL ) + { + return error_setInpError(ERR_MEMORY, ""); + } + inflow->next = Node[j].extInflow; + Node[j].extInflow = inflow; + } + + // --- assign property values to the inflow object + inflow->param = param; + inflow->type = type; + inflow->tSeries = tseries; + inflow->cFactor = cf; + inflow->sFactor = sf; + inflow->baseline = baseline; + inflow->basePat = basePat; + return 0; +} + +//============================================================================= + +void inflow_deleteExtInflows(int j) +// +// Input: j = node index +// Output: none +// Purpose: deletes all time series inflow data for a node. +// +{ + TExtInflow* inflow1; + TExtInflow* inflow2; + inflow1 = Node[j].extInflow; + while ( inflow1 ) + { + inflow2 = inflow1->next; + free(inflow1); + inflow1 = inflow2; + } +} + +//============================================================================= + +double inflow_getExtInflow(TExtInflow* inflow, DateTime aDate) +// +// Input: inflow = external inflow data structure +// aDate = current simulation date/time +// Output: returns current value of external inflow parameter +// Purpose: retrieves the value of an external inflow at a specific +// date and time. +// +{ + int month, day, hour; + int p = inflow->basePat; // baseline pattern + int k = inflow->tSeries; // time series index + double cf = inflow->cFactor; // units conversion factor + double sf = inflow->sFactor; // scaling factor + double blv = inflow->baseline; // baseline value + double tsv = 0.0; // time series value + + if ( p >= 0 ) + { + month = datetime_monthOfYear(aDate) - 1; + day = datetime_dayOfWeek(aDate) - 1; + hour = datetime_hourOfDay(aDate); + blv *= getPatternFactor(p, month, day, hour); + } + if ( k >= 0 ) tsv = table_tseriesLookup(&Tseries[k], aDate, FALSE) * sf; + return cf * (tsv + blv); +} + +//============================================================================= + +int inflow_readDwfInflow(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error message +// Purpose: reads dry weather inflow parameters from line of input data. +// +// Format of data line is: +// nodeID FLOW/pollutID avgValue (pattern1 pattern2 ... pattern4) +// +{ + int i; + int j; // node index + int k; // pollutant index (-1 for flow) + int m; // time pattern index + int pats[4]; // time pattern index array + double x; // avg. DWF value + TDwfInflow* inflow; // dry weather flow inflow object + + // --- find index of node receiving the inflow + if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); + j = project_findObject(NODE, tok[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); + + // --- find index of inflow pollutant (-1 for FLOW) + k = project_findObject(POLLUT, tok[1]); + if ( k < 0 ) + { + if ( match(tok[1], w_FLOW) ) k = -1; + else return error_setInpError(ERR_NAME, tok[1]); + } + + // --- get avg. value of DWF inflow + if ( !getDouble(tok[2], &x) ) + return error_setInpError(ERR_NUMBER, tok[2]); + if ( k == -1 ) x /= UCF(FLOW); + + // --- get time patterns assigned to the inflow + for (i=0; i<4; i++) pats[i] = -1; + for (i=3; i<7; i++) + { + if ( i >= ntoks ) break; + if ( strlen(tok[i]) == 0 ) continue; + m = project_findObject(TIMEPATTERN, tok[i]); + if ( m < 0 ) return error_setInpError(ERR_NAME, tok[i]); + pats[i-3] = m; + } + + // --- check if inflow for this constituent already exists + inflow = Node[j].dwfInflow; + while ( inflow ) + { + if ( inflow->param == k ) break; + inflow = inflow->next; + } + + // --- if it doesn't exist, then create it + if ( inflow == NULL ) + { + inflow = (TDwfInflow *) malloc(sizeof(TDwfInflow)); + if ( inflow == NULL ) return error_setInpError(ERR_MEMORY, ""); + inflow->next = Node[j].dwfInflow; + Node[j].dwfInflow = inflow; + } + + // --- assign property values to the inflow object + inflow->param = k; + inflow->avgValue = x; + for (i=0; i<4; i++) inflow->patterns[i] = pats[i]; + return 0; +} + +//============================================================================= + +void inflow_deleteDwfInflows(int j) +// +// Input: j = node index +// Output: none +// Purpose: deletes all dry weather inflow data for a node. +// +{ + TDwfInflow* inflow1; + TDwfInflow* inflow2; + inflow1 = Node[j].dwfInflow; + while ( inflow1 ) + { + inflow2 = inflow1->next; + free(inflow1); + inflow1 = inflow2; + } +} + +//============================================================================= + +void inflow_initDwfInflow(TDwfInflow* inflow) +// +// Input: inflow = dry weather inflow data structure +// Output: none +// Purpose: initialzes a dry weather inflow by ordering its time patterns. +// +// This function sorts the user-supplied time patterns for a dry weather +// inflow in the order of the PatternType enumeration (monthly, daily, +// weekday hourly, weekend hourly) to help speed up pattern processing. +// +{ + int i, p; + int tmpPattern[4]; // index of each type of DWF pattern + + // --- assume no patterns were supplied + for (i=0; i<4; i++) tmpPattern[i] = -1; + + // --- assign supplied patterns to proper position (by type) in tmpPattern + for (i=0; i<4; i++) + { + p = inflow->patterns[i]; + if ( p >= 0 ) tmpPattern[Pattern[p].type] = p; + } + + // --- re-fill inflow pattern array by pattern type + for (i=0; i<4; i++) inflow->patterns[i] = tmpPattern[i]; +} + +//============================================================================= + +double inflow_getDwfInflow(TDwfInflow* inflow, int month, int day, int hour) +// +// Input: inflow = dry weather inflow data structure +// month = current month of year of simulation +// day = current day of week of simulation +// hour = current hour of day of simulation +// Output: returns value of dry weather inflow parameter +// Purpose: computes dry weather inflow value at a specific point in time. +// +{ + int p1, p2; // pattern index + double f = 1.0; // pattern factor + + p1 = inflow->patterns[MONTHLY_PATTERN]; + if ( p1 >= 0 ) f *= getPatternFactor(p1, month, day, hour); + p1 = inflow->patterns[DAILY_PATTERN]; + if ( p1 >= 0 ) f *= getPatternFactor(p1, month, day, hour); + p1 = inflow->patterns[HOURLY_PATTERN]; + p2 = inflow->patterns[WEEKEND_PATTERN]; + if ( p2 >= 0 ) + { + if ( day == 0 || day == 6 ) + f *= getPatternFactor(p2, month, day, hour); + else if ( p1 >= 0 ) + f *= getPatternFactor(p1, month, day, hour); + } + else if ( p1 >= 0 ) f *= getPatternFactor(p1, month, day, hour); + return f * inflow->avgValue; + +} + +//============================================================================= + +void inflow_initDwfPattern(int j) +// +// Input: j = time pattern index +// Output: none +// Purpose: initialzes a dry weather inflow time pattern. +// +{ + int i; + for (i=0; i<24; i++) Pattern[j].factor[i] = 1.0; + Pattern[j].count = 0; + Pattern[j].type = -1; + Pattern[j].ID = NULL; +} + +//============================================================================= + +int inflow_readDwfPattern(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error message +// Purpose: reads values of a time pattern from a line of input data. +// +// Format of data line is: +// patternID patternType value(1) value(2) ... +// patternID value(n) value(n+1) .... (for continuation lines) +{ + int i, j, k, n = 1; + + // --- check for minimum number of tokens + if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); + + // --- check that pattern exists in database + j = project_findObject(TIMEPATTERN, tok[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); + + // --- check if this is first line of pattern + // (ID pointer will not have been assigned yet) + if ( Pattern[j].ID == NULL ) + { + // --- assign ID pointer & pattern type + Pattern[j].ID = project_findID(TIMEPATTERN, tok[0]); + k = findmatch(tok[1], PatternTypeWords); + if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); + Pattern[j].type = k; + n = 2; + } + + // --- start reading pattern factors from rest of line + while ( ntoks > n && Pattern[j].count < 24 ) + { + i = Pattern[j].count; + if ( !getDouble(tok[n], &Pattern[j].factor[i]) ) + return error_setInpError(ERR_NUMBER, tok[n]); + Pattern[j].count++; + n++; + } + return 0; +} + +//============================================================================= + +double getPatternFactor(int p, int month, int day, int hour) +// +// Input: p = time pattern index +// month = current month of year of simulation +// day = current day of week of simulation +// hour = current hour of day of simulation +// Output: returns value of a time pattern multiplier +// Purpose: computes time pattern multiplier for a specific point in time. +{ + switch ( Pattern[p].type ) + { + case MONTHLY_PATTERN: + if ( month >= 0 && month < 12 ) return Pattern[p].factor[month]; + break; + case DAILY_PATTERN: + if ( day >= 0 && day < 7 ) return Pattern[p].factor[day]; + break; + case HOURLY_PATTERN: + if ( hour >= 0 && hour < 24 ) return Pattern[p].factor[hour]; + break; + case WEEKEND_PATTERN: + if ( day == 0 || day == 6 ) + { + if ( hour >= 0 && hour < 24 ) return Pattern[p].factor[hour]; + } + break; + } + return 1.0; +} diff --git a/src/inlet.c b/src/inlet.c new file mode 100644 index 000000000..4defe127e --- /dev/null +++ b/src/inlet.c @@ -0,0 +1,1955 @@ +//----------------------------------------------------------------------------- +// inlet.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 10/08/22 (Build 5.2.2) +// Author: L. Rossman +// +// Street/Channel Inlet Functions +// +// Computes capture efficiency of inlets placed in Street conduits +// or Rectangular/Trapezoidal channels using FHWA HEC-22 methods (see +// Brown, S.A. et al., Urban Drainage Design Manual, Federal Highway +// Administration Hydraulic Engineering Circular No. 22, 3rd Edition, +// FHWA-NHI-10-009, August 2013). +// +// Build 5.2.1: +// - Substitutes the constant BIG for HUGE. +// Build 5.2.2: +// - Additional statistics added to Street Flow Summary table. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include +#include "headers.h" + +// Grate inlet +typedef struct +{ + int type; // type of grate used + double length; // length (parallel to flow) (ft) + double width; // width (perpendicular to flow) (ft) + double fracOpenArea; // fraction of grate area that is open + double splashVeloc; // splash-over velocity (ft/s) +} TGrateInlet; + +// Slotted drain inlet +typedef struct +{ + double length; // length (parallel to flow) (ft) + double width; // width (perpendicular to flow) (ft) +} TSlottedInlet; + +// Curb opening inlet +typedef struct +{ + double length; // length of curb opening (ft) + double height; // height of curb opening (ft) + int throatAngle; // type of throat angle +} TCurbInlet; + +// Custom inlet +typedef struct +{ + int onGradeCurve; // flow diversion curve index + int onSagCurve; // flow rating curve index +} TCustomInlet; + +// Inlet design object +typedef struct +{ + char * ID; // name assigned to inlet design + int type; // type of inlet used (grate, curb, etc) + TGrateInlet grateInlet; // length = 0 if not used + TSlottedInlet slottedInlet; // length = 0 if not used + TCurbInlet curbInlet; // length = 0 if not used + int customCurve; // curve index = -1 if not used +} TInletDesign; + + +// Inlet performance statistics +typedef struct +{ + int flowPeriods; // # periods with approach flow + int capturePeriods; // # periods with captured flow + int backflowPeriods; // # periods with backflow + double peakFlow; // peak flow seen by inlet (cfs) + double peakFlowCapture; // capture efficiency at peak flow + double avgFlowCapture; // average capture efficiency + double bypassFreq; // frequency of bypass flow +} TInletStats; + +// Inlet list object +struct TInlet +{ + int linkIndex; // index of conduit link with the inlet + int designIndex; // index of inlet's design + int nodeIndex; // index of node receiving captured flow + int numInlets; // # inlets on each side of street or in channel + int placement; // whether inlet is on-grade or on-sag + double clogFactor; // fractional degree of inlet clogging + double flowLimit; // inlet flow restriction (cfs) + double localDepress; // local gutter depression (ft) + double localWidth; // local depression width (ft) + + double flowFactor; // flow = flowFactor * (flow spread)^2.67 + double flowCapture; // captured flow rate (cfs) + double backflow; // backflow from capture node (cfs) + double backflowRatio; // inlet backflow / capture node overflow + TInletStats stats; // inlet performance statistics + TInlet * nextInlet; // next inlet in list +}; + +// Shared inlet variables +TInletDesign * InletDesigns; // array of available inlet designs +int InletDesignCount; // number of inlet designs +int UsesInlets; // TRUE if project uses inlets + +//----------------------------------------------------------------------------- +// Enumerations +//----------------------------------------------------------------------------- + +enum InletType { + GRATE_INLET, CURB_INLET, COMBO_INLET, SLOTTED_INLET, + DROP_GRATE_INLET, DROP_CURB_INLET, CUSTOM_INLET +}; + +enum GrateType { + P50, P50x100, P30, CURVED_VANE, TILT_BAR_45, + TILT_BAR_30, RETICULINE, GENERIC +}; + +enum InletPlacementType { AUTOMATIC, ON_GRADE, ON_SAG }; + +enum ThroatAngleType { HORIZONTAL_THROAT, INCLINED_THROAT, VERTICAL_THROAT }; + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +static char* InletTypeWords[] = + {"GRATE", "CURB", "", "SLOTTED", "DROP_GRATE", "DROP_CURB", "CUSTOM", NULL}; + +static char* GrateTypeWords[] = + {"P_BAR-50", "P_BAR-50x100", "P_BAR-30", "CURVED_VANE", "TILT_BAR-45", "TILT_BAR-30", + "RETICULINE", "GENERIC", NULL}; + +static char* ThroatAngleWords[] = + {"HORIZONTAL", "INCLINED", "VERTICAL", NULL}; + +static char *PlacementTypeWords[] = + {"AUTOMATIC", "ON_GRADE", "ON_SAG"}; + +// Coefficients for cubic polynomials fitted to Splash Over Velocity v. +// Grate Length curves in Chart 5B of HEC-22 manual taken from Denver +// UDFCD manual. +static const double SplashCoeffs[][4] = { + {2.22, 4.03, 0.65, 0.06}, //P_BAR-50 + {0.74, 2.44, 0.27, 0.02}, //P_BAR-50x100 + {1.76, 3.12, 0.45, 0.03}, //P_BAR-30 + {0.30, 4.85, 1.31, 0.15}, //Curved_Vane + {0.99, 2.64, 0.36, 0.03}, //Tilt_Bar-45 + {0.51, 2.34, 0.2, 0.01}, //Tilt_Bar-30 + {0.28, 2.28, 0.18, 0.01}}; //Reticuline + +// Grate opening ratios (Chart 9B of HEC-22 manual) +static const double GrateOpeningRatios[] = { + 0.90, //P_BAR-50 + 0.80, //P_BAR-50x100 + 0.60, //P_BAR-30 + 0.35, //Curved_Vane + 0.17, //Tilt_Bar-45 (assumed) + 0.34, //Tilt_Bar-30 + 0.80, //Reticuline + 1.00}; //Generic + +//----------------------------------------------------------------------------- +// Imported Variables +//----------------------------------------------------------------------------- +extern TLinkStats* LinkStats; // defined in STATS.C +extern TNodeStats* NodeStats; // defined in STATS.C + +//----------------------------------------------------------------------------- +// Local Shared Variables +//----------------------------------------------------------------------------- +// Variables as named in the HEC-22 manual. +static double Sx; // street cross slope +static double SL; // conduit longitudinal slope +static double Sw; // gutter + cross slope +static double a; // street gutter depression (ft) +static double W; // street gutter width (ft) +static double T; // top width of flow spread (ft) +static double n; // Manning's roughness coeff. + +// Additional variables +static int Nsides; // 1- or 2-sided street +static double Tcrown; // distance from street curb to crown (ft) +static double Beta; // = 1.486 * sqrt(SL) / n +static double Qfactor; // factor f in Izzard's eqn. Q = f*T^2.67 +static TXsect* xsect; // cross-section data of inlet's conduit +static double* InletFlow; // captured inlet flow received by each node +static TInlet* FirstInlet; // head of list of deployed inlets + +//----------------------------------------------------------------------------- +// External functions (declared in inlet.h) +//----------------------------------------------------------------------------- +// inlet_create called by createObjects in project.c +// inlet_delete called by deleteObjects in project.c +// inlet_readDesignParams called by parseLine in input.c +// inlet_readUsageParams called by parseLine in input.c +// inlet_validate called by project_validate +// inlet_findCapturedFlows called by routing_execute +// inlet_adjustQualInflows called by routing_execute +// inlet_adjustQualOutflows called by routing execute +// inlet_writeStatsReport called by statsrpt_writeReport +// inlet_capturedFlow called by findLinkMassFlow in qualrout.c + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static int readGrateInletParams(int inletIndex, char* tok[], int ntoks); +static int readCurbInletParams(int inletIndex, char* tok[], int ntoks); +static int readSlottedInletParams(int inletIndex, char* tok[], int ntoks); +static int readCustomInletParams(int inletIndex, char* tok[], int ntoks); + +static void initInletStats(TInlet* inlet); +static void updateInletStats(TInlet* inlet, double q); +static void writeStreetStatsHeader(); +static void writeStreetStats(int link); + +static void getBackflowRatios(); +static double getInletArea(TInlet* inlet); + +static int getInletPlacement(TInlet* inlet, int node); +static void getConduitGeometry(TInlet* inlet); +static double getFlowSpread(double flow); +static double getEo(double slopeRatio, double spread, double gutterWidth); + +static double getCustomCapturedFlow(TInlet* inlet, double flow, double depth); +static double getOnGradeCapturedFlow(TInlet* inlet, double flow, double depth); +static double getOnGradeInletCapture(int inletIndex, double flow, double depth); +static double getGrateInletCapture(int inletIndex, double flow); +static double getCurbInletCapture(double flow, double length); + +static double getGutterFlowRatio(double gutterWidth); +static double getGutterAreaRatio(double grateWidth, double area); +static double getSplashOverVelocity(int grateType, double grateLength); + +static double getOnSagCapturedFlow(TInlet* inlet, double flow, double depth); +static double getOnSagInletCapture(int inletIndex, double depth); +static void findOnSagGrateFlows(int inletIndex, double depth, + double *weirFlow, double *orificeFlow); +static void findOnSagCurbFlows(int inletIndex, double depth, + double openingLength, double *weirFlow, + double *orificeFlow); +static double getCurbOrificeFlow(double flowDepth, double openingHeight, + double openingLength, int throatAngle); +static double getOnSagSlottedFlow(int inletIndex, double depth); + +//============================================================================= + +int inlet_create(int numInlets) +// +// Input: numInlets = number of inlet designs to create +// Output: none +// Purpose: creats a collection of inlet designs. +// +{ + int i; + + InletDesigns = NULL; + InletFlow = NULL; + InletDesignCount = 0; + UsesInlets = FALSE; + FirstInlet = NULL; + InletDesigns = (TInletDesign *)calloc(numInlets, sizeof(TInletDesign)); + if (InletDesigns == NULL) return ERR_MEMORY; + InletDesignCount = numInlets; + + InletFlow = (double *)calloc(Nobjects[NODE], sizeof(double)); + if (InletFlow == NULL) return ERR_MEMORY; + + for (i = 0; i < InletDesignCount; i++) + { + InletDesigns[i].customCurve = -1; + InletDesigns[i].curbInlet.length = 0.0; + InletDesigns[i].grateInlet.length = 0.0; + InletDesigns[i].slottedInlet.length = 0.0; + InletDesigns[i].type = CUSTOM_INLET; + } + return 0; +} + +//============================================================================= + +void inlet_delete() +// +// Input: none +// Output: none +// Purpose: frees all memory allocated for inlet analysis. +// +{ + TInlet* inlet = FirstInlet; + TInlet* nextInlet; + while (inlet) + { + nextInlet = inlet->nextInlet; + free(inlet); + inlet = nextInlet; + } + FirstInlet = NULL; + FREE(InletFlow); + FREE(InletDesigns); +} + +//============================================================================= + +int inlet_readDesignParams(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: extracts a set of inlet design parameters from a tokenized line +// of the [INLETS] section of a SWMM input file. +// +// Format of input line is: +// ID GRATE Length Width GrateType (OpenArea) (SplashVeloc) +// ID CURB Length Height (ThroatType) +// ID SLOTTED Length Width +// ID DROP_GRATE Length Width GrateType (OpenArea) (SplashVeloc) +// ID DROP_CURB Length Height +// ID CUSTOM CurveID +// +{ + int i; + + // --- check for minimum number of tokens + if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); + + // --- check that design ID already registered in project + i = project_findObject(INLET, tok[0]); + if ( i < 0 ) return error_setInpError(ERR_NAME, tok[0]); + InletDesigns[i].ID = project_findID(INLET, tok[0]); + + // --- retrieve type of inlet design + InletDesigns[i].type = findmatch(tok[1], InletTypeWords); + + // --- read inlet's design parameters + switch (InletDesigns[i].type) + { + case GRATE_INLET: + case DROP_GRATE_INLET: + return readGrateInletParams(i, tok, ntoks); + case CURB_INLET: + case DROP_CURB_INLET: + return readCurbInletParams(i, tok, ntoks); + case SLOTTED_INLET: + return readSlottedInletParams(i, tok, ntoks); + case CUSTOM_INLET: + return readCustomInletParams(i, tok, ntoks); + default: return error_setInpError(ERR_KEYWORD, tok[1]); + } + return 0; +} +//============================================================================= + +int inlet_readUsageParams(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: extracts inlet usage parameters from a tokenized line +// of the [INLET_USAGE] section of a SWMM input file. +// +// Format of input line is: +// linkID inletID nodeID (#Inlets %Clog Qmax aLocal wLocal placement) +// where +// linkID = ID name of link containing the inlet +// inletID = ID name of inlet design being used +// nodeID = ID name of node receiving captured flow +// #Inlets = number of identical inlets used (default = 1) +// %Clog = percent that inlet is clogged +// Qmax = maximum flow that inlet can capture (default = 0 (no limit)) +// aLocal = local gutter depression (ft or m) (default = 0) +// wLocal = width of local gutter depression (ft or m) (default = 0) +// placement = ON_GRADE, ON_SAG, or AUTO (the default) +// +{ + int linkIndex, designIndex, nodeIndex, numInlets = 1; + int placement = AUTOMATIC; + double flowLimit = 0.0, pctClogged = 0.0; + double aLocal = 0.0, wLocal = 0.0; + TInlet* inlet; + + // --- check that inlet's link exists + if (ntoks < 3) return error_setInpError(ERR_ITEMS, ""); + linkIndex = project_findObject(LINK, tok[0]); + if (linkIndex < 0) return error_setInpError(ERR_NAME, tok[0]); + + // --- check that inlet design type exists + designIndex = project_findObject(INLET, tok[1]); + if (designIndex < 0) return error_setInpError(ERR_NAME, tok[1]); + + // --- check that receiving node exists + nodeIndex = project_findObject(NODE, tok[2]); + if (nodeIndex < 0) return error_setInpError(ERR_NAME, tok[2]); + + // --- get number of inlets + if (ntoks > 3) + if (!getInt(tok[3], &numInlets) || numInlets < 1) + return error_setInpError(ERR_NUMBER, tok[3]); + + // --- get flow limit & percent clogged + if (ntoks > 4) + { + if (!getDouble(tok[4], &pctClogged) || pctClogged < 0.0 + || pctClogged > 99.) + return error_setInpError(ERR_NUMBER, tok[4]); + } + if (ntoks > 5) + if (!getDouble(tok[5], &flowLimit) || flowLimit < 0.0) + return error_setInpError(ERR_NUMBER, tok[5]); + + // --- get local depression parameters + if (ntoks > 6) + if (!getDouble(tok[6], &aLocal) || aLocal < 0.0) + return error_setInpError(ERR_NUMBER, tok[6]); + if (ntoks > 7) + if (!getDouble(tok[7], &wLocal) || wLocal < 0.0) + return error_setInpError(ERR_NUMBER, tok[7]); + + // --- get inlet placement + if (ntoks > 8) + { + placement = findmatch(tok[8], PlacementTypeWords); + if (placement < 0) return error_setInpError(ERR_KEYWORD, tok[8]); + } + + // --- create an inlet usage object for the link + inlet = Link[linkIndex].inlet; + if (inlet == NULL) + { + inlet = (TInlet *)malloc(sizeof(TInlet)); + if (!inlet) return error_setInpError(ERR_MEMORY, ""); + Link[linkIndex].inlet = inlet; + inlet->nextInlet = FirstInlet; + FirstInlet = inlet; + } + + // --- save inlet usage parameters + inlet->linkIndex = linkIndex; + inlet->designIndex = designIndex; + inlet->nodeIndex = nodeIndex; + inlet->numInlets = numInlets; + inlet->placement = placement; + inlet->clogFactor = 1.0 - (pctClogged / 100.); + inlet->flowLimit = flowLimit / UCF(FLOW); + inlet->localDepress = aLocal / UCF(LENGTH); + inlet->localWidth = wLocal / UCF(LENGTH); + inlet->flowFactor = 0.0; + inlet->backflowRatio = 0.0; + initInletStats(inlet); + UsesInlets = TRUE; + return 0; +} + +//============================================================================= + +void inlet_validate() +// +// Input: none +// Output: none +// Purpose: checks that inlets have been assigned to conduits with proper +// cross section shapes and counts the number of inlets that each +// node receives either bypased or captured flow from. +// +{ + int i, j, inletType, inletValid; + TInlet* inlet; + TInlet* prevInlet; + + // --- traverse the list of inlets placed in conduits + if (!UsesInlets) return; + prevInlet = FirstInlet; + inlet = FirstInlet; + while (inlet) + { + // --- check that inlet's conduit can accept the inlet's type + inletValid = FALSE; + i = inlet->linkIndex; + xsect = &Link[i].xsect; + inletType = InletDesigns[inlet->designIndex].type; + if (inletType == CUSTOM_INLET) + { + j = InletDesigns[inlet->designIndex].customCurve; + if (j >= 0) + { + if (Curve[j].curveType == DIVERSION_CURVE || + Curve[j].curveType == RATING_CURVE) + inletValid = TRUE; + } + } + else if ((xsect->type == TRAPEZOIDAL || xsect->type == RECT_OPEN) && + (inletType == DROP_GRATE_INLET || + inletType == DROP_CURB_INLET)) + inletValid = TRUE; + else if (xsect->type == STREET_XSECT && + inletType != DROP_GRATE_INLET && + inletType != DROP_CURB_INLET) + inletValid = TRUE; + + // --- if inlet placement is valid then + if (inletValid) + { + // --- record that receptor node has inlets + Node[Link[i].node2].inlet = BYPASS; + Node[inlet->nodeIndex].inlet = CAPTURE; + + // --- initialize inlet's backflow + inlet->backflow = 0.0; + + // --- compute street inlet's flow factor for Izzard's eqn. + // (used in Q = flowFactor * Spread^2.67 equation) + getConduitGeometry(inlet); + inlet->flowFactor = (0.56/n) * pow(SL,0.5) * pow(Sx,1.67); + + // --- save reference to current inlet & continue to next inlet + prevInlet = inlet; + inlet = inlet->nextInlet; + } + + // --- if inlet placement is not valid then issue a warning message + // and remove the inlet from the conduit + else + { + report_writeWarningMsg(WARN12, Link[i].ID); + if (inlet == FirstInlet) + { + FirstInlet = inlet->nextInlet; + prevInlet = FirstInlet; + free(inlet); + inlet = FirstInlet; + } + else + { + prevInlet->nextInlet = inlet->nextInlet; + free(inlet); + inlet = prevInlet->nextInlet; + } + Link[i].inlet = NULL; + } + } + + // --- determine how capture node's overflow is split between its inlets + getBackflowRatios(); +} + +//============================================================================= + +void inlet_findCapturedFlows(double tStep) +// +// Input: tStep = current flow routing time step (sec) +// Output: none +// Purpose: computes flow captured by each inlet and adjusts the +// lateral flows of the inlet's bypass and capture nodes accordingly. +// +// This function is called after regular lateral flows to all nodes have been +// set but before a flow routing step has been taken. +{ + int i, j, m, placement; + double q; + TInlet *inlet; + + // --- For non-DW routing find conduit flow into each node + // (used to limit max. amount of on-sag capture) + if (!UsesInlets) return; + memset(InletFlow, 0, Nobjects[NODE]*sizeof(double)); + if (RouteModel != DW) + { + for (j = 0; j < Nobjects[NODE]; j++) + Node[j].inflow = MAX(0., Node[j].newLatFlow); + for (i = 0; i < Nobjects[LINK]; i++) + Node[Link[i].node2].inflow += MAX(0.0, Link[i].newFlow); + } + + // --- loop through each inlet + for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) + { + // --- identify indexes of inlet's bypass (j) and capture (m) nodes + i = inlet->linkIndex; + j = Link[i].node2; + m = inlet->nodeIndex; + + // --- get inlet's placement (ON_GRADE or ON_SAG) + placement = getInletPlacement(inlet, j); + + // --- find flow captured by a Custom inlet + if (InletDesigns[inlet->designIndex].type == CUSTOM_INLET) + { + q = fabs(Link[i].newFlow); + inlet->flowCapture = getCustomCapturedFlow(inlet, q, Node[j].newDepth); + } + + // --- find flow captured by on-grade inlet + else if (placement == ON_GRADE) + { + q = fabs(Link[i].newFlow); + inlet->flowCapture = getOnGradeCapturedFlow(inlet, q, Node[j].newDepth); + } + + // --- find flow captured by on-sag inlet + else + { + q = Node[j].inflow; + inlet->flowCapture = getOnSagCapturedFlow(inlet, q, Node[j].newDepth); + } + if (fabs(inlet->flowCapture) < FUDGE) inlet->flowCapture = 0.0; + + // --- add to total flow captured by inlet's node + InletFlow[j] += inlet->flowCapture; + + // --- capture node's overflow becomes inlet's backflow + inlet->backflow = Node[m].overflow * inlet->backflowRatio; + if (fabs(inlet->backflow) < FUDGE) inlet->backflow = 0.0; + } + + // --- make second pass through each inlet + for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) + { + // --- identify indexes of inlet's bypass (j) and capture (m) nodes + i = inlet->linkIndex; + j = Link[i].node2; + m = inlet->nodeIndex; + + // --- for on-sag placement under non-DW routing, captured flow + // is limited to inlet's share of bypass node's inflow plus + // any stored volume + if (RouteModel != DW && getInletPlacement(inlet, j) == ON_SAG) + { + q = Node[j].newVolume / tStep; + q += MAX(Node[j].inflow, 0.0); + if (InletFlow[j] > q) + inlet->flowCapture *= q / InletFlow[j]; + } + + // --- adjust lateral flows at bypass and capture nodes + // (subtract captured flow from bypass node, add it to capture + // node, and add any backflow to bypass node) + Node[j].newLatFlow -= (inlet->flowCapture - inlet->backflow); + Node[m].newLatFlow += inlet->flowCapture; + + // --- update inlet's performance if reporting has begun + if (getDateTime(NewRoutingTime) > ReportStart) + updateInletStats(inlet, fabs(Link[i].newFlow)); + } +} + +//============================================================================= + +void inlet_adjustQualInflows() +// +// Input: none +// Output: none +// Purpose: adjusts accumulated flow rates and pollutant mass inflows at each +// inlet's bypass and capture nodes after a flow routing step has +// been taken prior to a quality routing step. +// +{ + int i, j, m, p; + double qNet; + TInlet* inlet; + + if (!UsesInlets) return; + if (IgnoreQuality || Nobjects[POLLUT] == 0) return; + for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) + { + // --- identify indexes of inlet's bypass (j) and capture (m) nodes + i = inlet->linkIndex; + j = Link[i].node2; + m = inlet->nodeIndex; + + // --- there's a net flow from the bypass to the capture node + qNet = inlet->flowCapture - inlet->backflow; + if (qNet > 0.0) + { + // --- add net capture flow to capture node's accumulated flow + // inflow for quality routing + Node[m].qualInflow += qNet; + + // --- and do the same for pollutant mass flows + // (Node[m].newQual is the mass inflow accumulator for node m) + for (p = 0; p < Nobjects[POLLUT]; p++) + Node[m].newQual[p] += qNet * Node[j].oldQual[p]; + } + + // --- there's a net backflow from the capture to the bypass node + else + { + // --- add the backflow flow rate and pollutant mass flow to the + // bypass node's accumulated flow and pollutant mass inflow + qNet = -qNet; + Node[j].qualInflow += qNet; + for (p = 0; p < Nobjects[POLLUT]; p++) + Node[j].newQual[p] += qNet * Node[m].oldQual[p]; + } + } +} + +//============================================================================= + +void inlet_adjustQualOutflows() +// +// Input: none +// Output: none +// Purpose: adjusts mass balance totals after a complete routing step has been +// taken so as not to treat inlet transfer flows as system outflows. +// +{ + int j, p; + double q, w; + TInlet* inlet; + + // --- these variables, declared in massbal.c, accumulate system-wide flow and + // pollutant mass fluxes over a time step to use in mass balances + extern TRoutingTotals StepFlowTotals; + extern TRoutingTotals* StepQualTotals; + + // --- examine each node + for (j = 0; j < Nobjects[NODE]; j++) + { + // --- node receives captured flow from an inlet + if (Node[j].inlet == CAPTURE) + { + // --- node also has an overflow (e.g., it's a surcharged sewer node) + q = Node[j].overflow; + if (q > 0.0) + { + // --- remove overflow from system flooding total since it does + // not leave the system (it is sent to inlet's bypass node) + StepFlowTotals.flooding -= q; + + // --- also remove pollutant overflow mass from system totals + if (!IgnoreQuality) + for (p = 0; p < Nobjects[POLLUT]; p++) + { + w = q * Node[j].newQual[p]; + StepQualTotals[p].flooding -= w; + } + } + } + } + + // --- for WQ analysis, examine each inlet's bypass node + if (!IgnoreQuality && Nobjects[POLLUT] > 0) + { + for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) + { + j = Link[inlet->linkIndex].node2; + + // --- inlet has net positive flow capture leading to + // node having a net negative lateral inflow + q = inlet->flowCapture - inlet->backflow; + if (q > 0.0 && Node[j].newLatFlow < 0.0) + + // --- remove the pollutant mass in the captured flow from + // the system totals since it does not leave the system + // (it is sent to the inlet's capture node) + for (p = 0; p < Nobjects[POLLUT]; p++) + { + w = q * Node[j].newQual[p]; + StepQualTotals[p].outflow -= w; + } + } + } +} + +//============================================================================= + +void inlet_writeStatsReport() +// +// Input: none +// Output: none +// Purpose: writes table of street & inlet flow statistics to SWMM's report file. +// +{ + int j, header = FALSE; + + if (Nobjects[STREET] == 0) return; + for (j = 0; j < Nobjects[LINK]; j++) + { + if (Link[j].xsect.type == STREET_XSECT) + { + if (!header) + { + writeStreetStatsHeader(); + header = TRUE; + } + writeStreetStats(j); + } + } + report_writeLine(""); +} + +//============================================================================= + +double inlet_capturedFlow(int i) +// +// Input: i = a link index +// Output: returns captured flow rate (cfs) +// Purpose: gets the current flow captured by an inlet. +// +{ + if (Link[i].inlet) return Link[i].inlet->flowCapture; + return 0.0; +} + +//============================================================================= + +int readGrateInletParams(int i, char* tok[], int ntoks) +{ +// +// Input: i = inlet index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: extracts a grate's inlet parameters from a set of string tokens. +// + int grateType; + double width, length, areaRatio = 0.0, vSplash = 0.0; + + // --- check for enough tokens + if (ntoks < 5) return error_setInpError(ERR_ITEMS, ""); + + // --- retrieve length & width + if (!getDouble(tok[2], &length) || length <= 0.0) + return error_setInpError(ERR_NUMBER, tok[2]); + if (!getDouble(tok[3], &width) || width <= 0.0) + return error_setInpError(ERR_NUMBER, tok[3]); + + // --- retrieve grate type + grateType = findmatch(tok[4], GrateTypeWords); + if (grateType < 0) return error_setInpError(ERR_KEYWORD, tok[4]); + + // --- only read open area & splash velocity for GENERIC type grate + if (grateType == GENERIC) + { + if (ntoks < 6) return error_setInpError(ERR_ITEMS, ""); + if (!getDouble(tok[5], &areaRatio) || areaRatio <= 0.0 + || areaRatio > 1.0) return error_setInpError(ERR_NUMBER, tok[5]); + if (ntoks > 6) + { + if (!getDouble(tok[6], &vSplash) || vSplash < 0.0) + return error_setInpError(ERR_NUMBER, tok[6]); + } + } + + // --- save grate inlet parameters + InletDesigns[i].grateInlet.length = length / UCF(LENGTH); + InletDesigns[i].grateInlet.width = width / UCF(LENGTH); + InletDesigns[i].grateInlet.type = grateType; + InletDesigns[i].grateInlet.fracOpenArea = areaRatio; + InletDesigns[i].grateInlet.splashVeloc = vSplash / UCF(LENGTH); + + // --- check if grate is part of a combo inlet + if (InletDesigns[i].type == GRATE_INLET && + InletDesigns[i].curbInlet.length > 0.0) + InletDesigns[i].type = COMBO_INLET; + return 0; +} + +//============================================================================= + +int readCurbInletParams(int i, char* tok[], int ntoks) +// +// Input: i = inlet index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: extracts curb opening inlet parameters from a set of string tokens. +// +{ + int throatAngle; + double height, length; + + // --- check for enough tokens + if (ntoks < 4) return error_setInpError(ERR_ITEMS, ""); + + // --- retrieve length & width of opening + if (!getDouble(tok[2], &length) || length <= 0.0) + return error_setInpError(ERR_NUMBER, tok[2]); + if (!getDouble(tok[3], &height) || height <= 0.0) + return error_setInpError(ERR_NUMBER, tok[3]); + + // --- retrieve type of throat angle for curb inlet + throatAngle = VERTICAL_THROAT; + if (InletDesigns[i].type == CURB_INLET && ntoks > 4) + { + throatAngle = findmatch(tok[4], ThroatAngleWords); + if (throatAngle < 0) return error_setInpError(ERR_KEYWORD, tok[4]); + } + + // ---- save curb opening inlet parameters + InletDesigns[i].curbInlet.length = length / UCF(LENGTH); + InletDesigns[i].curbInlet.height = height / UCF(LENGTH); + InletDesigns[i].curbInlet.throatAngle = throatAngle; + + // --- check if curb inlet is part of a combo inlet + if (InletDesigns[i].type == CURB_INLET && + InletDesigns[i].grateInlet.length > 0.0) + InletDesigns[i].type = COMBO_INLET; + return 0; +} + +//============================================================================= + +int readSlottedInletParams(int i, char* tok[], int ntoks) +// +// Input: i = inlet index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: extracts slotted drain inlet parameters from a set of string tokens. +// +{ + double width, length; + + // --- check for enough tokens + if (ntoks < 4) return error_setInpError(ERR_ITEMS, ""); + + // --- retrieve length and width + if (!getDouble(tok[2], &length) || length <= 0.0) + return error_setInpError(ERR_NUMBER, tok[2]); + if (!getDouble(tok[3], &width) || width <= 0.0) + return error_setInpError(ERR_NUMBER, tok[3]); + + // --- save slotted inlet parameters + InletDesigns[i].slottedInlet.length = length / UCF(LENGTH); + InletDesigns[i].slottedInlet.width = width / UCF(LENGTH); + return 0; +} + +//============================================================================= + +int readCustomInletParams(int i, char* tok[], int ntoks) +// +// Input: i = inlet index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: extracts custom inlet parameters from a set of string tokens. +// +{ + int c; // capture curve index + + if (ntoks < 3) return error_setInpError(ERR_ITEMS, ""); + else + { + c = project_findObject(CURVE, tok[2]); + if (c < 0) return error_setInpError(ERR_NAME, tok[2]); + } + InletDesigns[i].customCurve = c; + return 0; +} + +//============================================================================= + +void initInletStats(TInlet* inlet) +// +// Input: inlet = an inlet object placed in a conduit link +// Output: none +// Purpose: initializes the performance statistics of an inlet. +// +{ + if (inlet) + { + inlet->flowCapture = 0.0; + inlet->backflow = 0.0; + inlet->stats.flowPeriods = 0; + inlet->stats.capturePeriods = 0; + inlet->stats.backflowPeriods = 0; + inlet->stats.peakFlow = 0.0; + inlet->stats.peakFlowCapture = 0; + inlet->stats.avgFlowCapture = 0; + inlet->stats.bypassFreq = 0; + } +} + +//============================================================================= + +void updateInletStats(TInlet* inlet, double q) +// +// Input: inlet = an inlet object placed in a conduit link +// q = inlet's approach flow (cfs) +// Output: none +// Purpose: updates the performance statistics of an inlet. +// +{ + double qCapture = inlet->flowCapture, + qBackflow = inlet->backflow, + qNet = qCapture - qBackflow, + qBypass = q - qNet, + fCapture = 0.0; + + // --- check for no flow condition + if (q < MIN_RUNOFF_FLOW && qBackflow <= 0.0) return; + inlet->stats.flowPeriods++; + + // --- there is positive net flow from inlet to capture node + if (qNet > 0.0) + { + inlet->stats.capturePeriods++; + fCapture = qNet / q; + fCapture = MIN(fCapture, 1.0); + inlet->stats.avgFlowCapture += fCapture; + if (qBypass > MIN_RUNOFF_FLOW) inlet->stats.bypassFreq++; + } + + // --- otherwise inlet receives backflow from capture node + else inlet->stats.backflowPeriods++; + + // --- update peak flow stats + if (q > inlet->stats.peakFlow) + { + inlet->stats.peakFlow = q; + inlet->stats.peakFlowCapture = fCapture * 100.0; + } +} + +//============================================================================= + +void writeStreetStatsHeader() +// +// Input: none +// Output: none +// Purpose: writes column headers for Street Flow Summary table to SWMM's report file. +// +{ + report_writeLine(""); + report_writeLine("*******************"); + report_writeLine("Street Flow Summary"); + report_writeLine("*******************"); + report_writeLine(""); + fprintf(Frpt.file, +"\n ---------------------------------------------------------------------------------------------------------------------------------------" +"\n Peak Avg. Bypass Back Peak Peak" +"\n Peak Maximum Maximum Flow Flow Flow Flow Capture Bypass" +"\n Flow Spread Depth Inlet Inlet Inlet Capture Capture Freq Freq / Inlet Flow"); + if (UnitSystem == US) fprintf(Frpt.file, +"\n Street Conduit %3s ft ft Design Location Count Pcnt Pcnt Pcnt Pcnt %3s %3s", + FlowUnitWords[FlowUnits], FlowUnitWords[FlowUnits], FlowUnitWords[FlowUnits]); + else fprintf(Frpt.file, +"\n Street Conduit %3s m m Design Location Pcnt Pcnt Pcnt Pcnt %3s %3s", + FlowUnitWords[FlowUnits], FlowUnitWords[FlowUnits], FlowUnitWords[FlowUnits]); + fprintf(Frpt.file, +"\n ---------------------------------------------------------------------------------------------------------------------------------------"); +} + +//============================================================================= + +void writeStreetStats(int link) +// +// Input: link = index of a conduit link containing an inlet +// Output: none +// Purpose: writes flow statistics for a Street conduit and its inlet to +// SWMM's report file. +// +{ + int k, t, placement; + double maxSpread, maxDepth, maxFlow; + double fp, cp, afc = 0.0, bpf = 0.0; + TInlet* inlet; + + // --- retrieve street parameters + k = Link[link].subIndex; + t = Link[link].xsect.transect; + inlet = Link[link].inlet; + + // --- get recorded max flow and depth + maxFlow = LinkStats[link].maxFlow; + maxDepth = LinkStats[link].maxDepth; + + // --- SWMM's spread (flow width) at max depth + maxSpread = xsect_getWofY(&Link[link].xsect, maxDepth) / Street[t].sides; + maxSpread = MIN(maxSpread, Street[t].width); +/* + // HEC-22's spread based on max flow (doesn't account for backwater) + Sx = Street[t].slope; + a = Street[t].gutterDepression; + W = Street[t].gutterWidth; + n = Street[t].roughness; + Qfactor = (0.56 / n) * sqrt(Conduit[k].slope) * pow(Sx, 1.67); + maxSpread = getFlowSpread(maxFlow / Street[t].sides); + maxSpread = MIN(maxSpread, Street[t].width); +*/ + // --- write street stats + fprintf(Frpt.file, "\n %-16s", Link[link].ID); + fprintf(Frpt.file, " %9.3f", maxFlow * UCF(FLOW)); + fprintf(Frpt.file, " %9.3f", maxSpread * UCF(LENGTH)); + fprintf(Frpt.file, " %9.3f", maxDepth * UCF(LENGTH)); + + // --- write inlet stats + if (inlet) + { + fprintf(Frpt.file, " %-16s", InletDesigns[inlet->designIndex].ID); + placement = getInletPlacement(inlet, Link[inlet->linkIndex].node2); + if (placement == ON_GRADE) + fprintf(Frpt.file, " ON-GRADE"); + else + fprintf(Frpt.file, " ON-SAG "); + fprintf(Frpt.file, " %5d", inlet->numInlets); + fp = inlet->stats.flowPeriods / 100.0; + if (fp > 0.0) + { + cp = inlet->stats.capturePeriods / 100.0; + fprintf(Frpt.file, " %7.2f", inlet->stats.peakFlowCapture); + if (cp > 0.0) + { + afc = inlet->stats.avgFlowCapture / cp; + bpf = inlet->stats.bypassFreq / cp; + } + fprintf(Frpt.file, " %7.2f", afc); + fprintf(Frpt.file, " %7.2f", bpf); + fprintf(Frpt.file, " %7.2f", inlet->stats.backflowPeriods / fp); + fprintf(Frpt.file, " %7.2f", (maxFlow / Street[t].sides) * UCF(FLOW) * + 0.01 * inlet->stats.peakFlowCapture / inlet->numInlets); + fprintf(Frpt.file, " %7.2f", maxFlow * UCF(FLOW) * 0.01 * + (100.0 - inlet->stats.peakFlowCapture)); + } + } +} + +//============================================================================= + +int getInletPlacement(TInlet* inlet, int j) +// +// Input: inlet = an inlet object placed in a conduit link +// j = index of inlet's bypass node +// Output: returns type of inlet placement +// Purpose: determines actual placement for an inlet with AUTOMATIC placement. +// +{ + if (inlet->placement == AUTOMATIC) + { + if (Node[j].degree > 0) return ON_GRADE; + else return ON_SAG; + } + else return inlet->placement; +} + +//============================================================================= + +void getConduitGeometry(TInlet* inlet) +// +// Input: inlet = an inlet object placed in a conduit link +// Output: none +// Purpose: assigns properties of an inlet's conduit to +// module-level shared variables used by other functions. +// +{ + int linkIndex = inlet->linkIndex; + int t, k = Link[linkIndex].subIndex; + + SL = Conduit[k].slope; // longitudinal slope + Beta = Conduit[k].beta; // 1.486 * sqrt(SL) / n + xsect = &Link[linkIndex].xsect; + + // --- if conduit has a Street cross section + if (xsect->type == STREET_XSECT) + { + t = xsect->transect; + Sx = Street[t].slope; // street cross slope + a = Street[t].gutterDepression; // gutter depression + W = Street[t].gutterWidth; // gutter width + n = Street[t].roughness; // street roughness + Nsides = Street[t].sides; // 1 or 2 sided street + Tcrown = Street[t].width; // distance from curb to crown + Qfactor = inlet->flowFactor; // factor used in Izzard's eqn. + + // --- add inlet's local depression to street's continuous depression + if (inlet && inlet->localDepress * inlet->localWidth > 0) + { + a += inlet->localDepress; // inlet depression + W = inlet->localWidth; // inlet depressed width + } + + // --- slope of depressed gutter section + if (W * a > 0.0) Sw = Sx + a / W; + else Sw = Sx; + } + + // --- conduit has rectangular or trapezoidal cross section + else + { + a = 0.0; + W = 0.0; + n = Conduit[k].roughness; + Nsides = 1; + Sx = 0.01; + Sw = Sx; + } +} + +//============================================================================= + +double getFlowSpread(double Q) +// +// Input: Q = conduit flow rate (cfs) +// Output: returns width of flow spread (ft) +// Purpose: computes width of flow spread across a Street cross section using +// HEC-22 equations derived from Izzard's form of the Manning eqn. +// +{ + int iter; + double f, f1, Sr, Ts1, Ts2, Tw, Qs, Eo; + + f = Qfactor; // = (0.56/n) * SL^0.5 * Sx^1.67 + + // --- no depressed curb + if (a == 0.0) + { + Ts1 = pow(Q / f, 0.375); //HEC-22 Eq(4-2) + } + else + { + // --- check if spread is within curb width + f1 = f * pow((a / W) / Sx, 1.67); + Tw = pow(Q / f1, 0.375); //HEC-22 Eq(4-2) + if (Tw <= W) Ts1 = Tw; + else + { + // --- spread extends beyond curb width + Sr = (Sx + a / W) / Sx; + iter = 1; + Ts1 = pow(Q / f, 0.375) - W; + if (Ts1 <= 0) Ts1 = Tw - W; + while (iter < 11) + { + Eo = getEo(Sr, Ts1, W); + Qs = (1.0 - Eo) * Q; //HEC-22 Eq(4-6) + Ts2 = pow(Qs / f, 0.375); //HEC-22 Eq(4-2) + if (fabs(Ts2 - Ts1) < 0.01) break; + Ts1 = Ts2; + iter++; + } + Ts1 = Ts2 + W; + } + } + return MIN(Ts1, Tcrown); +} + +//============================================================================= + +double getEo(double Sr, double Ts, double w) +// +// Input: Sr = ratio of gutter slope to street cross slope +// Ts = amount of flow spread outside of gutter width (ft) +// w = gutter width (ft) +// Output: returns ratio of gutter flow to total flow in street cross section +// Purpose: solves HEC-22 Eq. (4-4) for Eo with Ts/w substituted for +// (T/w) - 1 where Ts = T - w. +// +{ + double x; + x = Sr / (Ts / w); + x = pow((1.0 + x), 2.67) - 1.0; + x = 1.0 + Sr / x; + return 1.0 / x; +} + +//============================================================================= + +double getOnGradeCapturedFlow(TInlet* inlet, double q, double d) +// +// Input: inlet = an inlet object placed in a conduit link +// q = flow in link prior to any inlet capture (cfs) +// d = flow depth seen by inlet (ft) +// Output: returns flow captured by the inlet (cfs) +// Purpose: computes flow captured by an inlet placed on-grade. +// +// An inlet object placed in a conduit can have multiple inlets of +// the same type distributed along the conduit's length that all +// send their captured flow to the same sewer node. This function +// finds the total captured flow as each individual inlet is analyzed +// sequentially, where its approach flow has been reduced by the +// amount of flow captured by prior inlets. +{ + int i, + linkIndex; // index of link containing inlets + double qApproach, // single inlet's approach flow (cfs) + qc, // single inlet's captured flow (cfs) + qCaptured, // total flow captured by link's inlets (cfs) + qBypassed, // total flow bypassed by link's inlets (cfs) + qMax; // max. flow that a single inlet can capture (cfs) + + if (inlet->numInlets == 0) return 0.0; + linkIndex = inlet->linkIndex; + + // --- check that link has flow + qApproach = q; + if (qApproach < MIN_RUNOFF_FLOW) return 0.0; + + // --- store conduit geometry in shared variables + getConduitGeometry(inlet); + + // --- adjust flow for 2-sided street + qApproach /= Nsides; + qBypassed = qApproach; + qCaptured = 0.0; + + // --- set limit on max. flow captured per inlet + qMax = BIG; + if (inlet->flowLimit > 0.0) qMax = inlet->flowLimit; + + // --- evaluate each inlet + for (i = 1; i <= inlet->numInlets; i++) + { + qc = getOnGradeInletCapture(inlet->designIndex, qBypassed, d) * + inlet->clogFactor; + qc = MIN(qc, qMax); + qc = MIN(qc, qBypassed); + qCaptured += qc; + qBypassed -= qc; + if (qBypassed < MIN_RUNOFF_FLOW) break; + } + return qCaptured *= Nsides; +} + +//============================================================================= + +double getOnGradeInletCapture(int i, double Q, double d) +// +// Input: i = an InletDesigns index +// Q = flow rate seen by inlet (cfs) +// d = flow depth seen by inlet (ft) +// Output: returns captured flow rate (cfs) +// Purpose: finds the flow captured by a single on-grade inlet. +// +{ + double Q1 = Q, Qc = 0.0, Lsweep = 0.0, Lcurb = 0.0, Lgrate = 0.0; + + // --- drop curb inlet (in non-Street conduit) only operates in on sag mode + if (InletDesigns[i].type == DROP_CURB_INLET) + { + Qc = getOnSagInletCapture(i, d); + return MIN(Qc, Q); + } + + // --- drop grate inlet (in non-Street conduit) + if (InletDesigns[i].type == DROP_GRATE_INLET) + { + Qc = getGrateInletCapture(i, Q); + return MIN(Qc, Q); + } + + // --- Remaining inlet types apply to Street conduits + + // --- find flow spread + T = getFlowSpread(Q); + + // --- slotted inlet (behaves as a curb opening inlet per HEC-22) + if (InletDesigns[i].type == SLOTTED_INLET) + { + Qc = getCurbInletCapture(Q, InletDesigns[i].slottedInlet.length); + return MIN(Qc, Q); + } + + Lcurb = InletDesigns[i].curbInlet.length; + Lgrate = InletDesigns[i].grateInlet.length; + + // --- curb opening inlet + if (Lcurb > 0.0) + { + Lsweep = Lcurb - Lgrate; + if (Lsweep > 0.0) + { + Qc = getCurbInletCapture(Q1, Lsweep); + Q1 -= Qc; + } + } + + // --- grate inlet + if (Lgrate > 0.0 && Q1 > 0.0) + { + if (Q1 != Q) T = getFlowSpread(Q1); + Qc += getGrateInletCapture(i, Q1); + } + return Qc; +} + +//============================================================================= + +double getGrateInletCapture(int i, double Q) +// +// Input: i = inlet type index +// Q = flow rate seen by inlet (cfs) +// Output: returns captured flow rate (cfs) +// Purpose: finds the flow captured by an on-grade grate inlet. +// +{ + int grateType; + double Lg, // grate length (ft) + Wg, // grate width (ft) + A, // total cross section flow area (ft2) + Y, // flow depth (ft) + Eo, // ratio of gutter to total flow + V, // flow velocity (ft/s) + Vo, // splash-over velocity (ft/s) + Qo = Q, // flow over street area (cfs) + Rf = 1.0, // ratio of intercepted to total frontal flow + Rs = 0.0; // ratio of intercepted to total side flow + +// xsect, a, W, & Sx were from getConduitGeometry(). T was from getFlowSpread(). + + Lg = InletDesigns[i].grateInlet.length; + Wg = InletDesigns[i].grateInlet.width; + + // --- flow ratio for drop inlet + if (xsect->type == TRAPEZOIDAL || xsect->type == RECT_OPEN) + { + A = xsect_getAofS(xsect, Q / Beta); + Y = xsect_getYofA(xsect, A); + T = xsect_getWofY(xsect, Y); + Eo = Beta * pow(Y*Wg, 1.67) / pow(Wg + 2*Y, 0.67) / Q; + if (Wg > 0.99*xsect->yBot && xsect->type == TRAPEZOIDAL && xsect->sBot > 0.0) + { + Wg = xsect->yBot; + Sx = 1.0 / xsect->sBot; + } + } + + // --- flow ratio & area for conventional street gutter + else if (a == 0.0) + { + A = T * T * Sx / 2.0; + Eo = getGutterFlowRatio(Wg); // flow ratio based on grate width + if (T >= Tcrown) Qo = Qfactor * pow(Tcrown, 2.67); + } + + // --- flow ratio & area for composite street gutter + else + { + // --- spread confined to gutter + if (T <= W) A = T * T * Sw / 2.0; + + // --- spread beyond gutter width + else A = (T * T * Sx + a * W) / 2.0; + + // flow ratio based on gutter width corrected for grate width + Eo = getGutterFlowRatio(W); + if (Eo < 1.0) + { + if (T >= Tcrown) + Qo = Qfactor * pow(Tcrown, 2.67) / (1.0 - Eo); + Eo = Eo * getGutterAreaRatio(Wg, A); //HEC-22 Eq(4-20a) + } + } + + // --- flow and splash-over velocities + V = Qo / A; + grateType = InletDesigns[i].grateInlet.type; + if (grateType < 0 || grateType == GENERIC) + Vo = InletDesigns[i].grateInlet.splashVeloc; + else + Vo = getSplashOverVelocity(grateType, Lg); + + // --- frontal flow capture efficiency + if (V > Vo) Rf = 1.0 - 0.09 * (V - Vo); //HEC-22 Eq(4-18) + + // --- side flow capture efficiency + if (Eo < 1.0) + { + Rs = 1.0 / (1.0 + (0.15 * pow(V, 1.8) / + Sx / pow(Lg, 2.3))); //HEC-22 Eq(4-19) + } + + // --- return total flow captured + return Q * (Rf * Eo + Rs * (1.0 - Eo)); //HEC-22 Eq(4-21) +} + +//============================================================================= + +double getCurbInletCapture(double Q, double L) +// +// Input: Q = flow rate seen by inlet (cfs) +// L = length of inlet opening (ft) +// Output: returns captured flow rate (cfs) +// Purpose: finds the flow captured by an on-sag inlet. +// +{ + double Se = Sx, // equivalent gutter slope + Lt, // length for full capture + Sr, // ratio of gutter slope to cross slope + Eo = 0.0, // ratio of gutter to total flow + E = 1.0; // capture efficiency + +// a, W, Sx, Sw, SL, & n were from getConduitGeometry(). T was from getFlowSpread(). + + // --- for depressed gutter section + if (a > 0.0) + { + Sr = Sw / Sx; + Eo = getEo(Sr, T-W, W); + Se = Sx + Sw * Eo; //HEC-22 Eq(4-24) + } + + // --- opening length for full capture + Lt = 0.6 * pow(Q, 0.42) * pow(SL, 0.3) * + pow(1.0/(n*Se), 0.6); //HEC-22 Eq(4-22a) + + // --- capture efficiency for actual opening length + if (L < Lt) + { + E = 1.0 - (L/Lt); + E = 1 - pow(E, 1.8); //HEC-22 Eq(4-23) + } + E = MIN(E, 1.0); + E = MAX(E, 0.0); + return E * Q; +} + +//============================================================================= + +double getGutterFlowRatio(double w) +// +// Input: w = gutter width (ft) +// Output: returns a flow ratio +// Purpose: computes the ratio of flow over a width of gutter to the total +// flow in a street cross section. +// +{ + if (T <= w) return 1.0; + else if (a > 0.0) + return getEo(Sw / Sx, T - w, w); + else + return 1.0 - pow((1.0 - w / T), 2.67); //HEC-22 Eq(4-16) +} + +//============================================================================= + +double getGutterAreaRatio(double Wg, double A) +// +// Input: Wg = width of grate inlet (ft) +// A = total flow area (ft2) +// Output: returns an area ratio +// Purpose: computes the ratio of the flow area above a grate to the flow +// area above depressed gutter in a street cross section. +// +{ + double As, // flow area beyond gutter width (ft2) + Ag; // flow area over grate width (ft2) + + if (Wg >= W) return 1.0; + if (T <= Wg) return 1.0; + if (T <= W) return Wg / T; + As = 0.5 * SQR((T - W)) * Sx; + Ag = Wg * ( (T * Sx) + a - (Wg * Sw / 2.) ); + return Ag / (A - As); +} + +//============================================================================= + +double getSplashOverVelocity(int grateType, double L) +// +// Input: grateType = grate inlet type code +// L = length of grate inlet (ft) +// Output: returns a splash over velocity +// Purpose: computes the splash over velocity for a standard type of grate +// inlet as a function of its length. +// +{ + return SplashCoeffs[grateType][0] + + SplashCoeffs[grateType][1] * L - + SplashCoeffs[grateType][2] * L * L + + SplashCoeffs[grateType][3] * L * L * L; +} + +//============================================================================= + +double getOnSagCapturedFlow(TInlet* inlet, double q, double d) +// +// Input: inlet = an inlet object placed in a conduit link +// q = flow in link prior to any inlet capture (cfs) +// d = flow depth seen by inlet (ft) +// Output: returns flow captured by the inlet (cfs) +// Purpose: computes flow captured by an inlet placed on-sag. +// +{ + int linkIndex, designIndex, totalInlets; + double qCaptured = 0.0, qMax = BIG; + + if (inlet->numInlets == 0) return 0.0; + totalInlets = Nsides * inlet->numInlets; + linkIndex = inlet->linkIndex; + designIndex = inlet->designIndex; + + // --- store conduit geometry in shared variables + getConduitGeometry(inlet); + + // --- set flow limit per inlet + if (inlet->flowLimit > 0.0) + qMax = inlet->flowLimit; + + // --- find nominal flow captured by inlet + qCaptured = getOnSagInletCapture(designIndex, fabs(d)); + + // --- find actual flow captured by the inlet + qCaptured *= inlet->clogFactor; + qCaptured = MIN(qCaptured, qMax); + qCaptured *= (double)totalInlets; + return qCaptured; +} + +//============================================================================= + +double getOnSagInletCapture(int i, double d) +// +// Input: i = inlet type index +// d = water level seen by inlet (ft) +// Output: returns captured flow rate (cfs) +// Purpose: finds the flow captured by an on-sag inlet. +// +{ + double Lsweep = 0.0, Lcurb = 0.0, Lgrate = 0.0; + double Qsw = 0.0, //Sweeper curb opening weir flow + Qso = 0.0, //Sweeper curb opening orifice flow + Qgw = 0.0, //Grate weir flow + Qgo = 0.0, //Grate orifice flow + Qcw = 0.0, //Curb opening weir flow + Qco = 0.0; //Curb opening orifice flow + + if (InletDesigns[i].slottedInlet.length > 0.0) + return getOnSagSlottedFlow(i, d); + + Lgrate = InletDesigns[i].grateInlet.length; + if (Lgrate > 0.0) findOnSagGrateFlows(i, d, &Qgw, &Qgo); + + Lcurb = InletDesigns[i].curbInlet.length; + if (Lcurb > 0.0) + { + Lsweep = Lcurb - Lgrate; + if (Lsweep > 0.0) findOnSagCurbFlows(i, d, Lsweep, &Qsw, &Qso); + if (Qgo > 0.0) findOnSagCurbFlows(i, d, Lgrate, &Qcw, &Qco); + } + return Qgw + Qgo + Qsw + Qso + Qco; +} + +//============================================================================= + +void findOnSagGrateFlows(int i, double d, double *Qw, double *Qo) +// +// Input: i = inlet type index +// d = water level seen by inlet (ft) +// Output: Qw = flow captured in weir mode (cfs) +// Qo = flow captured in orifice mode (cfs) +// Purpose: finds the flow captured by an on-sag grate inlet. +// +{ + int grateType = InletDesigns[i].grateInlet.type; + double Lg = InletDesigns[i].grateInlet.length; + double Wg = InletDesigns[i].grateInlet.width; + double P, // grate perimeter (ft) + Ao, // grate opening area (ft2) + di; // average flow depth across grate (ft) + + // --- for drop grate inlets + if (InletDesigns[i].type == DROP_GRATE_INLET) + { + di = d; + P = 2.0 * (Lg + Wg); + } + + // --- for gutter grate inlets: + else + { + // --- check for spread within grate width + if (d <= Wg * Sw) + Wg = d / Sw; + + // --- avergage depth over grate + di = d - (Wg / 2.0) * Sw; + + // --- effective grate perimeter + P = Lg + 2.0 * Wg; + } + + if (grateType == GENERIC) + Ao = Lg * Wg * InletDesigns[i].grateInlet.fracOpenArea; + else + Ao = Lg * Wg * GrateOpeningRatios[grateType]; + + // --- weir flow applies (based on depth where result of + // weir eqn. equals result of orifice eqn.) + + if (d <= 1.79 * Ao / P) + { + *Qw = 3.0 * P * pow(di, 1.5); //HEC-22 Eq(4-26) + } + + // --- orifice flow applies + else + { + *Qo = 0.67 * Ao * sqrt(2.0 * 32.16 * di); //HEC-22 Eq(4-27) + } +} + +//============================================================================= + +void findOnSagCurbFlows(int i, double d, double L, double *Qw, double *Qo) +// +// Input: i = inlet type index +// d = water level seen by inlet (ft) +// L = length of curb opening (ft) +// Output: Qw = flow captured in weir mode (cfs) +// Qo = flow captured in orifice mode (cfs) +// Purpose: finds the flow captured by an on-sag curb opening inlet. +// +{ + int throatAngle = InletDesigns[i].curbInlet.throatAngle; + double h = InletDesigns[i].curbInlet.height; + double Qweir, Qorif, P; + double dweir, dorif, r; + + // --- check for orifice flow + if (L <= 0.0) return; + if (InletDesigns[i].type == DROP_CURB_INLET) L = L * 4.0; + dorif = 1.4 * h; + if (d > dorif) + { + *Qo = getCurbOrificeFlow(d, h, L, throatAngle); + return; + } + + // --- for uniform cross slope or very long opening + if (a == 0.0 || L > 12.0) + { + // --- check for weir flow + dweir = h; + if (d < dweir) + { + *Qw = 3.0 * L * pow(d, 1.5); //HEC-22 Eq(4-30) + return; + } + else Qweir = 3.0 * L * pow(dweir, 1.5); + } + + // --- for depressed gutter + else + { + // --- check for weir flow + P = L + 1.8 * W; + dweir = h + a; + if (d < dweir) + { + *Qw = 2.3 * P * pow(d, 1.5); //HEC-22 Eq(4-28) + return; + } + else Qweir = 2.3 * P * pow(dweir, 1.5); + } + + // --- interpolate between Qweir at depth dweir and Qorif at depth dorif + Qorif = getCurbOrificeFlow(dorif, h, L, throatAngle); + r = (d - dweir) / (dorif - dweir); + *Qw = (1.0 -r) * Qweir; + *Qo = r * Qorif; +} + +//============================================================================= + +double getCurbOrificeFlow(double di, double h, double L, int throatAngle) +// +// Input: di = water level at lip of inlet opening (ft) +// h = height of curb opening (ft) +// L = length of curb opening (ft) +// throatAngle = type of throat angle in curb opening +// Output: return flow captured by inlet (cfs) +// Purpose: finds the flow captured by an on-sag curb opening inlet under +// orifice flow conditions. +// +{ + double d = di; + if (throatAngle == HORIZONTAL_THROAT) + d = di - h / 2.0; + else if (throatAngle == INCLINED_THROAT) + d = di + (h / 2.0) * 0.7071; + return 0.67 * h * L * sqrt(2.0 * 32.16 * d); //HEC-22 Eq(4-31a) +} + +//============================================================================= + +double getOnSagSlottedFlow(int i, double d) +// +// Input: i = inlet type index +// d = water level seen by inlet (ft) +// Output: returns captured flow rate (cfs) +// Purpose: finds the flow captured by an on-sag slotted inlet. +// +// Note: weir flow = orifice flow at d = 2.587 * inlet width +{ + double L = InletDesigns[i].slottedInlet.length; + double w = InletDesigns[i].slottedInlet.width; + + if (d <= 2.587 * w) + return 2.48 * L * pow(d, 1.5); //HEC-22 Eq(4-32) + else + return 0.8 * L * w * sqrt(64.32 * d); //HEC-22 Eq(4-33) +} + +//============================================================================= + +void getBackflowRatios() +// +// Input: none +// Output: overflow ratio for each inlet +// Purpose: finds the fraction of the overflow produced by an inlet's capture +// node that becomes backflow into the inlet. +// +// Note: when a capture node receives flow from two or more inlets +// its backflow is divided among the inlets based on: +// i) the fraction of total open area for standard inlets +// ii) the fraction of total number of inlets for custom inlets +{ + TInlet* inlet; + double area; + double f; + int n; + + // --- info for each node receiving flow from an inlet + typedef struct + { + int numInletLinks; // total # inlet links + int numStdInletLinks; // total # standard inlet links + int numCustomInlets; // # custom inlets + double totalInletArea; // open area of standard inlets + } TInletNode; + TInletNode* inletNodes = (TInletNode *) calloc(Nobjects[NODE], sizeof(TInletNode)); + if (inletNodes == NULL) return; + + // --- Finds each inlet's contribution to its capture node + for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) + { + n = inlet->nodeIndex; + inletNodes[n].numInletLinks++; + area = getInletArea(inlet); + if (area > 0.0) + { + inletNodes[n].numStdInletLinks++; + inletNodes[n].totalInletArea += area; + } + else + inletNodes[n].numCustomInlets += inlet->numInlets; + } + + // --- find fraction of capture node's overflow that becomes inlet backflow + for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) + { + // --- f is ratio of links with standard inlets to all inlet links + // connected to receptor node n + n = inlet->nodeIndex; + f = (double) inletNodes[n].numStdInletLinks / + (double) inletNodes[n].numInletLinks; + + // --- backflow ratio depends if inlet is standard or custom (area = 0) + area = getInletArea(inlet); + if (area == 0.0) + inlet->backflowRatio = (double)inlet->numInlets / + (double)inletNodes[n].numCustomInlets * (1. - f); + else + inlet->backflowRatio = area / inletNodes[n].totalInletArea * f; + } + free(inletNodes); +} + +//============================================================================= + +double getInletArea(TInlet* inlet) +// +// Input: inlet = an inlet object placed in a conduit link +// Output: returns the unclogged open area of the inlet (ft2) +// Purpose: finds the total open flow area inlets placed in a conduit. +// +{ + double area = 0.0; + double curbLength; + int i = inlet->designIndex; + int grateType = InletDesigns[i].grateInlet.type; + + if (InletDesigns[i].grateInlet.length > 0.0) + { + area = InletDesigns[i].grateInlet.length * InletDesigns[i].grateInlet.width; + if (grateType == GENERIC) + area *= InletDesigns[i].grateInlet.fracOpenArea; + else + area *= GrateOpeningRatios[grateType]; + } + + curbLength = InletDesigns[i].curbInlet.length - InletDesigns[i].grateInlet.length; + if (curbLength > 0.0) + area += curbLength * InletDesigns[i].curbInlet.height; + + if (InletDesigns[i].slottedInlet.length > 0.0) + area = InletDesigns[i].slottedInlet.length * InletDesigns[i].slottedInlet.width; + return area * inlet->numInlets * inlet->clogFactor; +} + +//============================================================================= + +double getCustomCapturedFlow(TInlet* inlet, double q, double d) +{ + int i = inlet->designIndex; // inlet's position in InletDesigns array + int j; // counter for replicate inlets + int sides = 1; // number of sides for inlet's street (1 or 2) + int c; // an index into the Curve array + double qApproach, // inlet's approach flow (cfs) + qBypassed, // inlet's bypassed flow (cfs) + qCaptured, // inlet's captured flow (cfs) + qIncrement, // increment to captured flow (cfs) + qMax = BIG; // user-supplied flow capture limit (cfs) + + if (inlet->numInlets == 0) return 0.0; + + // --- set limit on max. flow captured per inlet + qMax = BIG; + if (inlet->flowLimit > 0.0) qMax = inlet->flowLimit; + + // --- get number of sides to a street xsection + xsect = &Link[inlet->linkIndex].xsect; + if (xsect->type == STREET_XSECT) + sides = Street[xsect->transect].sides; + + // --- adjust flow for 2-sided street + qApproach = q / sides; + qBypassed = qApproach; + qCaptured = 0.0; + + // --- get index of inlet's capture curve + c = InletDesigns[i].customCurve; + if (c >= 0) + { + // --- curve is captured flow v. approach flow + if (Curve[c].curveType == DIVERSION_CURVE) + { + // --- add up incrmental capture of each replicate inlet + for (j = 1; j <= inlet->numInlets; j++) + { + qIncrement = inlet->clogFactor * + table_lookupEx(&Curve[c], qBypassed * UCF(FLOW)) / UCF(FLOW); + qIncrement = MIN(qIncrement, qMax); + qIncrement = MIN(qIncrement, qBypassed); + qCaptured += qIncrement; + qBypassed -= qIncrement; + if (qBypassed < MIN_RUNOFF_FLOW) break; + } + } + + // --- curve is captured flow v. downstream node depth + else if (Curve[c].curveType == RATING_CURVE) + { + qCaptured = inlet->numInlets * inlet->clogFactor * + table_lookupEx(&Curve[c], d * UCF(LENGTH)) / UCF(FLOW); + } + qCaptured *= sides; + } + return qCaptured; +} diff --git a/src/inlet.h b/src/inlet.h new file mode 100644 index 000000000..ca7830725 --- /dev/null +++ b/src/inlet.h @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------------- +// inlet.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Street/Channel Inlet Functions +// +//----------------------------------------------------------------------------- +#ifndef INLET_H +#define INLET_H + +typedef struct TInlet TInlet; + +int inlet_create(int nInlets); +void inlet_delete(); +int inlet_readDesignParams(char* tok[], int ntoks); +int inlet_readUsageParams(char* tok[], int ntoks); +void inlet_validate(); + +void inlet_findCapturedFlows(double tStep); +void inlet_adjustQualInflows(); +void inlet_adjustQualOutflows(); + +void inlet_writeStatsReport(); +double inlet_capturedFlow(int link); + +#endif diff --git a/src/input.c b/src/input.c new file mode 100644 index 000000000..c0f7a0d9f --- /dev/null +++ b/src/input.c @@ -0,0 +1,930 @@ +//----------------------------------------------------------------------------- +// input.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 06/01/22 (Build 5.2.1) +// Author: L. Rossman +// +// Input data processing functions. +// +// Update History +// ============== +// Build 5.1.007: +// - Support added for climate adjustment input data. +// Build 5.1.011: +// - Support added for reading hydraulic event dates. +// Build 5.1.015: +// - Support added for multiple infiltration methods within a project. +// Build 5.2.0: +// - Support added for Streets and Inlets. +// - Support added for named variables & math expressions in control rules. +// Build 5.2.1: +// - Possible integer underflow avoided in getTokens() function. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include +#include "headers.h" +#include "lid.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +static const int MAXERRS = 100; // Max. input errors reported + +//----------------------------------------------------------------------------- +// Shared variables +//----------------------------------------------------------------------------- +static char *Tok[MAXTOKS]; // String tokens from line of input +static int Ntokens; // Number of tokens in line of input +static int Mobjects[MAX_OBJ_TYPES]; // Working number of objects of each type +static int Mnodes[MAX_NODE_TYPES]; // Working number of node objects +static int Mlinks[MAX_LINK_TYPES]; // Working number of link objects +static int Mevents; // Working number of event periods + +//----------------------------------------------------------------------------- +// External Functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// input_countObjects (called by swmm_open in swmm5.c) +// input_readData (called by swmm_open in swmm5.c) + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static int addObject(int objType, char* id); +static int getTokens(char *s); +static int parseLine(int sect, char* line); +static int readOption(char* line); +static int readTitle(char* line); +static int readControl(char* tok[], int ntoks); +static int readNode(int type); +static int readLink(int type); +static int readEvent(char* tok[], int ntoks); + +//============================================================================= + +int input_countObjects() +// +// Input: none +// Output: returns error code +// Purpose: reads input file to determine number of system objects. +// +{ + char line[MAXLINE+1]; // line from input data file + char wLine[MAXLINE+1]; // working copy of input line + char *tok; // first string token of line + int sect = -1, newsect; // input data sections + int errcode = 0; // error code + int errsum = 0; // number of errors found + int i; + long lineCount = 0; + + // --- initialize number of objects & set default values + if ( ErrorCode ) return ErrorCode; + error_setInpError(0, ""); + for (i = 0; i < MAX_OBJ_TYPES; i++) Nobjects[i] = 0; + for (i = 0; i < MAX_NODE_TYPES; i++) Nnodes[i] = 0; + for (i = 0; i < MAX_LINK_TYPES; i++) Nlinks[i] = 0; + controls_init(); + + // --- make pass through data file counting number of each object + while ( fgets(line, MAXLINE, Finp.file) != NULL ) + { + // --- skip blank lines & those beginning with a comment + lineCount++; + sstrncpy(wLine, line, MAXLINE); // make working copy of line + tok = strtok(wLine, SEPSTR); // get first text token on line + if ( tok == NULL ) continue; + if ( *tok == ';' ) continue; + + // --- check if line begins with a new section heading + if ( *tok == '[' ) + { + // --- look for heading in list of section keywords + newsect = findmatch(tok, SectWords); + if ( newsect >= 0 ) + { + sect = newsect; + continue; + } + else + { + sect = -1; + errcode = ERR_KEYWORD; + } + } + + // --- if in OPTIONS section then read the option setting + // otherwise add object and its ID name (tok) to project + if ( sect == s_OPTION ) errcode = readOption(line); + else if ( sect >= 0 ) errcode = addObject(sect, tok); + + // --- report any error found + if ( errcode ) + { + report_writeInputErrorMsg(errcode, sect, line, lineCount); + errsum++; + if (errsum >= MAXERRS ) break; + } + } + + // --- set global error code if input errors were found + if ( errsum > 0 ) ErrorCode = ERR_INPUT; + return ErrorCode; +} + +//============================================================================= + +int input_readData() +// +// Input: none +// Output: returns error code +// Purpose: reads input file to determine input parameters for each object. +// +{ + char line[MAXLINE+1]; // line from input data file + char wLine[MAXLINE+1]; // working copy of input line + char* comment; // ptr. to start of comment in input line + int sect, newsect; // data sections + int inperr, errsum; // error code & total error count + int lineLength; // number of characters in input line + int i; + long lineCount = 0; + + // --- initialize working item count arrays + // (final counts in Mobjects, Mnodes & Mlinks should + // match those in Nobjects, Nnodes and Nlinks). + if ( ErrorCode ) return ErrorCode; + error_setInpError(0, ""); + for (i = 0; i < MAX_OBJ_TYPES; i++) Mobjects[i] = 0; + for (i = 0; i < MAX_NODE_TYPES; i++) Mnodes[i] = 0; + for (i = 0; i < MAX_LINK_TYPES; i++) Mlinks[i] = 0; + Mevents = 0; + + // --- initialize starting date for all time series + for ( i = 0; i < Nobjects[TSERIES]; i++ ) + { + Tseries[i].lastDate = StartDate + StartTime; + } + + // --- read each line from input file + sect = 0; + errsum = 0; + rewind(Finp.file); + while ( fgets(line, MAXLINE, Finp.file) != NULL ) + { + // --- make copy of line and scan for tokens + lineCount++; + sstrncpy(wLine, line, MAXLINE); + Ntokens = getTokens(wLine); + + // --- skip blank lines and comments + if ( Ntokens == 0 ) continue; + if ( *Tok[0] == ';' ) continue; + + // --- check if max. line length exceeded + lineLength = (int)strlen(line); + if ( lineLength >= MAXLINE ) + { + // --- don't count comment if present + comment = strchr(line, ';'); + if ( comment ) lineLength = (int)(comment - line); // Pointer math here + if ( lineLength >= MAXLINE ) + { + inperr = ERR_LINE_LENGTH; + report_writeInputErrorMsg(inperr, sect, line, lineCount); + errsum++; + } + } + + // --- check if at start of a new input section + if (*Tok[0] == '[') + { + // --- match token against list of section keywords + newsect = findmatch(Tok[0], SectWords); + if (newsect >= 0) + { + // --- SPECIAL CASE FOR TRANSECTS + // finish processing the last set of transect data + if ( sect == s_TRANSECT ) + transect_validate(Nobjects[TRANSECT]-1); + + // --- begin a new input section + sect = newsect; + continue; + } + else + { + inperr = error_setInpError(ERR_KEYWORD, Tok[0]); + report_writeInputErrorMsg(inperr, sect, line, lineCount); + errsum++; + break; + } + } + + // --- otherwise parse tokens from input line + else + { + inperr = parseLine(sect, line); + if ( inperr > 0 ) + { + errsum++; + if ( errsum > MAXERRS ) report_writeLine(FMT19); + else report_writeInputErrorMsg(inperr, sect, line, lineCount); + } + } + + // --- stop if reach end of file or max. error count + if (errsum > MAXERRS) break; + } /* End of while */ + + // --- check for errors + if (errsum > 0) ErrorCode = ERR_INPUT; + return ErrorCode; +} + +//============================================================================= + +int addObject(int objType, char* id) +// +// Input: objType = object type index +// id = object's ID string +// Output: returns an error code +// Purpose: adds a new object to the project. +// +{ + int errcode = 0; + switch( objType ) + { + case s_RAINGAGE: + if ( !project_addObject(GAGE, id, Nobjects[GAGE]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[GAGE]++; + break; + + case s_SUBCATCH: + if ( !project_addObject(SUBCATCH, id, Nobjects[SUBCATCH]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[SUBCATCH]++; + break; + + case s_AQUIFER: + if ( !project_addObject(AQUIFER, id, Nobjects[AQUIFER]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[AQUIFER]++; + break; + + case s_UNITHYD: + // --- the same Unit Hydrograph can span several lines + if ( project_findObject(UNITHYD, id) < 0 ) + { + if ( !project_addObject(UNITHYD, id, Nobjects[UNITHYD]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[UNITHYD]++; + } + break; + + case s_SNOWMELT: + // --- the same Snowmelt object can appear on several lines + if ( project_findObject(SNOWMELT, id) < 0 ) + { + if ( !project_addObject(SNOWMELT, id, Nobjects[SNOWMELT]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[SNOWMELT]++; + } + break; + + case s_JUNCTION: + if ( !project_addObject(NODE, id, Nobjects[NODE]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[NODE]++; + Nnodes[JUNCTION]++; + break; + + case s_OUTFALL: + if ( !project_addObject(NODE, id, Nobjects[NODE]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[NODE]++; + Nnodes[OUTFALL]++; + break; + + case s_STORAGE: + if ( !project_addObject(NODE, id, Nobjects[NODE]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[NODE]++; + Nnodes[STORAGE]++; + break; + + case s_DIVIDER: + if ( !project_addObject(NODE, id, Nobjects[NODE]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[NODE]++; + Nnodes[DIVIDER]++; + break; + + case s_CONDUIT: + if ( !project_addObject(LINK, id, Nobjects[LINK]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[LINK]++; + Nlinks[CONDUIT]++; + break; + + case s_PUMP: + if ( !project_addObject(LINK, id, Nobjects[LINK]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[LINK]++; + Nlinks[PUMP]++; + break; + + case s_ORIFICE: + if ( !project_addObject(LINK, id, Nobjects[LINK]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[LINK]++; + Nlinks[ORIFICE]++; + break; + + case s_WEIR: + if ( !project_addObject(LINK, id, Nobjects[LINK]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[LINK]++; + Nlinks[WEIR]++; + break; + + case s_OUTLET: + if ( !project_addObject(LINK, id, Nobjects[LINK]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[LINK]++; + Nlinks[OUTLET]++; + break; + + case s_POLLUTANT: + if ( !project_addObject(POLLUT, id, Nobjects[POLLUT]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[POLLUT]++; + break; + + case s_LANDUSE: + if ( !project_addObject(LANDUSE, id, Nobjects[LANDUSE]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[LANDUSE]++; + break; + + case s_PATTERN: + // --- a time pattern can span several lines + if ( project_findObject(TIMEPATTERN, id) < 0 ) + { + if ( !project_addObject(TIMEPATTERN, id, Nobjects[TIMEPATTERN]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[TIMEPATTERN]++; + } + break; + + case s_CURVE: + // --- a Curve can span several lines + if ( project_findObject(CURVE, id) < 0 ) + { + if ( !project_addObject(CURVE, id, Nobjects[CURVE]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[CURVE]++; + + // --- check for a conduit shape curve + id = strtok(NULL, SEPSTR); + if ( findmatch(id, CurveTypeWords) == SHAPE_CURVE ) + Nobjects[SHAPE]++; + } + break; + + case s_TIMESERIES: + // --- a Time Series can span several lines + if ( project_findObject(TSERIES, id) < 0 ) + { + if ( !project_addObject(TSERIES, id, Nobjects[TSERIES]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[TSERIES]++; + } + break; + + case s_CONTROL: + if ( match(id, w_RULE) ) Nobjects[CONTROL]++; + else controls_addToCount(id); + break; + + case s_TRANSECT: + // --- for TRANSECTS, ID name appears as second entry on X1 line + if ( match(id, "X1") ) + { + id = strtok(NULL, SEPSTR); + if ( id ) + { + if ( !project_addObject(TRANSECT, id, Nobjects[TRANSECT]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[TRANSECT]++; + } + } + break; + + case s_LID_CONTROL: + // --- an LID object can span several lines + if ( project_findObject(LID, id) < 0 ) + { + if ( !project_addObject(LID, id, Nobjects[LID]) ) + { + errcode = error_setInpError(ERR_DUP_NAME, id); + } + Nobjects[LID]++; + } + break; + + case s_EVENT: NumEvents++; break; + + case s_STREET: + if ( !project_addObject(STREET, id, Nobjects[STREET]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[STREET]++; + break; + + case s_INLET: + // --- an INLET object can span several lines + if (project_findObject(INLET, id) < 0) + { + if ( !project_addObject(INLET, id, Nobjects[INLET]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[INLET]++; + } + break; + } + return errcode; +} + +//============================================================================= + +int parseLine(int sect, char *line) +// +// Input: sect = current section of input file +// *line = line of text read from input file +// Output: returns error code or 0 if no error found +// Purpose: parses contents of a tokenized line of text read from input file. +// +{ + int j, err; + switch (sect) + { + case s_TITLE: + return readTitle(line); + + case s_RAINGAGE: + j = Mobjects[GAGE]; + err = gage_readParams(j, Tok, Ntokens); + Mobjects[GAGE]++; + return err; + + case s_TEMP: + return climate_readParams(Tok, Ntokens); + + case s_EVAP: + return climate_readEvapParams(Tok, Ntokens); + + case s_ADJUST: + return climate_readAdjustments(Tok, Ntokens); + + case s_SUBCATCH: + j = Mobjects[SUBCATCH]; + err = subcatch_readParams(j, Tok, Ntokens); + Mobjects[SUBCATCH]++; + return err; + + case s_SUBAREA: + return subcatch_readSubareaParams(Tok, Ntokens); + + case s_INFIL: + return infil_readParams(InfilModel, Tok, Ntokens); + + case s_AQUIFER: + j = Mobjects[AQUIFER]; + err = gwater_readAquiferParams(j, Tok, Ntokens); + Mobjects[AQUIFER]++; + return err; + + case s_GROUNDWATER: + return gwater_readGroundwaterParams(Tok, Ntokens); + + case s_GWF: + return gwater_readFlowExpression(Tok, Ntokens); + + case s_SNOWMELT: + return snow_readMeltParams(Tok, Ntokens); + + case s_JUNCTION: + return readNode(JUNCTION); + + case s_OUTFALL: + return readNode(OUTFALL); + + case s_STORAGE: + return readNode(STORAGE); + + case s_DIVIDER: + return readNode(DIVIDER); + + case s_CONDUIT: + return readLink(CONDUIT); + + case s_PUMP: + return readLink(PUMP); + + case s_ORIFICE: + return readLink(ORIFICE); + + case s_WEIR: + return readLink(WEIR); + + case s_OUTLET: + return readLink(OUTLET); + + case s_XSECTION: + return link_readXsectParams(Tok, Ntokens); + + case s_TRANSECT: + return transect_readParams(&Mobjects[TRANSECT], Tok, Ntokens); + + case s_LOSSES: + return link_readLossParams(Tok, Ntokens); + + case s_POLLUTANT: + j = Mobjects[POLLUT]; + err = landuse_readPollutParams(j, Tok, Ntokens); + Mobjects[POLLUT]++; + return err; + + case s_LANDUSE: + j = Mobjects[LANDUSE]; + err = landuse_readParams(j, Tok, Ntokens); + Mobjects[LANDUSE]++; + return err; + + case s_BUILDUP: + return landuse_readBuildupParams(Tok, Ntokens); + + case s_WASHOFF: + return landuse_readWashoffParams(Tok, Ntokens); + + case s_COVERAGE: + return subcatch_readLanduseParams(Tok, Ntokens); + + case s_INFLOW: + return inflow_readExtInflow(Tok, Ntokens); + + case s_DWF: + return inflow_readDwfInflow(Tok, Ntokens); + + case s_PATTERN: + return inflow_readDwfPattern(Tok, Ntokens); + + case s_RDII: + return rdii_readRdiiInflow(Tok, Ntokens); + + case s_UNITHYD: + return rdii_readUnitHydParams(Tok, Ntokens); + + case s_LOADING: + return subcatch_readInitBuildup(Tok, Ntokens); + + case s_TREATMENT: + return treatmnt_readExpression(Tok, Ntokens); + + case s_CURVE: + return table_readCurve(Tok, Ntokens); + + case s_TIMESERIES: + return table_readTimeseries(Tok, Ntokens); + + case s_CONTROL: + return readControl(Tok, Ntokens); + + case s_REPORT: + return report_readOptions(Tok, Ntokens); + + case s_FILE: + return iface_readFileParams(Tok, Ntokens); + + case s_LID_CONTROL: + return lid_readProcParams(Tok, Ntokens); + + case s_LID_USAGE: + return lid_readGroupParams(Tok, Ntokens); + + case s_EVENT: + return readEvent(Tok, Ntokens); + + case s_STREET: + return street_readParams(Tok, Ntokens); + + case s_INLET: + return inlet_readDesignParams(Tok, Ntokens); + + case s_INLET_USAGE: + return inlet_readUsageParams(Tok, Ntokens); + + default: return 0; + } +} + +//============================================================================= + +int readControl(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// Purpose: reads a line of input for a control rule. +// +{ + int index; + int keyword; + + // --- check for minimum number of tokens + if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); + + if (match(tok[0], w_VARIABLE)) + return controls_addVariable(tok, ntoks); + if (match(tok[0], w_EXPRESSION)) + return controls_addExpression(tok, ntoks); + + // --- get index of control rule keyword + keyword = findmatch(tok[0], RuleKeyWords); + if ( keyword < 0 ) return error_setInpError(ERR_KEYWORD, tok[0]); + + // --- if line begins a new control rule, add rule ID to the database + if ( keyword == 0 ) + { + if ( !project_addObject(CONTROL, tok[1], Mobjects[CONTROL]) ) + { + return error_setInpError(ERR_DUP_NAME, Tok[1]); + } + Mobjects[CONTROL]++; + } + + // --- get index of last control rule processed + index = Mobjects[CONTROL] - 1; + if ( index < 0 ) return error_setInpError(ERR_RULE, ""); + + // --- add current line as a new clause to the control rule + return controls_addRuleClause(index, keyword, Tok, Ntokens); +} + +//============================================================================= + +int readOption(char* line) +// +// Input: line = line of input data +// Output: returns error code +// Purpose: reads an input line containing a project option. +// +{ + Ntokens = getTokens(line); + if ( Ntokens < 2 ) return 0; + return project_readOption(Tok[0], Tok[1]); +} + +//============================================================================= + +int readTitle(char* line) +// +// Input: line = line from input file +// Output: returns error code +// Purpose: reads project title from line of input. +// +{ + int i, n; + for (i = 0; i < MAXTITLE; i++) + { + // --- find next empty Title entry + if ( strlen(Title[i]) == 0 ) + { + // --- strip line feed character from input line + n = (int)strlen(line); + if (line[n-1] == 10) line[n-1] = ' '; + + // --- copy input line into Title entry + sstrncpy(Title[i], line, MAXMSG); + break; + } + } + return 0; +} + +//============================================================================= + +int readNode(int type) +// +// Input: type = type of node +// Output: returns error code +// Purpose: reads data for a node from a line of input. +// +{ + int j = Mobjects[NODE]; + int k = Mnodes[type]; + int err = node_readParams(j, type, k, Tok, Ntokens); + Mobjects[NODE]++; + Mnodes[type]++; + return err; +} + +//============================================================================= + +int readLink(int type) +// +// Input: type = type of link +// Output: returns error code +// Purpose: reads data for a link from a line of input. +// +{ + int j = Mobjects[LINK]; + int k = Mlinks[type]; + int err = link_readParams(j, type, k, Tok, Ntokens); + Mobjects[LINK]++; + Mlinks[type]++; + return err; +} + +//============================================================================= + +int readEvent(char* tok[], int ntoks) +{ + DateTime x[4]; + + if ( ntoks < 4 ) return error_setInpError(ERR_ITEMS, ""); + if ( !datetime_strToDate(tok[0], &x[0]) ) + return error_setInpError(ERR_DATETIME, tok[0]); + if ( !datetime_strToTime(tok[1], &x[1]) ) + return error_setInpError(ERR_DATETIME, tok[1]); + if ( !datetime_strToDate(tok[2], &x[2]) ) + return error_setInpError(ERR_DATETIME, tok[2]); + if ( !datetime_strToTime(tok[3], &x[3]) ) + return error_setInpError(ERR_DATETIME, tok[3]); + + Event[Mevents].start = x[0] + x[1]; + Event[Mevents].end = x[2] + x[3]; + if ( Event[Mevents].start >= Event[Mevents].end ) + return error_setInpError(ERR_DATETIME, " - start date exceeds end date"); + Mevents++; + return 0; +} + +//============================================================================= + +int findmatch(char *s, char *keyword[]) +// +// Input: s = character string +// keyword = array of keyword strings +// Output: returns index of matching keyword or -1 if no match found +// Purpose: finds match between string and array of keyword strings. +// +{ + int i = 0; + while (keyword[i] != NULL) + { + if (match(s, keyword[i])) return(i); + i++; + } + return(-1); +} + +//============================================================================= + +int match(char *str, char *substr) +// +// Input: str = character string being searched +// substr = sub-string being searched for +// Output: returns 1 if sub-string found, 0 if not +// Purpose: sees if a sub-string of characters appears in a string +// (not case sensitive). +// +{ + int i,j,k; + + // --- fail if substring is empty + if (!substr[0]) return(0); + + // --- skip leading blanks of str + for (k = 0; str[k]; k++) + { + if (str[k] != ' ') break; + } + + // --- check if substr matches remainder of str + for (i = k,j = 0; substr[j]; i++,j++) + { + if (!str[i] || UCHAR(str[i]) != UCHAR(substr[j])) return(0); + } + return(1); +} + +//============================================================================= + +int getInt(char *s, int *y) +// +// Input: s = a character string +// Output: y = converted value of s, +// returns 1 if conversion successful, 0 if not +// Purpose: converts a string to an integer number. +// +{ + double x; + if ( getDouble(s, &x) ) + { + if ( x < 0.0 ) x -= 0.01; + else x += 0.01; + *y = (int)x; + return 1; + } + *y = 0; + return 0; +} + +//============================================================================= + +int getFloat(char *s, float *y) +// +// Input: s = a character string +// Output: y = converted value of s, +// returns 1 if conversion successful, 0 if not +// Purpose: converts a string to a single precision floating point number. +// +{ + char *endptr; + *y = (float) strtod(s, &endptr); + if (*endptr > 0) return(0); + return(1); +} + +//============================================================================= + +int getDouble(char *s, double *y) +// +// Input: s = a character string +// Output: y = converted value of s, +// returns 1 if conversion successful, 0 if not +// Purpose: converts a string to a double precision floating point number. +// +{ + char *endptr; + *y = strtod(s, &endptr); + if (*endptr > 0) return(0); + return(1); +} + +//============================================================================= + +int getTokens(char *s) +// +// Input: s = a character string +// Output: returns number of tokens found in s +// Purpose: scans a string for tokens, saving pointers to them +// in shared variable Tok[]. +// +// Notes: Tokens can be separated by the characters listed in SEPSTR +// (spaces, tabs, newline, carriage return) which is defined +// in CONSTS.H. Text between quotes is treated as a single token. +// +{ + int len, n; + int m; + char *c; + + // --- begin with no tokens + for (n = 0; n < MAXTOKS; n++) Tok[n] = NULL; + n = 0; + + // --- truncate s at start of comment + c = strchr(s,';'); + if (c) *c = '\0'; + len = (int)strlen(s); + + // --- scan s for tokens until nothing left + while (len > 0 && n < MAXTOKS) + { + m = (int)strcspn(s,SEPSTR); // find token length + if (m == 0) s++; // no token found + else + { + if (*s == '"') // token begins with quote + { + s++; // start token after quote + len--; // reduce length of s + m = (int)strcspn(s,"\"\n"); // find end quote or new line + } + s[m] = '\0'; // null-terminate the token + Tok[n] = s; // save pointer to token + n++; // update token count + s += m+1; // begin next token + } + len -= m+1; // update length of s + } + return n; +} + +//============================================================================= diff --git a/src/inputrpt.c b/src/inputrpt.c new file mode 100644 index 000000000..46a6f952f --- /dev/null +++ b/src/inputrpt.c @@ -0,0 +1,354 @@ +//----------------------------------------------------------------------------- +// inputrpt.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Report writing functions for input data summary. +// +// Update History +// ============== +// Build 5.2.0: +// - Support added for reporting Street geometry tables. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include "headers.h" +#include "lid.h" + +#define WRITE(x) (report_writeLine((x))) + +//============================================================================= + +void inputrpt_writeInput() +// +// Input: none +// Output: none +// Purpose: writes summary of input data to report file. +// +{ + int m; + int i, k; + int lidCount = 0; + if ( ErrorCode ) return; + + WRITE(""); + WRITE("*************"); + WRITE("Element Count"); + WRITE("*************"); + fprintf(Frpt.file, "\n Number of rain gages ...... %d", Nobjects[GAGE]); + fprintf(Frpt.file, "\n Number of subcatchments ... %d", Nobjects[SUBCATCH]); + fprintf(Frpt.file, "\n Number of nodes ........... %d", Nobjects[NODE]); + fprintf(Frpt.file, "\n Number of links ........... %d", Nobjects[LINK]); + fprintf(Frpt.file, "\n Number of pollutants ...... %d", Nobjects[POLLUT]); + fprintf(Frpt.file, "\n Number of land uses ....... %d", Nobjects[LANDUSE]); + + if ( Nobjects[POLLUT] > 0 ) + { + WRITE(""); + WRITE(""); + WRITE("*****************"); + WRITE("Pollutant Summary"); + WRITE("*****************"); + fprintf(Frpt.file, + "\n Ppt. GW Kdecay"); + fprintf(Frpt.file, + "\n Name Units Concen. Concen. 1/days CoPollutant"); + fprintf(Frpt.file, + "\n -----------------------------------------------------------------------"); + for (i = 0; i < Nobjects[POLLUT]; i++) + { + fprintf(Frpt.file, "\n %-20s %5s%10.2f%10.2f%10.2f", Pollut[i].ID, + QualUnitsWords[Pollut[i].units], Pollut[i].pptConcen, + Pollut[i].gwConcen, Pollut[i].kDecay*SECperDAY); + if ( Pollut[i].coPollut >= 0 ) + fprintf(Frpt.file, " %-s (%.2f)", + Pollut[Pollut[i].coPollut].ID, Pollut[i].coFraction); + } + } + + if ( Nobjects[LANDUSE] > 0 ) + { + WRITE(""); + WRITE(""); + WRITE("***************"); + WRITE("Landuse Summary"); + WRITE("***************"); + fprintf(Frpt.file, + "\n Sweeping Maximum Last"); + fprintf(Frpt.file, + "\n Name Interval Removal Swept"); + fprintf(Frpt.file, + "\n ---------------------------------------------------"); + for (i=0; i 0 ) + { + WRITE(""); + WRITE(""); + WRITE("****************"); + WRITE("Raingage Summary"); + WRITE("****************"); + fprintf(Frpt.file, +"\n Data Recording"); + fprintf(Frpt.file, +"\n Name Data Source Type Interval "); + fprintf(Frpt.file, +"\n ------------------------------------------------------------------------"); + for (i = 0; i < Nobjects[GAGE]; i++) + { + if ( Gage[i].tSeries >= 0 ) + { + fprintf(Frpt.file, "\n %-20s %-30s ", + Gage[i].ID, Tseries[Gage[i].tSeries].ID); + fprintf(Frpt.file, "%-10s %3d min.", + RainTypeWords[Gage[i].rainType], + (Gage[i].rainInterval)/60); + } + else fprintf(Frpt.file, "\n %-20s %-30s", + Gage[i].ID, Gage[i].fname); + } + } + + if ( Nobjects[SUBCATCH] > 0 ) + { + WRITE(""); + WRITE(""); + WRITE("********************"); + WRITE("Subcatchment Summary"); + WRITE("********************"); + fprintf(Frpt.file, +"\n Name Area Width %%Imperv %%Slope Rain Gage Outlet "); + fprintf(Frpt.file, +"\n -----------------------------------------------------------------------------------------------------------"); + for (i = 0; i < Nobjects[SUBCATCH]; i++) + { + fprintf(Frpt.file,"\n %-20s %10.2f%10.2f%10.2f%10.4f %-20s ", + Subcatch[i].ID, Subcatch[i].area*UCF(LANDAREA), + Subcatch[i].width*UCF(LENGTH), Subcatch[i].fracImperv*100.0, + Subcatch[i].slope*100.0, Gage[Subcatch[i].gage].ID); + if ( Subcatch[i].outNode >= 0 ) + { + fprintf(Frpt.file, "%-20s", Node[Subcatch[i].outNode].ID); + } + else if ( Subcatch[i].outSubcatch >= 0 ) + { + fprintf(Frpt.file, "%-20s", Subcatch[Subcatch[i].outSubcatch].ID); + } + if ( Subcatch[i].lidArea ) lidCount++; + } + } + if ( lidCount > 0 ) lid_writeSummary(); + + if ( Nobjects[NODE] > 0 ) + { + WRITE(""); + WRITE(""); + WRITE("************"); + WRITE("Node Summary"); + WRITE("************"); + fprintf(Frpt.file, +"\n Invert Max. Ponded External"); + fprintf(Frpt.file, +"\n Name Type Elev. Depth Area Inflow "); + fprintf(Frpt.file, +"\n -------------------------------------------------------------------------------"); + for (i = 0; i < Nobjects[NODE]; i++) + { + fprintf(Frpt.file, "\n %-20s %-16s%10.2f%10.2f%10.1f", Node[i].ID, + NodeTypeWords[Node[i].type-JUNCTION], + Node[i].invertElev*UCF(LENGTH), + Node[i].fullDepth*UCF(LENGTH), + Node[i].pondedArea*UCF(LENGTH)*UCF(LENGTH)); + if ( Node[i].extInflow || Node[i].dwfInflow || Node[i].rdiiInflow ) + { + fprintf(Frpt.file, " Yes"); + } + } + } + + if ( Nobjects[LINK] > 0 ) + { + WRITE(""); + WRITE(""); + WRITE("************"); + WRITE("Link Summary"); + WRITE("************"); + fprintf(Frpt.file, +"\n Name From Node To Node Type Length %%Slope Roughness"); + fprintf(Frpt.file, +"\n ---------------------------------------------------------------------------------------------"); + for (i = 0; i < Nobjects[LINK]; i++) + { + // --- list end nodes in their original orientation + if ( Link[i].direction == 1 ) + fprintf(Frpt.file, "\n %-16s %-16s %-16s ", + Link[i].ID, Node[Link[i].node1].ID, Node[Link[i].node2].ID); + else + fprintf(Frpt.file, "\n %-16s %-16s %-16s ", + Link[i].ID, Node[Link[i].node2].ID, Node[Link[i].node1].ID); + + // --- list link type + if ( Link[i].type == PUMP ) + { + k = Link[i].subIndex; + fprintf(Frpt.file, "%-5s PUMP ", + PumpTypeWords[Pump[k].type]); + } + else fprintf(Frpt.file, "%-12s", + LinkTypeWords[Link[i].type-CONDUIT]); + + // --- list length, slope and roughness for conduit links + if (Link[i].type == CONDUIT) + { + k = Link[i].subIndex; + fprintf(Frpt.file, "%10.1f%10.4f%10.4f", + Conduit[k].length*UCF(LENGTH), + Conduit[k].slope*100.0*Link[i].direction, + Conduit[k].roughness); + } + } + + WRITE(""); + WRITE(""); + WRITE("*********************"); + WRITE("Cross Section Summary"); + WRITE("*********************"); + fprintf(Frpt.file, +"\n Full Full Hyd. Max. No. of Full"); + fprintf(Frpt.file, +"\n Conduit Shape Depth Area Rad. Width Barrels Flow"); + fprintf(Frpt.file, +"\n ---------------------------------------------------------------------------------------"); + for (i = 0; i < Nobjects[LINK]; i++) + { + if (Link[i].type == CONDUIT) + { + k = Link[i].subIndex; + fprintf(Frpt.file, "\n %-16s ", Link[i].ID); + if ( Link[i].xsect.type == CUSTOM ) + fprintf(Frpt.file, "%-16s ", Curve[Link[i].xsect.transect].ID); + else if ( Link[i].xsect.type == IRREGULAR ) + fprintf(Frpt.file, "%-16s ", + Transect[Link[i].xsect.transect].ID); + else if ( Link[i].xsect.type == STREET_XSECT ) + fprintf(Frpt.file, "%-16s ", + Street[Link[i].xsect.transect].ID); + else fprintf(Frpt.file, "%-16s ", + XsectTypeWords[Link[i].xsect.type]); + fprintf(Frpt.file, "%8.2f %8.2f %8.2f %8.2f %3d %8.2f", + Link[i].xsect.yFull*UCF(LENGTH), + Link[i].xsect.aFull*UCF(LENGTH)*UCF(LENGTH), + Link[i].xsect.rFull*UCF(LENGTH), + Link[i].xsect.wMax*UCF(LENGTH), + Conduit[k].barrels, + Link[i].qFull*UCF(FLOW)); + } + } + } + + if (Nobjects[SHAPE] > 0) + { + WRITE(""); + WRITE(""); + WRITE("*************"); + WRITE("Shape Summary"); + WRITE("*************"); + for (i = 0; i < Nobjects[SHAPE]; i++) + { + k = Shape[i].curve; + fprintf(Frpt.file, "\n\n Shape %s", Curve[k].ID); + fprintf(Frpt.file, "\n Area: "); + for ( m = 1; m < N_SHAPE_TBL; m++) + { + if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); + fprintf(Frpt.file, "%10.4f ", Shape[i].areaTbl[m]); + } + fprintf(Frpt.file, "\n Hrad: "); + for ( m = 1; m < N_SHAPE_TBL; m++) + { + if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); + fprintf(Frpt.file, "%10.4f ", Shape[i].hradTbl[m]); + } + fprintf(Frpt.file, "\n Width: "); + for ( m = 1; m < N_SHAPE_TBL; m++) + { + if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); + fprintf(Frpt.file, "%10.4f ", Shape[i].widthTbl[m]); + } + } + } + + if (Nobjects[TRANSECT] > 0) + { + WRITE(""); + WRITE(""); + WRITE("****************"); + WRITE("Transect Summary"); + WRITE("****************"); + for (i = 0; i < Nobjects[TRANSECT]; i++) + { + fprintf(Frpt.file, "\n\n Transect %s", Transect[i].ID); + fprintf(Frpt.file, "\n Area: "); + for ( m = 1; m < N_TRANSECT_TBL; m++) + { + if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); + fprintf(Frpt.file, "%10.4f ", Transect[i].areaTbl[m]); + } + fprintf(Frpt.file, "\n Hrad: "); + for ( m = 1; m < N_TRANSECT_TBL; m++) + { + if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); + fprintf(Frpt.file, "%10.4f ", Transect[i].hradTbl[m]); + } + fprintf(Frpt.file, "\n Width: "); + for ( m = 1; m < N_TRANSECT_TBL; m++) + { + if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); + fprintf(Frpt.file, "%10.4f ", Transect[i].widthTbl[m]); + } + } + } + + if (Nobjects[STREET] > 0) + { + WRITE(""); + WRITE(""); + WRITE("**************"); + WRITE("Street Summary"); + WRITE("**************"); + for (i = 0; i < Nobjects[STREET]; i++) + { + fprintf(Frpt.file, "\n\n Street %s", Street[i].ID); + fprintf(Frpt.file, "\n Area: "); + for (m = 1; m < Street[i].transect.nTbl; m++) + { + if (m % 5 == 1) fprintf(Frpt.file, "\n "); + fprintf(Frpt.file, "%10.4f ", Street[i].transect.areaTbl[m]); + } + fprintf(Frpt.file, "\n Hrad: "); + for (m = 1; m < Street[i].transect.nTbl; m++) + { + if (m % 5 == 1) fprintf(Frpt.file, "\n "); + fprintf(Frpt.file, "%10.4f ", Street[i].transect.hradTbl[m]); + } + fprintf(Frpt.file, "\n Width: "); + for (m = 1; m < Street[i].transect.nTbl; m++) + { + if (m % 5 == 1) fprintf(Frpt.file, "\n "); + fprintf(Frpt.file, "%10.4f ", Street[i].transect.widthTbl[m]); + } + } + } + WRITE(""); +} diff --git a/src/keywords.c b/src/keywords.c new file mode 100644 index 000000000..5f4b4e443 --- /dev/null +++ b/src/keywords.c @@ -0,0 +1,172 @@ +//----------------------------------------------------------------------------- +// keywords.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 06/01/22 (Build 5.2.1) +// Author: L. Rossman +// +// Exportable keyword dictionary +// +// NOTE: the keywords in each list must appear in same order used +// by its complementary enumerated variable in enums.h and +// must be terminated by NULL. The actual text of each keyword +// is defined in text.h. +// +// Update History +// ============== +// Build 5.1.007: +// - Keywords for Ignore RDII option and groundwater flow equation +// and climate adjustment input sections added. +// Build 5.1.008: +// - Keyword arrays placed in alphabetical order for better readability. +// - Keywords added for Minimum Routing Step and Number of Threads options. +// Build 5.1.010: +// - New Modified Green Ampt keyword added to InfilModelWords. +// - New Roadway weir keyword added to WeirTypeWords. +// Build 5.1.011: +// - New section keyword for [EVENTS] added. +// Build 5.1.013: +// - New option keywords w_SURCHARGE_METHOD, w_RULE_STEP, w_AVERAGES +// and w_WEIR added. +// Build 5.2.0: +// - Support added for Streets and Inlets. +// - Support added for variable speed pumps. +// - Support added for analytical storage shapes. +// - Support added for RptFlags.disabled option. +// Build 5.2.1: +// - Adds NONE to the list of NormalFlowWords. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include // need this to define NULL +#include "text.h" + +char* BuildupTypeWords[] = { w_NONE, w_POW, w_EXP, w_SAT, w_EXT, NULL}; +char* CurveTypeWords[] = { w_STORAGE, w_DIVERSION, w_TIDAL, w_RATING, + w_CONTROLS, w_SHAPE, w_WEIR, + w_PUMP1, w_PUMP2, w_PUMP3, w_PUMP4, + w_PUMP5, NULL}; +char* DividerTypeWords[] = { w_CUTOFF, w_TABULAR, w_WEIR, w_OVERFLOW, NULL}; +char* EvapTypeWords[] = { w_CONSTANT, w_MONTHLY, w_TIMESERIES, + w_TEMPERATURE, w_FILE, w_RECOVERY, + w_DRYONLY, NULL}; +char* FileTypeWords[] = { w_RAINFALL, w_RUNOFF, w_HOTSTART, w_RDII, + w_INFLOWS, w_OUTFLOWS, NULL}; +char* FileModeWords[] = { w_NO, w_SCRATCH, w_USE, w_SAVE, NULL}; +char* FlowUnitWords[] = { w_CFS, w_GPM, w_MGD, w_CMS, w_LPS, w_MLD, NULL}; +char* ForceMainEqnWords[] = { w_H_W, w_D_W, NULL}; +char* GageDataWords[] = { w_TIMESERIES, w_FILE, NULL}; +char* InfilModelWords[] = { w_HORTON, w_MOD_HORTON, w_GREEN_AMPT, + w_MOD_GREEN_AMPT, w_CURVE_NUMEBR, NULL}; +char* InertDampingWords[] = { w_NONE, w_PARTIAL, w_FULL, NULL}; +char* LinkOffsetWords[] = { w_DEPTH, w_ELEVATION, NULL}; +char* LinkTypeWords[] = { w_CONDUIT, w_PUMP, w_ORIFICE, + w_WEIR, w_OUTLET }; +char* LoadUnitsWords[] = { w_LBS, w_KG, w_LOGN }; +char* NodeTypeWords[] = { w_JUNCTION, w_OUTFALL, + w_STORAGE, w_DIVIDER }; +char* NoneAllWords[] = { w_NONE, w_ALL, NULL}; +char* NormalFlowWords[] = { w_SLOPE, w_FROUDE, w_BOTH, w_NONE, NULL}; +char* NormalizerWords[] = { w_PER_AREA, w_PER_CURB, NULL}; +char* NoYesWords[] = { w_NO, w_YES, NULL}; +char* OffOnWords[] = { w_OFF, w_ON, NULL}; +char* OldRouteModelWords[] = { w_NONE, w_NF, w_KW, w_EKW, w_DW, NULL}; +char* OptionWords[] = { w_FLOW_UNITS, w_INFIL_MODEL, + w_ROUTE_MODEL, w_START_DATE, + w_START_TIME, w_END_DATE, + w_END_TIME, w_REPORT_START_DATE, + w_REPORT_START_TIME, w_SWEEP_START, + w_SWEEP_END, w_START_DRY_DAYS, + w_WET_STEP, w_DRY_STEP, + w_ROUTE_STEP, w_RULE_STEP, + w_REPORT_STEP, + w_ALLOW_PONDING, w_INERT_DAMPING, + w_SLOPE_WEIGHTING, w_VARIABLE_STEP, + w_NORMAL_FLOW_LTD, w_LENGTHENING_STEP, + w_MIN_SURFAREA, w_COMPATIBILITY, + w_SKIP_STEADY_STATE, w_TEMPDIR, + w_IGNORE_RAINFALL, w_FORCE_MAIN_EQN, + w_LINK_OFFSETS, w_MIN_SLOPE, + w_IGNORE_SNOWMELT, w_IGNORE_GWATER, + w_IGNORE_ROUTING, w_IGNORE_QUALITY, + 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, + NULL }; +char* OrificeTypeWords[] = { w_SIDE, w_BOTTOM, NULL}; +char* OutfallTypeWords[] = { w_FREE, w_NORMAL, w_FIXED, w_TIDAL, + w_TIMESERIES, NULL}; +char* PatternTypeWords[] = { w_MONTHLY, w_DAILY, w_HOURLY, w_WEEKEND, NULL}; +char* PondingUnitsWords[] = { w_PONDED_FEET, w_PONDED_METERS }; +char* ProcessVarWords[] = { w_HRT, w_DT, w_FLOW, w_DEPTH, w_AREA, NULL}; +char* PumpTypeWords[] = { w_TYPE1, w_TYPE2, w_TYPE3, w_TYPE4, w_TYPE5, w_IDEAL }; +char* QualUnitsWords[] = { w_MGperL, w_UGperL, w_COUNTperL, NULL}; +char* RainTypeWords[] = { w_INTENSITY, w_VOLUME, w_CUMULATIVE, NULL}; +char* RainUnitsWords[] = { w_INCHES, w_MMETER, NULL}; +char* RelationWords[] = { w_TABULAR, w_FUNCTIONAL, + w_CYLINDRICAL, w_CONICAL, w_PARABOLIC, + w_PYRAMIDAL, NULL}; +char* ReportWords[] = { w_DISABLED, w_INPUT, w_SUBCATCH, w_NODE, w_LINK, + w_CONTINUITY, w_FLOWSTATS,w_CONTROLS, + w_AVERAGES, w_NODESTATS, NULL}; +char* RouteModelWords[] = { w_NONE, w_STEADY, w_KINWAVE, w_XKINWAVE, + w_DYNWAVE, NULL}; +char* RuleKeyWords[] = { w_RULE, w_IF, w_AND, w_OR, w_THEN, w_ELSE, + w_PRIORITY, NULL}; +char* SectWords[] = { ws_TITLE, ws_OPTION, + ws_FILE, ws_RAINGAGE, + ws_TEMP, ws_EVAP, + ws_SUBCATCH, ws_SUBAREA, + ws_INFIL, ws_AQUIFER, + ws_GROUNDWATER, ws_SNOWMELT, + ws_JUNCTION, ws_OUTFALL, + ws_STORAGE, ws_DIVIDER, + ws_CONDUIT, ws_PUMP, + ws_ORIFICE, ws_WEIR, + ws_OUTLET, ws_XSECTION, + ws_TRANSECT, ws_LOSS, + ws_CONTROL, ws_POLLUTANT, + ws_LANDUSE, ws_BUILDUP, + ws_WASHOFF, ws_COVERAGE, + ws_INFLOW, ws_DWF, + ws_PATTERN, ws_RDII, + ws_UNITHYD, ws_LOADING, + ws_TREATMENT, ws_CURVE, + ws_TIMESERIES, ws_REPORT, + ws_COORDINATE, ws_VERTICES, + ws_POLYGON, ws_LABEL, + ws_SYMBOL, ws_BACKDROP, + ws_TAG, ws_PROFILE, + ws_MAP, ws_LID_CONTROL, + ws_LID_USAGE, ws_GWF, + ws_ADJUST, ws_EVENT, + ws_STREET, ws_INLET_USAGE, + ws_INLET, NULL}; +char* SnowmeltWords[] = { w_PLOWABLE, w_IMPERV, w_PERV, w_REMOVAL, NULL}; +char* SurchargeWords[] = { w_EXTRAN, w_SLOT, NULL}; +char* TempKeyWords[] = { w_TIMESERIES, w_FILE, w_WINDSPEED, w_SNOWMELT, + w_ADC, NULL}; +char* TransectKeyWords[] = { w_NC, w_X1, w_GR, NULL}; +char* TreatTypeWords[] = { w_REMOVAL, w_CONCEN, NULL}; +char* UHTypeWords[] = { w_SHORT, w_MEDIUM, w_LONG, NULL}; +char* VolUnitsWords[] = { w_MGAL, w_MLTRS }; +char* VolUnitsWords2[] = { w_GAL, w_LTR }; +char* WashoffTypeWords[] = { w_NONE, w_EXP, w_RC, w_EMC, NULL}; +char* WeirTypeWords[] = { w_TRANSVERSE, w_SIDEFLOW, w_VNOTCH, + w_TRAPEZOIDAL, w_ROADWAY, NULL}; +char* XsectTypeWords[] = { w_DUMMY, w_CIRCULAR, + w_FILLED_CIRCULAR, w_RECT_CLOSED, + w_RECT_OPEN, w_TRAPEZOIDAL, + w_TRIANGULAR, w_PARABOLIC, + w_POWERFUNC, w_RECT_TRIANG, + w_RECT_ROUND, w_MOD_BASKET, + w_HORIZELLIPSE, w_VERTELLIPSE, + w_ARCH, w_EGGSHAPED, + w_HORSESHOE, w_GOTHIC, + w_CATENARY, w_SEMIELLIPTICAL, + w_BASKETHANDLE, w_SEMICIRCULAR, + w_IRREGULAR, w_CUSTOM, + w_FORCE_MAIN, w_STREET, + NULL}; diff --git a/src/keywords.h b/src/keywords.h new file mode 100644 index 000000000..e330c59bc --- /dev/null +++ b/src/keywords.h @@ -0,0 +1,73 @@ +//----------------------------------------------------------------------------- +// keywords.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Exportable keyword dictionary +// +// Update History +// ============== +// Build 5.1.008: +// - Keyword arrays listed in alphabetical order. +// Build 5.1.013: +// - New keyword array defined for surcharge method. +//----------------------------------------------------------------------------- + +#ifndef KEYWORDS_H +#define KEYWORDS_H + + +extern char* BuildupTypeWords[]; +extern char* CurveTypeWords[]; +extern char* DividerTypeWords[]; +extern char* DynWaveMethodWords[]; +extern char* EvapTypeWords[]; +extern char* FileModeWords[]; +extern char* FileTypeWords[]; +extern char* FlowUnitWords[]; +extern char* ForceMainEqnWords[]; +extern char* GageDataWords[]; +extern char* InertDampingWords[]; +extern char* InfilModelWords[]; +extern char* LinkOffsetWords[]; +extern char* LinkTypeWords[]; +extern char* LoadUnitsWords[]; +extern char* NodeTypeWords[]; +extern char* NoneAllWords[]; +extern char* NormalFlowWords[]; +extern char* NormalizerWords[]; +extern char* NoYesWords[]; +extern char* OldRouteModelWords[]; +extern char* OffOnWords[]; +extern char* OptionWords[]; +extern char* OrificeTypeWords[]; +extern char* OutfallTypeWords[]; +extern char* PatternTypeWords[]; +extern char* PondingUnitsWords[]; +extern char* ProcessVarWords[]; +extern char* PumpTypeWords[]; +extern char* QualUnitsWords[]; +extern char* RainTypeWords[]; +extern char* RainUnitsWords[]; +extern char* ReportWords[]; +extern char* RelationWords[]; +extern char* RouteModelWords[]; +extern char* RuleKeyWords[]; +extern char* SectWords[]; +extern char* SnowmeltWords[]; +extern char* SurchargeWords[]; +extern char* TempKeyWords[]; +extern char* TransectKeyWords[]; +extern char* TreatTypeWords[]; +extern char* UHTypeWords[]; +extern char* VolUnitsWords[]; +extern char* VolUnitsWords2[]; +extern char* WashoffTypeWords[]; +extern char* WeirTypeWords[]; +extern char* XsectTypeWords[]; + + +#endif //KEYWORDS_H diff --git a/src/kinwave.c b/src/kinwave.c new file mode 100644 index 000000000..b137e4ce9 --- /dev/null +++ b/src/kinwave.c @@ -0,0 +1,272 @@ +//----------------------------------------------------------------------------- +// kinwave.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// M. Tryby (EPA) +// +// Kinematic wave flow routing functions. +// +// Update History +// ============== +// 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 + +#include +#include "headers.h" +#include "findroot.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +static const double WX = 0.6; // distance weighting +static const double WT = 0.6; // time weighting +static const double EPSIL = 0.001; // convergence criterion + +//----------------------------------------------------------------------------- +// Shared variables +//----------------------------------------------------------------------------- +static double Beta1; +static double C1; +static double C2; +static double Afull; +static double Qfull; +static TXsect* pXsect; + +//----------------------------------------------------------------------------- +// External functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// kinwave_execute (called by flowrout_execute) + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static int solveContinuity(double qin, double ain, double* aout); +static void evalContinuity(double a, double* f, double* df, void* p); + +//============================================================================= + +int kinwave_execute(int j, double* qinflow, double* qoutflow, double tStep) +// +// Input: j = link index +// qinflow = inflow at current time (cfs) +// tStep = time step (sec) +// Output: qoutflow = outflow at current time (cfs), +// returns number of iterations used +// Purpose: finds outflow over time step tStep given flow entering a +// conduit using Kinematic Wave flow routing. +// +// +// ^ q3 +// t | +// | qin, ain |-------------------| qout, aout +// | | Flow ---> | +// |----> x q1, a1 |-------------------| q2, a2 +// +// +{ + int k; + int result = 1; + double dxdt, dq; + double ain, aout; + double qin, qout; + double a1, a2, q1, q2, q3; + + // --- no routing for non-conduit link + (*qoutflow) = (*qinflow); + if ( Link[j].type != CONDUIT ) return result; + + // --- no routing for dummy xsection + if ( Link[j].xsect.type == DUMMY ) return result; + + // --- assign module-level variables + pXsect = &Link[j].xsect; + Qfull = Link[j].qFull; + Afull = Link[j].xsect.aFull; + k = Link[j].subIndex; + Beta1 = Conduit[k].beta / Qfull; + + // --- normalize previous flows + q1 = Conduit[k].q1 / Qfull; + q2 = Conduit[k].q2 / Qfull; + + // --- normalize inflow + qin = (*qinflow) / Conduit[k].barrels / Qfull; + + // --- compute evaporation and infiltration loss rate + q3 = link_getLossRate(j, qin*Qfull) / Qfull; + + // --- normalize previous areas + a1 = Conduit[k].a1 / Afull; + a2 = Conduit[k].a2 / Afull; + + // --- use full area when inlet flow >= full flow + if ( qin >= 1.0 ) ain = 1.0; + + // --- get normalized inlet area corresponding to inlet flow + else ain = xsect_getAofS(pXsect, qin/Beta1) / Afull; + + // --- check for no flow + if ( qin <= TINY && q2 <= TINY ) + { + qout = 0.0; + aout = 0.0; + } + + // --- otherwise solve finite difference form of continuity eqn. + else + { + // --- compute constant factors + dxdt = link_getLength(j) / tStep * Afull / Qfull; + dq = q2 - q1; + C1 = dxdt * WT / WX; + C2 = (1.0 - WT) * (ain - a1); + C2 = C2 - WT * a2; + C2 = C2 * dxdt / WX; + C2 = C2 + (1.0 - WX) / WX * dq - qin; + C2 = C2 + q3 / WX; + + // --- starting guess for aout is value from previous time step + aout = a2; + + // --- solve continuity equation for aout + result = solveContinuity(qin, ain, &aout); + + // --- report error if continuity eqn. not solved + if ( result == -1 ) + { + report_writeErrorMsg(ERR_KINWAVE, Link[j].ID); + return 1; + } + if ( result <= 0 ) result = 1; + + // --- compute normalized outlet flow from outlet area + qout = Beta1 * xsect_getSofA(pXsect, aout*Afull); + if ( qin > 1.0 ) qin = 1.0; + } + + // --- save new flows and areas + Conduit[k].q1 = qin * Qfull; + Conduit[k].a1 = ain * Afull; + Conduit[k].q2 = qout * Qfull; + Conduit[k].a2 = aout * Afull; + Conduit[k].fullState = + link_getFullState(Conduit[k].a1, Conduit[k].a2, Afull); + (*qinflow) = Conduit[k].q1 * Conduit[k].barrels; + (*qoutflow) = Conduit[k].q2 * Conduit[k].barrels; + return result; +} + +//============================================================================= + +int solveContinuity(double qin, double ain, double* aout) +// +// Input: qin = upstream normalized flow +// ain = upstream normalized area +// aout = downstream normalized area +// Output: new value for aout; returns an error code +// Purpose: solves continuity equation f(a) = Beta1*S(a) + C1*a + C2 = 0 +// for 'a' using the Newton-Raphson root finder function. +// Return code has the following meanings: +// >= 0 number of function evaluations used +// -1 Newton function failed +// -2 flow always above max. flow +// -3 flow always below zero +// +// Note: pXsect (pointer to conduit's cross-section), and constants Beta1, +// C1, and C2 are module-level shared variables assigned values +// in kinwave_execute(). +// +{ + int n; // # evaluations or error code + double aLo, aHi, aTmp; // lower/upper bounds on a + double fLo, fHi; // lower/upper bounds on f + double tol = EPSIL; // absolute convergence tol. + + // --- first determine bounds on 'a' so that f(a) passes through 0. + + // --- set upper bound to area at full flow + aHi = 1.0; + fHi = 1.0 + C1 + C2; + + // --- try setting lower bound to area where section factor is maximum + aLo = xsect_getAmax(pXsect) / Afull; + if ( aLo < aHi ) + { + fLo = ( Beta1 * pXsect->sMax ) + (C1 * aLo) + C2; + } + else fLo = fHi; + + // --- if fLo and fHi have same sign then set lower bound to 0 + if ( fHi*fLo > 0.0 ) + { + aHi = aLo; + fHi = fLo; + aLo = 0.0; + fLo = C2; + } + + // --- proceed with search for root if fLo and fHi have different signs + if ( fHi*fLo <= 0.0 ) + { + // --- start search at midpoint of lower/upper bounds + // if initial value outside of these bounds + if ( *aout < aLo || *aout > aHi ) *aout = 0.5*(aLo + aHi); + + // --- if fLo > fHi then switch aLo and aHi + if ( fLo > fHi ) + { + aTmp = aLo; + aLo = aHi; + aHi = aTmp; + } + + // --- call the Newton root finder method passing it the + // evalContinuity function to evaluate the function + // and its derivatives + n = findroot_Newton(aLo, aHi, aout, tol, evalContinuity, NULL); + + // --- check if root finder succeeded + if ( n <= 0 ) n = -1; + } + + // --- if lower/upper bound functions both negative then use full flow + else if ( fLo < 0.0 ) + { + if ( qin > 1.0 ) *aout = ain; + else *aout = 1.0; + n = -2; + } + + // --- if lower/upper bound functions both positive then use no flow + else if ( fLo > 0 ) + { + *aout = 0.0; + n = -3; + } + else n = -1; + return n; +} + +//============================================================================= + +void evalContinuity(double a, double* f, double* df, void* p) +// +// Input: a = outlet normalized area +// Output: f = value of continuity eqn. +// df = derivative of continuity eqn. +// Purpose: computes value of continuity equation (f) and its derivative (df) +// w.r.t. normalized area for link with normalized outlet area 'a'. +// +{ + *f = (Beta1 * xsect_getSofA(pXsect, a*Afull)) + (C1 * a) + C2; + *df = (Beta1 * Afull * xsect_getdSdA(pXsect, a*Afull)) + C1; +} + +//============================================================================= diff --git a/src/landuse.c b/src/landuse.c new file mode 100644 index 000000000..4c8402790 --- /dev/null +++ b/src/landuse.c @@ -0,0 +1,723 @@ +//----------------------------------------------------------------------------- +// landuse.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Pollutant buildup and washoff functions. +// +// Update History +// ============== +// Build 5.1.008: +// - landuse_getWashoffMass() re-named to landuse_getWashoffQual() and +// modified to return concentration instead of mass load. +// - landuse_getRunoffLoad() re-named to landuse_getWashoffLoad() and +// modified to work with landuse_getWashoffQual(). +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include "headers.h" + +//----------------------------------------------------------------------------- +// External functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// landuse_readParams (called by parseLine in input.c) +// landuse_readPollutParams (called by parseLine in input.c) +// landuse_readBuildupParams (called by parseLine in input.c) +// landuse_readWashoffParams (called by parseLine in input.c) + +// landuse_getInitBuildup (called by subcatch_initState) +// landuse_getBuildup (called by surfqual_getBuildup) +// landuse_getWashoffLoad (called by surfqual_getWashoff) +// landuse_getCoPollutLoad (called by surfqual_getwashoff)); +// landuse_getAvgBMPEffic (called by updatePondedQual in surfqual.c) + +//----------------------------------------------------------------------------- +// Function declarations +//----------------------------------------------------------------------------- +static double landuse_getBuildupDays(int landuse, int pollut, double buildup); +static double landuse_getBuildupMass(int landuse, int pollut, double days); +static double landuse_getWashoffQual(int landuse, int pollut, double buildup, + double runoff, double area); +static double landuse_getExternalBuildup(int i, int p, double buildup, + double tStep); + +//============================================================================= + +int landuse_readParams(int j, char* tok[], int ntoks) +// +// Input: j = land use index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads landuse parameters from a tokenized line of input. +// +// Data format is: +// landuseID (sweepInterval sweepRemoval sweepDays0) +// +{ + char *id; + if ( ntoks < 1 ) return error_setInpError(ERR_ITEMS, ""); + id = project_findID(LANDUSE, tok[0]); + if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); + Landuse[j].ID = id; + if ( ntoks > 1 ) + { + if ( ntoks < 4 ) return error_setInpError(ERR_ITEMS, ""); + if ( ! getDouble(tok[1], &Landuse[j].sweepInterval) ) + return error_setInpError(ERR_NUMBER, tok[1]); + if ( ! getDouble(tok[2], &Landuse[j].sweepRemoval) ) + return error_setInpError(ERR_NUMBER, tok[2]); + if ( ! getDouble(tok[3], &Landuse[j].sweepDays0) ) + return error_setInpError(ERR_NUMBER, tok[3]); + } + else + { + Landuse[j].sweepInterval = 0.0; + Landuse[j].sweepRemoval = 0.0; + Landuse[j].sweepDays0 = 0.0; + } + if ( Landuse[j].sweepRemoval < 0.0 + || Landuse[j].sweepRemoval > 1.0 ) + return error_setInpError(ERR_NUMBER, tok[2]); + return 0; +} + +//============================================================================= + +int landuse_readPollutParams(int j, char* tok[], int ntoks) +// +// Input: j = pollutant index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads pollutant parameters from a tokenized line of input. +// +// Data format is: +// ID Units cRain cGW cRDII kDecay (snowOnly coPollut coFrac cDWF cInit) +// +{ + int i, k, coPollut, snowFlag; + double x[4], coFrac, cDWF, cInit; + char *id; + + // --- extract pollutant name & units + if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); + id = project_findID(POLLUT, tok[0]); + if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); + k = findmatch(tok[1], QualUnitsWords); + if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); + + // --- extract concen. in rain, gwater, & I&I + for ( i = 2; i <= 4; i++ ) + { + if ( ! getDouble(tok[i], &x[i-2]) || x[i-2] < 0.0 ) + { + return error_setInpError(ERR_NUMBER, tok[i]); + } + } + + // --- extract decay coeff. (which can be negative for growth) + if ( ! getDouble(tok[5], &x[3]) ) + { + return error_setInpError(ERR_NUMBER, tok[5]); + } + + // --- set defaults for snow only flag & co-pollut. parameters + snowFlag = 0; + coPollut = -1; + coFrac = 0.0; + cDWF = 0.0; + cInit = 0.0; + + // --- check for snow only flag + if ( ntoks >= 7 ) + { + snowFlag = findmatch(tok[6], NoYesWords); + if ( snowFlag < 0 ) return error_setInpError(ERR_KEYWORD, tok[6]); + } + + // --- check for co-pollutant + if ( ntoks >= 9 ) + { + if ( !strcomp(tok[7], "*") ) + { + coPollut = project_findObject(POLLUT, tok[7]); + if ( coPollut < 0 ) return error_setInpError(ERR_NAME, tok[7]); + if ( ! getDouble(tok[8], &coFrac) || coFrac < 0.0 ) + return error_setInpError(ERR_NUMBER, tok[8]); + } + } + + // --- check for DWF concen. + if ( ntoks >= 10 ) + { + if ( ! getDouble(tok[9], &cDWF) || cDWF < 0.0) + return error_setInpError(ERR_NUMBER, tok[9]); + } + + // --- check for initial concen. + if ( ntoks >= 11 ) + { + if ( ! getDouble(tok[10], &cInit) || cInit < 0.0 ) + return error_setInpError(ERR_NUMBER, tok[9]); + } + + // --- save values for pollutant object + Pollut[j].ID = id; + Pollut[j].units = k; + if ( Pollut[j].units == MG ) Pollut[j].mcf = UCF(MASS); + else if ( Pollut[j].units == UG ) Pollut[j].mcf = UCF(MASS) / 1000.0; + else Pollut[j].mcf = 1.0; + Pollut[j].pptConcen = x[0]; + Pollut[j].gwConcen = x[1]; + Pollut[j].rdiiConcen = x[2]; + Pollut[j].kDecay = x[3]/SECperDAY; + Pollut[j].snowOnly = snowFlag; + Pollut[j].coPollut = coPollut; + Pollut[j].coFraction = coFrac; + Pollut[j].dwfConcen = cDWF; + Pollut[j].initConcen = cInit; + return 0; +} + +//============================================================================= + +int landuse_readBuildupParams(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads pollutant buildup parameters from a tokenized line of input. +// +// Data format is: +// landuseID pollutID buildupType c1 c2 c3 normalizerType +// +{ + int i, j, k, n, p; + double c[3] = {0, 0, 0}, tmax; + + if ( ntoks < 3 ) return 0; + j = project_findObject(LANDUSE, tok[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); + p = project_findObject(POLLUT, tok[1]); + if ( p < 0 ) return error_setInpError(ERR_NAME, tok[1]); + k = findmatch(tok[2], BuildupTypeWords); + if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[2]); + Landuse[j].buildupFunc[p].funcType = k; + if ( k > NO_BUILDUP ) + { + if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); + if ( k != EXTERNAL_BUILDUP ) for (i=0; i<3; i++) + { + if ( ! getDouble(tok[i+3], &c[i]) || c[i] < 0.0 ) + { + return error_setInpError(ERR_NUMBER, tok[i+3]); + } + } + n = findmatch(tok[6], NormalizerWords); + if (n < 0 ) return error_setInpError(ERR_KEYWORD, tok[6]); + Landuse[j].buildupFunc[p].normalizer = n; + } + + // Find time until max. buildup (or time series for external buildup) + switch (Landuse[j].buildupFunc[p].funcType) + { + case POWER_BUILDUP: + // --- check for too small or large an exponent + if ( c[2] > 0.0 && (c[2] < 0.01 || c[2] > 10.0) ) + return error_setInpError(ERR_KEYWORD, tok[5]); + + // --- find time to reach max. buildup + // --- use zero if coeffs. are 0 + if ( c[1]*c[2] == 0.0 ) tmax = 0.0; + + // --- use 10 years if inverse power function tends to blow up + else if ( log10(c[0]) / c[2] > 3.5 ) tmax = 3650.0; + + // --- otherwise use inverse power function + else tmax = pow(c[0]/c[1], 1.0/c[2]); + break; + + case EXPON_BUILDUP: + if ( c[1] == 0.0 ) tmax = 0.0; + else tmax = -log(0.001)/c[1]; + break; + + case SATUR_BUILDUP: + tmax = 1000.0*c[2]; + break; + + case EXTERNAL_BUILDUP: + if ( !getDouble(tok[3], &c[0]) || c[0] < 0.0 ) //max. buildup + return error_setInpError(ERR_NUMBER, tok[3]); + if ( !getDouble(tok[4], &c[1]) || c[1] < 0.0 ) //scaling factor + return error_setInpError(ERR_NUMBER, tok[3]); + n = project_findObject(TSERIES, tok[5]); //time series + if ( n < 0 ) return error_setInpError(ERR_NAME, tok[4]); + Tseries[n].refersTo = EXTERNAL_BUILDUP; + c[2] = n; + tmax = 0.0; + break; + + default: + tmax = 0.0; + } + + // Assign parameters to buildup object + Landuse[j].buildupFunc[p].coeff[0] = c[0]; + Landuse[j].buildupFunc[p].coeff[1] = c[1]; + Landuse[j].buildupFunc[p].coeff[2] = c[2]; + Landuse[j].buildupFunc[p].maxDays = tmax; + return 0; +} + +//============================================================================= + +int landuse_readWashoffParams(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads pollutant washoff parameters from a tokenized line of input. +// +// Data format is: +// landuseID pollutID washoffType c1 c2 sweepEffic bmpRemoval +{ + int i, j, p; + int func; + double x[4]; + + if ( ntoks < 3 ) return 0; + for (i=0; i<4; i++) x[i] = 0.0; + func = NO_WASHOFF; + j = project_findObject(LANDUSE, tok[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); + p = project_findObject(POLLUT, tok[1]); + if ( p < 0 ) return error_setInpError(ERR_NAME, tok[1]); + if ( ntoks > 2 ) + { + func = findmatch(tok[2], WashoffTypeWords); + if ( func < 0 ) return error_setInpError(ERR_KEYWORD, tok[2]); + if ( func != NO_WASHOFF ) + { + if ( ntoks < 5 ) return error_setInpError(ERR_ITEMS, ""); + if ( ! getDouble(tok[3], &x[0]) ) + return error_setInpError(ERR_NUMBER, tok[3]); + if ( ! getDouble(tok[4], &x[1]) ) + return error_setInpError(ERR_NUMBER, tok[4]); + if ( ntoks >= 6 ) + { + if ( ! getDouble(tok[5], &x[2]) ) + return error_setInpError(ERR_NUMBER, tok[5]); + } + if ( ntoks >= 7 ) + { + if ( ! getDouble(tok[6], &x[3]) ) + return error_setInpError(ERR_NUMBER, tok[6]); + } + } + } + + // --- check for valid parameter values + // x[0] = washoff coeff. + // x[1] = washoff expon. + // x[2] = sweep effic. + // x[3] = BMP effic. + if ( x[0] < 0.0 ) return error_setInpError(ERR_NUMBER, tok[3]); + if ( x[1] < -10.0 || x[1] > 10.0 ) + return error_setInpError(ERR_NUMBER, tok[4]);; + if ( x[2] < 0.0 || x[2] > 100.0 ) + return error_setInpError(ERR_NUMBER, tok[5]); + if ( x[3] < 0.0 || x[3] > 100.0 ) + return error_setInpError(ERR_NUMBER, tok[6]); + + // --- convert units of washoff coeff. + if ( func == EXPON_WASHOFF ) x[0] /= 3600.0; + if ( func == RATING_WASHOFF ) x[0] *= pow(UCF(FLOW), x[1]); + if ( func == EMC_WASHOFF ) x[0] *= LperFT3; + + // --- assign washoff parameters to washoff object + Landuse[j].washoffFunc[p].funcType = func; + Landuse[j].washoffFunc[p].coeff = x[0]; + Landuse[j].washoffFunc[p].expon = x[1]; + Landuse[j].washoffFunc[p].sweepEffic = x[2] / 100.0; + Landuse[j].washoffFunc[p].bmpEffic = x[3] / 100.0; + return 0; +} + +//============================================================================= + +void landuse_getInitBuildup(TLandFactor* landFactor, double* initBuildup, + double area, double curb) +// +// Input: landFactor = array of land use factors +// initBuildup = total initial buildup of each pollutant +// area = subcatchment's area (ft2) +// curb = subcatchment's curb length (users units) +// Output: modifies each land use factor's initial pollutant buildup +// Purpose: determines the initial buildup of each pollutant on +// each land use for a given subcatchment. +// +// Notes: Contributions from co-pollutants to initial buildup are not +// included since the co-pollutant mechanism only applies to +// washoff. +// +{ + int i, p; + double startDrySeconds; // antecedent dry period (sec) + double f; // faction of total land area + double fArea; // area of land use (ft2) + double fCurb; // curb length of land use + double buildup; // pollutant mass buildup + + // --- convert antecedent dry days into seconds + startDrySeconds = StartDryDays*SECperDAY; + + // --- examine each land use + for (i = 0; i < Nobjects[LANDUSE]; i++) + { + // --- initialize date when last swept + landFactor[i].lastSwept = StartDateTime - Landuse[i].sweepDays0; + + // --- determine area and curb length covered by land use + f = landFactor[i].fraction; + fArea = f * area * UCF(LANDAREA); + fCurb = f * curb; + + // --- determine buildup of each pollutant + for (p = 0; p < Nobjects[POLLUT]; p++) + { + // --- if an initial loading was supplied, then use it to + // find the starting buildup over the land use + buildup = 0.0; + if ( initBuildup[p] > 0.0 ) buildup = initBuildup[p] * fArea; + + // --- otherwise use the land use's buildup function to + // compute a buildup over the antecedent dry period + else buildup = landuse_getBuildup(i, p, fArea, fCurb, buildup, + startDrySeconds); + landFactor[i].buildup[p] = buildup; + } + } +} + +//============================================================================= + +double landuse_getBuildup(int i, int p, double area, double curb, double buildup, + double tStep) +// +// Input: i = land use index +// p = pollutant index +// area = land use area (ac or ha) +// curb = land use curb length (users units) +// buildup = current pollutant buildup (lbs or kg) +// tStep = time increment for buildup (sec) +// Output: returns new buildup mass (lbs or kg) +// Purpose: computes new pollutant buildup on a landuse after a time increment. +// +{ + int n; // normalizer code + double days; // accumulated days of buildup + double perUnit; // normalizer value (area or curb length) + + // --- return current buildup if no buildup function or time increment + if ( Landuse[i].buildupFunc[p].funcType == NO_BUILDUP || tStep == 0.0 ) + { + return buildup; + } + + // --- see what buildup is normalized to + n = Landuse[i].buildupFunc[p].normalizer; + perUnit = 1.0; + if ( n == PER_AREA ) perUnit = area; + if ( n == PER_CURB ) perUnit = curb; + if ( perUnit == 0.0 ) return 0.0; + + // --- buildup determined by loading time series + if ( Landuse[i].buildupFunc[p].funcType == EXTERNAL_BUILDUP ) + { + return landuse_getExternalBuildup(i, p, buildup/perUnit, tStep) * + perUnit; + } + + // --- determine equivalent days of current buildup + days = landuse_getBuildupDays(i, p, buildup/perUnit); + + // --- compute buildup after adding on time increment + days += tStep / SECperDAY; + return landuse_getBuildupMass(i, p, days) * perUnit; +} + +//============================================================================= + +double landuse_getBuildupDays(int i, int p, double buildup) +// +// Input: i = land use index +// p = pollutant index +// buildup = amount of pollutant buildup +// Output: returns number of days it takes for buildup to reach a given level +// Purpose: finds the number of days corresponding to a pollutant buildup. +// +{ + double c0 = Landuse[i].buildupFunc[p].coeff[0]; + double c1 = Landuse[i].buildupFunc[p].coeff[1]; + double c2 = Landuse[i].buildupFunc[p].coeff[2]; + + if ( buildup == 0.0 ) return 0.0; + if ( buildup >= c0 ) return Landuse[i].buildupFunc[p].maxDays; + switch (Landuse[i].buildupFunc[p].funcType) + { + case POWER_BUILDUP: + if ( c1*c2 == 0.0 ) return 0.0; + else return pow( (buildup/c1), (1.0/c2) ); + + case EXPON_BUILDUP: + if ( c0*c1 == 0.0 ) return 0.0; + else return -log(1. - buildup/c0) / c1; + + case SATUR_BUILDUP: + if ( c0 == 0.0 ) return 0.0; + else return buildup*c2 / (c0 - buildup); + + default: + return 0.0; + } +} + +//============================================================================= + +double landuse_getBuildupMass(int i, int p, double days) +// +// Input: i = land use index +// p = pollutant index +// days = time over which buildup has occurred (days) +// Output: returns mass of pollutant buildup (lbs or kg per area or curblength) +// Purpose: finds amount of buildup of pollutant on a land use. +// +{ + double b; + double c0 = Landuse[i].buildupFunc[p].coeff[0]; + double c1 = Landuse[i].buildupFunc[p].coeff[1]; + double c2 = Landuse[i].buildupFunc[p].coeff[2]; + + if ( days == 0.0 ) return 0.0; + if ( days >= Landuse[i].buildupFunc[p].maxDays ) return c0; + switch (Landuse[i].buildupFunc[p].funcType) + { + case POWER_BUILDUP: + b = c1 * pow(days, c2); + if ( b > c0 ) b = c0; + break; + + case EXPON_BUILDUP: + b = c0*(1.0 - exp(-days*c1)); + break; + + case SATUR_BUILDUP: + b = days*c0/(c2 + days); + break; + + default: b = 0.0; + } + return b; +} + +//============================================================================= + +double landuse_getAvgBmpEffic(int j, int p) +// +// Input: j = subcatchment index +// p = pollutant index +// Output: returns a BMP removal fraction for pollutant p +// Purpose: finds the overall average BMP removal achieved for pollutant p +// treated in subcatchment j. +// +{ + int i; + double r = 0.0; + for (i = 0; i < Nobjects[LANDUSE]; i++) + { + r += Subcatch[j].landFactor[i].fraction * + Landuse[i].washoffFunc[p].bmpEffic; + } + return r; +} + +//============================================================================= + +double landuse_getWashoffLoad(int i, int p, double area, + TLandFactor landFactor[], double runoff, double vOutflow) +// +// Input: i = land use index +// p = pollut. index +// area = sucatchment area (ft2) +// landFactor[] = array of land use data for subcatchment +// runoff = runoff flow generated by subcatchment (ft/sec) +// vOutflow = runoff volume leaving the subcatchment (ft3) +// Output: returns pollutant runoff load (mass) +// Purpose: computes pollutant load generated by a land use over a time step. +// +{ + double landuseArea; // area of current land use (ft2) + double buildup; // current pollutant buildup (lb or kg) + double washoffQual; // pollutant concentration in washoff (mass/ft3) + double washoffLoad; // pollutant washoff load over time step (lb or kg) + double bmpRemoval; // pollutant load removed by BMP treatment (lb or kg) + + // --- compute concen. of pollutant in washoff (mass/ft3) + buildup = landFactor[i].buildup[p]; + landuseArea = landFactor[i].fraction * area; + washoffQual = landuse_getWashoffQual(i, p, buildup, runoff, landuseArea); + + // --- compute washoff load exported (lbs or kg) from landuse + // (Pollut[].mcf converts from mg (or ug) mass units to lbs (or kg) + washoffLoad = washoffQual * vOutflow * landuseArea / area * Pollut[p].mcf; + + // --- if buildup modelled, reduce it by amount of washoff + if ( Landuse[i].buildupFunc[p].funcType != NO_BUILDUP || + buildup > washoffLoad ) + { + washoffLoad = MIN(washoffLoad, buildup); + buildup -= washoffLoad; + landFactor[i].buildup[p] = buildup; + } + + // --- otherwise add washoff to buildup mass balance totals + // so that things will balance + else + { + massbal_updateLoadingTotals(BUILDUP_LOAD, p, washoffLoad); + landFactor[i].buildup[p] = 0.0; + } + + // --- apply any BMP removal to washoff + bmpRemoval = Landuse[i].washoffFunc[p].bmpEffic * washoffLoad; + if ( bmpRemoval > 0.0 ) + { + massbal_updateLoadingTotals(BMP_REMOVAL_LOAD, p, bmpRemoval); + washoffLoad -= bmpRemoval; + } + + // --- return washoff load converted back to mass (mg or ug) + return washoffLoad / Pollut[p].mcf; +} + +//============================================================================= + +double landuse_getWashoffQual(int i, int p, double buildup, double runoff, + double area) +// +// Input: i = land use index +// p = pollutant index +// buildup = current buildup over land use (lbs or kg) +// runoff = current runoff on subcatchment (ft/sec) +// area = area devoted to land use (ft2) +// Output: returns pollutant concentration in washoff (mass/ft3) +// Purpose: finds concentration of pollutant washed off a land use. +// +// Notes: "coeff" for each washoff function was previously adjusted to +// result in units of mass/sec +// +{ + double cWashoff = 0.0; + double coeff = Landuse[i].washoffFunc[p].coeff; + double expon = Landuse[i].washoffFunc[p].expon; + int func = Landuse[i].washoffFunc[p].funcType; + + // --- if no washoff function or no runoff, return 0 + if ( func == NO_WASHOFF || runoff == 0.0 ) return 0.0; + + // --- if buildup function exists but no current buildup, return 0 + if ( Landuse[i].buildupFunc[p].funcType != NO_BUILDUP && buildup == 0.0 ) + return 0.0; + + // --- Exponential Washoff function + if ( func == EXPON_WASHOFF ) + { + // --- evaluate washoff eqn. with runoff in in/hr (or mm/hr) + // and buildup converted from lbs (or kg) to concen. mass units + cWashoff = coeff * pow(runoff * UCF(RAINFALL), expon) * + buildup / Pollut[p].mcf; + cWashoff /= runoff * area; + } + + // --- Rating Curve Washoff function + else if ( func == RATING_WASHOFF ) + { + cWashoff = coeff * pow(runoff * area, expon-1.0); + } + + // --- Event Mean Concentration Washoff + else if ( func == EMC_WASHOFF ) + { + cWashoff = coeff; // coeff includes LperFT3 factor + } + return cWashoff; +} + +//============================================================================= + +double landuse_getCoPollutLoad(int p, double washoff[]) +// +// Input: p = pollutant index +// washoff = pollut. washoff rate (mass/sec) +// Output: returns washoff mass added by co-pollutant relation (mass) +// Purpose: finds washoff mass added by a co-pollutant of a given pollutant. +// +{ + int k; + double w; + + // --- check if pollutant p has a co-pollutant k + k = Pollut[p].coPollut; + if ( k >= 0 ) + { + // --- compute addition to washoff from co-pollutant + w = Pollut[p].coFraction * washoff[k]; + + // --- add washoff to buildup mass balance totals + // so that things will balance + massbal_updateLoadingTotals(BUILDUP_LOAD, p, w * Pollut[p].mcf); + return w; + } + return 0.0; +} + +//============================================================================= + +double landuse_getExternalBuildup(int i, int p, double buildup, double tStep) +// +// Input: i = landuse index +// p = pollutant index +// buildup = buildup at start of time step (mass/unit) +// tStep = time step (sec) +// Output: returns pollutant buildup at end of time interval (mass/unit) +// Purpose: finds pollutant buildup contributed by external loading over a +// given time step. +// +{ + double maxBuildup = Landuse[i].buildupFunc[p].coeff[0]; + double sf = Landuse[i].buildupFunc[p].coeff[1]; // scaling factor + int ts = (int)floor(Landuse[i].buildupFunc[p].coeff[2]); // time series index + double rate = 0.0; + + // --- no buildup increment at start of simulation + if (NewRunoffTime == 0.0) return 0.0; + + // --- get buildup rate (mass/unit/day) over the interval + if ( ts >= 0 ) + { + rate = sf * table_tseriesLookup(&Tseries[ts], + getDateTime(NewRunoffTime), FALSE); + } + + // --- compute buildup at end of time interval + buildup = buildup + rate * tStep / SECperDAY; + buildup = MIN(buildup, maxBuildup); + return buildup; +} diff --git a/src/lid.c b/src/lid.c new file mode 100644 index 000000000..7bb69dcef --- /dev/null +++ b/src/lid.c @@ -0,0 +1,2031 @@ +//----------------------------------------------------------------------------- +// lid.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// This module handles all data processing involving LID (Low Impact +// Development) practices used to treat runoff for individual subcatchments +// within a project. The actual computation of LID performance is made by +// functions within the lidproc.c module. See LidTypes below for the types +// of LIDs that can be modeled. +// +// An LID process is described by the TLidProc data structure and consists of +// size-independent design data for the different vertical layers that make +// up a specific type of LID. The collection of these LID process designs is +// stored in the LidProcs array. +// +// When a member of LidProcs is to be deployed in a particular subcatchment, +// its sizing and treatment data are stored in a TLidUnit data structure. +// The collection of all TLidUnits deployed in a subcatchment is held in a +// TLidGroup list data structure. The LidGroups array contains a TLidGroup +// list for each subcatchment in the project. +// +// During a runoff time step, each subcatchment calls the lid_getRunoff() +// function to compute flux rates and a water balance through each layer +// of each LID unit in the subcatchment. The resulting outflows (runoff, +// drain flow, evaporation and infiltration) are added to those computed +// for the non-LID portion of the subcatchment. +// +// An option exists for the detailed time series of flux rates and storage +// levels for a specific LID unit to be written to a text file named by the +// user for viewing outside of the SWMM program. +// +// Update History +// ============== +// Build 5.1.008: +// - More input error reporting added. +// - Rooftop Disconnection added to the types of LIDs. +// - LID drain flows are now tracked separately. +// - LID drain flows can now be routed to separate outlets. +// - Check added to insure LID flows not returned to nonexistent pervious area. +// Build 5.1.009: +// - Fixed bug where LID's could return outflow to non-LID area when LIDs +// make up entire subcatchment. +// Build 5.1.010: +// - Support for new Modified Green Ampt infiltration model added. +// - Imported variable HasWetLids now properly initialized. +// - Initial state of reporting (lidUnit->rptFile->wasDry) changed to +// prevent duplicate printing of first line of detailed report file. +// Build 5.1.011: +// - The top of the storage layer is no longer used as a limit for an +// underdrain offset thus allowing upturned drains to be modeled. +// - Column headings for the detailed LID report file were modified. +// Build 5.1.012: +// - Redefined initialization of wasDry for LID reporting. +// Build 5.1.013: +// - Support added for LID units treating pervious area runoff. +// - Support added for open/closed head levels and multiplier v. head +// 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. +// Build 5.1.015: +// - Support added for mutiple infiltration methods within a project. +// Build 5.2.0: +// - Covered property added to RAIN_BARREL parameters +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include "headers.h" +#include "lid.h" + +#define ERR_PAVE_LAYER " - check pavement layer parameters" +#define ERR_SOIL_LAYER " - check soil layer parameters" +#define ERR_STOR_LAYER " - check storage layer parameters" +#define ERR_SWALE_SURF " - check swale surface parameters" +#define ERR_GREEN_AMPT " - check subcatchment Green-Ampt parameters" +#define ERR_DRAIN_OFFSET " - drain offset exceeds storage height" +#define ERR_DRAIN_HEADS " - invalid drain open/closed heads" +#define ERR_SWALE_WIDTH " - invalid swale width" + +//----------------------------------------------------------------------------- +// Enumerations +//----------------------------------------------------------------------------- +enum LidLayerTypes { + SURF, // surface layer + SOIL, // soil layer + STOR, // storage layer + PAVE, // pavement layer + DRAINMAT, // drainage mat layer + DRAIN, // underdrain system + REMOVALS}; // pollutant removals + +//// Note: DRAINMAT must be placed before DRAIN so the two keywords can +/// be distinguished from one another when parsing a line of input. + +char* LidLayerWords[] = + {"SURFACE", "SOIL", "STORAGE", "PAVEMENT", "DRAINMAT", "DRAIN", + "REMOVALS", NULL}; + +char* LidTypeWords[] = + {"BC", //bio-retention cell + "RG", //rain garden + "GR", //green roof + "IT", //infiltration trench + "PP", //porous pavement + "RB", //rain barrel + "VS", //vegetative swale + "RD", //rooftop disconnection + NULL}; + +//----------------------------------------------------------------------------- +// Data Structures +//----------------------------------------------------------------------------- + +// LID List - list of LID units contained in an LID group +struct LidList +{ + TLidUnit* lidUnit; // ptr. to a LID unit + struct LidList* nextLidUnit; +}; +typedef struct LidList TLidList; + +// LID Group - collection of LID units applied to a specific subcatchment +struct LidGroup +{ + double pervArea; // amount of pervious area in group (ft2) + double flowToPerv; // total flow sent to pervious area (cfs) + double oldDrainFlow; // total drain flow in previous period (cfs) + double newDrainFlow; // total drain flow in current period (cfs) + TLidList* lidList; // list of LID units in the group +}; +typedef struct LidGroup* TLidGroup; + + +//----------------------------------------------------------------------------- +// Shared Variables +//----------------------------------------------------------------------------- +static TLidProc* LidProcs; // array of LID processes +static int LidCount; // number of LID processes +static TLidGroup* LidGroups; // array of LID process groups +static int GroupCount; // number of LID groups (subcatchments) + +static double EvapRate; // evaporation rate (ft/s) +static double NativeInfil; // native soil infil. rate (ft/s) +static double MaxNativeInfil; // native soil infil. rate limit (ft/s) + +//----------------------------------------------------------------------------- +// Imported Variables (from SUBCATCH.C) +//----------------------------------------------------------------------------- +// Volumes (ft3) for a subcatchment over a time step +extern double Vevap; // evaporation +extern double Vpevap; // pervious area evaporation +extern double Vinfil; // non-LID infiltration +extern double VlidInfil; // infiltration from LID units +extern double VlidIn; // impervious area flow to LID units +extern double VlidOut; // surface outflow from LID units +extern double VlidDrain; // drain outflow from LID units +extern double VlidReturn; // LID outflow returned to pervious area +extern char HasWetLids; // TRUE if any LIDs are wet + // (from RUNOFF.C) + +//----------------------------------------------------------------------------- +// External Functions (prototyped in lid.h) +//----------------------------------------------------------------------------- +// lid_create called by createObjects in project.c +// lid_delete called by deleteObjects in project.c +// lid_validate called by project_validate +// lid_initState called by project_init + +// lid_readProcParams called by parseLine in input.c +// lid_readGroupParams called by parseLine in input.c + +// lid_setOldGroupState called by subcatch_setOldState +// lid_setReturnQual called by findLidLoads in surfqual.c +// lid_getReturnQual called by subcatch_getRunon + +// lid_getPervArea called by subcatch_getFracPerv +// lid_getFlowToPerv called by subcatch_getRunon +// lid_getSurfaceDepth called by subcatch_getDepth +// lid_getDepthOnPavement called by sweptSurfacesDry in subcatch.c +// lid_getStoredVolume called by subcatch_getStorage +// lid_getRunon called by subcatch_getRunon +// lid_getRunoff called by subcatch_getRunoff + +// lid_addDrainRunon called by subcatch_getRunon +// lid_addDrainLoads called by surfqual_getWashoff +// lid_addDrainInflow called by addLidDrainInflows in routing.c + +// lid_writeSummary called by inputrpt_writeInput +// lid_writeWaterBalance called by statsrpt_writeReport + + +//----------------------------------------------------------------------------- +// Local Functions +//----------------------------------------------------------------------------- +static void freeLidGroup(int j); +static int readSurfaceData(int j, char* tok[], int ntoks); +static int readPavementData(int j, char* tok[], int ntoks); +static int readSoilData(int j, char* tok[], int ntoks); +static int readStorageData(int j, char* tok[], int ntoks); +static int readDrainData(int j, char* tok[], int ntoks); +static int readDrainMatData(int j, char* toks[], int ntoks); +static int readRemovalsData(int j, char* toks[], int ntoks); + +static int addLidUnit(int j, int k, int n, double x[], char* fname, + int drainSubcatch, int drainNode); +static int createLidRptFile(TLidUnit* lidUnit, char* fname); +static void initLidRptFile(char* title, char* lidID, char* subcatchID, + TLidUnit* lidUnit); +static void validateLidProc(int j); +static void validateLidGroup(int j); + +static int isLidPervious(int k); +static double getImpervAreaRunoff(int j); +static double getPervAreaRunoff(int j); +static double getSurfaceDepth(int subcatch); +static double getRainInflow(int j, TLidUnit* lidUnit); +static void findNativeInfil(int j, double tStep); + + +static void evalLidUnit(int j, TLidUnit* lidUnit, double lidArea, + double lidInflow, double tStep, double *qRunoff, + double *qDrain, double *qReturn); + +//============================================================================= + +void lid_create(int lidCount, int subcatchCount) +// +// Purpose: creates an array of LID objects. +// Input: n = number of LID processes +// Output: none +// +{ + int j; + + //... assign NULL values to LID arrays + LidProcs = NULL; + LidGroups = NULL; + LidCount = lidCount; + + //... create LID groups + GroupCount = subcatchCount; + if ( GroupCount > 0 ) + { + 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)); + if ( LidProcs == NULL ) + { + ErrorCode = ERR_MEMORY; + return; + } + + //... initialize LID objects + for (j = 0; j < LidCount; j++) + { + LidProcs[j].lidType = -1; + LidProcs[j].surface.thickness = 0.0; + LidProcs[j].surface.voidFrac = 1.0; + LidProcs[j].surface.roughness = 0.0; + LidProcs[j].surface.surfSlope = 0.0; + LidProcs[j].pavement.thickness = 0.0; + LidProcs[j].soil.thickness = 0.0; + LidProcs[j].storage.thickness = 0.0; + LidProcs[j].storage.kSat = 0.0; + LidProcs[j].drain.coeff = 0.0; + LidProcs[j].drain.offset = 0.0; + LidProcs[j].drainMat.thickness = 0.0; + LidProcs[j].drainMat.roughness = 0.0; + LidProcs[j].drainRmvl = NULL; + LidProcs[j].drainRmvl = (double *) + calloc(Nobjects[POLLUT], sizeof(double)); + if (LidProcs[j].drainRmvl == NULL) + { + ErrorCode = ERR_MEMORY; + return; + } + } +} + +//============================================================================= + +void lid_delete() +// +// Purpose: deletes all LID objects +// Input: none +// Output: none +// +{ + int j; + for (j = 0; j < GroupCount; j++) freeLidGroup(j); + FREE(LidGroups); + for (j = 0; j < LidCount; j++) FREE(LidProcs[j].drainRmvl); + FREE(LidProcs); + GroupCount = 0; + LidCount = 0; +} + +//============================================================================= + +void freeLidGroup(int j) +// +// Purpose: frees all LID units associated with a subcatchment. +// Input: j = group (or subcatchment) index +// Output: none +// +{ + TLidGroup lidGroup = LidGroups[j]; + TLidList* lidList; + TLidUnit* lidUnit; + TLidList* nextLidUnit; + + if ( lidGroup == NULL ) return; + lidList = lidGroup->lidList; + while (lidList) + { + lidUnit = lidList->lidUnit; + if ( lidUnit->rptFile ) + { + if ( lidUnit->rptFile->file ) fclose(lidUnit->rptFile->file); + free(lidUnit->rptFile); + } + nextLidUnit = lidList->nextLidUnit; + free(lidUnit); + free(lidList); + lidList = nextLidUnit; + } + free(lidGroup); + LidGroups[j] = NULL; +} + +//============================================================================= + +int lid_readProcParams(char* toks[], int ntoks) +// +// Purpose: reads LID process information from line of input data file +// Input: toks = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// +// Format for first line that defines a LID process is: +// LID_ID LID_Type +// +// Followed by some combination of lines below depending on LID_Type: +// LID_ID SURFACE +// LID_ID PAVEMENT +// LID_ID SOIL +// LID_ID STORAGE +// LID_ID DRAIN +// LID_ID DRAINMAT +// LID_ID REMOVALS +// +{ + int j, m; + + // --- check for minimum number of tokens + if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); + + // --- check that LID exists in database + j = project_findObject(LID, toks[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, toks[0]); + + // --- assign ID if not done yet + if ( LidProcs[j].ID == NULL ) + LidProcs[j].ID = project_findID(LID, toks[0]); + + // --- check if second token is the type of LID + m = findmatch(toks[1], LidTypeWords); + if ( m >= 0 ) + { + LidProcs[j].lidType = m; + return 0; + } + + // --- check if second token is name of LID layer + else m = findmatch(toks[1], LidLayerWords); + + // --- read input parameters for the identified layer + switch (m) + { + case SURF: return readSurfaceData(j, toks, ntoks); + case SOIL: return readSoilData(j, toks, ntoks); + case STOR: return readStorageData(j, toks, ntoks); + case PAVE: return readPavementData(j, toks, ntoks); + case DRAIN: return readDrainData(j, toks, ntoks); + case DRAINMAT: return readDrainMatData(j, toks, ntoks); + case REMOVALS: return readRemovalsData(j, toks, ntoks); + } + return error_setInpError(ERR_KEYWORD, toks[1]); +} + +//============================================================================= + +int lid_readGroupParams(char* toks[], int ntoks) +// +// Purpose: reads input data for a LID unit placed in a subcatchment. +// Input: toks = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// +// Format of input data line is: +// Subcatch_ID LID_ID Number Area Width InitSat FromImp ToPerv +// (RptFile DrainTo FromPerv) +// where: +// Subcatch_ID = name of subcatchment +// LID_ID = name of LID process +// Number (n) = number of replicate units +// Area (x[0]) = area of each unit +// Width (x[1]) = outflow width of each unit +// InitSat (x[2]) = % that LID is initially saturated +// FromImp (x[3]) = % of impervious runoff sent to LID +// ToPerv (x[4]) = 1 if outflow goes to pervious sub-area; 0 if not +// RptFile = name of detailed results file (optional) +// DrainTo = name of subcatch/node for drain flow (optional) +// FromPerv (x[5]) = % of pervious runoff sent to LID +// +{ + int i, j, k, n; + double x[6]; + char* fname = NULL; + int drainSubcatch = -1, drainNode = -1; + + //... check for valid number of input tokens + if ( ntoks < 8 ) return error_setInpError(ERR_ITEMS, ""); + + //... find subcatchment + j = project_findObject(SUBCATCH, toks[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, toks[0]); + + //... find LID process in list of LID processes + k = project_findObject(LID, toks[1]); + if ( k < 0 ) return error_setInpError(ERR_NAME, toks[1]); + + //... get number of replicates + n = atoi(toks[2]); + if ( n < 0 ) return error_setInpError(ERR_NUMBER, toks[2]); + if ( n == 0 ) return 0; + + //... convert next 4 tokens to doubles + for (i = 3; i <= 7; i++) + { + if ( ! getDouble(toks[i], &x[i-3]) || x[i-3] < 0.0 ) + return error_setInpError(ERR_NUMBER, toks[i]); + } + + //... check for valid percentages on tokens 5 & 6 (x[2] & x[3]) + for (i = 2; i <= 3; i++) if ( x[i] > 100.0 ) + return error_setInpError(ERR_NUMBER, toks[i+3]); + + //... read optional report file name + if ( ntoks >= 9 && strcmp(toks[8], "*") != 0 ) fname = toks[8]; + + //... read optional underdrain outlet + if ( ntoks >= 10 && strcmp(toks[9], "*") != 0 ) + { + drainSubcatch = project_findObject(SUBCATCH, toks[9]); + if ( drainSubcatch < 0 ) + { + drainNode = project_findObject(NODE, toks[9]); + if ( drainNode < 0 ) return error_setInpError(ERR_NAME, toks[9]); + } + } + + //... read percent of pervious area treated by LID unit + x[5] = 0.0; + if (ntoks >= 11) + { + if (!getDouble(toks[10], &x[5]) || x[5] < 0.0 || x[5] > 100.0) + return error_setInpError(ERR_NUMBER, toks[10]); + } + + //... create a new LID unit and add it to the subcatchment's LID group + return addLidUnit(j, k, n, x, fname, drainSubcatch, drainNode); +} + +//============================================================================= + +int addLidUnit(int j, int k, int n, double x[], char* fname, + int drainSubcatch, int drainNode) +// +// Purpose: adds an LID unit to a subcatchment's LID group. +// Input: j = subcatchment index +// k = LID control index +// n = number of replicate units +// x = LID unit's parameters +// fname = name of detailed performance report file +// drainSubcatch = index of subcatchment receiving underdrain flow +// drainNode = index of node receiving underdrain flow +// Output: returns an error code +// +{ + TLidUnit* lidUnit; + TLidList* lidList; + TLidGroup lidGroup; + + //... create a LID group (pointer to an LidGroup struct) + // if one doesn't already exist + lidGroup = LidGroups[j]; + if ( !lidGroup ) + { + lidGroup = (struct LidGroup *) malloc(sizeof(struct LidGroup)); + if ( !lidGroup ) return error_setInpError(ERR_MEMORY, ""); + lidGroup->lidList = NULL; + LidGroups[j] = lidGroup; + } + + //... create a new LID unit to add to the group + lidUnit = (TLidUnit *) malloc(sizeof(TLidUnit)); + if ( !lidUnit ) return error_setInpError(ERR_MEMORY, ""); + lidUnit->rptFile = NULL; + + //... add the LID unit to the group + lidList = (TLidList *) malloc(sizeof(TLidList)); + if ( !lidList ) + { + free(lidUnit); + return error_setInpError(ERR_MEMORY, ""); + } + lidList->lidUnit = lidUnit; + lidList->nextLidUnit = lidGroup->lidList; + lidGroup->lidList = lidList; + + //... assign parameter values to LID unit + lidUnit->lidIndex = k; + lidUnit->number = n; + lidUnit->area = x[0] / SQR(UCF(LENGTH)); + lidUnit->fullWidth = x[1] / UCF(LENGTH); + lidUnit->initSat = x[2] / 100.0; + lidUnit->fromImperv = x[3] / 100.0; + lidUnit->toPerv = (x[4] > 0.0); + lidUnit->fromPerv = x[5] / 100.0; + lidUnit->drainSubcatch = drainSubcatch; + lidUnit->drainNode = drainNode; + + //... open report file if it was supplied + if ( fname != NULL ) + { + if ( !createLidRptFile(lidUnit, fname) ) + return error_setInpError(ERR_RPT_FILE, fname); + } + return 0; +} + +//============================================================================= + +int createLidRptFile(TLidUnit* lidUnit, char* fname) +{ + TLidRptFile* rptFile; + + rptFile = (TLidRptFile *) malloc(sizeof(TLidRptFile)); + if ( rptFile == NULL ) return 0; + lidUnit->rptFile = rptFile; + rptFile->file = fopen(fname, "wt"); + if ( rptFile->file == NULL ) return 0; + return 1; +} + +//============================================================================= + +int readSurfaceData(int j, char* toks[], int ntoks) +// +// Purpose: reads surface layer data for a LID process from line of input +// data file +// Input: j = LID process index +// toks = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// +// Format of data is: +// LID_ID SURFACE StorageHt VegVolFrac Roughness SurfSlope SideSlope +// +{ + int i; + double x[5]; + + if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); + for (i = 2; i < 7; i++) + { + if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) + return error_setInpError(ERR_NUMBER, toks[i]); + } + if ( x[1] >= 1.0 ) return error_setInpError(ERR_NUMBER, toks[3]); + if ( x[0] == 0.0 ) x[1] = 0.0; + + LidProcs[j].surface.thickness = x[0] / UCF(RAINDEPTH); + LidProcs[j].surface.voidFrac = 1.0 - x[1]; + LidProcs[j].surface.roughness = x[2]; + LidProcs[j].surface.surfSlope = x[3] / 100.0; + LidProcs[j].surface.sideSlope = x[4]; + return 0; +} + +//============================================================================= + +int readPavementData(int j, char* toks[], int ntoks) +// +// Purpose: reads pavement layer data for a LID process from line of input +// data file +// Input: j = LID process index +// toks = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// +// Format of data is: +// LID_ID PAVEMENT Thickness VoidRatio FracImperv Permeability ClogFactor +// (RegenDays RegenDegree) +// +{ + int i; + double x[7]; + + if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); + for (i = 2; i < 7; i++) + { + if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) + return error_setInpError(ERR_NUMBER, toks[i]); + } + + // ... read optional clogging regeneration properties + x[5] = 0.0; + if (ntoks > 7) + { + if (!getDouble(toks[7], &x[5]) || x[5] < 0.0) + return error_setInpError(ERR_NUMBER, toks[7]); + } + x[6] = 0.0; + if (ntoks > 8) + { + if (!getDouble(toks[8], &x[6]) || x[6] < 0.0 || x[6] > 1.0) + return error_setInpError(ERR_NUMBER, toks[8]); + } + + //... convert void ratio to void fraction + x[1] = x[1]/(x[1] + 1.0); + + LidProcs[j].pavement.thickness = x[0] / UCF(RAINDEPTH); + LidProcs[j].pavement.voidFrac = x[1]; + LidProcs[j].pavement.impervFrac = x[2]; + LidProcs[j].pavement.kSat = x[3] / UCF(RAINFALL); + LidProcs[j].pavement.clogFactor = x[4]; + LidProcs[j].pavement.regenDays = x[5]; + LidProcs[j].pavement.regenDegree = x[6]; + return 0; +} + +//============================================================================= + +int readSoilData(int j, char* toks[], int ntoks) +// +// Purpose: reads soil layer data for a LID process from line of input +// data file +// Input: j = LID process index +// toks = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// +// Format of data is: +// LID_ID SOIL Thickness Porosity FieldCap WiltPt Ksat Kslope Suction +// +{ + int i; + double x[7]; + + if ( ntoks < 9 ) return error_setInpError(ERR_ITEMS, ""); + for (i = 2; i < 9; i++) + { + if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) + return error_setInpError(ERR_NUMBER, toks[i]); + } + LidProcs[j].soil.thickness = x[0] / UCF(RAINDEPTH); + LidProcs[j].soil.porosity = x[1]; + LidProcs[j].soil.fieldCap = x[2]; + LidProcs[j].soil.wiltPoint = x[3]; + LidProcs[j].soil.kSat = x[4] / UCF(RAINFALL); + LidProcs[j].soil.kSlope = x[5]; + LidProcs[j].soil.suction = x[6] / UCF(RAINDEPTH); + return 0; +} + +//============================================================================= + +int readStorageData(int j, char* toks[], int ntoks) +// +// Purpose: reads drainage layer data for a LID process from line of input +// data file +// Input: j = LID process index +// toks = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// +// Format of data is: +// LID_ID STORAGE Thickness VoidRatio Ksat ClogFactor (YES/NO) +// +{ + int i; + int covered = FALSE; + double x[6]; + + //... read numerical parameters + if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); + for (i = 2; i < 6; i++) + { + if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) + return error_setInpError(ERR_NUMBER, toks[i]); + } + + //... check if rain barrel is covered + if (ntoks > 6) + { + if (match(toks[6], w_YES)) + covered = TRUE; + } + + //... convert void ratio to void fraction + x[1] = x[1]/(x[1] + 1.0); + + //... save parameters to LID storage layer structure + LidProcs[j].storage.thickness = x[0] / UCF(RAINDEPTH); + LidProcs[j].storage.voidFrac = x[1]; + LidProcs[j].storage.kSat = x[2] / UCF(RAINFALL); + LidProcs[j].storage.clogFactor = x[3]; + LidProcs[j].storage.covered = covered; + return 0; +} + +//============================================================================= + +int readDrainData(int j, char* toks[], int ntoks) +// +// Purpose: reads underdrain data for a LID process from line of input +// data file +// Input: j = LID process index +// toks = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// +// Format of data is: +// LID_ID DRAIN coeff expon offset delay hOpen hClose curve +// +{ + int i; + double x[6]; + + //... read numerical parameters + if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); + for (i = 0; i < 6; i++) x[i] = 0.0; + for (i = 2; i < 8; i++) + { + if ( (ntoks > i) && (! getDouble(toks[i], &x[i-2]) || x[i-2]) < 0.0 ) + return error_setInpError(ERR_NUMBER, toks[i]); + } + + i = -1; + if ( ntoks >= 9 ) + { + i = project_findObject(CURVE, toks[8]); + if (i < 0) return error_setInpError(ERR_NAME, toks[8]); + } + + //... save parameters to LID drain layer structure + LidProcs[j].drain.coeff = x[0]; + LidProcs[j].drain.expon = x[1]; + LidProcs[j].drain.offset = x[2] / UCF(RAINDEPTH); + LidProcs[j].drain.delay = x[3] * 3600.0; + LidProcs[j].drain.hOpen = x[4] / UCF(RAINDEPTH); + LidProcs[j].drain.hClose = x[5] / UCF(RAINDEPTH); + LidProcs[j].drain.qCurve = i; + return 0; +} + +//============================================================================= + +int readDrainMatData(int j, char* toks[], int ntoks) +// +// Purpose: reads drainage mat data for a LID process from line of input +// data file +// Input: j = LID process index +// toks = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// +// Format of data is: +// LID_ID DRAINMAT thickness voidRatio roughness +// +{ + int i; + double x[3]; + + //... read numerical parameters + if ( ntoks < 5 ) return error_setInpError(ERR_ITEMS, ""); + if ( LidProcs[j].lidType != GREEN_ROOF ) return 0; + for (i = 2; i < 5; i++) + { + if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) + return error_setInpError(ERR_NUMBER, toks[i]); + } + + //... save parameters to LID drain layer structure + LidProcs[j].drainMat.thickness = x[0] / UCF(RAINDEPTH);; + LidProcs[j].drainMat.voidFrac = x[1]; + LidProcs[j].drainMat.roughness = x[2]; + return 0; +} + +//============================================================================= + +int readRemovalsData(int j, char* toks[], int ntoks) +// +// Purpose: reads pollutant removal data for a LID process from line of input +// data file +// Input: j = LID process index +// toks = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// +// Format of data is: +// LID_ID REMOVALS pollut1 %removal1 pollut2 %removal2 ... +// +{ + int i = 2; + int p; + double rmvl; + + //... start with 3rd token + if (ntoks < 4) return error_setInpError(ERR_ITEMS, ""); + while (ntoks > i) + { + //... find pollutant index from its name + p = project_findObject(POLLUT, toks[i]); + if (p < 0) return error_setInpError(ERR_NAME, toks[i]); + + //... check that a next token exists + i++; + if (ntoks == i) return error_setInpError(ERR_ITEMS, ""); + + //... get the % removal value from the next token + if (!getDouble(toks[i], &rmvl) || rmvl < 0.0 || rmvl > 100.0) + return error_setInpError(ERR_NUMBER, toks[i]); + + //... save the pollutant removal for the LID process as a fraction + LidProcs[j].drainRmvl[p] = rmvl / 100.0; + i++; + } + return 0; +} +//============================================================================= + +void lid_writeSummary() +// +// Purpose: writes summary of LID processes used to report file. +// Input: none +// Output: none +// +{ + int j, k; + double pctArea; + TLidUnit* lidUnit; + TLidList* lidList; + TLidGroup lidGroup; + + fprintf(Frpt.file, "\n"); + fprintf(Frpt.file, "\n"); + fprintf(Frpt.file, "\n *******************"); + fprintf(Frpt.file, "\n LID Control Summary"); + fprintf(Frpt.file, "\n *******************"); + + + fprintf(Frpt.file, +"\n No. of Unit Unit %% Area %% Imperv %% Perv"); //(5.1.013) + fprintf(Frpt.file, // +"\n Subcatchment LID Control Units Area Width Covered Treated Treated"); // + fprintf(Frpt.file, // +"\n ---------------------------------------------------------------------------------------------------"); // + + for (j = 0; j < GroupCount; j++) + { + lidGroup = LidGroups[j]; + if ( lidGroup == NULL ) continue; + lidList = lidGroup->lidList; + while ( lidList ) + { + lidUnit = lidList->lidUnit; + k = lidUnit->lidIndex; + pctArea = lidUnit->area * lidUnit->number / Subcatch[j].area * 100.0; + fprintf(Frpt.file, "\n %-16s %-16s", Subcatch[j].ID, LidProcs[k].ID); + fprintf(Frpt.file, "%6d %10.2f %10.2f %10.2f %10.2f %10.2f", + lidUnit->number, lidUnit->area * SQR(UCF(LENGTH)), + lidUnit->fullWidth * UCF(LENGTH), pctArea, + lidUnit->fromImperv*100.0, lidUnit->fromPerv*100.0); + lidList = lidList->nextLidUnit; + } + } +} + +//============================================================================= + +void lid_validate() +// +// Purpose: validates LID process and group parameters. +// Input: none +// Output: none +// +{ + int j; + for (j = 0; j < LidCount; j++) validateLidProc(j); + for (j = 0; j < GroupCount; j++) validateLidGroup(j); +} + +//============================================================================= + +void validateLidProc(int j) +// +// Purpose: validates LID process parameters. +// Input: j = LID process index +// Output: none +// +{ + int layerMissing = FALSE; + + //... check that LID type was supplied + if ( LidProcs[j].lidType < 0 ) + { + report_writeErrorMsg(ERR_LID_TYPE, LidProcs[j].ID); + return; + } + + //... check that required layers were defined + switch (LidProcs[j].lidType) + { + case BIO_CELL: + case RAIN_GARDEN: + if ( LidProcs[j].soil.thickness <= 0.0 ) layerMissing = TRUE; + break; + case GREEN_ROOF: + if ( LidProcs[j].soil.thickness <= 0.0 ) layerMissing = TRUE; + if ( LidProcs[j].drainMat.thickness <= 0.0) layerMissing = TRUE; + break; + case POROUS_PAVEMENT: + if ( LidProcs[j].pavement.thickness <= 0.0 ) layerMissing = TRUE; + break; + case INFIL_TRENCH: + if ( LidProcs[j].storage.thickness <= 0.0 ) layerMissing = TRUE; + break; + } + if ( layerMissing ) + { + report_writeErrorMsg(ERR_LID_LAYER, LidProcs[j].ID); + return; + } + + //... check pavement layer parameters + if ( LidProcs[j].lidType == POROUS_PAVEMENT ) + { + if ( LidProcs[j].pavement.thickness <= 0.0 + || LidProcs[j].pavement.kSat <= 0.0 + || LidProcs[j].pavement.voidFrac <= 0.0 + || LidProcs[j].pavement.voidFrac > 1.0 + || LidProcs[j].pavement.impervFrac > 1.0 ) + + { + sstrncpy(Msg, LidProcs[j].ID, MAXMSG); + sstrcat(Msg, ERR_PAVE_LAYER, MAXMSG); + report_writeErrorMsg(ERR_LID_PARAMS, Msg); + } + } + + //... check soil layer parameters + if ( LidProcs[j].soil.thickness > 0.0 ) + { + if ( LidProcs[j].soil.porosity <= 0.0 + || LidProcs[j].soil.fieldCap >= LidProcs[j].soil.porosity + || LidProcs[j].soil.wiltPoint >= LidProcs[j].soil.fieldCap + || LidProcs[j].soil.kSat <= 0.0 + || LidProcs[j].soil.kSlope < 0.0 ) + { + sstrncpy(Msg, LidProcs[j].ID, MAXMSG); + sstrcat(Msg, ERR_SOIL_LAYER, MAXMSG); + report_writeErrorMsg(ERR_LID_PARAMS, Msg); + } + } + + //... check storage layer parameters + if ( LidProcs[j].storage.thickness > 0.0 ) + { + if ( LidProcs[j].storage.voidFrac <= 0.0 || + LidProcs[j].storage.voidFrac > 1.0 ) + { + sstrncpy(Msg, LidProcs[j].ID, MAXMSG); + sstrcat(Msg, ERR_STOR_LAYER, MAXMSG); + report_writeErrorMsg(ERR_LID_PARAMS, Msg); + } + } + + //... if no storage layer adjust void fraction and drain offset + else + { + LidProcs[j].storage.voidFrac = 1.0; + LidProcs[j].drain.offset = 0.0; + } + + //... check for invalid drain open/closed heads + if (LidProcs[j].drain.hOpen > 0.0 && + LidProcs[j].drain.hOpen <= LidProcs[j].drain.hClose) + { + sstrncpy(Msg, LidProcs[j].ID, MAXMSG); + sstrcat(Msg, ERR_DRAIN_HEADS, MAXMSG); + report_writeErrorMsg(ERR_LID_PARAMS, Msg); + } + + //... compute the surface layer's overland flow constant (alpha) + if ( LidProcs[j].lidType == VEG_SWALE ) + { + if ( LidProcs[j].surface.roughness * + LidProcs[j].surface.surfSlope <= 0.0 || + LidProcs[j].surface.thickness == 0.0 + ) + { + sstrncpy(Msg, LidProcs[j].ID, MAXMSG); + sstrcat(Msg, ERR_SWALE_SURF, MAXMSG); + report_writeErrorMsg(ERR_LID_PARAMS, Msg); + } + else LidProcs[j].surface.alpha = + 1.49 * sqrt(LidProcs[j].surface.surfSlope) / + LidProcs[j].surface.roughness; + } + else + { + //... compute surface overland flow coeff. + if ( LidProcs[j].surface.roughness > 0.0 ) + LidProcs[j].surface.alpha = 1.49 / LidProcs[j].surface.roughness * + sqrt(LidProcs[j].surface.surfSlope); + else LidProcs[j].surface.alpha = 0.0; + } + + //... compute drainage mat layer's flow coeff. + if ( LidProcs[j].drainMat.roughness > 0.0 ) + { + LidProcs[j].drainMat.alpha = 1.49 / LidProcs[j].drainMat.roughness * + sqrt(LidProcs[j].surface.surfSlope); + } + else LidProcs[j].drainMat.alpha = 0.0; + + + //... convert clogging factors to void volume basis + if ( LidProcs[j].pavement.thickness > 0.0 ) + { + LidProcs[j].pavement.clogFactor *= + LidProcs[j].pavement.thickness * LidProcs[j].pavement.voidFrac * + (1.0 - LidProcs[j].pavement.impervFrac); + } + if ( LidProcs[j].storage.thickness > 0.0 ) + { + LidProcs[j].storage.clogFactor *= + LidProcs[j].storage.thickness * LidProcs[j].storage.voidFrac; + } + else LidProcs[j].storage.clogFactor = 0.0; + + //... for certain LID types, immediate overflow of excess surface water + // occurs if either the surface roughness or slope is zero + LidProcs[j].surface.canOverflow = TRUE; + switch (LidProcs[j].lidType) + { + case ROOF_DISCON: LidProcs[j].surface.canOverflow = FALSE; break; + case INFIL_TRENCH: + case POROUS_PAVEMENT: + case BIO_CELL: + case RAIN_GARDEN: + case GREEN_ROOF: + if ( LidProcs[j].surface.alpha > 0.0 ) + LidProcs[j].surface.canOverflow = FALSE; + } + + //... rain barrels have 100% void space and impermeable bottom + if ( LidProcs[j].lidType == RAIN_BARREL ) + { + LidProcs[j].storage.voidFrac = 1.0; + LidProcs[j].storage.kSat = 0.0; + } + + //... set storage layer parameters of a green roof + if ( LidProcs[j].lidType == GREEN_ROOF ) + { + LidProcs[j].storage.thickness = LidProcs[j].drainMat.thickness; + LidProcs[j].storage.voidFrac = LidProcs[j].drainMat.voidFrac; + LidProcs[j].storage.clogFactor = 0.0; + LidProcs[j].storage.kSat = 0.0; + } +} + +//============================================================================= + +void validateLidGroup(int j) +// +// Purpose: validates properties of LID units grouped in a subcatchment. +// Input: j = subcatchment index +// Output: returns 1 if data are valid, 0 if not +// +{ + int k; + double p[3]; + double totalArea = Subcatch[j].area; + double totalLidArea = 0.0; + double fromImperv = 0.0; + double fromPerv = 0.0; + TLidUnit* lidUnit; + TLidList* lidList; + TLidGroup lidGroup; + + lidGroup = LidGroups[j]; + if ( lidGroup == NULL ) return; + lidList = lidGroup->lidList; + while ( lidList ) + { + lidUnit = lidList->lidUnit; + k = lidUnit->lidIndex; + + //... update contributing fractions + totalLidArea += (lidUnit->area * lidUnit->number); + fromImperv += lidUnit->fromImperv; + fromPerv += lidUnit->fromPerv; + + //... assign biocell soil layer infiltration parameters + lidUnit->soilInfil.Ks = 0.0; + if ( LidProcs[k].soil.thickness > 0.0 ) + { + p[0] = LidProcs[k].soil.suction * UCF(RAINDEPTH); + p[1] = LidProcs[k].soil.kSat * UCF(RAINFALL); + p[2] = (LidProcs[k].soil.porosity - LidProcs[k].soil.wiltPoint) * + (1.0 - lidUnit->initSat); + if ( grnampt_setParams(&(lidUnit->soilInfil), p) == FALSE ) + { + sstrncpy(Msg, LidProcs[k].ID, MAXMSG); + sstrcat(Msg, ERR_SOIL_LAYER, MAXMSG); + report_writeErrorMsg(ERR_LID_PARAMS, Msg); + } + } + + //... assign vegetative swale infiltration parameters + if ( LidProcs[k].lidType == VEG_SWALE ) + { + if ( Subcatch[j].infilModel == GREEN_AMPT || + Subcatch[j].infilModel == MOD_GREEN_AMPT ) + { + grnampt_getParams(j, p); + if ( grnampt_setParams(&(lidUnit->soilInfil), p) == FALSE ) + { + sstrncpy(Msg, LidProcs[k].ID, MAXMSG); + sstrcat(Msg, ERR_GREEN_AMPT, MAXMSG); + report_writeErrorMsg(ERR_LID_PARAMS, Msg); + } + } + if ( lidUnit->fullWidth <= 0.0 ) + { + sstrncpy(Msg, LidProcs[k].ID, MAXMSG); + sstrcat(Msg, ERR_SWALE_WIDTH, MAXMSG); + report_writeErrorMsg(ERR_LID_PARAMS, Msg); + } + } + + //... LID unit cannot send outflow back to subcatchment's + // pervious area if none exists + if ( Subcatch[j].fracImperv >= 0.999 ) lidUnit->toPerv = 0; + + //... assign drain outlet if not set by user + if ( lidUnit->drainNode == -1 && lidUnit->drainSubcatch == -1 ) + { + lidUnit->drainNode = Subcatch[j].outNode; + lidUnit->drainSubcatch = Subcatch[j].outSubcatch; + } + lidList = lidList->nextLidUnit; + } + + //... check contributing area fractions + if ( totalLidArea > 1.001 * totalArea ) + { + report_writeErrorMsg(ERR_LID_AREAS, Subcatch[j].ID); + } + if ( fromImperv > 1.001 || fromPerv > 1.001 ) + { + report_writeErrorMsg(ERR_LID_CAPTURE_AREA, Subcatch[j].ID); + } + + //... Make subcatchment LID area equal total area if the two are close + if ( totalLidArea > 0.999 * totalArea ) totalLidArea = totalArea; + Subcatch[j].lidArea = totalLidArea; +} + +//============================================================================= + +void lid_initState() +// +// Purpose: initializes the internal state of each LID in a subcatchment. +// Input: none +// Output: none +// +{ + int i, j, k; + TLidUnit* lidUnit; + TLidList* lidList; + TLidGroup lidGroup; + double initVol; + double initDryTime = StartDryDays * SECperDAY; + + HasWetLids = FALSE; + for (j = 0; j < GroupCount; j++) + { + //... check if group exists + lidGroup = LidGroups[j]; + if ( lidGroup == NULL ) continue; + + //... initialize group variables + lidGroup->pervArea = 0.0; + lidGroup->flowToPerv = 0.0; + lidGroup->oldDrainFlow = 0.0; + lidGroup->newDrainFlow = 0.0; + + //... examine each LID in the group + lidList = lidGroup->lidList; + while ( lidList ) + { + //... initialize depth & moisture content + lidUnit = lidList->lidUnit; + k = lidUnit->lidIndex; + lidUnit->surfaceDepth = 0.0; + lidUnit->storageDepth = 0.0; + lidUnit->soilMoisture = 0.0; + lidUnit->paveDepth = 0.0; + lidUnit->dryTime = initDryTime; + lidUnit->volTreated = 0.0; + lidUnit->nextRegenDay = LidProcs[k].pavement.regenDays; + initVol = 0.0; + if ( LidProcs[k].soil.thickness > 0.0 ) + { + lidUnit->soilMoisture = LidProcs[k].soil.wiltPoint + + lidUnit->initSat * (LidProcs[k].soil.porosity - + LidProcs[k].soil.wiltPoint); + initVol += lidUnit->soilMoisture * LidProcs[k].soil.thickness; + } + if ( LidProcs[k].storage.thickness > 0.0 ) + { + lidUnit->storageDepth = lidUnit->initSat * + LidProcs[k].storage.thickness; + initVol += lidUnit->storageDepth * LidProcs[k].storage.voidFrac; + } + if ( LidProcs[k].drainMat.thickness > 0.0 ) + { + lidUnit->storageDepth = lidUnit->initSat * + LidProcs[k].drainMat.thickness; + initVol += lidUnit->storageDepth * LidProcs[k].drainMat.voidFrac; + } + if ( lidUnit->initSat > 0.0 ) HasWetLids = TRUE; + + //... initialize water balance totals + lidproc_initWaterBalance(lidUnit, initVol); + lidUnit->volTreated = 0.0; + + //... initialize report file for the LID + if ( lidUnit->rptFile ) + { + initLidRptFile(Title[0], LidProcs[k].ID, Subcatch[j].ID, lidUnit); + } + + //... initialize drain flows + lidUnit->oldDrainFlow = 0.0; + lidUnit->newDrainFlow = 0.0; + + //... set previous flux rates to 0 + for (i = 0; i < MAX_LAYERS; i++) + { + lidUnit->oldFluxRates[i] = 0.0; + } + + //... initialize infiltration state variables + if ( lidUnit->soilInfil.Ks > 0.0 ) + grnampt_initState(&(lidUnit->soilInfil)); + + //... add contribution to pervious LID area + if ( isLidPervious(lidUnit->lidIndex) ) + lidGroup->pervArea += (lidUnit->area * lidUnit->number); + lidList = lidList->nextLidUnit; + } + } +} + +//============================================================================= + +void lid_setOldGroupState(int j) +// +// Purpose: saves the current drain flow rate for the LIDs in a subcatchment. +// Input: j = subcatchment index +// Output: none +// +{ + TLidList* lidList; + if ( LidGroups[j] != NULL ) + { + LidGroups[j]->oldDrainFlow = LidGroups[j]->newDrainFlow; + LidGroups[j]->newDrainFlow = 0.0; + lidList = LidGroups[j]->lidList; + while (lidList) + { + lidList->lidUnit->oldDrainFlow = lidList->lidUnit->newDrainFlow; + lidList->lidUnit->newDrainFlow = 0.0; + lidList = lidList->nextLidUnit; + } + } +} + +//============================================================================= + +int isLidPervious(int k) +// +// Purpose: determines if a LID process allows infiltration or not. +// Input: k = LID process index +// Output: returns 1 if process is pervious or 0 if not +// +{ + return ( LidProcs[k].storage.thickness == 0.0 || + LidProcs[k].storage.kSat > 0.0 ); +} + +//============================================================================= + +double getSurfaceDepth(int j) +// +// Purpose: computes the depth (volume per unit area) of ponded water on the +// surface of all LIDs within a subcatchment. +// Input: j = subcatchment index +// Output: returns volumetric depth of ponded water (ft) +// +{ + int k; + double depth = 0.0; + TLidUnit* lidUnit; + TLidList* lidList; + TLidGroup lidGroup; + + lidGroup = LidGroups[j]; + if ( lidGroup == NULL ) return 0.0; + if ( Subcatch[j].lidArea == 0.0 ) return 0.0; + lidList = lidGroup->lidList; + while ( lidList ) + { + lidUnit = lidList->lidUnit; + k = lidUnit->lidIndex; + depth += lidUnit->surfaceDepth * LidProcs[k].surface.voidFrac * + lidUnit->area * lidUnit->number; + lidList = lidList->nextLidUnit; + } + return depth / Subcatch[j].lidArea; +} + +//============================================================================= + +double lid_getPervArea(int j) +// +// Purpose: retrieves amount of pervious LID area in a subcatchment. +// Input: j = subcatchment index +// Output: returns amount of pervious LID area (ft2) +// +{ + if ( LidGroups[j] ) return LidGroups[j]->pervArea; + else return 0.0; +} + +//============================================================================= + +double lid_getFlowToPerv(int j) +// +// Purpose: retrieves flow returned from LID treatment to pervious area of +// a subcatchment. +// Input: j = subcatchment index +// Output: returns flow returned to pervious area (cfs) +// +{ + if ( LidGroups[j] != NULL ) return LidGroups[j]->flowToPerv; + return 0.0; +} + +//============================================================================= + +double lid_getStoredVolume(int j) +// +// Purpose: computes stored volume of water for all LIDs +// grouped within a subcatchment. +// Input: j = subcatchment index +// Output: returns stored volume of water (ft3) +// +{ + double total = 0.0; + TLidUnit* lidUnit; + TLidList* lidList; + TLidGroup lidGroup; + + lidGroup = LidGroups[j]; + if ( lidGroup == NULL || Subcatch[j].lidArea == 0.0 ) return 0.0; + lidList = lidGroup->lidList; + while ( lidList ) + { + lidUnit = lidList->lidUnit; + total += lidUnit->waterBalance.finalVol * lidUnit->area * lidUnit->number; + lidList = lidList->nextLidUnit; + } + return total; +} + +//============================================================================= + +double lid_getDrainFlow(int j, int timePeriod) +// +// Purpose: returns flow from all of a subcatchment's LID drains for +// a designated time period +// Input: j = subcatchment index +// timePeriod = either PREVIOUS or CURRENT +// Output: total drain flow (cfs) from the subcatchment. +{ + if ( LidGroups[j] != NULL ) + { + if ( timePeriod == PREVIOUS ) return LidGroups[j]->oldDrainFlow; + else return LidGroups[j]->newDrainFlow; + } + return 0.0; +} + +//============================================================================= + +void lid_addDrainLoads(int j, double c[], double tStep) +// +// Purpose: adds pollutant loads routed from drains to system +// mass balance totals. +// Input: j = subcatchment index +// c = array of pollutant washoff concentrations (mass/L) +// tStep = time step (sec) +// Output: none. +// +{ + int isRunoffLoad; // true if drain becomes external runoff load + int p; // pollutant index + double r; // pollutant fractional removal + double w; // pollutant mass load (lb or kg) + TLidUnit* lidUnit; + TLidList* lidList; + TLidGroup lidGroup; + + //... check if LID group exists + lidGroup = LidGroups[j]; + if ( lidGroup != NULL ) + { + //... examine each LID unit in the group + lidList = lidGroup->lidList; + while ( lidList ) + { + lidUnit = lidList->lidUnit; + + //... see if unit's drain flow becomes external runoff + isRunoffLoad = (lidUnit->drainNode >= 0 || + lidUnit->drainSubcatch == j); + + //... 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; + + //... get fractional removal for this load + r = LidProcs[lidUnit->lidIndex].drainRmvl[p]; + + //... update system mass balance totals + massbal_updateLoadingTotals(BMP_REMOVAL_LOAD, p, r*w); + if (isRunoffLoad) + massbal_updateLoadingTotals(RUNOFF_LOAD, p, w*(1.0 - r)); + } + + // process next LID unit in the group + lidList = lidList->nextLidUnit; + } + } +} + +//============================================================================= + +void lid_addDrainRunon(int j) +// +// Purpose: adds drain flows from LIDs in a given subcatchment to the +// subcatchments that were designated to receive them +// Input: j = index of subcatchment contributing underdrain flows +// Output: none. +// +{ + int i; // index of an LID unit's LID process + int k; // index of subcatchment receiving LID drain flow + int p; // pollutant index + double q; // drain flow rate (cfs) + double w; // mass of polllutant from drain flow + TLidUnit* lidUnit; + TLidList* lidList; + TLidGroup lidGroup; + + //... check if LID group exists + lidGroup = LidGroups[j]; + if ( lidGroup != NULL ) + { + //... examine each LID in the group + lidList = lidGroup->lidList; + while ( lidList ) + { + //... see if LID's drain discharges to another subcatchment + lidUnit = lidList->lidUnit; + i = lidUnit->lidIndex; + k = lidUnit->drainSubcatch; + if ( k >= 0 && k != j ) + { + //... distribute drain flow across subcatchment's areas + q = lidUnit->oldDrainFlow; + subcatch_addRunonFlow(k, q); + + //... add pollutant loads from drain to subcatchment + // (newQual[] contains loading rate (mass/sec) at this + // point which is converted later on to a concentration) + for (p = 0; p < Nobjects[POLLUT]; p++) + { + w = q * Subcatch[j].oldQual[p] * LperFT3; + w = w * (1.0 - LidProcs[i].drainRmvl[p]); + Subcatch[k].newQual[p] += w; + } + } + lidList = lidList->nextLidUnit; + } + } +} + +//============================================================================= + +void lid_addDrainInflow(int j, double f) +// +// Purpose: adds LID drain flow to conveyance system nodes +// Input: j = subcatchment index +// f = time interval weighting factor +// Output: none. +// +// Note: this function updates the total lateral flow (Node[].newLatFlow) +// and pollutant mass (Node[].newQual[]) inflow seen by nodes that +// receive drain flow from the LID units in subcatchment j. +{ + int i, // LID process index + k, // node index + p; // pollutant index + double q, // drain flow (cfs) + w, w1, w2; // pollutant mass loads (mass/sec) + TLidUnit* lidUnit; + TLidList* lidList; + TLidGroup lidGroup; + + //... check if LID group exists + lidGroup = LidGroups[j]; + if ( lidGroup != NULL ) + { + //... examine each LID in the group + lidList = lidGroup->lidList; + while ( lidList ) + { + //... see if LID's drain discharges to conveyance system node + lidUnit = lidList->lidUnit; + i = lidUnit->lidIndex; + k = lidUnit->drainNode; + if ( k >= 0 ) + { + //... add drain flow to node's wet weather inflow + q = (1.0 - f) * lidUnit->oldDrainFlow + f * lidUnit->newDrainFlow; + Node[k].newLatFlow += q; + massbal_addInflowFlow(WET_WEATHER_INFLOW, q); + + //... add pollutant load, based on parent subcatchment quality + for (p = 0; p < Nobjects[POLLUT]; p++) + { + //... get previous & current drain loads + w1 = lidUnit->oldDrainFlow * Subcatch[j].oldQual[p]; + w2 = lidUnit->newDrainFlow * Subcatch[j].newQual[p]; + + //... add interpolated load to node's wet weather loading + w = (1.0 - f) * w1 + f * w2; + w = w * (1.0 - LidProcs[i].drainRmvl[p]); + Node[k].newQual[p] += w; + massbal_addInflowQual(WET_WEATHER_INFLOW, p, w); + } + } + lidList = lidList->nextLidUnit; + } + } +} + +//============================================================================= + +void lid_getRunoff(int j, double tStep) +// +// Purpose: computes runoff and drain flows from the LIDs in a subcatchment. +// Input: j = subcatchment index +// tStep = time step (sec) +// Output: updates following global quantities after LID treatment applied: +// Vevap, Vpevap, VlidInfil, VlidIn, VlidOut, VlidDrain. +// +{ + TLidGroup theLidGroup; // group of LIDs placed in the subcatchment + TLidList* lidList; // list of LID units in the group + TLidUnit* lidUnit; // a member of the list of LID units + double lidArea; // area of an LID unit + double qImperv = 0.0; // runoff from impervious areas (cfs) + double qPerv = 0.0; // runoff from pervious areas (cfs) + double lidInflow = 0.0; // inflow to an LID unit (ft/s) + double qRunoff = 0.0; // surface runoff from all LID units (cfs) + double qDrain = 0.0; // drain flow from all LID units (cfs) + double qReturn = 0.0; // LID outflow returned to pervious area (cfs) + + //... return if there are no LID's + theLidGroup = LidGroups[j]; + if ( !theLidGroup ) return; + lidList = theLidGroup->lidList; + if ( !lidList ) return; + + //... determine if evaporation can occur + EvapRate = Evap.rate; + if ( Evap.dryOnly && Subcatch[j].rainfall > 0.0 ) EvapRate = 0.0; + + //... find subcatchment's infiltration rate into native soil + findNativeInfil(j, tStep); + + //... get impervious and pervious area runoff from non-LID + // portion of subcatchment (cfs) + if ( Subcatch[j].area > Subcatch[j].lidArea ) + { + qImperv = getImpervAreaRunoff(j); + qPerv = getPervAreaRunoff(j); + } + + //... evaluate performance of each LID unit placed in the subcatchment + while ( lidList ) + { + //... find area of the LID unit + lidUnit = lidList->lidUnit; + lidArea = lidUnit->area * lidUnit->number; + + //... if LID unit has area, evaluate its performance + if ( lidArea > 0.0 ) + { + //... find runoff from non-LID area treated by LID area (ft/sec) + lidInflow = (qImperv * lidUnit->fromImperv + + qPerv * lidUnit->fromPerv) / lidArea; + + //... update total runoff volume treated + VlidIn += lidInflow * lidArea * tStep; + + //... add rainfall onto LID inflow (ft/s) + lidInflow = lidInflow + getRainInflow(j, lidUnit); + + // ... add upstream runon only if LID occupies full subcatchment + if ( Subcatch[j].area == Subcatch[j].lidArea ) + { + lidInflow += Subcatch[j].runon; + } + + //... evaluate the LID unit's performance, updating the LID group's + // total surface runoff, drain flow, and flow returned to + // pervious area + evalLidUnit(j, lidUnit, lidArea, lidInflow, tStep, + &qRunoff, &qDrain, &qReturn); + } + lidList = lidList->nextLidUnit; + } + + //... save the LID group's total drain & return flows + theLidGroup->newDrainFlow = qDrain; + theLidGroup->flowToPerv = qReturn; + + //... save the LID group's total surface, drain and return flow volumes + VlidOut = qRunoff * tStep; + VlidDrain = qDrain * tStep; + VlidReturn = qReturn * tStep; +} + +//============================================================================= + +void findNativeInfil(int j, double tStep) +// +// Purpose: determines a subcatchment's current infiltration rate into +// its native soil. +// Input: j = subcatchment index +// tStep = time step (sec) +// Output: sets values for module-level variables NativeInfil +// +{ + double nonLidArea; + + //... subcatchment has non-LID pervious area + nonLidArea = Subcatch[j].area - Subcatch[j].lidArea; + if ( nonLidArea > 0.0 && Subcatch[j].fracImperv < 1.0 ) + { + NativeInfil = Vinfil / nonLidArea / tStep; + } + + //... otherwise find infil. rate for the subcatchment's rainfall + runon + else + { + NativeInfil = infil_getInfil(j, tStep, + Subcatch[j].rainfall, + Subcatch[j].runon, + getSurfaceDepth(j)); + } + + //... see if there is any groundwater-imposed limit on infil. + if ( !IgnoreGwater && Subcatch[j].groundwater ) + { + MaxNativeInfil = Subcatch[j].groundwater->maxInfilVol / tStep; + } + else MaxNativeInfil = BIG; +} + +//============================================================================= + +double getRainInflow(int j, TLidUnit* lidUnit) +// +// Purpose: gets rainfall inflow to an LID unit. +// Input: j = subcatchment index +// lidUnit = ptr. to an LID unit +// Output: returns rainfall rate over the LID unit (ft/sec) +// +{ + TLidProc* lidProc = &LidProcs[lidUnit->lidIndex]; + + if (lidProc->lidType == RAIN_BARREL && + lidProc->storage.covered == TRUE) return 0.0; + return Subcatch[j].rainfall; +} + +//============================================================================= + +double getImpervAreaRunoff(int j) +// +// Purpose: computes runoff from impervious area of a subcatchment that +// is available for LID treatment. +// Input: j = subcatchment index +// Output: returns runoff flow rate (cfs) +// +{ + int i; + double q = 0.0, // runoff rate (ft/sec) + nonLidArea; // non-LID area (ft2) + + // --- runoff from impervious area w/ & w/o depression storage + for (i = IMPERV0; i <= IMPERV1; i++) + { + q += Subcatch[j].subArea[i].runoff * Subcatch[j].subArea[i].fArea; + } + + // --- adjust for any fraction of runoff sent to pervious area + if ( Subcatch[j].subArea[IMPERV0].routeTo == TO_PERV && + Subcatch[j].fracImperv < 1.0 ) + { + q *= Subcatch[j].subArea[IMPERV0].fOutlet; + } + nonLidArea = Subcatch[j].area - Subcatch[j].lidArea; + return q * nonLidArea; +} + +//============================================================================= + +double getPervAreaRunoff(int j) +// +// Purpose: computes runoff from pervious area of a subcatchment that +// is available for LID treatment. +// Input: j = subcatchment index +// Output: returns runoff flow rate (cfs) +// +{ + double q = 0.0, // runoff rate (ft/sec) + nonLidArea; // non-LID area (ft2) + + // --- runoff from pervious area + q = Subcatch[j].subArea[PERV].runoff * Subcatch[j].subArea[PERV].fArea; + + // --- adjust for any fraction of runoff sent to impervious area + if (Subcatch[j].subArea[PERV].routeTo == TO_IMPERV && + Subcatch[j].fracImperv > 0.0) + { + q *= Subcatch[j].subArea[PERV].fOutlet; + } + nonLidArea = Subcatch[j].area - Subcatch[j].lidArea; + return q * nonLidArea; +} + +//============================================================================= + +void evalLidUnit(int j, TLidUnit* lidUnit, double lidArea, double lidInflow, + double tStep, double *qRunoff, double *qDrain, double *qReturn) +// +// Purpose: evaluates performance of a specific LID unit over current time step. +// Input: j = subcatchment index +// lidUnit = ptr. to LID unit being evaluated +// lidArea = area of LID unit +// lidInflow = inflow to LID unit (ft/s) +// tStep = time step (sec) +// Output: qRunoff = sum of surface runoff from all LIDs (cfs) +// qDrain = sum of drain flows from all LIDs (cfs) +// qReturn = sum of LID flows returned to pervious area (cfs) +// +{ + TLidProc* lidProc; // LID process associated with lidUnit + double lidRunoff, // surface runoff from LID unit (cfs) + lidEvap, // evaporation rate from LID unit (ft/s) + lidInfil, // infiltration rate from LID unit (ft/s) + lidDrain; // drain flow rate from LID unit (ft/s & cfs) + + //... identify the LID process of the LID unit being analyzed + lidProc = &LidProcs[lidUnit->lidIndex]; + + //... initialize evap and infil losses + lidEvap = 0.0; + lidInfil = 0.0; + + //... find surface runoff from the LID unit (in cfs) + lidRunoff = lidproc_getOutflow(lidUnit, lidProc, lidInflow, EvapRate, + NativeInfil, MaxNativeInfil, tStep, + &lidEvap, &lidInfil, &lidDrain) * lidArea; + + //... convert drain flow to CFS + lidDrain *= lidArea; + + //... revise flows if LID outflow returned to pervious area + if ( lidUnit->toPerv && Subcatch[j].area > Subcatch[j].lidArea ) + { + //... surface runoff is always returned + *qReturn += lidRunoff; + lidRunoff = 0.0; + + //... drain flow returned if it has same outlet as subcatchment + if ( lidUnit->drainNode == Subcatch[j].outNode && + lidUnit->drainSubcatch == Subcatch[j].outSubcatch ) + { + *qReturn += lidDrain; + lidDrain = 0.0; + } + } + + //... update system flow balance if drain flow goes to a + // conveyance system node + if ( lidUnit->drainNode >= 0 ) + { + massbal_updateRunoffTotals(RUNOFF_DRAINS, lidDrain * tStep); + } + + //... save new drain outflow + lidUnit->newDrainFlow = lidDrain; + + //... update moisture losses (ft3) + Vevap += lidEvap * tStep * lidArea; + VlidInfil += lidInfil * tStep * lidArea; + if ( isLidPervious(lidUnit->lidIndex) ) + { + Vpevap += lidEvap * tStep * lidArea; + } + + //... update time since last rainfall (for Rain Barrel emptying) + if ( Subcatch[j].rainfall > MIN_RUNOFF ) lidUnit->dryTime = 0.0; + else lidUnit->dryTime += tStep; + + //... update LID water balance and save results + lidproc_saveResults(lidUnit, UCF(RAINFALL), UCF(RAINDEPTH)); + + //... update LID group totals + *qRunoff += lidRunoff; + *qDrain += lidDrain; +} + +//============================================================================= + +void lid_writeWaterBalance() +// +// Purpose: writes a LID performance summary table to the project's report file. +// Input: none +// Output: none +// +{ + int j; + int k = 0; + double ucf = UCF(RAINDEPTH); + double inflow; + double outflow; + double err; + TLidUnit* lidUnit; + TLidList* lidList; + TLidGroup lidGroup; + + //... check that project has LIDs + for ( j = 0; j < GroupCount; j++ ) + { + if ( LidGroups[j] ) k++; + } + if ( k == 0 ) return; + + //... write table header + fprintf(Frpt.file, + "\n" + "\n ***********************" + "\n LID Performance Summary" + "\n ***********************\n"); + + fprintf(Frpt.file, +"\n --------------------------------------------------------------------------------------------------------------------" +"\n Total Evap Infil Surface Drain Initial Final Continuity" +"\n Inflow Loss Loss Outflow Outflow Storage Storage Error"); + if ( UnitSystem == US ) fprintf(Frpt.file, +"\n Subcatchment LID Control in in in in in in in %%"); + else fprintf(Frpt.file, +"\n Subcatchment LID Control mm mm mm mm mm mm mm %%"); + fprintf(Frpt.file, +"\n --------------------------------------------------------------------------------------------------------------------"); + + //... examine each LID unit in each subcatchment + for ( j = 0; j < GroupCount; j++ ) + { + lidGroup = LidGroups[j]; + if ( !lidGroup || Subcatch[j].lidArea == 0.0 ) continue; + lidList = lidGroup->lidList; + while ( lidList ) + { + //... write water balance components to report file + lidUnit = lidList->lidUnit; + k = lidUnit->lidIndex; + fprintf(Frpt.file, "\n %-16s %-16s", Subcatch[j].ID, + LidProcs[k].ID); + fprintf(Frpt.file, "%10.2f%10.2f%10.2f%10.2f%10.2f%10.2f%10.2f", + lidUnit->waterBalance.inflow*ucf, + lidUnit->waterBalance.evap*ucf, + lidUnit->waterBalance.infil*ucf, + lidUnit->waterBalance.surfFlow*ucf, + lidUnit->waterBalance.drainFlow*ucf, + lidUnit->waterBalance.initVol*ucf, + lidUnit->waterBalance.finalVol*ucf); + + //... compute flow balance error + inflow = lidUnit->waterBalance.initVol + + lidUnit->waterBalance.inflow; + outflow = lidUnit->waterBalance.finalVol + + lidUnit->waterBalance.evap + + lidUnit->waterBalance.infil + + lidUnit->waterBalance.surfFlow + + lidUnit->waterBalance.drainFlow; + if ( inflow > 0.0 ) err = (inflow - outflow) / inflow; + else err = 1.0; + fprintf(Frpt.file, " %10.2f", err*100.0); + lidList = lidList->nextLidUnit; + } + } +} + +//============================================================================= + +void initLidRptFile(char* title, char* lidID, char* subcatchID, TLidUnit* lidUnit) +// +// Purpose: initializes the report file used for a specific LID unit +// Input: title = project's title +// lidID = LID process name +// subcatchID = subcatchment ID name +// lidUnit = ptr. to LID unit +// Output: none +// +{ + static int colCount = 14; + static char* head1[] = { + "\n \t", " Elapsed\t", + " Total\t", " Total\t", " Surface\t", " Pavement\t", " Soil\t", + " Storage\t", " Surface\t", " Drain\t", " Surface\t", " Pavement\t", + " Soil\t", " Storage"}; + static char* head2[] = { + "\n \t", " Time\t", + " Inflow\t", " Evap\t", " Infil\t", " Perc\t", " Perc\t", + " Exfil\t", " Runoff\t", " OutFlow\t", " Level\t", " Level\t", + " Moisture\t", " Level"}; + static char* units1[] = { + "\nDate Time \t", " Hours\t", + " in/hr\t", " in/hr\t", " in/hr\t", " in/hr\t", " in/hr\t", + " in/hr\t", " in/hr\t", " in/hr\t", " inches\t", " inches\t", + " Content\t", " inches"}; + static char* units2[] = { + "\nDate Time \t", " Hours\t", + " mm/hr\t", " mm/hr\t", " mm/hr\t", " mm/hr\t", " mm/hr\t", + " mm/hr\t", " mm/hr\t", " mm/hr\t", " mm\t", " mm\t", + " Content\t", " mm"}; + static char line9[] = " ---------"; + int i; + FILE* f = lidUnit->rptFile->file; + + //... check that file was opened + if ( f == NULL ) return; + + //... write title lines + fprintf(f, "SWMM5 LID Report File\n"); + fprintf(f, "\nProject: %s", title); + fprintf(f, "\nLID Unit: %s in Subcatchment %s\n", lidID, subcatchID); + + //... write column headings + for ( i = 0; i < colCount; i++) fprintf(f, "%s", head1[i]); + for ( i = 0; i < colCount; i++) fprintf(f, "%s", head2[i]); + if ( UnitSystem == US ) + { + for ( i = 0; i < colCount; i++) fprintf(f, "%s", units1[i]); + } + else for ( i = 0; i < colCount; i++) fprintf(f, "%s", units2[i]); + fprintf(f, "\n----------- --------"); + for ( i = 1; i < colCount; i++) fprintf(f, "\t%s", line9); + + //... initialize LID dryness state + lidUnit->rptFile->wasDry = 1; + sstrncpy(lidUnit->rptFile->results, "", 0); +} diff --git a/src/lid.h b/src/lid.h new file mode 100644 index 000000000..b250fcf3a --- /dev/null +++ b/src/lid.h @@ -0,0 +1,236 @@ +//----------------------------------------------------------------------------- +// lid.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Public interface for LID functions. +// +// Update History +// ============== +// Build 5.1.008: +// - Support added for Roof Disconnection LID. +// - Support added for separate routing of LID drain flows. +// - Detailed LID reporting modified. +// Build 5.1.011: +// - Water depth replaces moisture content for LID's pavement layer. +// - Arguments for lidproc_saveResults() modified. +// Build 5.1.012: +// - Redefined meaning of wasDry in TLidRptFile structure. +// Build 5.1.013: +// - New member fromPerv added to TLidUnit structure to allow LID +// units to also treat pervious area runoff. +// - New members hOpen and hClose addded to TDrainLayer to open/close +// drain when certain heads are reached. +// - New member qCurve added to TDrainLayer to allow underdrain flow to +// be adjusted by a curve of multiplier v. head. +// - New array drainRmvl added to TLidProc to allow for underdrain +// pollutant removal values. +// - New members added to TPavementLayer and TLidUnit to support +// unclogging permeable pavement at fixed intervals. +// Build 5.2.0: +// - Covered property added to RAIN_BARREL parameters +//----------------------------------------------------------------------------- + +#ifndef LID_H +#define LID_H + +#include +#include +#include +#include "infil.h" + +//----------------------------------------------------------------------------- +// Enumerations +//----------------------------------------------------------------------------- +enum LidTypes { + BIO_CELL, // bio-retention cell + RAIN_GARDEN, // rain garden + GREEN_ROOF, // green roof + INFIL_TRENCH, // infiltration trench + POROUS_PAVEMENT, // porous pavement + RAIN_BARREL, // rain barrel + VEG_SWALE, // vegetative swale + ROOF_DISCON}; // roof disconnection + +enum TimePeriod { + PREVIOUS, // previous time period + CURRENT}; // current time period + +//----------------------------------------------------------------------------- +// Data Structures +//----------------------------------------------------------------------------- +#define MAX_LAYERS 4 + +// LID Surface Layer +typedef struct +{ + double thickness; // depression storage or berm ht. (ft) + double voidFrac; // available fraction of storage volume + double roughness; // surface Mannings n + double surfSlope; // land surface slope (fraction) + double sideSlope; // swale side slope (run/rise) + double alpha; // slope/roughness term in Manning eqn. + char canOverflow; // 1 if immediate outflow of excess water +} TSurfaceLayer; + +// LID Pavement Layer +typedef struct +{ + double thickness; // layer thickness (ft) + double voidFrac; // void volume / total volume + double impervFrac; // impervious area fraction + double kSat; // permeability (ft/sec) + double clogFactor; // clogging factor + double regenDays; // clogging regeneration interval (days) + double regenDegree; // degree of clogging regeneration +} TPavementLayer; + +// LID Soil Layer +typedef struct +{ + double thickness; // layer thickness (ft) + double porosity; // void volume / total volume + double fieldCap; // field capacity + double wiltPoint; // wilting point + double suction; // suction head at wetting front (ft) + double kSat; // saturated hydraulic conductivity (ft/sec) + double kSlope; // slope of log(K) v. moisture content curve +} TSoilLayer; + +// LID Storage Layer +typedef struct +{ + double thickness; // layer thickness (ft) + double voidFrac; // void volume / total volume + double kSat; // saturated hydraulic conductivity (ft/sec) + double clogFactor; // clogging factor + int covered; // TRUE if rain barrel is covered +} TStorageLayer; + +// Underdrain System (part of Storage Layer) +typedef struct +{ + double coeff; // underdrain flow coeff. (in/hr or mm/hr) + double expon; // underdrain head exponent (for in or mm) + double offset; // offset height of underdrain (ft) + double delay; // rain barrel drain delay time (sec) + double hOpen; // head when drain opens (ft) + double hClose; // head when drain closes (ft) + int qCurve; // curve controlling flow rate (optional) +} TDrainLayer; + +// Drainage Mat Layer (for green roofs) +typedef struct +{ + double thickness; // layer thickness (ft) + double voidFrac; // void volume / total volume + double roughness; // Mannings n for green roof drainage mats + double alpha; // slope/roughness term in Manning equation +} TDrainMatLayer; + +// LID Process - generic LID design per unit of area +typedef struct +{ + char* ID; // identifying name + int lidType; // type of LID + TSurfaceLayer surface; // surface layer parameters + TPavementLayer pavement; // pavement layer parameters + TSoilLayer soil; // soil layer parameters + TStorageLayer storage; // storage layer parameters + TDrainLayer drain; // underdrain system parameters + TDrainMatLayer drainMat; // drainage mat layer + double* drainRmvl; // underdrain pollutant removals +} TLidProc; + +// Water Balance Statistics +typedef struct +{ + double inflow; // total inflow (ft) + double evap; // total evaporation (ft) + double infil; // total infiltration (ft) + double surfFlow; // total surface runoff (ft) + double drainFlow; // total underdrain flow (ft) + double initVol; // initial stored volume (ft) + double finalVol; // final stored volume (ft) +} TWaterBalance; + +// LID Report File +typedef struct +{ + FILE* file; // file pointer + int wasDry; // number of successive dry periods + char results[256]; // results for current time period +} TLidRptFile; + +// LID Unit - specific LID process applied over a given area +typedef struct +{ + int lidIndex; // index of LID process + int number; // number of replicate units + double area; // area of single replicate unit (ft2) + double fullWidth; // full top width of single unit (ft) + double botWidth; // bottom width of single unit (ft) + double initSat; // initial saturation of soil & storage layers + double fromImperv; // fraction of impervious area runoff treated + double fromPerv; // fraction of pervious area runoff treated + int toPerv; // 1 if outflow sent to pervious area; 0 if not + int drainSubcatch; // subcatchment receiving drain flow + int drainNode; // node receiving drain flow + TLidRptFile* rptFile; // pointer to detailed report file + + TGrnAmpt soilInfil; // infil. object for biocell soil layer + double surfaceDepth; // depth of ponded water on surface layer (ft) + double paveDepth; // depth of water in porous pavement layer + double soilMoisture; // moisture content of biocell soil layer + double storageDepth; // depth of water in storage layer (ft) + + // net inflow - outflow from previous time step for each LID layer (ft/s) + double oldFluxRates[MAX_LAYERS]; + + double dryTime; // time since last rainfall (sec) + double oldDrainFlow; // previous drain flow (cfs) + double newDrainFlow; // current drain flow (cfs) + double volTreated; // total volume treated (ft) + double nextRegenDay; // next day when unit regenerated + TWaterBalance waterBalance; // water balance quantites +} TLidUnit; + +//----------------------------------------------------------------------------- +// LID Methods +//----------------------------------------------------------------------------- +void lid_create(int lidCount, int subcatchCount); +void lid_delete(void); + +int lid_readProcParams(char* tok[], int ntoks); +int lid_readGroupParams(char* tok[], int ntoks); + +void lid_validate(void); +void lid_initState(void); +void lid_setOldGroupState(int subcatch); + +double lid_getPervArea(int subcatch); +double lid_getFlowToPerv(int subcatch); +double lid_getDrainFlow(int subcatch, int timePeriod); +double lid_getStoredVolume(int subcatch); +void lid_addDrainLoads(int subcatch, double c[], double tStep); +void lid_addDrainRunon(int subcatch); +void lid_addDrainInflow(int subcatch, double f); +void lid_getRunoff(int subcatch, double tStep); +void lid_writeSummary(void); +void lid_writeWaterBalance(void); + +//----------------------------------------------------------------------------- + +void lidproc_initWaterBalance(TLidUnit *lidUnit, double initVol); + +double lidproc_getOutflow(TLidUnit* lidUnit, TLidProc* lidProc, + double inflow, double evap, double infil, double maxInfil, + double tStep, double* lidEvap, double* lidInfil, double* lidDrain); + +void lidproc_saveResults(TLidUnit* lidUnit, double ucfRainfall, + double ucfRainDepth); + +#endif diff --git a/src/lidproc.c b/src/lidproc.c new file mode 100644 index 000000000..aae0ac9bd --- /dev/null +++ b/src/lidproc.c @@ -0,0 +1,1592 @@ +//----------------------------------------------------------------------------- +// lidproc.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// This module computes the hydrologic performance of an LID (Low Impact +// Development) unit at a given point in time. +// +// Update History +// ============== +// Build 5.1.007: +// - Euler integration now applied to all LID types except Vegetative +// Swale which continues to use successive approximation. +// - LID layer flux routines were re-written to more accurately model +// flooded conditions. +// Build 5.1.008: +// - MAX_STATE_VARS replaced with MAX_LAYERS. +// - Optional soil layer added to Porous Pavement LID. +// - Rooftop Disconnection added to types of LIDs. +// - Separate accounting of drain flows added. +// - Indicator for currently wet LIDs added. +// - Detailed reporting procedure fixed. +// - Possibile negative head on Bioretention Cell drain avoided. +// - Bug in computing flow through Green Roof drainage mat fixed. +// Build 5.1.009: +// - Fixed typo in net flux rate for vegetative swale LID. +// Build 5.1.010: +// - New modified version of Green-Ampt used for surface layer infiltration. +// Build 5.1.011: +// - Re-named STOR_INFIL to STOR_EXFIL and StorageInfil to StorageExfil to +// better reflect their meaning. +// - Evaporation rates from sub-surface layers reduced by fraction of +// surface that is pervious (applies to block paver systems) +// - Flux rate routines for LIDs with underdrains modified to produce more +// physically meaningful results. +// - Reporting of detailed results re-written. +// Build 5.1.012: +// - Modified upper limit for soil layer percolation. +// - Modified upper limit on surface infiltration into rain gardens. +// - Modified upper limit on drain flow for LIDs with storage layers. +// - Used re-defined wasDry variable for LID reports to fix duplicate lines. +// Build 5.1.013: +// - Support added for open/closed head levels and multiplier v. head curve +// 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. +// Build 5.2.0: +// - Fixed failure to account for effect of Impervious Surface Fraction on +// pavement permeability for Permeable Pavement LID +// - Fixed units conversion for pavement depth in detailed report file. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include +#include "lid.h" +#include "headers.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +#define STOPTOL 0.00328 // integration error tolerance in ft (= 1 mm) +#define MINFLOW 2.3e-8 // flow cutoff for dry conditions (= 0.001 in/hr) + +//----------------------------------------------------------------------------- +// Enumerations +//----------------------------------------------------------------------------- +enum LidLayerTypes { + SURF, // surface layer + SOIL, // soil layer + STOR, // storage layer + PAVE, // pavement layer + DRAIN}; // underdrain system + +enum LidRptVars { + SURF_INFLOW, // inflow to surface layer + TOTAL_EVAP, // evaporation rate from all layers + SURF_INFIL, // infiltration into surface layer + PAVE_PERC, // percolation through pavement layer + SOIL_PERC, // percolation through soil layer + STOR_EXFIL, // exfiltration out of storage layer + SURF_OUTFLOW, // outflow from surface layer + STOR_DRAIN, // outflow from storage layer + SURF_DEPTH, // ponded depth on surface layer + PAVE_DEPTH, // water level in pavement layer + SOIL_MOIST, // moisture content of soil layer + STOR_DEPTH, // water level in storage layer + MAX_RPT_VARS}; + +//----------------------------------------------------------------------------- +// Imported variables +//----------------------------------------------------------------------------- +extern char HasWetLids; // TRUE if any LIDs are wet (declared in runoff.c) + +//----------------------------------------------------------------------------- +// Local Variables +//----------------------------------------------------------------------------- +static TLidUnit* theLidUnit; // ptr. to a subcatchment's LID unit +static TLidProc* theLidProc; // ptr. to a LID process + +static double Tstep; // current time step (sec) +static double EvapRate; // evaporation rate (ft/s) +static double MaxNativeInfil; // native soil infil. rate limit (ft/s) + +static double SurfaceInflow; // precip. + runon to LID unit (ft/s) +static double SurfaceInfil; // infil. rate from surface layer (ft/s) +static double SurfaceEvap; // evap. rate from surface layer (ft/s) +static double SurfaceOutflow; // outflow from surface layer (ft/s) +static double SurfaceVolume; // volume in surface storage (ft) + +static double PaveEvap; // evap. from pavement layer (ft/s) +static double PavePerc; // percolation from pavement layer (ft/s) +static double PaveVolume; // volume stored in pavement layer (ft) + +static double SoilEvap; // evap. from soil layer (ft/s) +static double SoilPerc; // percolation from soil layer (ft/s) +static double SoilVolume; // volume in soil/pavement storage (ft) + +static double StorageInflow; // inflow rate to storage layer (ft/s) +static double StorageExfil; // exfil. rate from storage layer (ft/s) +static double StorageEvap; // evap.rate from storage layer (ft/s) +static double StorageDrain; // underdrain flow rate layer (ft/s) +static double StorageVolume; // volume in storage layer (ft) + +static double Xold[MAX_LAYERS]; // previous moisture level in LID layers + +//----------------------------------------------------------------------------- +// External Functions (declared in lid.h) +//----------------------------------------------------------------------------- +// lidproc_initWaterBalance (called by lid_initState) +// lidproc_getOutflow (called by evalLidUnit in lid.c) +// lidproc_saveResults (called by evalLidUnit in lid.c) + +//----------------------------------------------------------------------------- +// Local Functions +//----------------------------------------------------------------------------- +static void barrelFluxRates(double x[], double f[]); +static void biocellFluxRates(double x[], double f[]); +static void greenRoofFluxRates(double x[], double f[]); +static void pavementFluxRates(double x[], double f[]); +static void trenchFluxRates(double x[], double f[]); +static void swaleFluxRates(double x[], double f[]); +static void roofFluxRates(double x[], double f[]); + +static double getSurfaceOutflowRate(double depth); +static double getSurfaceOverflowRate(double* surfaceDepth); +static double getPavementPermRate(void); +static double getSoilPercRate(double theta); +static double getStorageExfilRate(void); +static double getStorageDrainRate(double storageDepth, double soilTheta, + double paveDepth, double surfaceDepth); +static double getDrainMatOutflow(double depth); +static void getEvapRates(double surfaceVol, double paveVol, + double soilVol, double storageVol, double pervFrac); + +static void updateWaterBalance(TLidUnit *lidUnit, double inflow, + double evap, double infil, double surfFlow, + double drainFlow, double storage); + +static int modpuls_solve(int n, double* x, double* xOld, double* xPrev, + double* xMin, double* xMax, double* xTol, + double* qOld, double* q, double dt, double omega, + void (*derivs)(double*, double*)); + +//============================================================================= + +void lidproc_initWaterBalance(TLidUnit *lidUnit, double initVol) +// +// Purpose: initializes the water balance components of a LID unit. +// Input: lidUnit = a particular LID unit +// initVol = initial water volume stored in the unit (ft) +// Output: none +// +{ + lidUnit->waterBalance.inflow = 0.0; + lidUnit->waterBalance.evap = 0.0; + lidUnit->waterBalance.infil = 0.0; + lidUnit->waterBalance.surfFlow = 0.0; + lidUnit->waterBalance.drainFlow = 0.0; + lidUnit->waterBalance.initVol = initVol; + lidUnit->waterBalance.finalVol = initVol; +} + +//============================================================================= + +double lidproc_getOutflow(TLidUnit* lidUnit, TLidProc* lidProc, double inflow, + double evap, double infil, double maxInfil, + double tStep, double* lidEvap, + double* lidInfil, double* lidDrain) +// +// Purpose: computes runoff outflow from a single LID unit. +// Input: lidUnit = ptr. to specific LID unit being analyzed +// lidProc = ptr. to generic LID process of the LID unit +// inflow = runoff rate captured by LID unit (ft/s) +// evap = potential evaporation rate (ft/s) +// infil = infiltration rate to native soil (ft/s) +// maxInfil = max. infiltration rate to native soil (ft/s) +// tStep = time step (sec) +// Output: lidEvap = evaporation rate for LID unit (ft/s) +// lidInfil = infiltration rate for LID unit (ft/s) +// lidDrain = drain flow for LID unit (ft/s) +// returns surface runoff rate from the LID unit (ft/s) +// +{ + int i; + double x[MAX_LAYERS]; // layer moisture levels + double xOld[MAX_LAYERS]; // work vector + double xPrev[MAX_LAYERS]; // work vector + double xMin[MAX_LAYERS]; // lower limit on moisture levels + double xMax[MAX_LAYERS]; // upper limit on moisture levels + double fOld[MAX_LAYERS]; // previously computed flux rates + double f[MAX_LAYERS]; // newly computed flux rates + + // convergence tolerance on moisture levels (ft, moisture fraction , ft) + double xTol[MAX_LAYERS] = {STOPTOL, STOPTOL, STOPTOL, STOPTOL}; + + double omega = 0.0; // integration time weighting + + //... define a pointer to function that computes flux rates through the LID + void (*fluxRates) (double *, double *) = NULL; + + //... save references to the LID process and LID unit + theLidProc = lidProc; + theLidUnit = lidUnit; + + //... save evap, max. infil. & time step to shared variables + EvapRate = evap; + MaxNativeInfil = maxInfil; + Tstep = tStep; + + //... store current moisture levels in vector x + x[SURF] = theLidUnit->surfaceDepth; + x[SOIL] = theLidUnit->soilMoisture; + x[STOR] = theLidUnit->storageDepth; + x[PAVE] = theLidUnit->paveDepth; + + //... 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; + SurfaceOutflow = 0.0; + PaveEvap = 0.0; + PavePerc = 0.0; + SoilEvap = 0.0; + SoilPerc = 0.0; + StorageInflow = 0.0; + StorageExfil = 0.0; + StorageEvap = 0.0; + StorageDrain = 0.0; + for (i = 0; i < MAX_LAYERS; i++) + { + f[i] = 0.0; + fOld[i] = theLidUnit->oldFluxRates[i]; + xMin[i] = 0.0; + xMax[i] = BIG; + Xold[i] = x[i]; + } + + //... find Green-Ampt infiltration from surface layer + if ( theLidProc->lidType == POROUS_PAVEMENT ) SurfaceInfil = 0.0; + else if ( theLidUnit->soilInfil.Ks > 0.0 ) + { + SurfaceInfil = + grnampt_getInfil(&theLidUnit->soilInfil, Tstep, + SurfaceInflow, theLidUnit->surfaceDepth, + MOD_GREEN_AMPT); + } + else SurfaceInfil = infil; + + //... set moisture limits for soil & storage layers + if ( theLidProc->soil.thickness > 0.0 ) + { + xMin[SOIL] = theLidProc->soil.wiltPoint; + xMax[SOIL] = theLidProc->soil.porosity; + } + if ( theLidProc->pavement.thickness > 0.0 ) + { + xMax[PAVE] = theLidProc->pavement.thickness; + } + if ( theLidProc->storage.thickness > 0.0 ) + { + xMax[STOR] = theLidProc->storage.thickness; + } + if ( theLidProc->lidType == GREEN_ROOF ) + { + xMax[STOR] = theLidProc->drainMat.thickness; + } + + //... determine which flux rate function to use + switch (theLidProc->lidType) + { + case BIO_CELL: + case RAIN_GARDEN: fluxRates = &biocellFluxRates; break; + case GREEN_ROOF: fluxRates = &greenRoofFluxRates; break; + case INFIL_TRENCH: fluxRates = &trenchFluxRates; break; + case POROUS_PAVEMENT: fluxRates = &pavementFluxRates; break; + case RAIN_BARREL: fluxRates = &barrelFluxRates; break; + case ROOF_DISCON: fluxRates = &roofFluxRates; break; + case VEG_SWALE: fluxRates = &swaleFluxRates; + omega = 0.5; + break; + default: return 0.0; + } + + //... update moisture levels and flux rates over the time step + i = modpuls_solve(MAX_LAYERS, x, xOld, xPrev, xMin, xMax, xTol, + fOld, f, tStep, omega, fluxRates); + +/** For debugging only ******************************************** + if (i == 0) + { + fprintf(Frpt.file, + "\n WARNING 09: integration failed to converge at %s %s", + theDate, theTime); + fprintf(Frpt.file, + "\n for LID %s placed in subcatchment %s.", + theLidProc->ID, theSubcatch->ID); + } +*******************************************************************/ + + //... add any surface overflow to surface outflow + if ( theLidProc->surface.canOverflow || theLidUnit->fullWidth == 0.0 ) + { + SurfaceOutflow += getSurfaceOverflowRate(&x[SURF]); + } + + //... save updated results + theLidUnit->surfaceDepth = x[SURF]; + theLidUnit->paveDepth = x[PAVE]; + theLidUnit->soilMoisture = x[SOIL]; + theLidUnit->storageDepth = x[STOR]; + for (i = 0; i < MAX_LAYERS; i++) theLidUnit->oldFluxRates[i] = f[i]; + + //... assign values to LID unit evaporation, infiltration & drain flow + *lidEvap = SurfaceEvap + PaveEvap + SoilEvap + StorageEvap; + *lidInfil = StorageExfil; + *lidDrain = StorageDrain; + + //... return surface outflow (per unit area) from unit + return SurfaceOutflow; +} + +//============================================================================= + +void lidproc_saveResults(TLidUnit* lidUnit, double ucfRainfall, double ucfRainDepth) +// +// Purpose: updates the mass balance for an LID unit and saves +// current flux rates to the LID report file. +// Input: lidUnit = ptr. to LID unit +// ucfRainfall = units conversion factor for rainfall rate +// ucfDepth = units conversion factor for rainfall depth +// Output: none +// +{ + double ucf; // units conversion factor + double totalEvap; // total evaporation rate (ft/s) + double totalVolume; // total volume stored in LID (ft) + double rptVars[MAX_RPT_VARS]; // array of reporting variables + int isDry = FALSE; // true if current state of LID is dry + char timeStamp[TIME_STAMP_SIZE + 1]; // date/time stamp + double elapsedHrs; // elapsed hours + + //... find total evap. rate and stored volume + totalEvap = SurfaceEvap + PaveEvap + SoilEvap + StorageEvap; + totalVolume = SurfaceVolume + PaveVolume + SoilVolume + StorageVolume; + + //... update mass balance totals + updateWaterBalance(theLidUnit, SurfaceInflow, totalEvap, StorageExfil, + SurfaceOutflow, StorageDrain, totalVolume); + + //... check if dry-weather conditions hold + if ( SurfaceInflow < MINFLOW && + SurfaceOutflow < MINFLOW && + StorageDrain < MINFLOW && + StorageExfil < MINFLOW && + totalEvap < MINFLOW + ) isDry = TRUE; + + //... update status of HasWetLids + if ( !isDry ) HasWetLids = TRUE; + + //... write results to LID report file + if ( lidUnit->rptFile ) + { + //... convert rate results to original units (in/hr or mm/hr) + ucf = ucfRainfall; + rptVars[SURF_INFLOW] = SurfaceInflow*ucf; + rptVars[TOTAL_EVAP] = totalEvap*ucf; + rptVars[SURF_INFIL] = SurfaceInfil*ucf; + rptVars[PAVE_PERC] = PavePerc*ucf; + rptVars[SOIL_PERC] = SoilPerc*ucf; + rptVars[STOR_EXFIL] = StorageExfil*ucf; + rptVars[SURF_OUTFLOW] = SurfaceOutflow*ucf; + rptVars[STOR_DRAIN] = StorageDrain*ucf; + + //... convert storage results to original units (in or mm) + ucf = ucfRainDepth; + rptVars[SURF_DEPTH] = theLidUnit->surfaceDepth*ucf; + rptVars[PAVE_DEPTH] = theLidUnit->paveDepth*ucf; + rptVars[SOIL_MOIST] = theLidUnit->soilMoisture; + rptVars[STOR_DEPTH] = theLidUnit->storageDepth*ucf; + + //... if the current LID state is wet but the previous state was dry + // for more than one period then write the saved previous results + // to the report file thus marking the end of a dry period + if ( !isDry && theLidUnit->rptFile->wasDry > 1) + { + fprintf(theLidUnit->rptFile->file, "%s", + theLidUnit->rptFile->results); + } + + //... write the current results to a string which is saved between + // reporting periods + elapsedHrs = NewRunoffTime / 1000.0 / 3600.0; + datetime_getTimeStamp( + M_D_Y, getDateTime(NewRunoffTime), TIME_STAMP_SIZE, timeStamp); + snprintf(theLidUnit->rptFile->results, sizeof(theLidUnit->rptFile->results), + "\n%20s\t %8.3f\t %8.3f\t %8.4f\t %8.3f\t %8.3f\t %8.3f\t %8.3f\t" + "%8.3f\t %8.3f\t %8.3f\t %8.3f\t %8.3f\t %8.3f", + timeStamp, elapsedHrs, rptVars[0], rptVars[1], rptVars[2], + rptVars[3], rptVars[4], rptVars[5], rptVars[6], rptVars[7], + rptVars[8], rptVars[9], rptVars[10], rptVars[11]); + + //... if the current LID state is dry + if ( isDry ) + { + //... if the previous state was wet then write the current + // results to file marking the start of a dry period + if ( theLidUnit->rptFile->wasDry == 0 ) + { + fprintf(theLidUnit->rptFile->file, "%s", + theLidUnit->rptFile->results); + } + + //... increment the number of successive dry periods + theLidUnit->rptFile->wasDry++; + } + + //... if the current LID state is wet + else + { + //... write the current results to the report file + fprintf(theLidUnit->rptFile->file, "%s", + theLidUnit->rptFile->results); + + //... re-set the number of successive dry periods to 0 + theLidUnit->rptFile->wasDry = 0; + } + } +} + +//============================================================================= + +void roofFluxRates(double x[], double f[]) +// +// Purpose: computes flux rates for roof disconnection. +// Input: x = vector of storage levels +// Output: f = vector of flux rates +// +{ + double surfaceDepth = x[SURF]; + + getEvapRates(surfaceDepth, 0.0, 0.0, 0.0, 1.0); + SurfaceVolume = surfaceDepth; + SurfaceInfil = 0.0; + if ( theLidProc->surface.alpha > 0.0 ) + SurfaceOutflow = getSurfaceOutflowRate(surfaceDepth); + else getSurfaceOverflowRate(&surfaceDepth); + StorageDrain = MIN(theLidProc->drain.coeff/UCF(RAINFALL), SurfaceOutflow); + SurfaceOutflow -= StorageDrain; + f[SURF] = (SurfaceInflow - SurfaceEvap - StorageDrain - SurfaceOutflow); +} + +//============================================================================= + +void greenRoofFluxRates(double x[], double f[]) +// +// Purpose: computes flux rates from the layers of a green roof. +// Input: x = vector of storage levels +// Output: f = vector of flux rates +// +{ + // Moisture level variables + double surfaceDepth; + double soilTheta; + double storageDepth; + + // Intermediate variables + double availVolume; + double maxRate; + + // Green roof properties + double soilThickness = theLidProc->soil.thickness; + double storageThickness = theLidProc->storage.thickness; + double soilPorosity = theLidProc->soil.porosity; + double storageVoidFrac = theLidProc->storage.voidFrac; + double soilFieldCap = theLidProc->soil.fieldCap; + double soilWiltPoint = theLidProc->soil.wiltPoint; + + //... retrieve moisture levels from input vector + surfaceDepth = x[SURF]; + soilTheta = x[SOIL]; + storageDepth = x[STOR]; + + //... convert moisture levels to volumes + SurfaceVolume = surfaceDepth * theLidProc->surface.voidFrac; + SoilVolume = soilTheta * soilThickness; + StorageVolume = storageDepth * storageVoidFrac; + + //... get ET rates + availVolume = SoilVolume - soilWiltPoint * soilThickness; + getEvapRates(SurfaceVolume, 0.0, availVolume, StorageVolume, 1.0); + if ( soilTheta >= soilPorosity ) StorageEvap = 0.0; + + //... soil layer perc rate + SoilPerc = getSoilPercRate(soilTheta); + + //... limit perc rate by available water + availVolume = (soilTheta - soilFieldCap) * soilThickness; + maxRate = MAX(availVolume, 0.0) / Tstep - SoilEvap; + SoilPerc = MIN(SoilPerc, maxRate); + SoilPerc = MAX(SoilPerc, 0.0); + + //... storage (drain mat) outflow rate + StorageExfil = 0.0; + StorageDrain = getDrainMatOutflow(storageDepth); + + //... unit is full + if ( soilTheta >= soilPorosity && storageDepth >= storageThickness ) + { + //... outflow from both layers equals limiting rate + maxRate = MIN(SoilPerc, StorageDrain); + SoilPerc = maxRate; + StorageDrain = maxRate; + + //... adjust inflow rate to soil layer + SurfaceInfil = MIN(SurfaceInfil, maxRate); + } + + //... unit not full + else + { + //... limit drainmat outflow by available storage volume + maxRate = storageDepth * storageVoidFrac / Tstep - StorageEvap; + if ( storageDepth >= storageThickness ) maxRate += SoilPerc; + maxRate = MAX(maxRate, 0.0); + StorageDrain = MIN(StorageDrain, maxRate); + + //... limit soil perc inflow by unused storage volume + maxRate = (storageThickness - storageDepth) * storageVoidFrac / Tstep + + StorageDrain + StorageEvap; + SoilPerc = MIN(SoilPerc, maxRate); + + //... adjust surface infil. so soil porosity not exceeded + maxRate = (soilPorosity - soilTheta) * soilThickness / Tstep + + SoilPerc + SoilEvap; + SurfaceInfil = MIN(SurfaceInfil, maxRate); + } + + // ... find surface outflow rate + SurfaceOutflow = getSurfaceOutflowRate(surfaceDepth); + + // ... compute overall layer flux rates + f[SURF] = (SurfaceInflow - SurfaceEvap - SurfaceInfil - SurfaceOutflow) / + theLidProc->surface.voidFrac; + f[SOIL] = (SurfaceInfil - SoilEvap - SoilPerc) / + theLidProc->soil.thickness; + f[STOR] = (SoilPerc - StorageEvap - StorageDrain) / + theLidProc->storage.voidFrac; +} + +//============================================================================= + +void biocellFluxRates(double x[], double f[]) +// +// Purpose: computes flux rates from the layers of a bio-retention cell LID. +// Input: x = vector of storage levels +// Output: f = vector of flux rates +// +{ + // Moisture level variables + double surfaceDepth; + double soilTheta; + double storageDepth; + + // Intermediate variables + double availVolume; + double maxRate; + + // LID layer properties + double soilThickness = theLidProc->soil.thickness; + double soilPorosity = theLidProc->soil.porosity; + double soilFieldCap = theLidProc->soil.fieldCap; + double soilWiltPoint = theLidProc->soil.wiltPoint; + double storageThickness = theLidProc->storage.thickness; + double storageVoidFrac = theLidProc->storage.voidFrac; + + //... retrieve moisture levels from input vector + surfaceDepth = x[SURF]; + soilTheta = x[SOIL]; + storageDepth = x[STOR]; + + //... convert moisture levels to volumes + SurfaceVolume = surfaceDepth * theLidProc->surface.voidFrac; + SoilVolume = soilTheta * soilThickness; + StorageVolume = storageDepth * storageVoidFrac; + + //... get ET rates + availVolume = SoilVolume - soilWiltPoint * soilThickness; + getEvapRates(SurfaceVolume, 0.0, availVolume, StorageVolume, 1.0); + if ( soilTheta >= soilPorosity ) StorageEvap = 0.0; + + //... soil layer perc rate + SoilPerc = getSoilPercRate(soilTheta); + + //... limit perc rate by available water + availVolume = (soilTheta - soilFieldCap) * soilThickness; + maxRate = MAX(availVolume, 0.0) / Tstep - SoilEvap; + SoilPerc = MIN(SoilPerc, maxRate); + SoilPerc = MAX(SoilPerc, 0.0); + + //... exfiltration rate out of storage layer + StorageExfil = getStorageExfilRate(); + + //... underdrain flow rate + StorageDrain = 0.0; + if ( theLidProc->drain.coeff > 0.0 ) + { + StorageDrain = getStorageDrainRate(storageDepth, soilTheta, 0.0, + surfaceDepth); + } + + //... special case of no storage layer present + if ( storageThickness == 0.0 ) + { + StorageEvap = 0.0; + maxRate = MIN(SoilPerc, StorageExfil); + SoilPerc = maxRate; + StorageExfil = maxRate; + + //... limit surface infil. by unused soil volume + maxRate = (soilPorosity - soilTheta) * soilThickness / Tstep + + SoilPerc + SoilEvap; + SurfaceInfil = MIN(SurfaceInfil, maxRate); + } + + //... storage & soil layers are full + else if ( soilTheta >= soilPorosity && storageDepth >= storageThickness ) + { + //... limiting rate is smaller of soil perc and storage outflow + maxRate = StorageExfil + StorageDrain; + if ( SoilPerc < maxRate ) + { + maxRate = SoilPerc; + if ( maxRate > StorageExfil ) StorageDrain = maxRate - StorageExfil; + else + { + StorageExfil = maxRate; + StorageDrain = 0.0; + } + } + else SoilPerc = maxRate; + + //... apply limiting rate to surface infil. + SurfaceInfil = MIN(SurfaceInfil, maxRate); + } + + //... either layer not full + else if ( storageThickness > 0.0 ) + { + //... limit storage exfiltration by available storage volume + maxRate = SoilPerc - StorageEvap + storageDepth*storageVoidFrac/Tstep; + StorageExfil = MIN(StorageExfil, maxRate); + StorageExfil = MAX(StorageExfil, 0.0); + + //... limit underdrain flow by volume above drain offset + if ( StorageDrain > 0.0 ) + { + maxRate = -StorageExfil - StorageEvap; + if ( storageDepth >= storageThickness) maxRate += SoilPerc; + if ( theLidProc->drain.offset <= storageDepth ) + { + maxRate += (storageDepth - theLidProc->drain.offset) * + storageVoidFrac/Tstep; + } + maxRate = MAX(maxRate, 0.0); + StorageDrain = MIN(StorageDrain, maxRate); + } + + //... limit soil perc by unused storage volume + maxRate = StorageExfil + StorageDrain + StorageEvap + + (storageThickness - storageDepth) * + storageVoidFrac/Tstep; + SoilPerc = MIN(SoilPerc, maxRate); + + //... limit surface infil. by unused soil volume + maxRate = (soilPorosity - soilTheta) * soilThickness / Tstep + + SoilPerc + SoilEvap; + SurfaceInfil = MIN(SurfaceInfil, maxRate); + } + + //... find surface layer outflow rate + SurfaceOutflow = getSurfaceOutflowRate(surfaceDepth); + + //... compute overall layer flux rates + f[SURF] = (SurfaceInflow - SurfaceEvap - SurfaceInfil - SurfaceOutflow) / + theLidProc->surface.voidFrac; + f[SOIL] = (SurfaceInfil - SoilEvap - SoilPerc) / + theLidProc->soil.thickness; + if ( storageThickness == 0.0 ) f[STOR] = 0.0; + else f[STOR] = (SoilPerc - StorageEvap - StorageExfil - StorageDrain) / + theLidProc->storage.voidFrac; +} + +//============================================================================= + +void trenchFluxRates(double x[], double f[]) +// +// Purpose: computes flux rates from the layers of an infiltration trench LID. +// Input: x = vector of storage levels +// Output: f = vector of flux rates +// +{ + // Moisture level variables + double surfaceDepth; + double storageDepth; + + // Intermediate variables + double availVolume; + double maxRate; + + // Storage layer properties + double storageThickness = theLidProc->storage.thickness; + double storageVoidFrac = theLidProc->storage.voidFrac; + + //... retrieve moisture levels from input vector + surfaceDepth = x[SURF]; + storageDepth = x[STOR]; + + //... convert moisture levels to volumes + SurfaceVolume = surfaceDepth * theLidProc->surface.voidFrac; + SoilVolume = 0.0; + StorageVolume = storageDepth * storageVoidFrac; + + //... get ET rates + availVolume = (storageThickness - storageDepth) * storageVoidFrac; + getEvapRates(SurfaceVolume, 0.0, 0.0, StorageVolume, 1.0); + + //... no storage evap if surface ponded + if ( surfaceDepth > 0.0 ) StorageEvap = 0.0; + + //... nominal storage inflow + StorageInflow = SurfaceInflow + SurfaceVolume / Tstep; + + //... exfiltration rate out of storage layer + StorageExfil = getStorageExfilRate(); + + //... underdrain flow rate + StorageDrain = 0.0; + if ( theLidProc->drain.coeff > 0.0 ) + { + StorageDrain = getStorageDrainRate(storageDepth, 0.0, 0.0, surfaceDepth); + } + + //... limit storage exfiltration by available storage volume + maxRate = StorageInflow - StorageEvap + storageDepth*storageVoidFrac/Tstep; + StorageExfil = MIN(StorageExfil, maxRate); + StorageExfil = MAX(StorageExfil, 0.0); + + //... limit underdrain flow by volume above drain offset + if ( StorageDrain > 0.0 ) + { + maxRate = -StorageExfil - StorageEvap; + if (storageDepth >= storageThickness ) maxRate += StorageInflow; + if ( theLidProc->drain.offset <= storageDepth ) + { + maxRate += (storageDepth - theLidProc->drain.offset) * + storageVoidFrac/Tstep; + } + maxRate = MAX(maxRate, 0.0); + StorageDrain = MIN(StorageDrain, maxRate); + } + + //... limit storage inflow to not exceed storage layer capacity + maxRate = (storageThickness - storageDepth)*storageVoidFrac/Tstep + + StorageExfil + StorageEvap + StorageDrain; + StorageInflow = MIN(StorageInflow, maxRate); + + //... equate surface infil to storage inflow + SurfaceInfil = StorageInflow; + + //... find surface outflow rate + SurfaceOutflow = getSurfaceOutflowRate(surfaceDepth); + + // ... find net fluxes for each layer + f[SURF] = SurfaceInflow - SurfaceEvap - StorageInflow - SurfaceOutflow / + theLidProc->surface.voidFrac;; + f[STOR] = (StorageInflow - StorageEvap - StorageExfil - StorageDrain) / + theLidProc->storage.voidFrac; + f[SOIL] = 0.0; +} + +//============================================================================= + +void pavementFluxRates(double x[], double f[]) +// +// Purpose: computes flux rates for the layers of a porous pavement LID. +// Input: x = vector of storage levels +// Output: f = vector of flux rates +// +{ + //... Moisture level variables + double surfaceDepth; + double paveDepth; + double soilTheta; + double storageDepth; + + //... Intermediate variables + double pervFrac = (1.0 - theLidProc->pavement.impervFrac); + double storageInflow; // inflow rate to storage layer (ft/s) + double availVolume; + double maxRate; + + //... LID layer properties + double paveVoidFrac = theLidProc->pavement.voidFrac * pervFrac; + double paveThickness = theLidProc->pavement.thickness; + double soilThickness = theLidProc->soil.thickness; + double soilPorosity = theLidProc->soil.porosity; + double soilFieldCap = theLidProc->soil.fieldCap; + double soilWiltPoint = theLidProc->soil.wiltPoint; + double storageThickness = theLidProc->storage.thickness; + double storageVoidFrac = theLidProc->storage.voidFrac; + + //... retrieve moisture levels from input vector + surfaceDepth = x[SURF]; + paveDepth = x[PAVE]; + soilTheta = x[SOIL]; + storageDepth = x[STOR]; + + //... convert moisture levels to volumes + SurfaceVolume = surfaceDepth * theLidProc->surface.voidFrac; + PaveVolume = paveDepth * paveVoidFrac; + SoilVolume = soilTheta * soilThickness; + StorageVolume = storageDepth * storageVoidFrac; + + //... get ET rates + availVolume = SoilVolume - soilWiltPoint * soilThickness; + getEvapRates(SurfaceVolume, PaveVolume, availVolume, StorageVolume, + pervFrac); + + //... no storage evap if soil or pavement layer saturated + if ( paveDepth >= paveThickness || + ( soilThickness > 0.0 && soilTheta >= soilPorosity ) + ) StorageEvap = 0.0; + + //... find nominal rate of surface infiltration into pavement layer + SurfaceInfil = SurfaceInflow + (SurfaceVolume / Tstep); + + //... find perc rate out of pavement layer + PavePerc = getPavementPermRate() * pervFrac; + + //... surface infiltration can't exceed pavement permeability + SurfaceInfil = MIN(SurfaceInfil, PavePerc); + + //... limit pavement perc by available water + maxRate = PaveVolume/Tstep + SurfaceInfil - PaveEvap; + maxRate = MAX(maxRate, 0.0); + PavePerc = MIN(PavePerc, maxRate); + + //... find soil layer perc rate + if ( soilThickness > 0.0 ) + { + SoilPerc = getSoilPercRate(soilTheta); + availVolume = (soilTheta - soilFieldCap) * soilThickness; + maxRate = MAX(availVolume, 0.0) / Tstep - SoilEvap; + SoilPerc = MIN(SoilPerc, maxRate); + SoilPerc = MAX(SoilPerc, 0.0); + } + else SoilPerc = PavePerc; + + //... exfiltration rate out of storage layer + StorageExfil = getStorageExfilRate(); + + //... underdrain flow rate + StorageDrain = 0.0; + if ( theLidProc->drain.coeff > 0.0 ) + { + StorageDrain = getStorageDrainRate(storageDepth, soilTheta, paveDepth, + surfaceDepth); + } + + //... check for adjacent saturated layers + + //... no soil layer, pavement & storage layers are full + if ( soilThickness == 0.0 && + storageDepth >= storageThickness && + paveDepth >= paveThickness ) + { + //... pavement outflow can't exceed storage outflow + maxRate = StorageEvap + StorageDrain + StorageExfil; + if ( PavePerc > maxRate ) PavePerc = maxRate; + + //... storage outflow can't exceed pavement outflow + else + { + //... use up available exfiltration capacity first + StorageExfil = MIN(StorageExfil, PavePerc); + StorageDrain = PavePerc - StorageExfil; + } + + //... set soil perc to pavement perc + SoilPerc = PavePerc; + + //... limit surface infil. by pavement perc + SurfaceInfil = MIN(SurfaceInfil, PavePerc); + } + + //... pavement, soil & storage layers are full + else if ( soilThickness > 0 && + storageDepth >= storageThickness && + soilTheta >= soilPorosity && + paveDepth >= paveThickness ) + { + //... find which layer has limiting flux rate + maxRate = StorageExfil + StorageDrain; + if ( SoilPerc < maxRate) maxRate = SoilPerc; + else maxRate = MIN(maxRate, PavePerc); + + //... use up available storage exfiltration capacity first + if ( maxRate > StorageExfil ) StorageDrain = maxRate - StorageExfil; + else + { + StorageExfil = maxRate; + StorageDrain = 0.0; + } + SoilPerc = maxRate; + PavePerc = maxRate; + + //... limit surface infil. by pavement perc + SurfaceInfil = MIN(SurfaceInfil, PavePerc); + } + + //... storage & soil layers are full + else if ( soilThickness > 0.0 && + storageDepth >= storageThickness && + soilTheta >= soilPorosity ) + { + //... soil perc can't exceed storage outflow + maxRate = StorageDrain + StorageExfil; + if ( SoilPerc > maxRate ) SoilPerc = maxRate; + + //... storage outflow can't exceed soil perc + else + { + //... use up available exfiltration capacity first + StorageExfil = MIN(StorageExfil, SoilPerc); + StorageDrain = SoilPerc - StorageExfil; + } + + //... limit surface infil. by available pavement volume + availVolume = (paveThickness - paveDepth) * paveVoidFrac; + maxRate = availVolume / Tstep + PavePerc + PaveEvap; + SurfaceInfil = MIN(SurfaceInfil, maxRate); + } + + //... soil and pavement layers are full + else if ( soilThickness > 0.0 && + paveDepth >= paveThickness && + soilTheta >= soilPorosity ) + { + PavePerc = MIN(PavePerc, SoilPerc); + SoilPerc = PavePerc; + SurfaceInfil = MIN(SurfaceInfil,PavePerc); + } + + //... no adjoining layers are full + else + { + //... limit storage exfiltration by available storage volume + // (if no soil layer, SoilPerc is same as PavePerc) + maxRate = SoilPerc - StorageEvap + StorageVolume / Tstep; + maxRate = MAX(0.0, maxRate); + StorageExfil = MIN(StorageExfil, maxRate); + + //... limit underdrain flow by volume above drain offset + if ( StorageDrain > 0.0 ) + { + maxRate = -StorageExfil - StorageEvap; + if (storageDepth >= storageThickness ) maxRate += SoilPerc; + if ( theLidProc->drain.offset <= storageDepth ) + { + maxRate += (storageDepth - theLidProc->drain.offset) * + storageVoidFrac/Tstep; + } + maxRate = MAX(maxRate, 0.0); + StorageDrain = MIN(StorageDrain, maxRate); + } + + //... limit soil & pavement outflow by unused storage volume + availVolume = (storageThickness - storageDepth) * storageVoidFrac; + maxRate = availVolume/Tstep + StorageEvap + StorageDrain + StorageExfil; + maxRate = MAX(maxRate, 0.0); + if ( soilThickness > 0.0 ) + { + SoilPerc = MIN(SoilPerc, maxRate); + maxRate = (soilPorosity - soilTheta) * soilThickness / Tstep + + SoilPerc; + } + PavePerc = MIN(PavePerc, maxRate); + + //... limit surface infil. by available pavement volume + availVolume = (paveThickness - paveDepth) * paveVoidFrac; + maxRate = availVolume / Tstep + PavePerc + PaveEvap; + SurfaceInfil = MIN(SurfaceInfil, maxRate); + } + + //... surface outflow + SurfaceOutflow = getSurfaceOutflowRate(surfaceDepth); + + //... compute overall layer flux rates + f[SURF] = SurfaceInflow - SurfaceEvap - SurfaceInfil - SurfaceOutflow; + f[PAVE] = (SurfaceInfil - PaveEvap - PavePerc) / paveVoidFrac; + if ( theLidProc->soil.thickness > 0.0) + { + f[SOIL] = (PavePerc - SoilEvap - SoilPerc) / soilThickness; + storageInflow = SoilPerc; + } + else + { + f[SOIL] = 0.0; + storageInflow = PavePerc; + SoilPerc = 0.0; + } + f[STOR] = (storageInflow - StorageEvap - StorageExfil - StorageDrain) / + storageVoidFrac; +} + +//============================================================================= + +void swaleFluxRates(double x[], double f[]) +// +// Purpose: computes flux rates from a vegetative swale LID. +// Input: x = vector of storage levels +// Output: f = vector of flux rates +// +{ + double depth; // depth of surface water in swale (ft) + double topWidth; // top width of full swale (ft) + double botWidth; // bottom width of swale (ft) + double length; // length of swale (ft) + double surfInflow; // inflow rate to swale (cfs) + double surfWidth; // top width at current water depth (ft) + double surfArea; // surface area of current water depth (ft2) + double flowArea; // x-section flow area (ft2) + double lidArea; // surface area of full swale (ft2) + double hydRadius; // hydraulic radius for current depth (ft) + double slope; // slope of swale side wall (run/rise) + double volume; // swale volume at current water depth (ft3) + double dVdT; // change in volume w.r.t. time (cfs) + double dStore; // depression storage depth (ft) + double xDepth; // depth above depression storage (ft) + + //... retrieve state variable from work vector + depth = x[SURF]; + depth = MIN(depth, theLidProc->surface.thickness); + + //... depression storage depth + dStore = 0.0; + + //... get swale's bottom width + // (0.5 ft minimum to avoid numerical problems) + slope = theLidProc->surface.sideSlope; + topWidth = theLidUnit->fullWidth; + topWidth = MAX(topWidth, 0.5); + botWidth = topWidth - 2.0 * slope * theLidProc->surface.thickness; + if ( botWidth < 0.5 ) + { + botWidth = 0.5; + slope = 0.5 * (topWidth - 0.5) / theLidProc->surface.thickness; + } + + //... swale's length + lidArea = theLidUnit->area; + length = lidArea / topWidth; + + //... top width, surface area and flow area of current ponded depth + surfWidth = botWidth + 2.0 * slope * depth; + surfArea = length * surfWidth; + flowArea = (depth * (botWidth + slope * depth)) * + theLidProc->surface.voidFrac; + + //... wet volume and effective depth + volume = length * flowArea; + + //... surface inflow into swale (cfs) + surfInflow = SurfaceInflow * lidArea; + + //... ET rate in cfs + SurfaceEvap = EvapRate * surfArea; + SurfaceEvap = MIN(SurfaceEvap, volume/Tstep); + + //... infiltration rate to native soil in cfs + StorageExfil = SurfaceInfil * surfArea; + + //... no surface outflow if depth below depression storage + xDepth = depth - dStore; + if ( xDepth <= ZERO ) SurfaceOutflow = 0.0; + + //... otherwise compute a surface outflow + else + { + //... modify flow area to remove depression storage, + flowArea -= (dStore * (botWidth + slope * dStore)) * + theLidProc->surface.voidFrac; + if ( flowArea < ZERO ) SurfaceOutflow = 0.0; + else + { + //... compute hydraulic radius + botWidth = botWidth + 2.0 * dStore * slope; + hydRadius = botWidth + 2.0 * xDepth * sqrt(1.0 + slope*slope); + hydRadius = flowArea / hydRadius; + + //... use Manning Eqn. to find outflow rate in cfs + SurfaceOutflow = theLidProc->surface.alpha * flowArea * + pow(hydRadius, 2./3.); + } + } + + //... net flux rate (dV/dt) in cfs + dVdT = surfInflow - SurfaceEvap - StorageExfil - SurfaceOutflow; + + //... when full, any net positive inflow becomes spillage + if ( depth == theLidProc->surface.thickness && dVdT > 0.0 ) + { + SurfaceOutflow += dVdT; + dVdT = 0.0; + } + + //... convert flux rates to ft/s + SurfaceEvap /= lidArea; + StorageExfil /= lidArea; + SurfaceOutflow /= lidArea; + f[SURF] = dVdT / surfArea; + f[SOIL] = 0.0; + f[STOR] = 0.0; + + //... assign values to layer volumes + SurfaceVolume = volume / lidArea; + SoilVolume = 0.0; + StorageVolume = 0.0; +} + +//============================================================================= + +void barrelFluxRates(double x[], double f[]) +// +// Purpose: computes flux rates for a rain barrel LID. +// Input: x = vector of storage levels +// Output: f = vector of flux rates +// +{ + double storageDepth = x[STOR]; + double head; + double maxValue; + + //... assign values to layer volumes + SurfaceVolume = 0.0; + SoilVolume = 0.0; + StorageVolume = storageDepth; + + //... initialize flows + SurfaceInfil = 0.0; + SurfaceOutflow = 0.0; + StorageDrain = 0.0; + + //... compute outflow if time since last rain exceeds drain delay + // (dryTime is updated in lid.evalLidUnit at each time step) + if ( theLidProc->drain.delay == 0.0 || + theLidUnit->dryTime >= theLidProc->drain.delay ) + { + head = storageDepth - theLidProc->drain.offset; + if ( head > 0.0 ) + { + StorageDrain = getStorageDrainRate(storageDepth, 0.0, 0.0, 0.0); + maxValue = (head/Tstep); + StorageDrain = MIN(StorageDrain, maxValue); + } + } + + //... limit inflow to available storage + StorageInflow = SurfaceInflow; + maxValue = (theLidProc->storage.thickness - storageDepth) / Tstep + + StorageDrain; + StorageInflow = MIN(StorageInflow, maxValue); + SurfaceInfil = StorageInflow; + + //... assign values to layer flux rates + f[SURF] = SurfaceInflow - StorageInflow; + f[STOR] = StorageInflow - StorageDrain; + f[SOIL] = 0.0; +} + +//============================================================================= + +double getSurfaceOutflowRate(double depth) +// +// Purpose: computes outflow rate from a LID's surface layer. +// Input: depth = depth of ponded water on surface layer (ft) +// Output: returns outflow from surface layer (ft/s) +// +// Note: this function should not be applied to swales or rain barrels. +// +{ + double delta; + double outflow; + + //... no outflow if ponded depth below storage depth + delta = depth - theLidProc->surface.thickness; + if ( delta < 0.0 ) return 0.0; + + //... compute outflow from overland flow Manning equation + outflow = theLidProc->surface.alpha * pow(delta, 5.0/3.0) * + theLidUnit->fullWidth / theLidUnit->area; + outflow = MIN(outflow, delta / Tstep); + return outflow; +} + +//============================================================================= + +double getPavementPermRate() +// +// Purpose: computes reduced permeability of a pavement layer due to +// clogging. +// Input: none +// Output: returns the reduced permeability of the pavement layer (ft/s). +// +{ + double permReduction = 0.0; + double clogFactor= theLidProc->pavement.clogFactor; + double regenDays = theLidProc->pavement.regenDays; + + // ... find permeability reduction due to clogging + if ( clogFactor > 0.0 ) + { + // ... see if permeability regeneration has occurred + // (regeneration is assumed to reduce the total + // volumetric loading that the pavement has received) + if ( regenDays > 0.0 ) + { + if ( OldRunoffTime / 1000.0 / SECperDAY >= theLidUnit->nextRegenDay ) + { + // ... reduce total volume treated by degree of regeneration + theLidUnit->volTreated *= + (1.0 - theLidProc->pavement.regenDegree); + + // ... update next day that regenration occurs + theLidUnit->nextRegenDay += regenDays; + } + } + + // ... find permeabiity reduction factor + permReduction = theLidUnit->volTreated / clogFactor; + permReduction = MIN(permReduction, 1.0); + } + + // ... return the effective pavement permeability + return theLidProc->pavement.kSat * (1.0 - permReduction); +} + +//============================================================================= + +double getSoilPercRate(double theta) +// +// Purpose: computes percolation rate of water through a LID's soil layer. +// Input: theta = moisture content (fraction) +// Output: returns percolation rate within soil layer (ft/s) +// +{ + double delta; // moisture deficit + + // ... no percolation if soil moisture <= field capacity + if ( theta <= theLidProc->soil.fieldCap ) return 0.0; + + // ... perc rate = unsaturated hydraulic conductivity + delta = theLidProc->soil.porosity - theta; + return theLidProc->soil.kSat * exp(-delta * theLidProc->soil.kSlope); + +} + +//============================================================================= + +double getStorageExfilRate() +// +// Purpose: computes exfiltration rate from storage zone into +// native soil beneath a LID. +// Input: depth = depth of water storage zone (ft) +// Output: returns infiltration rate (ft/s) +// +{ + double infil = 0.0; + double clogFactor = 0.0; + + if ( theLidProc->storage.kSat == 0.0 ) return 0.0; + if ( MaxNativeInfil == 0.0 ) return 0.0; + + //... reduction due to clogging + clogFactor = theLidProc->storage.clogFactor; + if ( clogFactor > 0.0 ) + { + clogFactor = theLidUnit->waterBalance.inflow / clogFactor; + clogFactor = MIN(clogFactor, 1.0); + } + + //... infiltration rate = storage Ksat reduced by any clogging + infil = theLidProc->storage.kSat * (1.0 - clogFactor); + + //... limit infiltration rate by any groundwater-imposed limit + return MIN(infil, MaxNativeInfil); +} + +//============================================================================= + +double getStorageDrainRate(double storageDepth, double soilTheta, + double paveDepth, double surfaceDepth) +// +// Purpose: computes underdrain flow rate in a LID's storage layer. +// Input: storageDepth = depth of water in storage layer (ft) +// soilTheta = moisture content of soil layer +// paveDepth = effective depth of water in pavement layer (ft) +// surfaceDepth = depth of ponded water on surface layer (ft) +// Output: returns flow in underdrain (ft/s) +// +// Note: drain eqn. is evaluated in user's units. +// Note: head on drain is water depth in storage layer plus the +// layers above it (soil, pavement, and surface in that order) +// minus the drain outlet offset. +{ + int curve = theLidProc->drain.qCurve; + double head = storageDepth; + double outflow = 0.0; + double paveThickness = theLidProc->pavement.thickness; + double soilThickness = theLidProc->soil.thickness; + double soilPorosity = theLidProc->soil.porosity; + double soilFieldCap = theLidProc->soil.fieldCap; + double storageThickness = theLidProc->storage.thickness; + + // --- storage layer is full + if ( storageDepth >= storageThickness ) + { + // --- a soil layer exists + if ( soilThickness > 0.0 ) + { + // --- increase head by fraction of soil layer saturated + if ( soilTheta > soilFieldCap ) + { + head += (soilTheta - soilFieldCap) / + (soilPorosity - soilFieldCap) * soilThickness; + + // --- soil layer is saturated, increase head by water + // depth in layer above it + if ( soilTheta >= soilPorosity ) + { + if ( paveThickness > 0.0 ) head += paveDepth; + else head += surfaceDepth; + } + } + } + + // --- no soil layer so increase head by water level in pavement + // layer and possibly surface layer + if ( paveThickness > 0.0 ) + { + head += paveDepth; + if ( paveDepth >= paveThickness ) head += surfaceDepth; + } + } + + // --- no outflow if: + // a) no prior outflow and head below open threshold + // b) prior outflow and head below closed threshold + if ( theLidUnit->oldDrainFlow == 0.0 && + head <= theLidProc->drain.hOpen ) return 0.0; + if ( theLidUnit->oldDrainFlow > 0.0 && + head <= theLidProc->drain.hClose ) return 0.0; + + // --- make head relative to drain offset + head -= theLidProc->drain.offset; + + // --- compute drain outflow from underdrain flow equation in user units + // (head in inches or mm, flow rate in in/hr or mm/hr) + if ( head > ZERO ) + { + // --- convert head to user units + head *= UCF(RAINDEPTH); + + // --- compute drain outflow in user units + outflow = theLidProc->drain.coeff * + pow(head, theLidProc->drain.expon); + + // --- apply user-supplied control curve to outflow + if (curve >= 0) outflow *= table_lookup(&Curve[curve], head); + + // --- convert outflow to ft/s + outflow /= UCF(RAINFALL); + } + return outflow; +} + +//============================================================================= + +double getDrainMatOutflow(double depth) +// +// Purpose: computes flow rate through a green roof's drainage mat. +// Input: depth = depth of water in drainage mat (ft) +// Output: returns flow in drainage mat (ft/s) +// +{ + //... default is to pass all inflow + double result = SoilPerc; + + //... otherwise use Manning eqn. if its parameters were supplied + if ( theLidProc->drainMat.alpha > 0.0 ) + { + result = theLidProc->drainMat.alpha * pow(depth, 5.0/3.0) * + theLidUnit->fullWidth / theLidUnit->area * + theLidProc->drainMat.voidFrac; + } + return result; +} + +//============================================================================= + +void getEvapRates(double surfaceVol, double paveVol, double soilVol, + double storageVol, double pervFrac) +// +// Purpose: computes surface, pavement, soil, and storage evaporation rates. +// Input: surfaceVol = volume/area of ponded water on surface layer (ft) +// paveVol = volume/area of water in pavement pores (ft) +// soilVol = volume/area of water in soil (or pavement) pores (ft) +// storageVol = volume/area of water in storage layer (ft) +// pervFrac = fraction of surface layer that is pervious +// Output: none +// +{ + double availEvap; + + //... surface evaporation flux + availEvap = EvapRate; + SurfaceEvap = MIN(availEvap, surfaceVol/Tstep); + SurfaceEvap = MAX(0.0, SurfaceEvap); + availEvap = MAX(0.0, (availEvap - SurfaceEvap)); + availEvap *= pervFrac; + + //... no subsurface evap if water is infiltrating + if ( SurfaceInfil > 0.0 ) + { + PaveEvap = 0.0; + SoilEvap = 0.0; + StorageEvap = 0.0; + } + else + { + //... pavement evaporation flux + PaveEvap = MIN(availEvap, paveVol / Tstep); + availEvap = MAX(0.0, (availEvap - PaveEvap)); + + //... soil evaporation flux + SoilEvap = MIN(availEvap, soilVol / Tstep); + availEvap = MAX(0.0, (availEvap - SoilEvap)); + + //... storage evaporation flux + StorageEvap = MIN(availEvap, storageVol / Tstep); + } +} + +//============================================================================= + +double getSurfaceOverflowRate(double* surfaceDepth) +// +// Purpose: finds surface overflow rate from a LID unit. +// Input: surfaceDepth = depth of water stored in surface layer (ft) +// Output: returns the overflow rate (ft/s) +// +{ + double delta = *surfaceDepth - theLidProc->surface.thickness; + if ( delta <= 0.0 ) return 0.0; + *surfaceDepth = theLidProc->surface.thickness; + return delta * theLidProc->surface.voidFrac / Tstep; +} + +//============================================================================= + +void updateWaterBalance(TLidUnit *lidUnit, double inflow, double evap, + double infil, double surfFlow, double drainFlow, double storage) +// +// Purpose: updates components of the water mass balance for a LID unit +// over the current time step. +// Input: lidUnit = a particular LID unit +// inflow = runon + rainfall to the LID unit (ft/s) +// evap = evaporation rate from the unit (ft/s) +// infil = infiltration out the bottom of the unit (ft/s) +// surfFlow = surface runoff from the unit (ft/s) +// drainFlow = underdrain flow from the unit +// storage = volume of water stored in the unit (ft) +// Output: none +// +{ + lidUnit->volTreated += inflow * Tstep; + lidUnit->waterBalance.inflow += inflow * Tstep; + lidUnit->waterBalance.evap += evap * Tstep; + lidUnit->waterBalance.infil += infil * Tstep; + lidUnit->waterBalance.surfFlow += surfFlow * Tstep; + lidUnit->waterBalance.drainFlow += drainFlow * Tstep; + lidUnit->waterBalance.finalVol = storage; +} + +//============================================================================= + +int modpuls_solve(int n, double* x, double* xOld, double* xPrev, + double* xMin, double* xMax, double* xTol, + double* qOld, double* q, double dt, double omega, + void (*derivs)(double*, double*)) +// +// Purpose: solves system of equations dx/dt = q(x) for x at end of time step +// dt using a modified Puls method. +// Input: n = number of state variables +// x = vector of state variables +// xOld = state variable values at start of time step +// xPrev = state variable values from previous iteration +// xMin = lower limits on state variables +// xMax = upper limits on state variables +// xTol = convergence tolerances on state variables +// qOld = flux rates at start of time step +// q = flux rates at end of time step +// dt = time step (sec) +// omega = time weighting parameter (use 0 for Euler method +// or 0.5 for modified Puls method) +// derivs = pointer to function that computes flux rates q as a +// function of state variables x +// Output: returns number of steps required for convergence (or 0 if +// process doesn't converge) +// +{ + int i; + int canStop; + int steps = 1; + int maxSteps = 20; + + //... initialize state variable values + for (i=0; i 0.0 && + fabs(x[i] - xPrev[i]) > xTol[i] ) canStop = 0; + xPrev[i] = x[i]; + } + + //... return if process converges + if (canStop) return steps; + steps++; + } + + //... no convergence so return 0 + return 0; +} diff --git a/src/link.c b/src/link.c new file mode 100644 index 000000000..052a030b9 --- /dev/null +++ b/src/link.c @@ -0,0 +1,2679 @@ +//----------------------------------------------------------------------------- +// link.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 10/29/22 (Build 5.2.2) +// Author: L. Rossman +// M. Tryby (EPA) +// +// Conveyance system link functions +// +// Update History +// ============== +// Build 5.1.007: +// - Optional surcharging of weirs introduced. +// Build 5.1.008: +// - Bug in finding flow through surcharged weir fixed. +// - Bug in finding if conduit is upstrm/dnstrm full fixed. +// - Monthly conductivity adjustment applied to conduit seepage. +// - Conduit seepage limited by conduit's flow rate. +// Build 5.1.010: +// - Support added for new ROADWAY_WEIR object. +// - Time of last setting change initialized for links. +// Build 5.1.011: +// - Crest elevation of regulator links raised to downstream invert. +// - Fixed converting roadWidth weir parameter to internal units. +// - Weir shape parameter deprecated. +// - Extra geometric parameters ignored for non-conduit open rectangular +// cross sections. +// Build 5.1.012: +// - Conduit seepage rate now based on flow width, not wetted perimeter. +// - Formula for side flow weir corrected. +// - Crest length contraction adjustments corrected. +// Build 5.1.013: +// - Maximum depth adjustments made for storage units that can surcharge. +// - 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(). +// Build 5.2.0: +// - Support added for Streets and Inlets. +// - Support added for variable speed pumps. +// Build 5.2.1 +// - Warning no longer issued when conduit elevation drop < MIN_DELTA_Z. +// Build 5.2.2: +// - Warning for conduit elevation drop < MIN_DELTA_Z restored. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include +#include "headers.h" +#include "inlet.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +static const double MIN_DELTA_Z = 0.001; // minimum elevation change for conduit + // slopes (ft) + +//----------------------------------------------------------------------------- +// External functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// link_readParams (called by parseLine in input.c) +// link_readXsectParams (called by parseLine in input.c) +// link_readLossParams (called by parseLine in input.c) +// link_validate (called by project_validate in project.c) +// link_initState (called by initObjects in swmm5.c) +// link_setOldHydState (called by routing_execute in routing.c) +// link_setOldQualState (called by routing_execute in routing.c) +// link_setTargetSetting (called by routing_execute in routing.c) +// link_setSetting (called by routing_execute in routing.c) +// link_getResults (called by output_saveLinkResults) +// link_getLength (called in dwflow.c, kinwave.c & flowrout.c) +// link_getFroude (called in dwflow.c) +// link_getInflow (called in flowrout.c & dynwave.c) +// link_setOutfallDepth (called in flowrout.c & dynwave.c) +// link_getYcrit (called by link_setOutfallDepth & in dwflow.c) +// link_getYnorm (called by conduit_initState, link_setOutfallDepth & in dwflow.c) +// link_getVelocity (called by link_getResults & stats_updateLinkStats) +// link_getPower (called by stats_updateLinkStats in stats.c) +// link_getLossRate (called in dwflow.c, kinwave.c & flowrout.c) + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static void link_setParams(int j, int type, int n1, int n2, int k, double x[]); +static void link_convertOffsets(int j); +static double link_getOffsetHeight(int j, double offset, double elev); + +static int conduit_readParams(int j, int k, char* tok[], int ntoks); +static void conduit_validate(int j, int k); +static void conduit_initState(int j, int k); +static void conduit_reverse(int j, int k); +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); + +static int pump_readParams(int j, int k, char* tok[], int ntoks); +static void pump_validate(int j, int k); +static void pump_initState(int j, int k); +static double pump_getInflow(int j); + +static int orifice_readParams(int j, int k, char* tok[], int ntoks); +static void orifice_validate(int j, int k); +static void orifice_setSetting(int j, double tstep); +static double orifice_getWeirCoeff(int j, int k, double h); +static double orifice_getInflow(int j); +static double orifice_getFlow(int j, int k, double head, double f, + int hasFlapGate); + +static int weir_readParams(int j, int k, char* tok[], int ntoks); +static void weir_validate(int j, int k); +static void weir_setSetting(int j); +static double weir_getInflow(int j); +static double weir_getOpenArea(int j, double y); +static void weir_getFlow(int j, int k, double head, double dir, + int hasFlapGate, double* q1, double* q2); +static double weir_getOrificeFlow(int j, double head, double y, double cOrif); +static double weir_getdqdh(int k, double dir, double h, double q1, double q2); + +static int outlet_readParams(int j, int k, char* tok[], int ntoks); +static double outlet_getFlow(int k, double head); +static double outlet_getInflow(int j); + + +//============================================================================= + +int link_readParams(int j, int type, int k, char* tok[], int ntoks) +// +// Input: j = link index +// type = link type code +// k = link type index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads parameters for a specific type of link from a +// tokenized line of input data. +// +{ + switch ( type ) + { + case CONDUIT: return conduit_readParams(j, k, tok, ntoks); + case PUMP: return pump_readParams(j, k, tok, ntoks); + case ORIFICE: return orifice_readParams(j, k, tok, ntoks); + case WEIR: return weir_readParams(j, k, tok, ntoks); + case OUTLET: return outlet_readParams(j, k, tok, ntoks); + default: return 0; + } +} + +//============================================================================= + +int link_readXsectParams(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads a link's cross section parameters from a tokenized +// line of input data. +// Formats: +// Link Shape Geom1 Geom2 Geom3 Geom4 (Barrels Culvert) +// Link IRREGULAR TransectID +// Link STREET StreetID +// +{ + int i, j, k; + double x[4]; + + // --- check for minimum number of tokens + if (ntoks < 3) return error_setInpError(ERR_ITEMS, ""); + + // --- get index of link + j = project_findObject(LINK, tok[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); + + // --- get code of xsection shape + k = findmatch(tok[1], XsectTypeWords); + if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); + + // --- assign default number of barrels to conduit + if ( Link[j].type == CONDUIT ) Conduit[Link[j].subIndex].barrels = 1; + + // --- assume link is not a culvert + Link[j].xsect.culvertCode = 0; + + // --- for irregular shape, find index of transect object + if ( k == IRREGULAR ) + { + i = project_findObject(TRANSECT, tok[2]); + if ( i < 0 ) return error_setInpError(ERR_NAME, tok[2]); + Link[j].xsect.type = k; + Link[j].xsect.transect = i; + return 0; + } + + // --- for street cross section, find index of Street object + else if (k == STREET_XSECT) + { + i = project_findObject(STREET, tok[2]); + if (i < 0) return error_setInpError(ERR_NAME, tok[2]); + Link[j].xsect.type = k; + Link[j].xsect.transect = i; + return 0; + } + + else + { + // --- check that geometric parameters are present + if (ntoks < 6) return error_setInpError(ERR_ITEMS, ""); + + // --- parse max. depth & shape curve for a custom shape + if ( k == CUSTOM ) + { + if ( !getDouble(tok[2], &x[0]) || x[0] <= 0.0 ) + return error_setInpError(ERR_NUMBER, tok[2]); + i = project_findObject(CURVE, tok[3]); + if ( i < 0 ) return error_setInpError(ERR_NAME, tok[3]); + Link[j].xsect.type = k; + Link[j].xsect.transect = i; + Link[j].xsect.yFull = x[0] / UCF(LENGTH); + } + + // --- parse and save geometric parameters + else for (i = 2; i <= 5; i++) + { + if ( !getDouble(tok[i], &x[i-2]) ) + return error_setInpError(ERR_NUMBER, tok[i]); + } + + // --- ignore extra parameters for non-conduit open rectangular shapes + if ( Link[j].type != CONDUIT && k == RECT_OPEN ) + { + x[2] = 0.0; + x[3] = 0.0; + } + if ( !xsect_setParams(&Link[j].xsect, k, x, UCF(LENGTH)) ) + { + return error_setInpError(ERR_NUMBER, ""); + } + + // --- parse number of barrels if present + if ( Link[j].type == CONDUIT && ntoks >= 7 ) + { + i = atoi(tok[6]); + if ( i <= 0 ) return error_setInpError(ERR_NUMBER, tok[6]); + else Conduit[Link[j].subIndex].barrels = (char)i; + } + + // --- parse culvert code if present + if ( Link[j].type == CONDUIT && ntoks >= 8 ) + { + i = atoi(tok[7]); + if ( i < 0 ) return error_setInpError(ERR_NUMBER, tok[7]); + else Link[j].xsect.culvertCode = i; + } + } + return 0; +} + +//============================================================================= + +int link_readLossParams(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads local loss parameters for a link from a tokenized +// line of input data. +// +// Format: LinkID cInlet cOutlet cAvg FlapGate(YES/NO) SeepRate +// +{ + int i, j, k; + double x[3]; + double seepRate = 0.0; + + if ( ntoks < 4 ) return error_setInpError(ERR_ITEMS, ""); + j = project_findObject(LINK, tok[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); + for (i=1; i<=3; i++) + { + if ( ! getDouble(tok[i], &x[i-1]) || x[i-1] < 0.0 ) + return error_setInpError(ERR_NUMBER, tok[i]); + } + k = 0; + if ( ntoks >= 5 ) + { + k = findmatch(tok[4], NoYesWords); + if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[4]); + } + if ( ntoks >= 6 ) + { + if ( ! getDouble(tok[5], &seepRate) ) + return error_setInpError(ERR_NUMBER, tok[5]); + } + Link[j].cLossInlet = x[0]; + Link[j].cLossOutlet = x[1]; + Link[j].cLossAvg = x[2]; + Link[j].hasFlapGate = k; + Link[j].seepRate = seepRate / UCF(RAINFALL); + return 0; +} + +//============================================================================= + +void link_setParams(int j, int type, int n1, int n2, int k, double x[]) +// +// Input: j = link index +// type = link type code +// n1 = index of upstream node +// n2 = index of downstream node +// k = index of link's sub-type +// x = array of parameter values +// Output: none +// Purpose: sets parameters for a link. +// +{ + Link[j].node1 = n1; + Link[j].node2 = n2; + Link[j].type = type; + Link[j].subIndex = k; + Link[j].offset1 = 0.0; + Link[j].offset2 = 0.0; + Link[j].q0 = 0.0; + Link[j].qFull = 0.0; + Link[j].setting = 1.0; + Link[j].targetSetting = 1.0; + Link[j].hasFlapGate = 0; + Link[j].qLimit = 0.0; // 0 means that no limit is defined + Link[j].direction = 1; + + switch (type) + { + case CONDUIT: + Conduit[k].length = x[0] / UCF(LENGTH); + Conduit[k].modLength = Conduit[k].length; + Conduit[k].roughness = x[1]; + Link[j].offset1 = x[2] / UCF(LENGTH); + Link[j].offset2 = x[3] / UCF(LENGTH); + Link[j].q0 = x[4] / UCF(FLOW); + Link[j].qLimit = x[5] / UCF(FLOW); + break; + + case PUMP: + Pump[k].pumpCurve = (int)x[0]; + Link[j].hasFlapGate = FALSE; + Pump[k].initSetting = x[1]; + Pump[k].yOn = x[2] / UCF(LENGTH); + Pump[k].yOff = x[3] / UCF(LENGTH); + Pump[k].xMin = 0.0; + Pump[k].xMax = 0.0; + break; + + case ORIFICE: + Orifice[k].type = (int)x[0]; + Link[j].offset1 = x[1] / UCF(LENGTH); + Link[j].offset2 = Link[j].offset1; + Orifice[k].cDisch = x[2]; + Link[j].hasFlapGate = (x[3] > 0.0) ? 1 : 0; + Orifice[k].orate = x[4] * 3600.0; + break; + + case WEIR: + Weir[k].type = (int)x[0]; + Link[j].offset1 = x[1] / UCF(LENGTH); + Link[j].offset2 = Link[j].offset1; + Weir[k].cDisch1 = x[2]; + Link[j].hasFlapGate = (x[3] > 0.0) ? 1 : 0; + Weir[k].endCon = x[4]; + Weir[k].cDisch2 = x[5]; + Weir[k].canSurcharge = (int)x[6]; + Weir[k].roadWidth = x[7] / UCF(LENGTH); + Weir[k].roadSurface = (int)x[8]; + Weir[k].cdCurve = (int)x[9]; + break; + + case OUTLET: + Link[j].offset1 = x[0] / UCF(LENGTH); + Link[j].offset2 = Link[j].offset1; + Outlet[k].qCoeff = x[1]; + Outlet[k].qExpon = x[2]; + Outlet[k].qCurve = (int)x[3]; + Link[j].hasFlapGate = (x[4] > 0.0) ? 1 : 0; + Outlet[k].curveType = (int)x[5]; + + xsect_setParams(&Link[j].xsect, DUMMY, NULL, 0.0); + break; + + } +} + +//============================================================================= + +void link_validate(int j) +// +// Input: j = link index +// Output: none +// Purpose: validates a link's properties. +// +{ + int n; + + if ( LinkOffsets == ELEV_OFFSET ) link_convertOffsets(j); + switch ( Link[j].type ) + { + case CONDUIT: conduit_validate(j, Link[j].subIndex); break; + case PUMP: pump_validate(j, Link[j].subIndex); break; + case ORIFICE: orifice_validate(j, Link[j].subIndex); break; + case WEIR: weir_validate(j, Link[j].subIndex); break; + } + + // --- check if crest of regulator opening < invert of downstream node + switch ( Link[j].type ) + { + case ORIFICE: + case WEIR: + case OUTLET: + if ( Node[Link[j].node1].invertElev + Link[j].offset1 < + Node[Link[j].node2].invertElev ) + { + if (RouteModel == DW) + { + Link[j].offset1 = Node[Link[j].node2].invertElev - + Node[Link[j].node1].invertElev; + report_writeWarningMsg(WARN10b, Link[j].ID); + } + else report_writeWarningMsg(WARN10a, Link[j].ID); + } + } + + // --- force max. depth of end nodes to be >= link crown height + // at non-storage nodes + + // --- skip pumps and bottom orifices + if ( Link[j].type == PUMP || + (Link[j].type == ORIFICE && + Orifice[Link[j].subIndex].type == BOTTOM_ORIFICE) ) return; + + // --- extend upstream node's full depth to link's crown elevation + n = Link[j].node1; + if ( Node[n].type != STORAGE || Node[n].surDepth > 0.0 ) + { + Node[n].fullDepth = MAX(Node[n].fullDepth, + Link[j].offset1 + Link[j].xsect.yFull); + } + + // --- do same for downstream node only for conduit links + n = Link[j].node2; + if ( (Node[n].type != STORAGE || Node[n].surDepth > 0.0) && + Link[j].type == CONDUIT ) + { + Node[n].fullDepth = MAX(Node[n].fullDepth, + Link[j].offset2 + Link[j].xsect.yFull); + } +} + +//============================================================================= + +void link_convertOffsets(int j) +// +// Input: j = link index +// Output: none +// Purpose: converts offset elevations to offset heights for a link. +// +{ + double elev; + + elev = Node[Link[j].node1].invertElev; + Link[j].offset1 = link_getOffsetHeight(j, Link[j].offset1, elev); + if ( Link[j].type == CONDUIT ) + { + elev = Node[Link[j].node2].invertElev; + Link[j].offset2 = link_getOffsetHeight(j, Link[j].offset2, elev); + } + else Link[j].offset2 = Link[j].offset1; +} + +//============================================================================= + +double link_getOffsetHeight(int j, double offset, double elev) +// +// Input: j = link index +// offset = link elevation offset (ft) +// elev = node invert elevation (ft) +// Output: returns offset distance above node invert (ft) +// Purpose: finds offset height for one end of a link. +// +{ + if ( offset <= MISSING || Link[j].type == PUMP) return 0.0; + offset -= elev; + if ( offset >= 0.0 ) return offset; + if ( offset >= -MIN_DELTA_Z ) return 0.0; + report_writeWarningMsg(WARN03, Link[j].ID); + return 0.0; +} + +//============================================================================= + +void link_initState(int j) +// +// Input: j = link index +// Output: none +// Purpose: initializes a link's state variables at start of simulation. +// +{ + int p; + + // --- initialize hydraulic state + Link[j].oldFlow = Link[j].q0; + Link[j].newFlow = Link[j].q0; + Link[j].oldDepth = 0.0; + Link[j].newDepth = 0.0; + Link[j].oldVolume = 0.0; + Link[j].newVolume = 0.0; + Link[j].setting = 1.0; + Link[j].targetSetting = 1.0; + Link[j].timeLastSet = StartDate; + Link[j].inletControl = FALSE; + Link[j].normalFlow = FALSE; + if ( Link[j].type == CONDUIT ) conduit_initState(j, Link[j].subIndex); + if ( Link[j].type == PUMP ) pump_initState(j, Link[j].subIndex); + + // --- initialize water quality state + for (p = 0; p < Nobjects[POLLUT]; p++) + { + Link[j].oldQual[p] = 0.0; + Link[j].newQual[p] = 0.0; + Link[j].totalLoad[p] = 0.0; + } +} + +//============================================================================= + +double link_getInflow(int j) +// +// Input: j = link index +// Output: returns link flow rate (cfs) +// Purpose: finds total flow entering a link during current time step. +// +{ + if ( Link[j].setting == 0 ) return 0.0; + switch ( Link[j].type ) + { + case CONDUIT: return conduit_getInflow(j); + case PUMP: return pump_getInflow(j); + case ORIFICE: return orifice_getInflow(j); + case WEIR: return weir_getInflow(j); + case OUTLET: return outlet_getInflow(j); + default: return node_getOutflow(Link[j].node1, j); + } +} + +//============================================================================= + +void link_setOldHydState(int j) +// +// Input: j = link index +// Output: none +// Purpose: replaces link's old hydraulic state values with current ones. +// +{ + int k; + + Link[j].oldDepth = Link[j].newDepth; + Link[j].oldFlow = Link[j].newFlow; + Link[j].oldVolume = Link[j].newVolume; + + if ( Link[j].type == CONDUIT ) + { + k = Link[j].subIndex; + Conduit[k].q1Old = Conduit[k].q1; + Conduit[k].q2Old = Conduit[k].q2; + } +} + +//============================================================================= + +void link_setOldQualState(int j) +// +// Input: j = link index +// Output: none +// Purpose: replaces link's old water quality state values with current ones. +// +{ + int p; + for (p = 0; p < Nobjects[POLLUT]; p++) + { + Link[j].oldQual[p] = Link[j].newQual[p]; + Link[j].newQual[p] = 0.0; + } +} + +//============================================================================= + +void link_setTargetSetting(int j) +// +// Input: j = link index +// Output: none +// Purpose: updates a link's target setting. +// +{ + int k, n1; + if ( Link[j].type == PUMP ) + { + k = Link[j].subIndex; + n1 = Link[j].node1; + Link[j].targetSetting = Link[j].setting; + if ( Pump[k].yOff > 0.0 && + Link[j].setting > 0.0 && + Node[n1].newDepth < Pump[k].yOff ) Link[j].targetSetting = 0.0; + if ( Pump[k].yOn > 0.0 && + Link[j].setting == 0.0 && + Node[n1].newDepth > Pump[k].yOn ) Link[j].targetSetting = 1.0; + } +} + +//============================================================================= + +void link_setSetting(int j, double tstep) +// +// Input: j = link index +// tstep = time step over which setting is adjusted +// Output: none +// Purpose: updates a link's setting as a result of a control action. +// +{ + if ( Link[j].type == ORIFICE ) orifice_setSetting(j, tstep); + else if ( Link[j].type == WEIR ) weir_setSetting(j); + else Link[j].setting = Link[j].targetSetting; +} + +//============================================================================= + +int link_setFlapGate(int j, int n1, int n2, double q) +// +// Input: j = link index +// n1 = index of node on upstream end of link +// n2 = index of node on downstream end of link +// q = signed flow value (value and units don't matter) +// Output: returns TRUE if there is reverse flow through a flap gate +// associated with the link. +// Purpose: based on the sign of the flow, determines if a flap gate +// associated with the link should close or not. +// +{ + int n = -1; + + // --- check for reverse flow through link's flap gate + if ( Link[j].hasFlapGate ) + { + if ( q * (double)Link[j].direction < 0.0 ) return TRUE; + } + + // --- check for Outfall with flap gate node on inflow end of link + if ( q < 0.0 ) n = n2; + if ( q > 0.0 ) n = n1; + if ( n >= 0 && + Node[n].type == OUTFALL && + Outfall[Node[n].subIndex].hasFlapGate ) return TRUE; + return FALSE; +} + +//============================================================================= + +void link_getResults(int j, double f, float x[]) +// +// Input: j = link index +// f = time weighting factor +// Output: x = array of weighted results +// Purpose: retrieves time-weighted average of old and new results for a link. +// +{ + int p; // pollutant index + double y, // depth + q, // flow + u, // velocity + v, // volume + c; // capacity, setting or concentration + double f1 = 1.0 - f; + + y = f1*Link[j].oldDepth + f*Link[j].newDepth; + q = f1*Link[j].oldFlow + f*Link[j].newFlow; + v = f1*Link[j].oldVolume + f*Link[j].newVolume; + u = link_getVelocity(j, q, y); + c = 0.0; + if (Link[j].type == CONDUIT) + { + if (Link[j].xsect.type != DUMMY) + c = xsect_getAofY(&Link[j].xsect, y) / Link[j].xsect.aFull; + } + else c = Link[j].setting; + + // --- override time weighting for pump flow between on/off states + if (Link[j].type == PUMP && Link[j].oldFlow*Link[j].newFlow == 0.0) + { + if ( f >= f1 ) q = Link[j].newFlow; + else q = Link[j].oldFlow; + } + + y *= UCF(LENGTH); + v *= UCF(VOLUME); + q *= UCF(FLOW) * (double)Link[j].direction; + u *= UCF(LENGTH) * (double)Link[j].direction; + x[LINK_DEPTH] = (float)y; + x[LINK_FLOW] = (float)q; + x[LINK_VELOCITY] = (float)u; + x[LINK_VOLUME] = (float)v; + x[LINK_CAPACITY] = (float)c; + + if ( !IgnoreQuality ) for (p = 0; p < Nobjects[POLLUT]; p++) + { + c = f1*Link[j].oldQual[p] + f*Link[j].newQual[p]; + x[LINK_QUAL+p] = (float)c; + } +} + +//============================================================================= + +void link_setOutfallDepth(int j) +// +// Input: j = link index +// Output: none +// Purpose: sets depth at outfall node connected to link j. +// +{ + int k; // conduit index + int n; // outfall node index + double z; // invert offset height (ft) + double q; // flow rate (cfs) + double yCrit = 0.0; // critical flow depth (ft) + double yNorm = 0.0; // normal flow depth (ft) + + // --- find which end node of link is an outfall + if ( Node[Link[j].node2].type == OUTFALL ) + { + n = Link[j].node2; + z = Link[j].offset2; + } + else if ( Node[Link[j].node1].type == OUTFALL ) + { + n = Link[j].node1; + z = Link[j].offset1; + } + else return; + + // --- find both normal & critical depth for current flow + if ( Link[j].type == CONDUIT ) + { + k = Link[j].subIndex; + q = fabs(Link[j].newFlow / Conduit[k].barrels); + yNorm = link_getYnorm(j, q); + yCrit = link_getYcrit(j, q); + } + + // --- set new depth at node + node_setOutletDepth(n, yNorm, yCrit, z); +} + +//============================================================================= + +double link_getYcrit(int j, double q) +// +// Input: j = link index +// q = link flow rate (cfs) +// Output: returns critical depth (ft) +// Purpose: computes critical depth for given flow rate. +// +{ + return xsect_getYcrit(&Link[j].xsect, q); +} + +//============================================================================= + +double link_getYnorm(int j, double q) +// +// Input: j = link index +// q = link flow rate (cfs) +// Output: returns normal depth (ft) +// Purpose: computes normal depth for given flow rate. +// +{ + int k; + double s, a, y; + + if ( Link[j].type != CONDUIT ) return 0.0; + if ( Link[j].xsect.type == DUMMY ) return 0.0; + q = fabs(q); + k = Link[j].subIndex; + if ( q > Conduit[k].qMax ) q = Conduit[k].qMax; + if ( q <= 0.0 ) return 0.0; + s = q / Conduit[k].beta; + a = xsect_getAofS(&Link[j].xsect, s); + y = xsect_getYofA(&Link[j].xsect, a); + return y; +} + +//============================================================================= + +double link_getLength(int j) +// +// Input: j = link index +// Output: returns length (ft) +// Purpose: finds true length of a link. +// +{ + if ( Link[j].type == CONDUIT ) return conduit_getLength(j); + return 0.0; +} + +//============================================================================= + +double link_getVelocity(int j, double flow, double depth) +// +// Input: j = link index +// flow = link flow rate (cfs) +// depth = link flow depth (ft) +// Output: returns flow velocity (fps) +// Purpose: finds flow velocity given flow and depth. +// +{ + double area; + double veloc = 0.0; + int k; + + if ( depth <= 0.01 ) return 0.0; + if ( Link[j].type == CONDUIT ) + { + k = Link[j].subIndex; + flow /= Conduit[k].barrels; + area = xsect_getAofY(&Link[j].xsect, depth); + if (area > FUDGE ) veloc = flow / area; + } + return veloc; +} + +//============================================================================= + +double link_getFroude(int j, double v, double y) +// +// Input: j = link index +// v = flow velocity (fps) +// y = flow depth (ft) +// Output: returns Froude Number +// Purpose: computes Froude Number for given velocity and flow depth +// +{ + TXsect* xsect = &Link[j].xsect; + + // --- return 0 if link is not a conduit + if ( Link[j].type != CONDUIT ) return 0.0; + + // --- return 0 if link empty or closed conduit is full + if ( y <= FUDGE ) return 0.0; + if ( !xsect_isOpen(xsect->type) && + xsect->yFull - y <= FUDGE ) return 0.0; + + // --- compute hydraulic depth + y = xsect_getAofY(xsect, y) / xsect_getWofY(xsect, y); + + // --- compute Froude No. + return fabs(v) / sqrt(GRAVITY * y); +} + +//============================================================================= + +double link_getPower(int j) +// +// Input: j = link index +// Output: returns power consumed by link in kwatts +// Purpose: computes power consumed by head loss (or head gain) of +// water flowing through a link +// +{ + int n1 = Link[j].node1; + int n2 = Link[j].node2; + double dh = (Node[n1].invertElev + Node[n1].newDepth) - + (Node[n2].invertElev + Node[n2].newDepth); + double q = fabs(Link[j].newFlow); + return fabs(dh) * q / 8.814 * KWperHP; +} + +//============================================================================= + +double link_getLossRate(int j, double q) +// +// Input: j = link index +// q = flow rate (ft3/sec) +// tstep = time step (sec) +// Output: returns uniform loss rate in link (ft3/sec) +// Purpose: computes rate at which flow volume is lost in a link due to +// evaporation and seepage. +// +{ + if ( Link[j].type == CONDUIT ) return conduit_getLossRate(j, q); + else return 0.0; +} + +//============================================================================= + +char link_getFullState(double a1, double a2, double aFull) +// +// Input: a1 = upstream link area (ft2) +// a2 = downstream link area (ft2) +// aFull = area of full conduit +// Output: returns fullness state of a link +// Purpose: determines if a link is upstream, downstream or completely full. +// +{ + if ( a1 >= aFull ) + { + if ( a2 >= aFull ) return ALL_FULL; + else return UP_FULL; + } + if ( a2 >= aFull ) return DN_FULL; + return 0; +} + +//============================================================================= +// C O N D U I T M E T H O D S +//============================================================================= + +int conduit_readParams(int j, int k, char* tok[], int ntoks) +// +// Input: j = link index +// k = conduit index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads conduit parameters from a tokenzed line of input. +// +{ + int n1, n2; + double x[6]; + char* id; + + // --- check for valid ID and end node IDs + if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); + id = project_findID(LINK, tok[0]); // link ID + if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); + n1 = project_findObject(NODE, tok[1]); // upstrm. node + if ( n1 < 0 ) return error_setInpError(ERR_NAME, tok[1]); + n2 = project_findObject(NODE, tok[2]); // dwnstrm. node + if ( n2 < 0 ) return error_setInpError(ERR_NAME, tok[2]); + + // --- parse length & Mannings N + if ( !getDouble(tok[3], &x[0]) ) + return error_setInpError(ERR_NUMBER, tok[3]); + if ( !getDouble(tok[4], &x[1]) ) + return error_setInpError(ERR_NUMBER, tok[4]); + + // --- parse offsets + if ( LinkOffsets == ELEV_OFFSET && *tok[5] == '*' ) x[2] = MISSING; + else if ( !getDouble(tok[5], &x[2]) ) + return error_setInpError(ERR_NUMBER, tok[5]); + if ( LinkOffsets == ELEV_OFFSET && *tok[6] == '*' ) x[3] = MISSING; + else if ( !getDouble(tok[6], &x[3]) ) + return error_setInpError(ERR_NUMBER, tok[6]); + + // --- parse optional parameters + x[4] = 0.0; // init. flow + if ( ntoks >= 8 ) + { + if ( !getDouble(tok[7], &x[4]) ) + return error_setInpError(ERR_NUMBER, tok[7]); + } + x[5] = 0.0; + if ( ntoks >= 9 ) + { + if ( !getDouble(tok[8], &x[5]) ) + return error_setInpError(ERR_NUMBER, tok[8]); + } + + // --- add parameters to data base + Link[j].ID = id; + link_setParams(j, CONDUIT, n1, n2, k, x); + return 0; +} + +//============================================================================= + +void conduit_validate(int j, int k) +// +// Input: j = link index +// k = conduit index +// Output: none +// Purpose: validates a conduit's properties. +// +{ + double aa; + double lengthFactor, roughness, slope; + + // --- a storage node cannot have a dummy outflow link + if ( Link[j].xsect.type == DUMMY && RouteModel == DW ) + { + if ( Node[Link[j].node1].type == STORAGE ) + { + report_writeErrorMsg(ERR_DUMMY_LINK, Node[Link[j].node1].ID); + return; + } + } + + // --- if custom xsection, then set its parameters + if ( Link[j].xsect.type == CUSTOM ) + xsect_setCustomXsectParams(&Link[j].xsect); + + // --- if irreg. xsection, assign transect roughness to conduit + if ( Link[j].xsect.type == IRREGULAR ) + { + xsect_setIrregXsectParams(&Link[j].xsect); + Conduit[k].roughness = Transect[Link[j].xsect.transect].roughness; + } + + // --- if street xsection, then set its parameters + if (Link[j].xsect.type == STREET_XSECT) + { + xsect_setStreetXsectParams(&Link[j].xsect); + Conduit[k].roughness = Street[Link[j].xsect.transect].roughness; + } + + // --- if force main xsection, adjust units on D-W roughness height + if ( Link[j].xsect.type == FORCE_MAIN ) + { + if ( ForceMainEqn == D_W ) Link[j].xsect.rBot /= UCF(RAINDEPTH); + if ( Link[j].xsect.rBot <= 0.0 ) + report_writeErrorMsg(ERR_XSECT, Link[j].ID); + } + + // --- check for valid length & roughness + if ( Conduit[k].length <= 0.0 ) + report_writeErrorMsg(ERR_LENGTH, Link[j].ID); + if ( Conduit[k].roughness <= 0.0 ) + report_writeErrorMsg(ERR_ROUGHNESS, Link[j].ID); + if ( Conduit[k].barrels <= 0 ) + report_writeErrorMsg(ERR_BARRELS, Link[j].ID); + + // --- check for valid xsection + if ( Link[j].xsect.type != DUMMY ) + { + if ( Link[j].xsect.type < 0 ) + report_writeErrorMsg(ERR_NO_XSECT, Link[j].ID); + else if ( Link[j].xsect.aFull <= 0.0 ) + report_writeErrorMsg(ERR_XSECT, Link[j].ID); + } + if ( ErrorCode ) return; + + // --- check for negative offsets + if ( Link[j].offset1 < 0.0 ) + { + report_writeWarningMsg(WARN03, Link[j].ID); + Link[j].offset1 = 0.0; + } + if ( Link[j].offset2 < 0.0 ) + { + report_writeWarningMsg(WARN03, Link[j].ID); + Link[j].offset2 = 0.0; + } + + // --- adjust conduit offsets for partly filled circular xsection + if ( Link[j].xsect.type == FILLED_CIRCULAR ) + { + Link[j].offset1 += Link[j].xsect.yBot; + Link[j].offset2 += Link[j].xsect.yBot; + } + + // --- compute conduit slope + slope = conduit_getSlope(j); + Conduit[k].slope = slope; + + // --- reverse orientation of conduit if using dynamic wave routing + // and slope is negative + if ( RouteModel == DW && + slope < 0.0 && + Link[j].xsect.type != DUMMY ) + { + conduit_reverse(j, k); + } + + // --- get equivalent Manning roughness for Force Mains + // for use when pipe is partly full + roughness = Conduit[k].roughness; + if ( RouteModel == DW && Link[j].xsect.type == FORCE_MAIN ) + { + roughness = forcemain_getEquivN(j, k); + } + + // --- adjust roughness for meandering natural channels + if ( Link[j].xsect.type == IRREGULAR ) + { + lengthFactor = Transect[Link[j].xsect.transect].lengthFactor; + roughness *= sqrt(lengthFactor); + } + + // --- lengthen conduit if lengthening option is in effect + lengthFactor = 1.0; + if ( RouteModel == DW && + LengtheningStep > 0.0 && + Link[j].xsect.type != DUMMY ) + { + lengthFactor = conduit_getLengthFactor(j, k, roughness); + } + + if ( lengthFactor != 1.0 ) + { + Conduit[k].modLength = lengthFactor * conduit_getLength(j); + slope /= lengthFactor; + roughness = roughness / sqrt(lengthFactor); + } + + // --- compute roughness factor used when computing friction + // slope term in Dynamic Wave flow routing + + // --- special case for non-Manning Force Mains + // (roughness factor for full flow is saved in xsect.sBot) + if ( RouteModel == DW && Link[j].xsect.type == FORCE_MAIN ) + { + Link[j].xsect.sBot = + forcemain_getRoughFactor(j, lengthFactor); + } + Conduit[k].roughFactor = GRAVITY * SQR(roughness/PHI); + + // --- compute full flow through cross section + if ( Link[j].xsect.type == DUMMY ) Conduit[k].beta = 0.0; + else Conduit[k].beta = PHI * sqrt(fabs(slope)) / roughness; + Link[j].qFull = Link[j].xsect.sFull * Conduit[k].beta; + Conduit[k].qMax = Link[j].xsect.sMax * Conduit[k].beta; + + // --- see if flow is supercritical most of time + // by comparing normal & critical velocities. + // (factor of 0.3 is for circular pipe 95% full) + // NOTE: this factor was used in the past for a modified version of + // Kinematic Wave routing but is now deprecated. + aa = Conduit[k].beta / sqrt(32.2) * + pow(Link[j].xsect.yFull, 0.1666667) * 0.3; + if ( aa >= 1.0 ) Conduit[k].superCritical = TRUE; + else Conduit[k].superCritical = FALSE; + + // --- set value of hasLosses flag + if ( Link[j].cLossInlet == 0.0 && + Link[j].cLossOutlet == 0.0 && + Link[j].cLossAvg == 0.0 + ) Conduit[k].hasLosses = FALSE; + else Conduit[k].hasLosses = TRUE; +} + +//============================================================================= + +void conduit_reverse(int j, int k) +// +// Input: j = link index +// k = conduit index +// Output: none +// Purpose: reverses direction of a conduit +// +{ + int i; + double z; + double cLoss; + + // --- reverse end nodes + i = Link[j].node1; + Link[j].node1 = Link[j].node2; + Link[j].node2 = i; + + // --- reverse node offsets + z = Link[j].offset1; + Link[j].offset1 = Link[j].offset2; + Link[j].offset2 = z; + + // --- reverse loss coeffs. + cLoss = Link[j].cLossInlet; + Link[j].cLossInlet = Link[j].cLossOutlet; + Link[j].cLossOutlet = cLoss; + + // --- reverse direction & slope + Conduit[k].slope = -Conduit[k].slope; + Link[j].direction *= (signed char)-1; + + // --- reverse initial flow value + Link[j].q0 = -Link[j].q0; +} + +//============================================================================= + +double conduit_getLength(int j) +// +// Input: j = link index +// Output: returns conduit's length (ft) +// Purpose: finds true length of a conduit. +// +// Note: for irregular natural channels, user inputs length of main +// channel (for FEMA purposes) but program should use length +// associated with entire flood plain. Transect.lengthFactor +// is the ratio of these two lengths. +// +{ + int k = Link[j].subIndex; + int t; + if ( Link[j].xsect.type != IRREGULAR ) return Conduit[k].length; + t = Link[j].xsect.transect; + if ( t < 0 || t >= Nobjects[TRANSECT] ) return Conduit[k].length; + return Conduit[k].length / Transect[t].lengthFactor; +} + +//============================================================================= + +double conduit_getLengthFactor(int j, int k, double roughness) +// +// Input: j = link index +// k = conduit index +// roughness = conduit Manning's n +// Output: returns factor by which a conduit should be lengthened +// Purpose: computes amount of conduit lengthing to improve numerical stability. +// +// The following form of the Courant criterion is used: +// L = t * v * (1 + Fr) / Fr +// where L = conduit length, t = time step, v = velocity, & Fr = Froude No. +// After substituting Fr = v / sqrt(gy), where y = flow depth, we get: +// L = t * ( sqrt(gy) + v ) +// +{ + double ratio; + double yFull; + double vFull; + double tStep; + + // --- evaluate flow depth and velocity at full normal flow condition + yFull = Link[j].xsect.yFull; + if ( xsect_isOpen(Link[j].xsect.type) ) + { + yFull = Link[j].xsect.aFull / xsect_getWofY(&Link[j].xsect, yFull); + } + vFull = PHI / roughness * Link[j].xsect.sFull * + sqrt(fabs(Conduit[k].slope)) / Link[j].xsect.aFull; + + // --- determine ratio of Courant length to actual length + if ( LengtheningStep == 0.0 ) tStep = RouteStep; + else tStep = MIN(RouteStep, LengtheningStep); + ratio = (sqrt(GRAVITY*yFull) + vFull) * tStep / conduit_getLength(j); + + // --- return max. of 1.0 and ratio + if ( ratio > 1.0 ) return ratio; + else return 1.0; +} + +//============================================================================= + +double conduit_getSlope(int j) +// +// Input: j = link index +// Output: returns conduit slope +// Purpose: computes conduit slope. +// +{ + double elev1, elev2, delta, slope; + double length = conduit_getLength(j); + + // --- check that elevation drop > minimum allowable drop + elev1 = Link[j].offset1 + Node[Link[j].node1].invertElev; + elev2 = Link[j].offset2 + Node[Link[j].node2].invertElev; + delta = fabs(elev1 - elev2); + if ( delta < MIN_DELTA_Z ) + { + report_writeWarningMsg(WARN04, Link[j].ID); + delta = MIN_DELTA_Z; + } + + // --- elevation drop cannot exceed conduit length + if ( delta >= length ) + { + report_writeWarningMsg(WARN08, Link[j].ID); + slope = delta / length; + } + + // --- slope = elev. drop / horizontal distance + else slope = delta / sqrt(SQR(length) - SQR(delta)); + + // -- check that slope exceeds minimum allowable slope + if ( MinSlope > 0.0 && slope < MinSlope ) + { + report_writeWarningMsg(WARN05, Link[j].ID); + slope = MinSlope; + // keep min. slope positive for SF or KW routing + if (RouteModel == SF || RouteModel == KW) return slope; + } + + // --- change sign for adverse slope + if ( elev1 < elev2 ) slope = -slope; + return slope; +} + +//============================================================================= + +void conduit_initState(int j, int k) +// +// Input: j = link index +// k = conduit index +// Output: none +// Purpose: sets initial conduit depth to normal depth of initial flow +// +{ + Link[j].newDepth = link_getYnorm(j, Link[j].q0 / Conduit[k].barrels); + Link[j].oldDepth = Link[j].newDepth; + Conduit[k].evapLossRate = 0.0; + Conduit[k].seepLossRate = 0.0; +} + +//============================================================================= + +double conduit_getInflow(int j) +// +// Input: j = link index +// Output: returns flow in link (cfs) +// Purpose: finds inflow to conduit from upstream node. +// +{ + double qIn = node_getOutflow(Link[j].node1, j); + if ( Link[j].qLimit > 0.0 ) qIn = MIN(qIn, Link[j].qLimit); + return qIn; +} + +//============================================================================= + +double conduit_getLossRate(int j, double q) +// +// Input: j = link index +// 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). +// +{ + TXsect *xsect; + double depth = 0.5 * (Link[j].oldDepth + Link[j].newDepth); + double length; + double topWidth; + double evapLossRate = 0.0, + seepLossRate = 0.0, + totalLossRate = 0.0; + + if ( depth > FUDGE ) + { + xsect = &Link[j].xsect; + length = conduit_getLength(j); + + // --- find evaporation rate for open conduits + if ( xsect_isOpen(xsect->type) && Evap.rate > 0.0 ) + { + topWidth = xsect_getWofY(xsect, depth); + evapLossRate = topWidth * length * Evap.rate; + } + + // --- compute seepage loss rate + if ( Link[j].seepRate > 0.0 ) + { + // limit depth to depth at max width + if ( depth >= xsect->ywMax ) depth = xsect->ywMax; + + // compute seepage loss rate across length of conduit + seepLossRate = Link[j].seepRate * xsect_getWofY(xsect, depth) * + length; + seepLossRate *= Adjust.hydconFactor; + } + + // --- compute total loss rate + totalLossRate = evapLossRate + seepLossRate; + + // --- total loss rate cannot exceed flow rate + q = ABS(q); + if (totalLossRate > q) + { + evapLossRate = evapLossRate * q / totalLossRate; + seepLossRate = seepLossRate * q / totalLossRate; + totalLossRate = q; + } + } + + Conduit[Link[j].subIndex].evapLossRate = evapLossRate; + Conduit[Link[j].subIndex].seepLossRate = seepLossRate; + return totalLossRate; +} + + +//============================================================================= +// P U M P M E T H O D S +//============================================================================= + +int pump_readParams(int j, int k, char* tok[], int ntoks) +// +// Input: j = link index +// k = pump index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads pump parameters from a tokenized line of input. +// +{ + int m; + int n1, n2; + double x[4]; + char* id; + + // --- check for valid ID and end node IDs + if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); + id = project_findID(LINK, tok[0]); + if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); + n1 = project_findObject(NODE, tok[1]); + if ( n1 < 0 ) return error_setInpError(ERR_NAME, tok[1]); + n2 = project_findObject(NODE, tok[2]); + if ( n2 < 0 ) return error_setInpError(ERR_NAME, tok[2]); + + // --- parse curve name + x[0] = -1.; + if ( ntoks >= 4 ) + { + if ( !strcomp(tok[3],"*") ) + { + m = project_findObject(CURVE, tok[3]); + if ( m < 0 ) return error_setInpError(ERR_NAME, tok[3]); + x[0] = m; + } + } + + // --- parse init. status if present + x[1] = 1.0; + if ( ntoks >= 5 ) + { + m = findmatch(tok[4], OffOnWords); + if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[4]); + x[1] = m; + } + + // --- parse startup/shutoff depths if present + x[2] = 0.0; + if ( ntoks >= 6 ) + { + if ( !getDouble(tok[5], &x[2]) || x[2] < 0.0) + return error_setInpError(ERR_NUMBER, tok[5]); + } + x[3] = 0.0; + if ( ntoks >= 7 ) + { + if ( !getDouble(tok[6], &x[3]) || x[3] < 0.0 ) + return error_setInpError(ERR_NUMBER, tok[6]); + } + + // --- add parameters to pump object + Link[j].ID = id; + link_setParams(j, PUMP, n1, n2, k, x); + return 0; +} + +//============================================================================= + +void pump_validate(int j, int k) +// +// Input: j = link index +// k = pump index +// Output: none +// Purpose: validates a pump's properties +// +{ + int m, n1; + double x, y; + + Link[j].xsect.yFull = 0.0; + + // --- check for valid curve type + m = Pump[k].pumpCurve; + if ( m < 0 ) + { + Pump[k].type = IDEAL_PUMP; + } + else + { + if ( Curve[m].curveType < PUMP1_CURVE || + Curve[m].curveType > PUMP5_CURVE ) + report_writeErrorMsg(ERR_NO_CURVE, Link[j].ID); + + // --- store pump curve type with pump's parameters + else + { + Pump[k].type = Curve[m].curveType - PUMP1_CURVE; + if ( table_getFirstEntry(&Curve[m], &x, &y) ) + { + Link[j].qFull = y; + Pump[k].xMin = x; + Pump[k].xMax = x; + while ( table_getNextEntry(&Curve[m], &x, &y) ) + { + Link[j].qFull = MAX(y, Link[j].qFull); + Pump[k].xMax = x; + } + } + Link[j].qFull /= UCF(FLOW); + } + } + + // --- check that shutoff depth < startup depth + if ( Pump[k].yOn > 0.0 && Pump[k].yOn <= Pump[k].yOff ) + report_writeErrorMsg(ERR_PUMP_LIMITS, Link[j].ID); + + // --- assign wet well volume to inlet node of Type 1 pump + if ( Pump[k].type == TYPE1_PUMP ) + { + n1 = Link[j].node1; + if ( Node[n1].type != STORAGE ) + Node[n1].fullVolume = MAX(Node[n1].fullVolume, + Pump[k].xMax / UCF(VOLUME)); + } + +} + +//============================================================================= + +void pump_initState(int j, int k) +// +// Input: j = link index +// k = pump index +// Output: none +// Purpose: initializes pump conditions at start of a simulation +// +{ + Link[j].setting = Pump[k].initSetting; + Link[j].targetSetting = Pump[k].initSetting; +} + +//============================================================================= + +double pump_getInflow(int j) +// +// Input: j = link index +// Output: returns pump flow (cfs) +// Purpose: finds flow produced by a pump. +// +{ + int k, m; + int n1, n2; + double vol, depth, head; + double qIn, qIn1, dh = 0.001; + double s = 1.0; // speed setting + + k = Link[j].subIndex; + m = Pump[k].pumpCurve; + n1 = Link[j].node1; + n2 = Link[j].node2; + + // --- no flow if setting is closed + Link[j].flowClass = NO; + Link[j].setting = Link[j].targetSetting; + if ( Link[j].setting == 0.0 ) return 0.0; + + // --- pump flow = node inflow for IDEAL_PUMP + if ( Pump[k].type == IDEAL_PUMP ) + qIn = Node[n1].inflow + Node[n1].overflow; + + // --- pumping rate depends on pump curve type + else switch(Curve[m].curveType) + { + case PUMP1_CURVE: + vol = Node[n1].newVolume * UCF(VOLUME); + qIn = table_intervalLookup(&Curve[m], vol) / UCF(FLOW); + + // --- check if off of pump curve + if ( vol < Pump[k].xMin || vol > Pump[k].xMax ) + Link[j].flowClass = YES; + break; + + case PUMP2_CURVE: + depth = Node[n1].newDepth * UCF(LENGTH); + qIn = table_intervalLookup(&Curve[m], depth) / UCF(FLOW); + + // --- check if off of pump curve + if ( depth < Pump[k].xMin || depth > Pump[k].xMax ) + Link[j].flowClass = YES; + break; + + case PUMP3_CURVE: + case PUMP5_CURVE: + if (Curve[m].curveType == PUMP5_CURVE) s = Link[j].setting; + head = ((Node[n2].newDepth + Node[n2].invertElev) - + (Node[n1].newDepth + Node[n1].invertElev)) / s / s; + head = MAX(head, 0.0) * UCF(LENGTH); + qIn = table_lookup(&Curve[m], head) / UCF(FLOW); + + // --- compute dQ/dh (slope of pump curve) and + // reverse sign since flow decreases with increasing head + Link[j].dqdh = -table_getSlope(&Curve[m], head) * + UCF(LENGTH) / UCF(FLOW) / s; + + // --- check if off of pump curve + if (head < Pump[k].xMin || head > Pump[k].xMax) + Link[j].flowClass = YES; + break; + + case PUMP4_CURVE: + depth = Node[n1].newDepth; + qIn = table_lookup(&Curve[m], depth*UCF(LENGTH)) / UCF(FLOW); + + // --- compute dQ/dh (slope of pump curve) + qIn1 = table_lookup(&Curve[m], (depth+dh)*UCF(LENGTH)) / UCF(FLOW); + Link[j].dqdh = (qIn1 - qIn) / dh; + + // --- check if off of pump curve + depth *= UCF(LENGTH); + if ( depth < Pump[k].xMin ) Link[j].flowClass = DN_DRY; + if ( depth > Pump[k].xMax ) Link[j].flowClass = UP_DRY; + break; + + default: qIn = 0.0; + } + + // --- do not allow reverse flow through pump + if ( qIn < 0.0 ) qIn = 0.0; + return qIn * Link[j].setting; +} + + +//============================================================================= +// O R I F I C E M E T H O D S +//============================================================================= + +int orifice_readParams(int j, int k, char* tok[], int ntoks) +// +// Input: j = link index +// k = orifice index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads orifice parameters from a tokenized line of input. +// +{ + int m; + int n1, n2; + double x[5]; + char* id; + + // --- check for valid ID and end node IDs + if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); + id = project_findID(LINK, tok[0]); + if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); + n1 = project_findObject(NODE, tok[1]); + if ( n1 < 0 ) return error_setInpError(ERR_NAME, tok[1]); + n2 = project_findObject(NODE, tok[2]); + if ( n2 < 0 ) return error_setInpError(ERR_NAME, tok[2]); + + // --- parse orifice parameters + m = findmatch(tok[3], OrificeTypeWords); + if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[3]); + x[0] = m; // type + if ( LinkOffsets == ELEV_OFFSET && *tok[4] == '*' ) x[1] = MISSING; + else if ( ! getDouble(tok[4], &x[1]) ) // crest height + return error_setInpError(ERR_NUMBER, tok[4]); + if ( ! getDouble(tok[5], &x[2]) || x[2] < 0.0 ) // cDisch + return error_setInpError(ERR_NUMBER, tok[5]); + x[3] = 0.0; + if ( ntoks >= 7 ) + { + m = findmatch(tok[6], NoYesWords); + if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[6]); + x[3] = m; // flap gate + } + x[4] = 0.0; + if ( ntoks >= 8 ) + { + if ( ! getDouble(tok[7], &x[4]) || x[4] < 0.0 ) // orate + return error_setInpError(ERR_NUMBER, tok[7]); + } + + // --- add parameters to orifice object + Link[j].ID = id; + link_setParams(j, ORIFICE, n1, n2, k, x); + return 0; +} + +//============================================================================= + +void orifice_validate(int j, int k) +// +// Input: j = link index +// k = orifice index +// Output: none +// Purpose: validates an orifice's properties +// +{ + int err = 0; + + // --- check for valid xsection + if ( Link[j].xsect.type != RECT_CLOSED + && Link[j].xsect.type != CIRCULAR ) err = ERR_REGULATOR_SHAPE; + if ( err > 0 ) + { + report_writeErrorMsg(err, Link[j].ID); + return; + } + + // --- check for negative offset + if ( Link[j].offset1 < 0.0 ) Link[j].offset1 = 0.0; + + // --- compute partial flow adjustment + orifice_setSetting(j, 0.0); + + // --- compute an equivalent length + Orifice[k].length = 2.0 * RouteStep * sqrt(GRAVITY * Link[j].xsect.yFull); + Orifice[k].length = MAX(200.0, Orifice[k].length); + Orifice[k].surfArea = 0.0; +} + +//============================================================================= + +void orifice_setSetting(int j, double tstep) +// +// Input: j = link index +// tstep = time step over which setting is adjusted (sec) +// Output: none +// Purpose: updates an orifice's setting as a result of a control action. +// +{ + int k = Link[j].subIndex; + double delta, step; + double h, f; + + // --- case where adjustment rate is instantaneous + if ( Orifice[k].orate == 0.0 || tstep == 0.0) + Link[j].setting = Link[j].targetSetting; + + // --- case where orifice setting depends on time step + else + { + delta = Link[j].targetSetting - Link[j].setting; + step = tstep / Orifice[k].orate; + if ( step + 0.001 >= fabs(delta) ) + Link[j].setting = Link[j].targetSetting; + else Link[j].setting += SGN(delta) * step; + } + + // --- find effective orifice discharge coeff. + h = Link[j].setting * Link[j].xsect.yFull; + f = xsect_getAofY(&Link[j].xsect, h) * sqrt(2.0 * GRAVITY); + Orifice[k].cOrif = Orifice[k].cDisch * f; + + // --- find equiv. discharge coeff. for when weir flow occurs + Orifice[k].cWeir = orifice_getWeirCoeff(j, k, h) * f; +} + +//============================================================================= + +double orifice_getWeirCoeff(int j, int k, double h) +// +// Input: j = link index +// k = orifice index +// h = height of orifice opening (ft) +// Output: returns a discharge coefficient (ft^1/2) +// Purpose: computes the discharge coefficient for an orifice +// at the critical depth where weir flow begins. +// +{ + double w, aOverL; + + // --- this is for bottom orifices + if ( Orifice[k].type == BOTTOM_ORIFICE ) + { + // --- find critical height above opening where orifice flow + // turns into weir flow. It equals (Co/Cw)*(Area/Length) + // where Co is the orifice coeff., Cw is the weir coeff/sqrt(2g), + // Area is the area of the opening, and Length = circumference + // of the opening. For a basic sharp crested weir, Cw = 0.414. + if (Link[j].xsect.type == CIRCULAR) aOverL = h / 4.0; + else + { + w = Link[j].xsect.wMax; + aOverL = (h*w) / (2.0*(h+w)); + } + h = Orifice[k].cDisch / 0.414 * aOverL; + Orifice[k].hCrit = h; + } + + // --- this is for side orifices + else + { + // --- critical height is simply height of opening + Orifice[k].hCrit = h; + + // --- head on orifice is distance to center line + h = h / 2.0; + } + + // --- return a coefficient for the critical depth + return Orifice[k].cDisch * sqrt(h); +} + +//============================================================================= + +double orifice_getInflow(int j) +// +// Input: j = link index +// Output: returns orifice flow rate (cfs) +// Purpose: finds the flow through an orifice. +// +{ + int k, n1, n2; + double head, h1, h2, y1, dir; + double f; + double hcrest = 0.0; + double hcrown = 0.0; + double hmidpt; + double q, ratio; + + // --- get indexes of end nodes and link's orifice + n1 = Link[j].node1; + n2 = Link[j].node2; + k = Link[j].subIndex; + + // --- find heads at upstream & downstream nodes + if ( RouteModel == DW ) + { + h1 = Node[n1].newDepth + Node[n1].invertElev; + h2 = Node[n2].newDepth + Node[n2].invertElev; + } + else + { + h1 = Node[n1].newDepth + Node[n1].invertElev; + h2 = Node[n1].invertElev; + } + dir = (h1 >= h2) ? +1.0 : -1.0; + + // --- exchange h1 and h2 for reverse flow + y1 = Node[n1].newDepth; + if ( dir < 0.0 ) + { + head = h1; + h1 = h2; + h2 = head; + y1 = Node[n2].newDepth; + } + + // --- orifice is a bottom orifice (oriented in horizontal plane) + if ( Orifice[k].type == BOTTOM_ORIFICE ) + { + // --- compute crest elevation + hcrest = Node[n1].invertElev + Link[j].offset1; + + // --- compute head on orifice + if (h1 < hcrest) head = 0.0; + else if (h2 > hcrest) head = h1 - h2; + else head = h1 - hcrest; + + // --- find fraction of critical height for which weir flow occurs + f = head / Orifice[k].hCrit; + f = MIN(f, 1.0); + } + + // --- otherwise orifice is a side orifice (oriented in vertical plane) + else + { + // --- compute elevations of orifice crest and crown + hcrest = Node[n1].invertElev + Link[j].offset1; + hcrown = hcrest + Link[j].xsect.yFull * Link[j].setting; + hmidpt = (hcrest + hcrown) / 2.0; + + // --- compute degree of inlet submergence + if ( h1 < hcrown && hcrown > hcrest ) + f = (h1 - hcrest) / (hcrown - hcrest); + else f = 1.0; + + // --- compute head on orifice + if ( f < 1.0 ) head = h1 - hcrest; + else if ( h2 < hmidpt ) head = h1 - hmidpt; + else head = h1 - h2; + } + + // --- return if head is negligible or flap gate closed + if ( head <= FUDGE || y1 <= FUDGE || + link_setFlapGate(j, n1, n2, dir) ) + { + Link[j].newDepth = 0.0; + Link[j].flowClass = DRY; + Orifice[k].surfArea = FUDGE * Orifice[k].length; + Link[j].dqdh = 0.0; + return 0.0; + } + + // --- determine flow class + Link[j].flowClass = SUBCRITICAL; + if ( hcrest > h2 ) + { + if ( dir == 1.0 ) Link[j].flowClass = DN_CRITICAL; + else Link[j].flowClass = UP_CRITICAL; + } + + // --- compute flow depth and surface area + y1 = Link[j].xsect.yFull * Link[j].setting; + if ( Orifice[k].type == SIDE_ORIFICE ) + { + Link[j].newDepth = y1 * f; + Orifice[k].surfArea = + xsect_getWofY(&Link[j].xsect, Link[j].newDepth) * + Orifice[k].length; + } + else + { + Link[j].newDepth = y1; + Orifice[k].surfArea = xsect_getAofY(&Link[j].xsect, y1); + } + + // --- find flow through the orifice + q = dir * orifice_getFlow(j, k, head, f, Link[j].hasFlapGate); + + // --- apply Villemonte eqn. to correct for submergence + if ( f < 1.0 && h2 > hcrest ) + { + ratio = (h2 - hcrest) / (h1 - hcrest); + q *= pow( (1.0 - pow(ratio, 1.5)), 0.385); + } + return q; +} + +//============================================================================= + +double orifice_getFlow(int j, int k, double head, double f, int hasFlapGate) +// +// Input: j = link index +// k = orifice index +// head = head across orifice +// f = fraction of critical depth filled +// hasFlapGate = flap gate indicator +// Output: returns flow through an orifice +// Purpose: computes flow through an orifice as a function of head. +// +{ + double area, q; + double veloc, hLoss; + + // --- case where orifice is closed + if ( head == 0.0 || f <= 0.0 ) + { + Link[j].dqdh = 0.0; + return 0.0; + } + + // --- case where inlet depth is below critical depth; + // orifice behaves as a weir + else if ( f < 1.0 ) + { + q = Orifice[k].cWeir * pow(f, 1.5); + Link[j].dqdh = 1.5 * q / (f * Orifice[k].hCrit); + } + + // --- case where normal orifice flow applies + else + { + q = Orifice[k].cOrif * sqrt(head); + Link[j].dqdh = q / (2.0 * head); + } + + // --- apply ARMCO adjustment for headloss from flap gate + if ( hasFlapGate ) + { + // --- compute velocity for current orifice flow + area = xsect_getAofY(&Link[j].xsect, + Link[j].setting * Link[j].xsect.yFull); + veloc = q / area; + + // --- compute head loss from gate + hLoss = (4.0 / GRAVITY) * veloc * veloc * + exp(-1.15 * veloc / sqrt(head) ); + + // --- update head (for orifice flow) + // or critical depth fraction (for weir flow) + if ( f < 1.0 ) + { + f = f - hLoss/Orifice[k].hCrit; + if ( f < 0.0 ) f = 0.0; + } + else + { + head = head - hLoss; + if ( head < 0.0 ) head = 0.0; + } + + // --- make recursive call to this function, with hasFlapGate + // set to false, to find flow values at adjusted head value + q = orifice_getFlow(j, k, head, f, FALSE); + } + return q; +} + +//============================================================================= +// W E I R M E T H O D S +//============================================================================= + +int weir_readParams(int j, int k, char* tok[], int ntoks) +// +// Input: j = link index +// k = weir index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads weir parameters from a tokenized line of input. +// +{ + int m; + int n1, n2; + double x[10]; + char* id; + + // --- check for valid ID and end node IDs + if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); + id = project_findID(LINK, tok[0]); + if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); + n1 = project_findObject(NODE, tok[1]); + if ( n1 < 0 ) return error_setInpError(ERR_NAME, tok[1]); + n2 = project_findObject(NODE, tok[2]); + if ( n2 < 0 ) return error_setInpError(ERR_NAME, tok[2]); + + // --- parse weir parameters + m = findmatch(tok[3], WeirTypeWords); + if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[3]); + x[0] = m; // type + if ( LinkOffsets == ELEV_OFFSET && *tok[4] == '*' ) x[1] = MISSING; + else if ( ! getDouble(tok[4], &x[1]) ) // height + return error_setInpError(ERR_NUMBER, tok[4]); + if ( ! getDouble(tok[5], &x[2]) || x[2] < 0.0 ) // cDisch1 + return error_setInpError(ERR_NUMBER, tok[5]); + x[3] = 0.0; + x[4] = 0.0; + x[5] = 0.0; + x[6] = 1.0; + x[7] = 0.0; + x[8] = 0.0; + x[9] = -1.0; + if ( ntoks >= 7 && *tok[6] != '*' ) + { + m = findmatch(tok[6], NoYesWords); + if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[6]); + x[3] = m; // flap gate + } + if ( ntoks >= 8 && *tok[7] != '*' ) + { + if ( ! getDouble(tok[7], &x[4]) || x[4] < 0.0 ) // endCon + return error_setInpError(ERR_NUMBER, tok[7]); + } + if ( ntoks >= 9 && *tok[8] != '*' ) + { + if ( ! getDouble(tok[8], &x[5]) || x[5] < 0.0 ) // cDisch2 + return error_setInpError(ERR_NUMBER, tok[8]); + } + + if ( ntoks >= 10 && *tok[9] != '*' ) + { + m = findmatch(tok[9], NoYesWords); + if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[9]); + x[6] = m; // canSurcharge + } + + if ( (m = (int)x[0]) == ROADWAY_WEIR ) + { + if ( ntoks >= 11 ) // road width + { + if ( ! getDouble(tok[10], &x[7]) || x[7] < 0.0 ) + return error_setInpError(ERR_NUMBER, tok[10]); + } + if ( ntoks >= 12 ) // road surface + { + if ( strcomp(tok[11], "PAVED") ) x[8] = 1.0; + else if ( strcomp(tok[11], "GRAVEL") ) x[8] = 2.0; + } + } + + if (ntoks >= 13 && *tok[12] != '*') + { + m = project_findObject(CURVE, tok[12]); // coeff. curve + if (m < 0) return error_setInpError(ERR_NAME, tok[12]); + x[9] = m; + } + + // --- add parameters to weir object + Link[j].ID = id; + link_setParams(j, WEIR, n1, n2, k, x); + return 0; +} + +//============================================================================= + +void weir_validate(int j, int k) +// +// Input: j = link index +// k = weir index +// Output: none +// Purpose: validates a weir's properties +// +{ + int err = 0; + double q, q1, q2, head; + + // --- check for valid cross section + switch ( Weir[k].type) + { + case TRANSVERSE_WEIR: + case SIDEFLOW_WEIR: + case ROADWAY_WEIR: + if ( Link[j].xsect.type != RECT_OPEN ) err = ERR_REGULATOR_SHAPE; + Weir[k].slope = 0.0; + break; + + case VNOTCH_WEIR: + if ( Link[j].xsect.type != TRIANGULAR ) err = ERR_REGULATOR_SHAPE; + else + { + Weir[k].slope = Link[j].xsect.sBot; + } + break; + + case TRAPEZOIDAL_WEIR: + if ( Link[j].xsect.type != TRAPEZOIDAL ) err = ERR_REGULATOR_SHAPE; + else + { + Weir[k].slope = Link[j].xsect.sBot; + } + break; + } + if ( err > 0 ) + { + report_writeErrorMsg(err, Link[j].ID); + return; + } + + // --- check for negative offset + if ( Link[j].offset1 < 0.0 ) Link[j].offset1 = 0.0; + + // --- compute an equivalent length + Weir[k].length = 2.0 * RouteStep * sqrt(GRAVITY * Link[j].xsect.yFull); + Weir[k].length = MAX(200.0, Weir[k].length); + Weir[k].surfArea = 0.0; + + // --- find flow through weir when water level equals weir height + head = Link[j].xsect.yFull; + weir_getFlow(j, k, head, 1.0, FALSE, &q1, &q2); + q = q1 + q2; + + // --- compute equivalent orifice coeff. (for CFS flow units) + head = head / 2.0; // head seen by equivalent orifice + Weir[k].cSurcharge = q / sqrt(head); +} + +//============================================================================= + +void weir_setSetting(int j) +// +// Input: j = link index +// Output: none +// Purpose: updates a weir's setting as a result of a control action. +// +{ + int k = Link[j].subIndex; + double h, q, q1, q2; + + // --- adjust weir setting + Link[j].setting = Link[j].targetSetting; + if ( !Weir[k].canSurcharge ) return; + if ( Weir[k].type == ROADWAY_WEIR ) return; + + // --- find orifice coeff. for surcharged flow + if ( Link[j].setting == 0.0 ) Weir[k].cSurcharge = 0.0; + else + { + // --- find flow through weir when water level equals weir height + h = Link[j].setting * Link[j].xsect.yFull; + weir_getFlow(j, k, h, 1.0, FALSE, &q1, &q2); + q = q1 + q2; + + // --- compute equivalent orifice coeff. (for CFS flow units) + h = h / 2.0; // head seen by equivalent orifice + Weir[k].cSurcharge = q / sqrt(h); + } +} + +//============================================================================= + +double weir_getInflow(int j) +// +// Input: j = link index +// Output: returns weir flow rate (cfs) +// Purpose: finds the flow over a weir. +// +{ + int n1; // index of upstream node + int n2; // index of downstream node + int k; // index of weir + double q1; // flow through central part of weir (cfs) + double q2; // flow through end sections of weir (cfs) + double head; // head on weir (ft) + double h1; // upstrm nodal head (ft) + double h2; // downstrm nodal head (ft) + double hcrest; // head at weir crest (ft) + double hcrown; // head at weir crown (ft) + double y; // water depth in weir (ft) + double dir; // direction multiplier + double ratio; + double weirPower[] = {1.5, // transverse weir + 5./3., // side flow weir + 2.5, // v-notch weir + 1.5}; // trapezoidal weir + + n1 = Link[j].node1; + n2 = Link[j].node2; + k = Link[j].subIndex; + if ( RouteModel == DW ) + { + h1 = Node[n1].newDepth + Node[n1].invertElev; + h2 = Node[n2].newDepth + Node[n2].invertElev; + } + else + { + h1 = Node[n1].newDepth + Node[n1].invertElev; + h2 = Node[n1].invertElev; + } + dir = (h1 > h2) ? +1.0 : -1.0; + + // --- exchange h1 and h2 for reverse flow + if ( dir < 0.0 ) + { + head = h1; + h1 = h2; + h2 = head; + } + + // --- find head of weir's crest and crown + hcrest = Node[n1].invertElev + Link[j].offset1; + hcrown = hcrest + Link[j].xsect.yFull; + + // --- treat a roadway weir as a special case + if ( Weir[k].type == ROADWAY_WEIR ) + return roadway_getInflow(j, dir, hcrest, h1, h2); + + // --- adjust crest ht. for partially open weir + hcrest += (1.0 - Link[j].setting) * Link[j].xsect.yFull; + + // --- compute head relative to weir crest + head = h1 - hcrest; + + // --- return if head is negligible or flap gate closed + Link[j].dqdh = 0.0; + if ( head <= FUDGE || hcrest >= hcrown || + link_setFlapGate(j, n1, n2, dir) ) + { + Link[j].newDepth = 0.0; + Link[j].flowClass = DRY; + return 0.0; + } + + // --- determine flow class + Link[j].flowClass = SUBCRITICAL; + if ( hcrest > h2 ) + { + if ( dir == 1.0 ) Link[j].flowClass = DN_CRITICAL; + else Link[j].flowClass = UP_CRITICAL; + } + + // --- compute new equivalent surface area + y = Link[j].xsect.yFull - (hcrown - MIN(h1, hcrown)); + Weir[k].surfArea = xsect_getWofY(&Link[j].xsect, y) * Weir[k].length; + + // --- head is above crown + if ( h1 >= hcrown ) + { + // --- use equivalent orifice if weir can surcharge + if ( Weir[k].canSurcharge ) + { + y = (hcrest + hcrown) / 2.0; + if ( h2 < y ) head = h1 - y; + else head = h1 - h2; + y = hcrown - hcrest; + q1 = weir_getOrificeFlow(j, head, y, Weir[k].cSurcharge); + Link[j].newDepth = y; + return dir * q1; + } + + // --- otherwise limit head to height of weir opening + else head = hcrown - hcrest; + } + + // --- use weir eqn. to find flows through central (q1) + // and end sections (q2) of weir + weir_getFlow(j, k, head, dir, Link[j].hasFlapGate, &q1, &q2); + + // --- apply Villemonte eqn. to correct for submergence + if ( h2 > hcrest ) + { + ratio = (h2 - hcrest) / (h1 - hcrest); + q1 *= pow( (1.0 - pow(ratio, weirPower[Weir[k].type])), 0.385); + if ( q2 > 0.0 ) + q2 *= pow( (1.0 - pow(ratio, weirPower[VNOTCH_WEIR])), 0.385); + } + + // --- return total flow through weir + Link[j].newDepth = MIN((h1 - hcrest), Link[j].xsect.yFull); + return dir * (q1 + q2); +} + +//============================================================================= + +void weir_getFlow(int j, int k, double head, double dir, int hasFlapGate, + double* q1, double* q2) +// +// Input: j = link index +// k = weir index +// head = head across weir (ft) +// dir = flow direction indicator +// hasFlapGate = flap gate indicator +// Output: q1 = flow through central portion of weir (cfs) +// q2 = flow through end sections of weir (cfs) +// Purpose: computes flow over weir given head. +// +{ + double length; + double h; + double y; + double hLoss; + double area; + double veloc; + int wType; + int cdCurve = Weir[k].cdCurve; + double cDisch1 = Weir[k].cDisch1; + + // --- q1 = flow through central portion of weir, + // q2 = flow through end sections of trapezoidal weir + *q1 = 0.0; + *q2 = 0.0; + Link[j].dqdh = 0.0; + if ( head <= 0.0 ) return; + + // --- convert weir length & head to original units + length = Link[j].xsect.wMax * UCF(LENGTH); + h = head * UCF(LENGTH); + + // --- lookup tabulated discharge coeff. + if ( cdCurve >= 0 ) cDisch1 = table_lookup(&Curve[cdCurve], h); + + // --- use appropriate formula for weir flow + wType = Weir[k].type; + if ( wType == VNOTCH_WEIR && + Link[j].setting < 1.0 ) wType = TRAPEZOIDAL_WEIR; + switch (wType) + { + case TRANSVERSE_WEIR: + + // --- reduce length when end contractions present + length -= 0.1 * Weir[k].endCon * h; + length = MAX(length, 0.0); + *q1 = cDisch1 * length * pow(h, 1.5); + break; + + case SIDEFLOW_WEIR: + + // --- reduce length when end contractions present + length -= 0.1 * Weir[k].endCon * h; + length = MAX(length, 0.0); + + // --- weir behaves as a transverse weir under reverse flow + if ( dir < 0.0 ) + *q1 = cDisch1 * length * pow(h, 1.5); + else + + // Corrected formula (see Metcalf & Eddy, Inc., + // Wastewater Engineering, McGraw-Hill, 1972 p. 164). + *q1 = cDisch1 * pow(length, 0.83) * pow(h, 1.67); + + break; + + case VNOTCH_WEIR: + *q1 = cDisch1 * Weir[k].slope * pow(h, 2.5); + break; + + case TRAPEZOIDAL_WEIR: + y = (1.0 - Link[j].setting) * Link[j].xsect.yFull; + length = xsect_getWofY(&Link[j].xsect, y) * UCF(LENGTH); + *q1 = cDisch1 * length * pow(h, 1.5); + *q2 = Weir[k].cDisch2 * Weir[k].slope * pow(h, 2.5); + } + + // --- convert CMS flows to CFS + if ( UnitSystem == SI ) + { + *q1 /= M3perFT3; + *q2 /= M3perFT3; + } + + // --- apply ARMCO adjustment for headloss from flap gate + if ( hasFlapGate ) + { + // --- compute flow area & velocity for current weir flow + area = weir_getOpenArea(j, head); + if ( area > TINY ) + { + veloc = (*q1 + *q2) / area; + + // --- compute headloss and subtract from original head + hLoss = (4.0 / GRAVITY) * veloc * veloc * + exp(-1.15 * veloc / sqrt(head) ); + head = head - hLoss; + if ( head < 0.0 ) head = 0.0; + + // --- make recursive call to this function, with hasFlapGate + // set to false, to find flow values at adjusted head value + weir_getFlow(j, k, head, dir, FALSE, q1, q2); + } + } + Link[j].dqdh = weir_getdqdh(k, dir, head, *q1, *q2); +} + +//============================================================================= + +double weir_getOrificeFlow(int j, double head, double y, double cOrif) +// +// Input: j = link index +// head = head across weir (ft) +// y = height of upstream water level above weir crest (ft) +// cOrif = orifice flow coefficient +// Output: returns flow through weir +// Purpose: finds flow through a surcharged weir using the orifice equation. +// +{ + double a, q, v, hloss; + + // --- evaluate the orifice flow equation + q = cOrif * sqrt(head); + + // --- apply Armco adjustment if weir has a flap gate + if ( Link[j].hasFlapGate ) + { + a = weir_getOpenArea(j, y); + if ( a > 0.0 ) + { + v = q / a; + hloss = (4.0 / GRAVITY) * v * v * exp(-1.15 * v / sqrt(y) ); + head -= hloss; + head = MAX(head, 0.0); + q = cOrif * sqrt(head); + } + } + if ( head > 0.0 ) Link[j].dqdh = q / (2.0 * head); + else Link[j].dqdh = 0.0; + return q; +} + +//============================================================================= + +double weir_getOpenArea(int j, double y) +// +// Input: j = link index +// y = depth of water above weir crest (ft) +// Output: returns area between weir crest and y (ft2) +// Purpose: finds flow area through a weir. +// +{ + double z, zy; + + // --- find offset of weir crest due to control setting + z = (1.0 - Link[j].setting) * Link[j].xsect.yFull; + + // --- ht. of crest + ht of water above crest + zy = z + y; + zy = MIN(zy, Link[j].xsect.yFull); + + // --- return difference between area of offset + water depth + // and area of just the offset + return xsect_getAofY(&Link[j].xsect, zy) - + xsect_getAofY(&Link[j].xsect, z); +} + +//============================================================================= + +double weir_getdqdh(int k, double dir, double h, double q1, double q2) +{ + double q1h; + double q2h; + + if ( fabs(h) < FUDGE ) return 0.0; + q1h = fabs(q1/h); + q2h = fabs(q2/h); + + switch (Weir[k].type) + { + case TRANSVERSE_WEIR: return 1.5 * q1h; + + case SIDEFLOW_WEIR: + // --- weir behaves as a transverse weir under reverse flow + if ( dir < 0.0 ) return 1.5 * q1h; + else return 1.67 * q1h; + + case VNOTCH_WEIR: + if ( q2h == 0.0 ) return 2.5 * q1h; // Fully open + else return 1.5 * q1h + 2.5 * q2h; // Partly open + + case TRAPEZOIDAL_WEIR: return 1.5 * q1h + 2.5 * q2h; + } + return 0.0; +} + + +//============================================================================= +// O U T L E T D E V I C E M E T H O D S +//============================================================================= + +int outlet_readParams(int j, int k, char* tok[], int ntoks) +// +// Input: j = link index +// k = outlet index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads outlet parameters from a tokenized line of input. +// +{ + int i, m, n; + int n1, n2; + double x[6]; + char* id; + char* s; + + // --- check for valid ID and end node IDs + if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); + id = project_findID(LINK, tok[0]); + if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); + n1 = project_findObject(NODE, tok[1]); + if ( n1 < 0 ) return error_setInpError(ERR_NAME, tok[1]); + n2 = project_findObject(NODE, tok[2]); + if ( n2 < 0 ) return error_setInpError(ERR_NAME, tok[2]); + + // --- get height above invert + if ( LinkOffsets == ELEV_OFFSET && *tok[3] == '*' ) x[0] = MISSING; + else + { + if ( ! getDouble(tok[3], &x[0]) ) + return error_setInpError(ERR_NUMBER, tok[3]); + if ( LinkOffsets == DEPTH_OFFSET && x[0] < 0.0 ) x[0] = 0.0; + } + + // --- see if outlet flow relation is tabular or functional + m = findmatch(tok[4], RelationWords); + if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[4]); + x[1] = 0.0; + x[2] = 0.0; + x[3] = -1.0; + x[4] = 0.0; + + // --- see if rating curve is head or depth based + x[5] = NODE_DEPTH; //default is depth-based + s = strtok(tok[4], "/"); //parse token for + s = strtok(NULL, "/"); // qualifier term + if ( strcomp(s, w_HEAD) ) x[5] = NODE_HEAD; //check if its "HEAD" + + // --- get params. for functional outlet device + if ( m == FUNCTIONAL ) + { + if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); + if ( ! getDouble(tok[5], &x[1]) ) + return error_setInpError(ERR_NUMBER, tok[5]); + if ( ! getDouble(tok[6], &x[2]) ) + return error_setInpError(ERR_NUMBER, tok[6]); + n = 7; + } + + // --- get name of outlet rating curve + else + { + i = project_findObject(CURVE, tok[5]); + if ( i < 0 ) return error_setInpError(ERR_NAME, tok[5]); + x[3] = i; + n = 6; + } + + // --- check if flap gate specified + if ( ntoks > n) + { + i = findmatch(tok[n], NoYesWords); + if ( i < 0 ) return error_setInpError(ERR_KEYWORD, tok[n]); + x[4] = i; + } + + // --- add parameters to outlet object + Link[j].ID = id; + link_setParams(j, OUTLET, n1, n2, k, x); + return 0; +} + +//============================================================================= + +double outlet_getInflow(int j) +// +// Input: j = link index +// Output: outlet flow rate (cfs) +// Purpose: finds the flow through an outlet. +// +{ + int k, n1, n2; + double head, hcrest, h1, h2, y1, dir; + + // --- get indexes of end nodes + n1 = Link[j].node1; + n2 = Link[j].node2; + k = Link[j].subIndex; + + // --- find heads at upstream & downstream nodes + if ( RouteModel == DW ) + { + h1 = Node[n1].newDepth + Node[n1].invertElev; + h2 = Node[n2].newDepth + Node[n2].invertElev; + } + else + { + h1 = Node[n1].newDepth + Node[n1].invertElev; + h2 = Node[n1].invertElev; + } + dir = (h1 >= h2) ? +1.0 : -1.0; + + // --- exchange h1 and h2 for reverse flow + y1 = Node[n1].newDepth; + if ( dir < 0.0 ) + { + y1 = h1; + h1 = h2; + h2 = y1; + y1 = Node[n2].newDepth; + } + + // --- for a NODE_DEPTH rating curve the effective head across the + // outlet is the depth above the crest elev. while for a NODE_HEAD + // curve it is the difference between upstream & downstream heads + hcrest = Node[n1].invertElev + Link[j].offset1; + if ( Outlet[k].curveType == NODE_HEAD && RouteModel == DW ) + head = h1 - MAX(h2, hcrest); + else head = h1 - hcrest; + + // --- no flow if either no effective head difference, + // no upstream water available, or closed flap gate + if ( head <= FUDGE || y1 <= FUDGE || + link_setFlapGate(j, n1, n2, dir) ) + { + Link[j].newDepth = 0.0; + Link[j].flowClass = DRY; + return 0.0; + } + + // --- otherwise use rating curve to compute flow + Link[j].newDepth = head; + Link[j].flowClass = SUBCRITICAL; + return dir * Link[j].setting * outlet_getFlow(k, head); +} + +//============================================================================= + +double outlet_getFlow(int k, double head) +// +// Input: k = outlet index +// head = head across outlet (ft) +// Output: returns outlet flow rate (cfs) +// Purpose: computes flow rate through an outlet given head. +// +{ + int m; + double h; + + // --- convert head to original units + h = head * UCF(LENGTH); + + // --- look-up flow in rating curve table if provided + m = Outlet[k].qCurve; + if ( m >= 0 ) return table_lookup(&Curve[m], h) / UCF(FLOW); + + // --- otherwise use function to find flow + else return Outlet[k].qCoeff * pow(h, Outlet[k].qExpon) / UCF(FLOW); +} diff --git a/src/macros.h b/src/macros.h new file mode 100644 index 000000000..c15749e4b --- /dev/null +++ b/src/macros.h @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------------- +// macros.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +//----------------------------------------------------------------------------- + +#ifndef MACROS_H +#define MACROS_H + + +//-------------------------------------------------- +// Macro to test for successful allocation of memory +//-------------------------------------------------- +#define MEMCHECK(x) (((x) == NULL) ? 101 : 0 ) + +//-------------------------------------------------- +// Macro to free a non-null pointer +//-------------------------------------------------- +#define FREE(x) { if (x) { free(x); x = NULL; } } + +//--------------------------------------------------- +// Conversion macros to be used in place of functions +//--------------------------------------------------- +#define ABS(x) (((x)<0) ? -(x) : (x)) /* absolute value of x */ +#define MIN(x,y) (((x)<=(y)) ? (x) : (y)) /* minimum of x and y */ +#define MAX(x,y) (((x)>=(y)) ? (x) : (y)) /* maximum of x and y */ +#define MOD(x,y) ((x)%(y)) /* x modulus y */ +#define LOG10(x) ((x) > 0.0 ? log10((x)) : (x)) /* safe log10 of x */ +#define SQR(x) ((x)*(x)) /* x-squared */ +#define SGN(x) (((x)<0) ? (-1) : (1)) /* sign of x */ +#define SIGN(x,y) ((y) >= 0.0 ? fabs(x) : -fabs(x)) +#define UCHAR(x) (((x) >= 'a' && (x) <= 'z') ? ((x)&~32) : (x)) + /* uppercase char of x */ +#define ARRAY_LENGTH(x) (sizeof(x)/sizeof(x[0])) /* length of array x */ + +//------------------------------------------------- +// Macro to evaluate function x with error checking +//------------------------------------------------- +#define CALL(x) (ErrorCode = ((ErrorCode>0) ? (ErrorCode) : (x))) + + +#endif //MACROS_H diff --git a/src/main.c b/src/main.c new file mode 100644 index 000000000..0fe1b40b7 --- /dev/null +++ b/src/main.c @@ -0,0 +1,104 @@ +//----------------------------------------------------------------------------- +// main.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 03/24/2021 +// Author: L. Rossman + +// Main stub for the command line version of EPA SWMM 5.2 +// to be run with swmm5.dll. + +#include +#include +#include +#include "swmm5.h" + +int main(int argc, char *argv[]) +// +// Input: argc = number of command line arguments +// argv = array of command line arguments +// Output: returns error status +// Purpose: runs the command line version of EPA SWMM 5.2. +// +// Command line is: runswmm f1 f2 f3 +// where f1 = name of input file, f2 = name of report file, and +// 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"); + } + else if (argc == 2) + { + // --- extract first argument + arg1 = argv[1]; + + if (strcmp(arg1, "--help") == 0 || strcmp(arg1, "-h") == 0) + { + // Help + printf("\n\nSTORMWATER MANAGEMENT MODEL (SWMM) HELP\n\n"); + printf("COMMANDS:\n"); + printf("\t--help (-h) SWMM Help\n"); + printf("\t--version (-v) Build Version\n"); + printf("\nRUNNING A SIMULATION:\n"); + printf("\t runswmm

-// -// consists of: -// value / -// where is -// E.g.: Node 123 Depth > 4.5 -// Node 456 Depth < Node 123 Depth -// -// consists of: -// = setting -// E.g.: Pump abc status = OFF -// Weir xyz setting = 0.5 -// -// Update History -// ============== -// Build 5.1.008: -// - Support added for r.h.s. variables in rule premises. -// - Node volume added as a premise variable. -// Build 5.1.009: -// - Fixed problem with parsing a RHS premise variable. -// Build 5.1.010: -// - Support added for link TIMEOPEN & TIMECLOSED premises. -// Build 5.1.011: -// - Support added for DAYOFYEAR attribute. -// - Modulated controls no longer included in reported control actions. -// Build 5.2.0: -// - Additional attributes added to condition clauses. -// - Support added for named variables in condition clauses. -// - Support added for math expressions in condition clauses. -// Build 5.2.1: -// - A refactoring bug from 5.2.0 causing duplicate actions to be added -// to the list of control actions to take was fixed. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include -#include "headers.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -enum RuleState {r_RULE, r_IF, r_AND, r_OR, r_THEN, r_ELSE, r_PRIORITY, - r_VARIABLE, r_EXPRESSION, r_ERROR}; -enum RuleObject {r_GAGE, r_NODE, r_LINK, r_CONDUIT, r_PUMP, r_ORIFICE, - r_WEIR, r_OUTLET, r_SIMULATION}; -enum RuleAttrib {r_DEPTH, r_MAXDEPTH, r_HEAD, r_VOLUME, r_INFLOW, - r_FLOW, r_FULLFLOW, r_FULLDEPTH, r_STATUS, r_SETTING, - r_LENGTH, r_SLOPE, r_VELOCITY, r_TIMEOPEN, r_TIMECLOSED, - r_TIME, r_DATE, r_CLOCKTIME, r_DAYOFYEAR, r_DAY, r_MONTH}; -enum RuleRelation {EQ, NE, LT, LE, GT, GE}; -enum RuleSetting {r_CURVE, r_TIMESERIES, r_PID, r_NUMERIC}; - -#define MAXVARNAME 32 - -static char* ObjectWords[] = - {"GAGE", "NODE", "LINK", "CONDUIT", "PUMP", "ORIFICE", "WEIR", "OUTLET", - "SIMULATION", NULL}; -static char* AttribWords[] = - {"DEPTH", "MAXDEPTH", "HEAD", "VOLUME", "INFLOW", - "FLOW", "FULLFLOW", "FULLDEPTH", "STATUS", "SETTING", - "LENGTH", "SLOPE", "VELOCITY", "TIMEOPEN", "TIMECLOSED", - "TIME", "DATE", "CLOCKTIME", "DAYOFYEAR", "DAY", "MONTH", NULL}; -static char* RelOpWords[] = {"=", "<>", "<", "<=", ">", ">=", NULL}; -static char* StatusWords[] = {"OFF", "ON", NULL}; -static char* ConduitWords[] = {"CLOSED", "OPEN", NULL}; -static char* SettingTypeWords[] = {"CURVE", "TIMESERIES", "PID", NULL}; -static char* IntensityWord = "INTENSITY"; - -//----------------------------------------------------------------------------- -// Data Structures -//----------------------------------------------------------------------------- -// Rule Premise Variable -struct TVariable -{ - int object; // type of object - int index; // index in object's array - int attribute; // object's attribute -}; - -// Named Variable -struct TNamedVariable -{ - struct TVariable variable; // a rule premise variable - char name[MAXVARNAME+1]; // name used in math expression -}; - -// Rule Premise Function -struct TExpression -{ - MathExpr* expression; // tokenized math expression - char name[MAXVARNAME+1]; // expression name -}; - -// Rule Premise Clause -struct TPremise -{ - int type; // clause type (IF/AND/OR) - int exprIndex; // expression index (-1 if N/A) - struct TVariable lhsVar; // left hand side variable - struct TVariable rhsVar; // right hand side variable - int relation; // relational operator (>, <, =, etc) - double value; // right hand side value - struct TPremise *next; // next premise clause of rule -}; - -// Rule Action Clause -struct TAction -{ - int rule; // index of rule that action belongs to - int link; // index of link being controlled - int attribute; // attribute of link being controlled - int curve; // index of curve for modulated control - int tseries; // index of time series for modulated control - double value; // control setting for link attribute - double kp, ki, kd; // coeffs. for PID modulated control - double e1, e2; // PID set point error from previous time steps - struct TAction *next; // next action clause of rule -}; - -// List of Control Actions -struct TActionList -{ - struct TAction* action; - struct TActionList* next; -}; - -// Control Rule -struct TRule -{ - char* ID; // rule ID - double priority; // priority level - struct TPremise* firstPremise; // pointer to first premise of rule - struct TPremise* lastPremise; // pointer to last premise of rule - struct TAction* thenActions; // linked list of actions if true - struct TAction* elseActions; // linked list of actions if false -}; - -//----------------------------------------------------------------------------- -// Shared variables -//----------------------------------------------------------------------------- -struct TRule* Rules; // array of control rules -struct TActionList* ActionList; // linked list of control actions -int InputState; // state of rule interpreter -int RuleCount; // total number of rules -double ControlValue; // value of controller variable -double SetPoint; // value of controller setpoint -DateTime CurrentDate; // current date in whole days -DateTime CurrentTime; // current time of day (decimal) - -int VariableCount; -int ExpressionCount; -int CurrentVariable; -int CurrentExpression; -struct TNamedVariable* NamedVariable; // array of named variables -struct TExpression* Expression; // array of math expressions - -//----------------------------------------------------------------------------- -// External functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// controls_create -// controls_delete -// controls_init -// controls_addToCount -// controls_addVariable -// controls_addExpression -// controls_addRuleClause -// controls_evaluate - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -int addPremise(int r, int type, char* Tok[], int nToks); -int getPremiseVariable(char* tok[], int nToks, int* k, struct TVariable* v); -int getPremiseValue(char* token, int attrib, double* value); -int addAction(int r, char* Tok[], int nToks); - -int evaluatePremise(struct TPremise* p, double tStep); -double getVariableValue(struct TVariable v); -int compareTimes(double lhsValue, int relation, double rhsValue, - double halfStep); -int compareValues(double lhsValue, int relation, double rhsValue); - -void updateActionList(struct TAction* a); -int executeActionList(DateTime currentTime); -void clearActionList(void); -void deleteActionList(void); -void deleteRules(void); - -int findExactMatch(char *s, char *keyword[]); -int setActionSetting(char* tok[], int nToks, int* curve, int* tseries, - int* attrib, double value[]); -void updateActionValue(struct TAction* a, DateTime currentTime, double dt); -double getPIDSetting(struct TAction* a, double dt); - -int getVariableIndex(char* varName); -double getNamedVariableValue(int varIndex); -int getExpressionIndex(char* exprName); -int getGageAttrib(char* token); -double getRainValue(struct TVariable v); - -//============================================================================= - -void controls_init() -// -// Input: none -// Output: none -// Purpose: initializes the control rule system. -// -{ - Rules = NULL; - NamedVariable = NULL; - Expression = NULL; - RuleCount = 0; - VariableCount = 0; - ExpressionCount = 0; -} - -//============================================================================= - -void controls_addToCount(char* s) -// -// Input: s = either VARIABLE or EXPRESSION -// Output: none -// Purpose: updates the number of named variables or math expressions used -// by control rules. -// -{ - if (match(s, w_VARIABLE)) VariableCount++; - else if (match(s, w_EXPRESSION)) ExpressionCount++; -} - -//============================================================================= - -int controls_create(int n) -// -// Input: n = total number of control rules -// Output: returns error code -// Purpose: creates an array of control rules. -// -{ - int r; - ActionList = NULL; - InputState = r_PRIORITY; - RuleCount = n; - if (RuleCount > 0) - { - Rules = (struct TRule *) calloc(RuleCount, sizeof(struct TRule)); - if (Rules == NULL) return ERR_MEMORY; - for ( r=0; r 0) - { - NamedVariable = (struct TNamedVariable *) calloc(VariableCount, - sizeof(struct TNamedVariable)); - if (NamedVariable == NULL) return ERR_MEMORY; - } - if (ExpressionCount > 0) - { - Expression = (struct TExpression *) calloc(ExpressionCount, - sizeof(struct TExpression)); - if (Expression == NULL) return ERR_MEMORY; - } - return 0; -} - -//============================================================================= - -void controls_delete(void) -// -// Input: none -// Output: none -// Purpose: deletes all control rules. -// -{ - int i; - - for (i = 0; i < ExpressionCount; i++) - { - mathexpr_delete(Expression[i].expression); - Expression[i].expression = NULL; - } - FREE(Expression); - FREE(NamedVariable); - - if ( RuleCount == 0 ) return; - deleteActionList(); - deleteRules(); -} - -//============================================================================= - -int controls_addVariable(char* tok[], int nToks) -// -// Input: tok = an array of string tokens -// n = the size of tok[] -// Output: returns error code -// Purpose: adds a named variable to the control rule system from a -// tokenized line of input with formats: -// VARIABLE name = Object id attribute -// VARIABLE name = SIMULATION attribute -// -{ - struct TVariable v1; - int k, err; - - CurrentVariable++; - if (nToks < 5) return ERR_ITEMS; - if (findExactMatch(tok[1], AttribWords) >= 0) - return error_setInpError(ERR_KEYWORD, tok[1]); - if (!match(tok[2], "=")) return error_setInpError(ERR_KEYWORD, tok[2]); - if (!match(tok[3], "SIMULATION") && nToks < 6) return ERR_ITEMS; - k = 3; - err = getPremiseVariable(tok, nToks, &k, &v1); - if (err > 0) return err; - k = CurrentVariable; - NamedVariable[k].variable = v1; - sstrncpy(NamedVariable[k].name, tok[1], MAXVARNAME); - return 0; -} - -//============================================================================= - -int controls_addExpression(char* tok[], int nToks) -// -// Input: tok = an array of string tokens -// n = number of tokens -// Output: returns error code -// Purpose: adds a math expression to the control rule system from a -// a tokenized line of input with format: -// EXPRESSION name = -// -{ - int i, k; - char s[MAXLINE + 1]; - MathExpr* expr; - - CurrentExpression++; - if (nToks < 4) return ERR_ITEMS; - k = CurrentExpression; - Expression[k].expression = NULL; - sstrncpy(Expression[k].name, tok[1], MAXVARNAME); - sstrncpy(s, tok[3], MAXLINE); - for (i = 4; i < nToks; i++) - { - sstrcat(s, " ", MAXLINE); - sstrcat(s, tok[i], MAXLINE); - } - - expr = mathexpr_create(s, getVariableIndex); - if (expr == NULL) - return error_setInpError(ERR_MATH_EXPR, ""); - - Expression[k].expression = expr; - return 0; -} - -//============================================================================= - -int getVariableIndex(char* varName) -// -// Input: varName = string containing a variable name -// Output: returns the index of the named variable or -1 if not found -// Purpose: finds the array index of a named variable. -// -{ - int i; - for (i = 0; i < VariableCount; i++) - { - if (match(varName, NamedVariable[i].name)) return i; - } - return -1; -} - -//============================================================================= - -double getNamedVariableValue(int varIndex) -// -// Input: varIndex = index of a named variable -// Output: returns the current value of the variable -// Purpose: finds the value of a named variable. -// -{ - return getVariableValue(NamedVariable[varIndex].variable); -} - -//============================================================================= - -int getExpressionIndex(char* exprName) -// -// Input: exprName = string containing an expression name -// Output: returns the index of the expression or -1 if not found -// Purpose: finds the array index of a math expression -// -{ - int i; - for (i = 0; i < ExpressionCount; i++) - { - if (match(exprName, Expression[i].name)) return i; - } - return -1; -} - -//============================================================================= - -int controls_addRuleClause(int r, int keyword, char* tok[], int nToks) -// -// Input: r = rule index -// keyword = the clause's keyword code (IF, THEN, etc.) -// tok = an array of string tokens that comprises the clause -// nToks = number of tokens -// Output: returns an error code -// Purpose: addd a new clause to a control rule. -// -{ - switch (keyword) - { - case r_RULE: - if ( Rules[r].ID == NULL ) - Rules[r].ID = project_findID(CONTROL, tok[1]); - InputState = r_RULE; - if ( nToks > 2 ) return ERR_RULE; - return 0; - - case r_IF: - if ( InputState != r_RULE ) return ERR_RULE; - InputState = r_IF; - return addPremise(r, r_AND, tok, nToks); - - case r_AND: - if ( InputState == r_IF ) return addPremise(r, r_AND, tok, nToks); - else if ( InputState == r_THEN || InputState == r_ELSE ) - return addAction(r, tok, nToks); - else return ERR_RULE; - - case r_OR: - if ( InputState != r_IF ) return ERR_RULE; - return addPremise(r, r_OR, tok, nToks); - - case r_THEN: - if ( InputState != r_IF ) return ERR_RULE; - InputState = r_THEN; - return addAction(r, tok, nToks); - - case r_ELSE: - if ( InputState != r_THEN ) return ERR_RULE; - InputState = r_ELSE; - return addAction(r, tok, nToks); - - case r_PRIORITY: - if ( InputState != r_THEN && InputState != r_ELSE ) return ERR_RULE; - InputState = r_PRIORITY; - if ( !getDouble(tok[1], &Rules[r].priority) ) return ERR_NUMBER; - if ( nToks > 2 ) return ERR_RULE; - return 0; - } - return 0; -} - -//============================================================================= - -int controls_evaluate(DateTime currentTime, DateTime elapsedTime, double tStep) -// -// Input: currentTime = current simulation date/time -// elapsedTime = decimal days since start of simulation -// tStep = simulation time step (days) -// Output: returns number of new actions taken -// Purpose: evaluates all control rules at current time of the simulation. -// -{ - int r; // control rule index - int result; // TRUE if rule premises satisfied - struct TPremise* p; // pointer to rule premise clause - struct TAction* a; // pointer to rule action clause - - // --- save date and time to shared variables - CurrentDate = floor(currentTime); - CurrentTime = currentTime - floor(currentTime); - ElapsedTime = elapsedTime; - - // --- evaluate each rule - if ( RuleCount == 0 ) return 0; - clearActionList(); - for (r=0; rtype == r_OR ) - { - if ( result == FALSE ) - result = evaluatePremise(p, tStep); - } - else - { - if ( result == FALSE ) break; - result = evaluatePremise(p, tStep); - } - p = p->next; - } - - // --- if premises true, add THEN clauses to action list - // else add ELSE clauses to action list - if ( result == TRUE ) a = Rules[r].thenActions; - else a = Rules[r].elseActions; - while (a) - { - updateActionValue(a, currentTime, tStep); - updateActionList(a); - a = a->next; - } - } - - // --- execute actions on action list - if ( ActionList ) return executeActionList(currentTime); - else return 0; -} - -//============================================================================= - -int addPremise(int r, int type, char* tok[], int nToks) -// -// Input: r = control rule index -// type = type of premise (IF, AND, OR) -// tok = array of string tokens containing premise statement -// nToks = number of string tokens -// Output: returns an error code -// Purpose: adds a new premise to a control rule. -// -{ - int relation, n, err = 0; - double value = MISSING; - struct TPremise* p; - struct TVariable v1; - struct TVariable v2; - int obj, exprIndex, varIndex = -1; - - // --- initialize LHS variable v1 - if (nToks < 4) return ERR_ITEMS; - v1.attribute = -1; - v1.object = -1; - v1.index = -1; - n = 1; - - // --- check if 2nd token is a math expression - exprIndex = getExpressionIndex(tok[1]); - - // --- if not then check if it's a named variable - if (exprIndex < 0) - { - varIndex = getVariableIndex(tok[n]); - if (varIndex >= 0) - { - v1 = NamedVariable[varIndex].variable; - } - - // otherwise parse object|index|attribute tokens - else - { - err = getPremiseVariable(tok, nToks, &n, &v1); - if ( err > 0 ) return err; - } - } - - // --- get relational operator - n++; - if ( n >= nToks ) return error_setInpError(ERR_ITEMS, ""); - relation = findExactMatch(tok[n], RelOpWords); - if ( relation < 0 ) return error_setInpError(ERR_KEYWORD, tok[n]); - - // --- initialize RHS variable v2 - v2.attribute = -1; - v2.object = -1; - v2.index = -1; - n++; - if (n >= nToks) return error_setInpError(ERR_ITEMS, ""); - - // --- check for named RHS variable - varIndex = getVariableIndex(tok[n]); - if (varIndex >= 0) - { - v2 = NamedVariable[varIndex].variable; - } - - // --- check for object|index|attribute variable - else - { - obj = findmatch(tok[n], ObjectWords); - if (obj >= 0) - { - err = getPremiseVariable(tok, nToks, &n, &v2); - if ( err > 0 ) return ERR_RULE; - if (exprIndex < 0 && v1.attribute != v2.attribute) - report_writeWarningMsg(WARN11, Rules[r].ID); - } - - // --- check for a single RHS value - else - { - err = getPremiseValue(tok[n], v1.attribute, &value); - if ( err > 0 ) return err; - } - } - - // --- make sure another clause is not on same line - n++; - if ( n < nToks && findmatch(tok[n], RuleKeyWords) >= 0 ) return ERR_RULE; - - // --- create the premise object - p = (struct TPremise *) malloc(sizeof(struct TPremise)); - if ( !p ) return ERR_MEMORY; - p->type = type; - p->exprIndex = exprIndex; - p->lhsVar = v1; - p->rhsVar = v2; - p->relation = relation; - p->value = value; - p->next = NULL; - if ( Rules[r].firstPremise == NULL ) - { - Rules[r].firstPremise = p; - } - else - { - Rules[r].lastPremise->next = p; - } - Rules[r].lastPremise = p; - return 0; -} - -//============================================================================= - -int getPremiseVariable(char* tok[], int nToks, int* k, struct TVariable* v) -// -// Input: tok = array of string tokens -// nToks = number of tokens -// k = index of current token -// Output: returns an error code; updates k to new current token and -// places identity of specified variable in v -// Purpose: parses a variable (e.g., Node 123 Depth) used in a control rule. -// -{ - int n = *k; - int object = -1; - int index = -1; - int obj, attrib; - - // --- get object type - obj = findmatch(tok[n], ObjectWords); - if ( obj < 0 ) return error_setInpError(ERR_KEYWORD, tok[n]); - - // --- get object index from its name - n++; - if (n >= nToks) return error_setInpError(ERR_ITEMS, ""); - switch (obj) - { - case r_GAGE: - index = project_findObject(GAGE, tok[n]); - if (index < 0) return error_setInpError(ERR_NAME, tok[n]); - object = r_GAGE; - break; - - case r_NODE: - index = project_findObject(NODE, tok[n]); - if ( index < 0 ) return error_setInpError(ERR_NAME, tok[n]); - object = r_NODE; - break; - - case r_LINK: - case r_CONDUIT: - case r_PUMP: - case r_ORIFICE: - case r_WEIR: - case r_OUTLET: - index = project_findObject(LINK, tok[n]); - if ( index < 0 ) return error_setInpError(ERR_NAME, tok[n]); - object = r_LINK; - break; - default: n--; - } - n++; - if (n >= nToks) return error_setInpError(ERR_ITEMS, ""); - - // --- get attribute index from its name - if (object == r_GAGE) - attrib = getGageAttrib(tok[n]); - else - attrib = findmatch(tok[n], AttribWords); - if ( attrib < 0 ) return error_setInpError(ERR_KEYWORD, tok[n]); - - // --- check that attribute belongs to object type - if (obj == r_GAGE) - { - - } - - else if ( obj == r_NODE ) switch (attrib) - { - case r_DEPTH: - case r_MAXDEPTH: - case r_HEAD: - case r_VOLUME: - case r_INFLOW: break; - default: return error_setInpError(ERR_KEYWORD, tok[n]); - } - - // --- check for link TIMEOPEN & TIMECLOSED attributes - else if ( object == r_LINK && index >= 0 && - ( (attrib == r_TIMEOPEN || attrib == r_TIMECLOSED) - )) - { - // nothing to do here - } - - else if ( obj == r_LINK || obj == r_CONDUIT ) switch (attrib) - { - case r_STATUS: - case r_DEPTH: - case r_FULLFLOW: - case r_FULLDEPTH: - case r_FLOW: - case r_LENGTH: - case r_SLOPE: - case r_VELOCITY: break; - default: return error_setInpError(ERR_KEYWORD, tok[n]); - } - else if ( obj == r_PUMP ) switch (attrib) - { - case r_FLOW: - case r_SETTING: - case r_STATUS: break; - default: return error_setInpError(ERR_KEYWORD, tok[n]); - } - else if ( obj == r_ORIFICE || obj == r_WEIR || - obj == r_OUTLET ) switch (attrib) - { - case r_FLOW: - case r_SETTING: break; - default: return error_setInpError(ERR_KEYWORD, tok[n]); - } - else switch (attrib) - { - case r_TIME: - case r_DATE: - case r_CLOCKTIME: - case r_DAY: - case r_MONTH: - case r_DAYOFYEAR: break; - default: return error_setInpError(ERR_KEYWORD, tok[n]); - } - - // --- populate variable structure - v->object = object; - v->index = index; - v->attribute = attrib; - *k = n; - return 0; -} - -//============================================================================= - -int getGageAttrib(char* token) -// -// Input: token = a string token -// Output: returns an attribute code or -1 if an error occurred -// Purpose: determines the atrribute code for a rain gage variable. -// Note: a valid token is INTENSITY for current rainfall intensity -// (attribute code = 0) or nHR_PRECIP for total rain depth -// over past n hours (attribute code = n). -// -{ - int attrib; - - // --- check if token is currrent rainfall intensity - if (match(token, IntensityWord)) - return 0; - - // --- token is past rain depth - read number of past hours - attrib = atoi(token); - - // --- check that number of hours is in allowable range - if (attrib < 1 || attrib > MAXPASTRAIN) - return -1; - return attrib; -} - -//============================================================================= - -int getPremiseValue(char* token, int attrib, double* value) -// -// Input: token = a string token -// attrib = index of a node/link attribute -// Output: value = attribute value; -// returns an error code; -// Purpose: parses the numerical value of a particular node/link attribute -// in the premise clause of a control rule. -// -{ - char strDate[25]; - switch (attrib) - { - case r_STATUS: - *value = findmatch(token, StatusWords); - if ( *value < 0.0 ) *value = findmatch(token, ConduitWords); - if ( *value < 0.0 ) return error_setInpError(ERR_KEYWORD, token); - break; - - case r_TIME: - case r_CLOCKTIME: - case r_TIMEOPEN: - case r_TIMECLOSED: - if ( !datetime_strToTime(token, value) ) - return error_setInpError(ERR_DATETIME, token); - break; - - case r_DATE: - if ( !datetime_strToDate(token, value) ) - return error_setInpError(ERR_DATETIME, token); - break; - - case r_DAY: - if ( !getDouble(token, value) ) - return error_setInpError(ERR_NUMBER, token); - if ( *value < 1.0 || *value > 7.0 ) - return error_setInpError(ERR_DATETIME, token); - break; - - case r_MONTH: - if ( !getDouble(token, value) ) - return error_setInpError(ERR_NUMBER, token); - if ( *value < 1.0 || *value > 12.0 ) - return error_setInpError(ERR_DATETIME, token); - break; - - case r_DAYOFYEAR: - sstrncpy(strDate, token, 6); - sstrcat(strDate, "/1947", 25); - if ( datetime_strToDate(strDate, value) ) - { - *value = datetime_dayOfYear(*value); - } - else if ( !getDouble(token, value) || *value < 1 || *value > 365 ) - return error_setInpError(ERR_DATETIME, token); - break; - - default: if ( !getDouble(token, value) ) - return error_setInpError(ERR_NUMBER, token); - } - return 0; -} - -//============================================================================= - -int addAction(int r, char* tok[], int nToks) -// -// Input: r = control rule index -// tok = array of string tokens containing action statement -// nToks = number of string tokens -// Output: returns an error code -// Purpose: adds a new action to a control rule. -// -{ - int obj, link, attrib; - int curve = -1, tseries = -1; - int n; - int err; - double values[] = {1.0, 0.0, 0.0}; - - struct TAction* a; - - // --- check for proper number of tokens - if ( nToks < 6 ) return error_setInpError(ERR_ITEMS, ""); - - // --- check for valid object type - obj = findmatch(tok[1], ObjectWords); - if ( obj != r_LINK && obj != r_CONDUIT && obj != r_PUMP && - obj != r_ORIFICE && obj != r_WEIR && obj != r_OUTLET ) - return error_setInpError(ERR_KEYWORD, tok[1]); - - // --- check that object name exists and is of correct type - link = project_findObject(LINK, tok[2]); - if ( link < 0 ) return error_setInpError(ERR_NAME, tok[2]); - switch (obj) - { - case r_CONDUIT: - if ( Link[link].type != CONDUIT ) - return error_setInpError(ERR_NAME, tok[2]); - break; - case r_PUMP: - if ( Link[link].type != PUMP ) - return error_setInpError(ERR_NAME, tok[2]); - break; - case r_ORIFICE: - if ( Link[link].type != ORIFICE ) - return error_setInpError(ERR_NAME, tok[2]); - break; - case r_WEIR: - if ( Link[link].type != WEIR ) - return error_setInpError(ERR_NAME, tok[2]); - break; - case r_OUTLET: - if ( Link[link].type != OUTLET ) - return error_setInpError(ERR_NAME, tok[2]); - break; - } - - // --- check for valid attribute name - attrib = findmatch(tok[3], AttribWords); - if ( attrib < 0 ) return error_setInpError(ERR_KEYWORD, tok[3]); - - // --- get control action setting - if ( obj == r_CONDUIT ) - { - if ( attrib == r_STATUS ) - { - values[0] = findmatch(tok[5], ConduitWords); - if ( values[0] < 0.0 ) - return error_setInpError(ERR_KEYWORD, tok[5]); - } - else return error_setInpError(ERR_KEYWORD, tok[3]); - } - - else if ( obj == r_PUMP ) - { - if ( attrib == r_STATUS ) - { - values[0] = findmatch(tok[5], StatusWords); - if ( values[0] < 0.0 ) - return error_setInpError(ERR_KEYWORD, tok[5]); - } - else if ( attrib == r_SETTING ) - { - err = setActionSetting(tok, nToks, &curve, &tseries, - &attrib, values); - if ( err > 0 ) return err; - } - else return error_setInpError(ERR_KEYWORD, tok[3]); - } - - else if ( obj == r_ORIFICE || obj == r_WEIR || obj == r_OUTLET ) - { - if ( attrib == r_SETTING ) - { - err = setActionSetting(tok, nToks, &curve, &tseries, - &attrib, values); - if ( err > 0 ) return err; - if ( attrib == r_SETTING - && (values[0] < 0.0 || values[0] > 1.0) ) - return error_setInpError(ERR_NUMBER, tok[5]); - } - else return error_setInpError(ERR_KEYWORD, tok[3]); - } - else return error_setInpError(ERR_KEYWORD, tok[1]); - - // --- check if another clause is on same line - n = 6; - if ( curve >= 0 || tseries >= 0 ) n = 7; - if ( attrib == r_PID ) n = 9; - if ( n < nToks && findmatch(tok[n], RuleKeyWords) >= 0 ) return ERR_RULE; - - // --- create the action object - a = (struct TAction *) malloc(sizeof(struct TAction)); - if ( !a ) return ERR_MEMORY; - a->rule = r; - a->link = link; - a->attribute = attrib; - a->curve = curve; - a->tseries = tseries; - a->value = values[0]; - if ( attrib == r_PID ) - { - a->kp = values[0]; - a->ki = values[1]; - a->kd = values[2]; - a->e1 = 0.0; - a->e2 = 0.0; - } - if ( InputState == r_THEN ) - { - a->next = Rules[r].thenActions; - Rules[r].thenActions = a; - } - else - { - a->next = Rules[r].elseActions; - Rules[r].elseActions = a; - } - return 0; -} - -//============================================================================= - -int setActionSetting(char* tok[], int nToks, int* curve, int* tseries, - int* attrib, double values[]) -// -// Input: tok = array of string tokens containing action statement -// nToks = number of string tokens -// Output: curve = index of controller curve -// tseries = index of controller time series -// attrib = r_PID if PID controller used -// values = values of control settings -// returns an error code -// Purpose: identifies how control actions settings are determined. -// -{ - int k, m; - - // --- see if control action is determined by a Curve or Time Series - if (nToks < 6) return error_setInpError(ERR_ITEMS, ""); - k = findmatch(tok[5], SettingTypeWords); - if ( k >= 0 && nToks < 7 ) return error_setInpError(ERR_ITEMS, ""); - switch (k) - { - - // --- control determined by a curve - find curve index - case r_CURVE: - m = project_findObject(CURVE, tok[6]); - if ( m < 0 ) return error_setInpError(ERR_NAME, tok[6]); - *curve = m; - break; - - // --- control determined by a time series - find time series index - case r_TIMESERIES: - m = project_findObject(TSERIES, tok[6]); - if ( m < 0 ) return error_setInpError(ERR_NAME, tok[6]); - *tseries = m; - Tseries[m].refersTo = CONTROL; - break; - - // --- control determined by PID controller - case r_PID: - if (nToks < 9) return error_setInpError(ERR_ITEMS, ""); - for (m=6; m<=8; m++) - { - if ( !getDouble(tok[m], &values[m-6]) ) - return error_setInpError(ERR_NUMBER, tok[m]); - } - *attrib = r_PID; - break; - - // --- direct numerical control is used - default: - if ( !getDouble(tok[5], &values[0]) ) - return error_setInpError(ERR_NUMBER, tok[5]); - } - return 0; -} - -//============================================================================= - -void updateActionValue(struct TAction* a, DateTime currentTime, double dt) -// -// Input: a = an action object -// currentTime = current simulation date/time (days) -// dt = time step (days) -// Output: none -// Purpose: updates value of actions found from Curves or Time Series. -// -{ - if ( a->curve >= 0 ) - { - a->value = table_lookup(&Curve[a->curve], ControlValue); - } - else if ( a->tseries >= 0 ) - { - a->value = table_tseriesLookup(&Tseries[a->tseries], currentTime, TRUE); - } - else if ( a->attribute == r_PID ) - { - a->value = getPIDSetting(a, dt); - } -} - -//============================================================================= - -double getPIDSetting(struct TAction* a, double dt) -// -// Input: a = an action object -// dt = current time step (days) -// Output: returns a new link setting -// Purpose: computes a new setting for a link subject to a PID controller. -// -// Note: a->kp = gain coefficient, -// a->ki = integral time (minutes) -// a->k2 = derivative time (minutes) -// a->e1 = error from previous time step -// a->e2 = error from two time steps ago -{ - double e0, setting; - double p, i, d, update; - double tolerance = 0.0001; - - // --- convert time step from days to minutes - dt *= 1440.0; - - // --- determine relative error in achieving controller set point - e0 = SetPoint - ControlValue; - if ( fabs(e0) > TINY ) - { - if ( SetPoint != 0.0 ) e0 = e0/SetPoint; - else e0 = e0/ControlValue; - } - - // --- reset previous errors to 0 if controller gets stuck - if (fabs(e0 - a->e1) < tolerance) - { - a->e2 = 0.0; - a->e1 = 0.0; - } - - // --- use the recursive form of the PID controller equation to - // determine the new setting for the controlled link - p = (e0 - a->e1); - if ( a->ki == 0.0 ) i = 0.0; - else i = e0 * dt / a->ki; - d = a->kd * (e0 - 2.0*a->e1 + a->e2) / dt; - update = a->kp * (p + i + d); - if ( fabs(update) < tolerance ) update = 0.0; - setting = Link[a->link].targetSetting + update; - - // --- update previous errors - a->e2 = a->e1; - a->e1 = e0; - - // --- check that new setting lies within feasible limits - if ( setting < 0.0 ) setting = 0.0; - if (Link[a->link].type != PUMP && setting > 1.0 ) setting = 1.0; - return setting; -} - -//============================================================================= - -void updateActionList(struct TAction* a) -// -// Input: a = an action object -// Output: none -// Purpose: adds a new action to the list of actions to be taken. -// -{ - struct TActionList* listItem; - struct TAction* a1; - double priority = Rules[a->rule].priority; - - // --- check if link referred to in action is already listed - listItem = ActionList; - while ( listItem ) - { - a1 = listItem->action; - if ( !a1 ) break; - if ( a1->link == a->link ) - { - // --- replace old action if new action has higher priority - if ( priority > Rules[a1->rule].priority ) listItem->action = a; - return; - } - listItem = listItem->next; - } - - // --- action not listed so add it to ActionList //5.2.1 - if ( !listItem ) - { - listItem = (struct TActionList *) malloc(sizeof(struct TActionList)); - listItem->next = ActionList; - ActionList = listItem; - } - listItem->action = a; -} - -//============================================================================= - -int executeActionList(DateTime currentTime) -// -// Input: currentTime = current date/time of the simulation -// Output: returns number of new actions taken -// Purpose: executes all actions required by fired control rules. -// -{ - struct TActionList* listItem; - struct TActionList* nextItem; - struct TAction* a1; - int count = 0; - - listItem = ActionList; - while ( listItem ) - { - a1 = listItem->action; - if ( !a1 ) break; - if ( a1->link >= 0 ) - { - if ( Link[a1->link].targetSetting != a1->value ) - { - Link[a1->link].targetSetting = a1->value; - if ( RptFlags.controls && a1->curve < 0 - && a1->tseries < 0 && a1->attribute != r_PID ) - report_writeControlAction(currentTime, Link[a1->link].ID, - a1->value, Rules[a1->rule].ID); - count++; - } - } - nextItem = listItem->next; - listItem = nextItem; - } - return count; -} - -//============================================================================= - -int evaluatePremise(struct TPremise* p, double tStep) -// -// Input: p = a control rule premise condition -// tStep = current time step (days) -// Output: returns TRUE if the condition is true or FALSE otherwise -// Purpose: evaluates the truth of a control rule premise condition. -// -{ - double lhsValue, rhsValue; - int result = FALSE; - - // --- check if left hand side (lhs) of premise is an expression - if (p->exprIndex >= 0) - lhsValue = mathexpr_eval(Expression[p->exprIndex].expression, - getNamedVariableValue); - - // --- otherwise get value of the lhs variable - else - lhsValue = getVariableValue(p->lhsVar); - - // --- if right hand side (rhs) of premise is a variable then get its value - if ( p->value == MISSING ) rhsValue = getVariableValue(p->rhsVar); - else rhsValue = p->value; - if ( lhsValue == MISSING || rhsValue == MISSING ) return FALSE; - - // --- compare the lhs of the premise to the rhs - switch (p->lhsVar.attribute) - { - case r_TIME: - case r_CLOCKTIME: - return compareTimes(lhsValue, p->relation, rhsValue, tStep/2.0); - case r_TIMEOPEN: - case r_TIMECLOSED: - result = compareTimes(lhsValue, p->relation, rhsValue, tStep/2.0); - ControlValue = lhsValue * 24.0; // convert time from days to hours - return result; - default: - return compareValues(lhsValue, p->relation, rhsValue); - } -} - -//============================================================================= - -double getVariableValue(struct TVariable v) -{ - int i = -1; // a node index - int j = -1; // a link index - - if (v.object == r_GAGE) - return getRainValue(v); - if (v.object == r_NODE) i = v.index; - if (v.object == r_LINK) j = v.index; - - switch ( v.attribute ) - { - case r_TIME: - return ElapsedTime; - - case r_DATE: - return CurrentDate; - - case r_CLOCKTIME: - return CurrentTime; - - case r_DAY: - return datetime_dayOfWeek(CurrentDate); - - case r_MONTH: - return datetime_monthOfYear(CurrentDate); - - case r_DAYOFYEAR: - return datetime_dayOfYear(CurrentDate); - - case r_STATUS: - if ( j < 0 || - (Link[j].type != CONDUIT && Link[j].type != PUMP) ) return MISSING; - else return Link[j].setting; - - case r_SETTING: - if ( j < 0 || (Link[j].type != PUMP && - Link[j].type != ORIFICE && - Link[j].type != WEIR) ) - return MISSING; - else return Link[j].setting; - - case r_FLOW: - if ( j < 0 ) return MISSING; - else return Link[j].direction*Link[j].newFlow*UCF(FLOW); - - case r_FULLFLOW: - case r_FULLDEPTH: - case r_VELOCITY: - case r_LENGTH: - case r_SLOPE: - if ( j < 0 ) return MISSING; - else if (Link[j].type != CONDUIT) return MISSING; - switch (v.attribute) - { - case r_FULLFLOW: return Link[j].qFull * UCF(FLOW); - case r_FULLDEPTH: return Link[j].xsect.yFull * UCF(LENGTH); - case r_VELOCITY: - return link_getVelocity(j, Link[j].newFlow, Link[j].newDepth) - * UCF(LENGTH); - case r_LENGTH: return Conduit[Link[j].subIndex].length * UCF(LENGTH); - case r_SLOPE: return Conduit[Link[j].subIndex].slope; - default: return MISSING; - } - case r_DEPTH: - if ( j >= 0 ) return Link[j].newDepth*UCF(LENGTH); - else if ( i >= 0 ) - return Node[i].newDepth*UCF(LENGTH); - else return MISSING; - - case r_MAXDEPTH: - if (i >= 0) return Node[i].fullDepth*UCF(LENGTH); - else return MISSING; - - case r_HEAD: - if ( i < 0 ) return MISSING; - return (Node[i].newDepth + Node[i].invertElev) * UCF(LENGTH); - - case r_VOLUME: - if ( i < 0 ) return MISSING; - return (Node[i].newVolume * UCF(VOLUME)); - - case r_INFLOW: - if ( i < 0 ) return MISSING; - else return Node[i].newLatFlow*UCF(FLOW); - - case r_TIMEOPEN: - if ( j < 0 ) return MISSING; - if ( Link[j].setting <= 0.0 ) return MISSING; - return CurrentDate + CurrentTime - Link[j].timeLastSet; - - case r_TIMECLOSED: - if ( j < 0 ) return MISSING; - if ( Link[j].setting > 0.0 ) return MISSING; - return CurrentDate + CurrentTime - Link[j].timeLastSet; - - default: return MISSING; - } -} - -//============================================================================= - -double getRainValue(struct TVariable v) -// -// Input: v = a rule premise variable for a rain gage -// Output: returns current or past rainfall amount -// Purpose: retrieves either the current rainfall intensity or the past -// rainfall total for a rain gage. -// -{ - if (v.index < 0) return MISSING; - else if (Gage[v.index].isUsed == FALSE) return 0.0; - else if (v.attribute == 0) - return Gage[v.index].rainfall; - else return gage_getPastRain(v.index, v.attribute); -} - -//============================================================================= - -int compareTimes(double lhsValue, int relation, double rhsValue, double halfStep) -// -// Input: lhsValue = date/time value on left hand side of relation -// relation = relational operator code (see RuleRelation enumeration) -// rhsValue = date/time value on right hand side of relation -// halfStep = 1/2 the current time step (days) -// Output: returns TRUE if time relation is satisfied -// Purpose: evaluates the truth of a relation between two date/times. -// -{ - if ( relation == EQ ) - { - if ( lhsValue >= rhsValue - halfStep - && lhsValue < rhsValue + halfStep ) return TRUE; - return FALSE; - } - else if ( relation == NE ) - { - if ( lhsValue < rhsValue - halfStep - || lhsValue >= rhsValue + halfStep ) return TRUE; - return FALSE; - } - else return compareValues(lhsValue, relation, rhsValue); -} - -//============================================================================= - -int compareValues(double lhsValue, int relation, double rhsValue) -// Input: lhsValue = value on left hand side of relation -// relation = relational operator code (see RuleRelation enumeration) -// rhsValue = value on right hand side of relation -// Output: returns TRUE if relation is satisfied -// Purpose: evaluates the truth of a relation between two values. -{ - SetPoint = rhsValue; - ControlValue = lhsValue; - switch (relation) - { - case EQ: if ( lhsValue == rhsValue ) return TRUE; break; - case NE: if ( lhsValue != rhsValue ) return TRUE; break; - case LT: if ( lhsValue < rhsValue ) return TRUE; break; - case LE: if ( lhsValue <= rhsValue ) return TRUE; break; - case GT: if ( lhsValue > rhsValue ) return TRUE; break; - case GE: if ( lhsValue >= rhsValue ) return TRUE; break; - } - return FALSE; -} - -//============================================================================= - -void clearActionList(void) -// -// Input: none -// Output: none -// Purpose: clears the list of actions to be executed. -// -{ - struct TActionList* listItem; - listItem = ActionList; - while ( listItem ) - { - listItem->action = NULL; - listItem = listItem->next; - } -} - -//============================================================================= - -void deleteActionList(void) -// -// Input: none -// Output: none -// Purpose: frees the memory used to hold the list of actions to be executed. -// -{ - struct TActionList* listItem; - struct TActionList* nextItem; - listItem = ActionList; - while ( listItem ) - { - nextItem = listItem->next; - free(listItem); - listItem = nextItem; - } - ActionList = NULL; -} - -//============================================================================= - -void deleteRules(void) -// -// Input: none -// Output: none -// Purpose: frees the memory used for all of the control rules. -// -{ - struct TPremise* p; - struct TPremise* pnext; - struct TAction* a; - struct TAction* anext; - int r; - for (r=0; rnext; - free(p); - p = pnext; - } - a = Rules[r].thenActions; - while (a ) - { - anext = a->next; - free(a); - a = anext; - } - a = Rules[r].elseActions; - while (a ) - { - anext = a->next; - free(a); - a = anext; - } - } - FREE(Rules); - RuleCount = 0; -} - -//============================================================================= - -int findExactMatch(char *s, char *keyword[]) -// -// Input: s = character string -// keyword = array of keyword strings -// Output: returns index of keyword which matches s or -1 if no match found -// Purpose: finds exact match between string and array of keyword strings. -// -{ - int i = 0; - while (keyword[i] != NULL) - { - if ( strcomp(s, keyword[i]) ) return(i); - i++; - } - return(-1); -} - -//============================================================================= diff --git a/src/culvert.c b/src/culvert.c deleted file mode 100644 index 5f62877b7..000000000 --- a/src/culvert.c +++ /dev/null @@ -1,411 +0,0 @@ -//----------------------------------------------------------------------------- -// culvert.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Culvert equations for SWMM5 -// -// Computes flow reduction in a culvert-type conduit due to -// inlet control using equations from the FHWA HEC-5 circular. -// -// Update History -// ============== -// Build 5.1.013: -// - C parameter corrected for Arch, Corrugated Metal, Mitered culvert. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include "findroot.h" -#include "headers.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -enum CulvertParam {FORM, K, M, C, Y}; -static const int MAX_CULVERT_CODE = 57; -static const double Params[58][5] = { - -// FORM K M C Y -//------------------------------------ - {0.0, 0.0, 0.0, 0.0, 0.00}, - - //Circular concrete - {1.0, 0.0098, 2.00, 0.0398, 0.67}, //Square edge w/headwall - {1.0, 0.0018, 2.00, 0.0292, 0.74}, //Groove end w/headwall - {1.0, 0.0045, 2.00, 0.0317, 0.69}, //Groove end projecting - - //Circular Corrugated Metal Pipe - {1.0, 0.0078, 2.00, 0.0379, 0.69}, //Headwall - {1.0, 0.0210, 1.33, 0.0463, 0.75}, //Mitered to slope - {1.0, 0.0340, 1.50, 0.0553, 0.54}, //Projecting - - //Circular Pipe, Beveled Ring Entrance - {1.0, 0.0018, 2.50, 0.0300, 0.74}, //Beveled ring, 45 deg bevels - {1.0, 0.0018, 2.50, 0.0243, 0.83}, //Beveled ring, 33.7 deg bevels - - //Rectangular Box with Flared Wingwalls - {1.0, 0.026, 1.0, 0.0347, 0.81}, //30-75 deg. wingwall flares - {1.0, 0.061, 0.75, 0.0400, 0.80}, //90 or 15 deg. wingwall flares - {1.0, 0.061, 0.75, 0.0423, 0.82}, //0 deg. wingwall flares (striaght sides) - - //Rectanglar Box with Flared Wingwalls & Top Edge Bevel - {2.0, 0.510, 0.667, 0.0309, 0.80}, //45 deg. flare; 0.43D top edge bevel - {2.0, 0.486, 0.667, 0.0249, 0.83}, //18-33.7 deg flare; 0.083D top edge bevel - - //Rectangular Box; 90-deg Headwall; Chamfered or Beveled Inlet Edges - {2.0, 0.515, 0.667, 0.0375, 0.79}, //chamfered 3/4-in - {2.0, 0.495, 0.667, 0.0314, 0.82}, //beveled 1/2-in/ft at 45 deg (1:1) - {2.0, 0.486, 0.667, 0.0252, 0.865}, //beveled 1-in/ft at 33.7 deg (1:1.5) - - //Rectangular Box; Skewed Headwall; Chamfered or Beveled Inlet Edges - {2.0, 0.545, 0.667, 0.04505,0.73}, //3/4" chamfered edge, 45 deg skewed headwall - {2.0, 0.533, 0.667, 0.0425, 0.705}, //3/4" chamfered edge, 30 deg skewed headwall - {2.0, 0.522, 0.667, 0.0402, 0.68}, //3/4" chamfered edge, 15 deg skewed headwall - {2.0, 0.498, 0.667, 0.0327, 0.75}, //45 deg beveled edge, 10-45 deg skewed headwall - - //Rectangular box, Non-offset Flared Wingwalls; 3/4" Chamfer at Top of Inlet - {2.0, 0.497, 0.667, 0.0339, 0.803}, //45 deg (1:1) wingwall flare - {2.0, 0.493, 0.667, 0.0361, 0.806}, //18.4 deg (3:1) wingwall flare - {2.0, 0.495, 0.667, 0.0386, 0.71}, //18.4 deg (3:1) wingwall flare, 30 deg inlet skew - - //Rectangular box, Offset Flared Wingwalls, Beveled Edge at Inlet Top - {2.0, 0.497, 0.667, 0.0302, 0.835}, //45 deg (1:1) flare, 0.042D top edge bevel - {2.0, 0.495, 0.667, 0.0252, 0.881}, //33.7 deg (1.5:1) flare, 0.083D top edge bevel - {2.0, 0.493, 0.667, 0.0227, 0.887}, //18.4 deg (3:1) flare, 0.083D top edge bevel - - // Corrugated Metal Box - {1.0, 0.0083, 2.00, 0.0379, 0.69}, //90 deg headwall - {1.0, 0.0145, 1.75, 0.0419, 0.64}, //Thick wall projecting - {1.0, 0.0340, 1.50, 0.0496, 0.57}, //Thin wall projecting - - // Horizontal Ellipse Concrete - {1.0, 0.0100, 2.00, 0.0398, 0.67}, //Square edge w/headwall - {1.0, 0.0018, 2.50, 0.0292, 0.74}, //Grooved end w/headwall - {1.0, 0.0045, 2.00, 0.0317, 0.69}, //Grooved end projecting - - // Vertical Ellipse Concrete - {1.0, 0.0100, 2.00, 0.0398, 0.67}, //Square edge w/headwall - {1.0, 0.0018, 2.50, 0.0292, 0.74}, //Grooved end w/headwall - {1.0, 0.0095, 2.00, 0.0317, 0.69}, //Grooved end projecting - - // Pipe Arch, 18" Corner Radius, Corrugated Metal - {1.0, 0.0083, 2.00, 0.0379, 0.69}, //90 deg headwall - {1.0, 0.0300, 1.00, 0.0463, 0.75}, //Mitered to slope - {1.0, 0.0340, 1.50, 0.0496, 0.57}, //Projecting - - // Pipe Arch, 18" Corner Radius, Corrugated Metal - {1.0, 0.0300, 1.50, 0.0496, 0.57}, //Projecting - {1.0, 0.0088, 2.00, 0.0368, 0.68}, //No bevels - {1.0, 0.0030, 2.00, 0.0269, 0.77}, //33.7 deg bevels - - // Pipe Arch, 31" Corner Radius, Corrugated Metal - {1.0, 0.0300, 1.50, 0.0496, 0.57}, //Projecting - {1.0, 0.0088, 2.00, 0.0368, 0.68}, //No bevels - {1.0, 0.0030, 2.00, 0.0269, 0.77}, //33.7 deg. bevels - - // Arch, Corrugated Metal - {1.0, 0.0083, 2.00, 0.0379, 0.69}, //90 deg headwall - {1.0, 0.0300, 1.00, 0.0473, 0.75}, //Mitered to slope - {1.0, 0.0340, 1.50, 0.0496, 0.57}, //Thin wall projecting - - // Circular Culvert - {2.0, 0.534, 0.555, 0.0196, 0.90}, //Smooth tapered inlet throat - {2.0, 0.519, 0.640, 0.0210, 0.90}, //Rough tapered inlet throat - - // Elliptical Inlet Face - {2.0, 0.536, 0.622, 0.0368, 0.83}, //Tapered inlet, beveled edges - {2.0, 0.5035,0.719, 0.0478, 0.80}, //Tapered inlet, square edges - {2.0, 0.547, 0.800, 0.0598, 0.75}, //Tapered inlet, thin edge projecting - - // Rectangular - {2.0, 0.475, 0.667, 0.0179, 0.97}, //Tapered inlet throat - - // Rectangular Concrete - {2.0, 0.560, 0.667, 0.0446, 0.85}, //Side tapered, less favorable edges - {2.0, 0.560, 0.667, 0.0378, 0.87}, //Side tapered, more favorable edges - - // Rectangular Concrete - {2.0, 0.500, 0.667, 0.0446, 0.65}, //Slope tapered, less favorable edges - {2.0, 0.500, 0.667, 0.0378, 0.71} //Slope tapered, more favorable edges - - }; - -//----------------------------------------------------------------------------- -// Culvert data structure -//----------------------------------------------------------------------------- -typedef struct -{ - double yFull; // full depth of culvert (ft) - double scf; // slope correction factor - double dQdH; // Derivative of flow w.r.t. head - double qc; // Unsubmerged critical flow - double kk; - double mm; // Coeffs. for unsubmerged flow - double ad; - double hPlus; // Intermediate terms - TXsect* xsect; // Pointer to culvert cross section -} TCulvert; - -//----------------------------------------------------------------------------- -// External functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// double culvert_getInflow - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static double getUnsubmergedFlow(int code, double h, TCulvert* culvert); -static double getSubmergedFlow(int code, double h, TCulvert* culvert); -static double getTransitionFlow(int code, double h, double h1, double h2, - TCulvert* culvert); -static double getForm1Flow(double h, TCulvert* culvert); -static double form1Eqn(double yc, void* p); -/* -static void report_CulvertControl(int j, double q0, double q, int condition, - double yRatio); //for debugging only -*/ - -//============================================================================= - -double culvert_getInflow(int j, double q0, double h) -// -// Input: j = link index -// q0 = unmodified flow rate (cfs) -// h = upstream head (ft) -// Output: returns modified flow rate through culvert (cfs) -// Purpose: uses FHWA HEC-5 equations to find flow through inlet -// controlled culverts -// -{ - int code, //culvert type code number - k, //conduit index - condition; //flow condition - double y, //current depth (ft) - y1, //unsubmerged depth limit (ft) - y2, //submerged depth limit (ft) - q; //inlet-controlled flow (cfs) - TCulvert culvert; //intermediate results - - // --- check that we have a culvert conduit - if ( Link[j].type != CONDUIT ) return q0; - culvert.xsect = &Link[j].xsect; - code = culvert.xsect->culvertCode; - if ( code <= 0 || code > MAX_CULVERT_CODE ) return q0; - - // --- compute often-used variables - k = Link[j].subIndex; - culvert.yFull = culvert.xsect->yFull; - culvert.ad = culvert.xsect->aFull * sqrt(culvert.yFull); - - // --- slope correction factor (-7 for mitered inlets, 0.5 for others) - switch (code) - { - case 5: - case 37: - case 46: culvert.scf = -7.0 * Conduit[k].slope; break; - default: culvert.scf = 0.5 * Conduit[k].slope; - } - - // --- find head relative to culvert's upstream invert - // (can be greater than yFull when inlet is submerged) - y = h - (Node[Link[j].node1].invertElev + Link[j].offset1); - - // --- check for submerged flow (based on FHWA criteria of Q/AD > 4) - y2 = culvert.yFull * (16.0 * Params[code][C] + Params[code][Y] - culvert.scf); - if ( y >= y2 ) - { - q = getSubmergedFlow(code, y, &culvert); - condition = 2; - } - else - { - // --- check for unsubmerged flow (based on arbitrary limit of 0.95 full) - y1 = 0.95 * culvert.yFull; - if ( y <= y1 ) - { - q = getUnsubmergedFlow(code, y, &culvert); - condition = 1; - } - // --- flow is in transition zone - else - { - q = getTransitionFlow(code, y, y1, y2, &culvert); - condition = 0; - } - } - - // --- check if inlet controls and replace conduit's value of dq/dh - if ( q < q0 ) - { - // --- for debugging only - //if ( RptFlags.controls ) report_CulvertControl(j, q0, q, condition, - // y / culvert.yFull); - - Link[j].inletControl = TRUE; - Link[j].dqdh = culvert.dQdH; - return q; - } - else return q0; -} - -//============================================================================= - -double getUnsubmergedFlow(int code, double h, TCulvert* culvert) -// -// Input: code = culvert type code number -// h = inlet water depth above culvert invert -// culvert = pointer to a culvert data structure -// Output: returns flow rate; -// computes value of variable Dqdh -// Purpose: computes flow rate and its derivative for unsubmerged -// culvert inlet. -// -{ - double arg; - double q; - - // --- assign shared variables - culvert->kk = Params[code][K]; - culvert->mm = Params[code][M]; - arg = h / culvert->yFull / culvert->kk; - - // --- evaluate correct equation form - if ( Params[code][FORM] == 1.0) - { - q = getForm1Flow(h, culvert); - } - else q = culvert->ad * pow(arg, 1.0/culvert->mm); - culvert->dQdH = q / h / culvert->mm; - return q; -} - -//============================================================================= - -double getSubmergedFlow(int code, double h, TCulvert* culvert) -// -// Input: code = culvert type code number -// h = inlet head (ft) -// culvert = pointer to a culvert data structure -// Output: returns flow rate; -// computes value of Dqdh -// Purpose: computes flow rate and its derivative for submerged -// culvert inlet. -// -{ - double cc = Params[code][C]; - double yy = Params[code][Y]; - double arg = (h/culvert->yFull - yy + culvert->scf) / cc ; - double q; - - if ( arg <= 0.0 ) - { - culvert->dQdH = 0.0; - return BIG; - } - q = sqrt(arg) * culvert->ad; - culvert->dQdH = 0.5 * q / arg / culvert->yFull / cc; - return q; -} - -//============================================================================= - -double getTransitionFlow(int code, double h, double h1, double h2, TCulvert* culvert) -// -// Input: code = culvert type code number -// h = inlet water depth above culvert invert (ft) -// h1 = head limit for unsubmerged condition (ft) -// h2 = head limit for submerged condition (ft) -// culvert = pointer to a culvert data structure -// Output: returns flow rate )cfs); -// computes value of Dqdh (cfs/ft) -// Purpose: computes flow rate and its derivative for inlet-controlled flow -// when inlet water depth lies in the transition range between -// submerged and unsubmerged conditions. -// -{ - double q1 = getUnsubmergedFlow(code, h1, culvert); - double q2 = getSubmergedFlow(code, h2, culvert); - double q = q1 + (q2 - q1) * (h - h1) / (h2 - h1); - culvert->dQdH = (q2 - q1) / (h2 - h1); - return q; -} - -//============================================================================= - -double getForm1Flow(double h, TCulvert* culvert) -// -// Input: h = inlet water depth above culvert invert -// culvert = pointer to a culvert data structure -// Output: returns inlet controlled flow rate -// Purpose: computes inlet-controlled flow rate for unsubmerged culvert -// using FHWA Equation Form1. -// -// See pages 195-196 of FHWA HEC-5 (2001) for details. -// -{ - // --- save re-used terms in culvert structure - culvert->hPlus = h / culvert->yFull + culvert->scf; - - // --- use Ridder's method to solve Equation Form 1 for critical depth - // between a range of 0.01h and h - findroot_Ridder(0.01*h, h, 0.001, form1Eqn, culvert); - - // --- return the flow value used in evaluating Equation Form 1 - return culvert->qc; -} - -//============================================================================= - -double form1Eqn(double yc, void* p) -// -// Input: yc = critical depth -// p = pointer to a TCulvert object -// Output: returns residual error -// Purpose: evaluates the error in satisfying FHWA culvert Equation Form1: -// -// h/yFull + 0.5*s = yc/yFull + yh/2/yFull + K[ac/aFull*sqrt(g*yh/yFull)]^M -// -// for a given value of critical depth yc where: -// h = inlet depth above culvert invert -// s = culvert slope -// yFull = full depth of culvert -// yh = hydraulic depth at critical depth -// ac = flow area at critical depth -// g = accel. of gravity -// K and M = coefficients -// -{ - double ac, wc, yh; - TCulvert* culvert = (TCulvert *)p; - - ac = xsect_getAofY(culvert->xsect, yc); - wc = xsect_getWofY(culvert->xsect, yc); - yh = ac/wc; - - culvert->qc = ac * sqrt(GRAVITY * yh); - return culvert->hPlus - yc/culvert->yFull - yh/2.0/culvert->yFull - - culvert->kk * pow(culvert->qc/culvert->ad, culvert->mm); -} - -//============================================================================= -/* -void report_CulvertControl(int j, double q0, double q, int condition, double yRatio) -// -// Used for debugging only -// -{ - static char* conditionTxt[] = {"transition", "unsubmerged", "submerged"}; - char theDate[12]; - char theTime[9]; - DateTime aDate = getDateTime(NewRoutingTime); - datetime_dateToStr(aDate, theDate); - datetime_timeToStr(aDate, theTime); - fprintf(Frpt.file, - "\n %11s: %8s Culvert %s flow reduced from %.3f to %.3f cfs for %s flow (%.2f).", - theDate, theTime, Link[j].ID, q0, q, conditionTxt[condition], yRatio); -} -*/ diff --git a/src/datetime.c b/src/datetime.c deleted file mode 100644 index d96ef7f3b..000000000 --- a/src/datetime.c +++ /dev/null @@ -1,528 +0,0 @@ -//----------------------------------------------------------------------------- -// datetime.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// DateTime functions. -// -// Update History -// ============== -// Build 5.1.011: -// - decodeTime() no longer rounds up. -// - New getTimeStamp function added. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include -#include -#include "datetime.h" - -// Macro to convert charcter x to upper case -#define UCHAR(x) (((x) >= 'a' && (x) <= 'z') ? ((x)&~32) : (x)) - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -static const char* MonthTxt[] = - {"JAN", "FEB", "MAR", "APR", - "MAY", "JUN", "JUL", "AUG", - "SEP", "OCT", "NOV", "DEC"}; -static const int DaysPerMonth[2][12] = // days per month - {{31, 28, 31, 30, 31, 30, // normal years - 31, 31, 30, 31, 30, 31}, - {31, 29, 31, 30, 31, 30, // leap years - 31, 31, 30, 31, 30, 31}}; -static const int DateDelta = 693594; // days since 01/01/00 -static const double SecsPerDay = 86400.; // seconds per day - -//----------------------------------------------------------------------------- -// Shared variables -//----------------------------------------------------------------------------- -static int DateFormat; - - -//============================================================================= - -void divMod(int n, int d, int* result, int* remainder) - -// Input: n = numerator -// d = denominator -// Output: result = integer part of n/d -// remainder = remainder of n/d -// Purpose: finds integer part and remainder of n/d. - -{ - if (d == 0) - { - *result = 0; - *remainder = 0; - } - else - { - *result = n/d; - *remainder = n - d*(*result); - } -} - -//============================================================================= - -int isLeapYear(int year) - -// Input: year = a year -// Output: returns 1 if year is a leap year, 0 if not -// Purpose: determines if year is a leap year. - -{ - if ((year % 4 == 0) - && ((year % 100 != 0) - || (year % 400 == 0))) return 1; - else return 0; -} - -//============================================================================= - -int datetime_findMonth(char* month) - -// Input: month = month of year as character string -// Output: returns: month of year as a number (1-12) -// Purpose: finds number (1-12) of month. - -{ - int i; - for (i = 0; i < 12; i++) - { - if (UCHAR(month[0]) == MonthTxt[i][0] - && UCHAR(month[1]) == MonthTxt[i][1] - && UCHAR(month[2]) == MonthTxt[i][2]) return i+1; - } - return 0; -} - -//============================================================================= - -DateTime datetime_encodeDate(int year, int month, int day) - -// Input: year = a year -// month = a month (1 to 12) -// day = a day of month -// Output: returns encoded value of year-month-day -// Purpose: encodes year-month-day to a DateTime value. - -{ - int i, j; - i = isLeapYear(year); - if ((year >= 1) - && (year <= 9999) - && (month >= 1) - && (month <= 12) - && (day >= 1) - && (day <= DaysPerMonth[i][month-1])) - { - for (j = 0; j < month-1; j++) day += DaysPerMonth[i][j]; - i = year - 1; - i = i*365 + i/4 - i/100 + i/400 + day - DateDelta; - return i; - } - else return -DateDelta; -} - -//============================================================================= - -DateTime datetime_encodeTime(int hour, int minute, int second) - -// Input: hour = hour of day (0-24) -// minute = minute of hour (0-60) -// second = seconds of minute (0-60) -// Output: returns time encoded as fractional part of a day -// Purpose: encodes hour:minute:second to a DateTime value - -{ - int s; - if ((hour >= 0) - && (minute >= 0) - && (second >= 0)) - { - s = (hour * 3600 + minute * 60 + second); - return (double)s/SecsPerDay; - } - else return 0.0; -} - -//============================================================================= - -void datetime_decodeDate(DateTime date, int* year, int* month, int* day) - -// Input: date = encoded date/time value -// Output: year = 4-digit year -// month = month of year (1-12) -// day = day of month -// Purpose: decodes DateTime value to year-month-day. - -{ - int D1, D4, D100, D400; - int y, m, d, i, k, t; - - D1 = 365; //365 - D4 = D1 * 4 + 1; //1461 - D100 = D4 * 25 - 1; //36524 - D400 = D100 * 4 + 1; //146097 - - t = (int)(floor (date)) + DateDelta; - if (t <= 0) - { - *year = 0; - *month = 1; - *day = 1; - } - else - { - t--; - y = 1; - while (t >= D400) - { - t -= D400; - y += 400; - } - divMod(t, D100, &i, &d); - if (i == 4) - { - i--; - d += D100; - } - y += i*100; - divMod(d, D4, &i, &d); - y += i*4; - divMod(d, D1, &i, &d); - if (i == 4) - { - i--; - d += D1; - } - y += i; - k = isLeapYear(y); - m = 1; - for (;;) - { - i = DaysPerMonth[k][m-1]; - if (d < i) break; - d -= i; - m++; - } - *year = y; - *month = m; - *day = d + 1; - } -} - -//============================================================================= - -void datetime_decodeTime(DateTime time, int* h, int* m, int* s) - -// Input: time = decimal fraction of a day -// Output: h = hour of day (0-23) -// m = minute of hour (0-59) -// s = second of minute (0-59) -// Purpose: decodes DateTime value to hour:minute:second. - -{ - int secs; - int mins; - double fracDay = (time - floor(time)) * SecsPerDay; - secs = (int)(floor(fracDay + 0.5)); - if ( secs >= 86400 ) secs = 86399; - divMod(secs, 60, &mins, s); - divMod(mins, 60, h, m); - if ( *h > 23 ) *h = 0; -} - -//============================================================================= - -void datetime_dateToStr(DateTime date, char* s) - -// Input: date = encoded date/time value -// Output: s = formatted date string -// Purpose: represents DateTime date value as a formatted string. - -{ - int y, m, d; - datetime_decodeDate(date, &y, &m, &d); - switch (DateFormat) - { - case Y_M_D: - snprintf(s, DATE_STR_SIZE, "%4d-%3s-%02d", y, MonthTxt[m-1], d); - break; - - case M_D_Y: - //sprintf(dateStr, "%3s-%02d-%4d", MonthTxt[m-1], d, y); - snprintf(s, DATE_STR_SIZE, "%02d/%02d/%04d", m, d, y); - break; - - default: - snprintf(s, DATE_STR_SIZE, "%02d-%3s-%4d", d, MonthTxt[m-1], y); - } -} - -void datetime_timeToStr(DateTime time, char* s) - -// Input: time = decimal fraction of a day -// Output: s = time in hr:min:sec format -// Purpose: represents DateTime time value as a formatted string. - -{ - int hr, min, sec; - datetime_decodeTime(time, &hr, &min, &sec); - snprintf(s, TIME_STR_SIZE, "%02d:%02d:%02d", hr, min, sec); -} - -//============================================================================= - -int datetime_strToDate(char* s, DateTime* d) - -// Input: s = date as string -// Output: d = encoded date; -// returns 1 if conversion successful, 0 if not -// Purpose: converts string date s to DateTime value. -// -{ - int yr = 0, mon = 0, day = 0, n; - char month[4]; - char sep1, sep2; - *d = -DateDelta; - if (strchr(s, '-') || strchr(s, '/')) - { - switch (DateFormat) - { - case Y_M_D: - n = sscanf(s, "%d%c%d%c%d", &yr, &sep1, &mon, &sep2, &day); - if ( n < 3 ) - { - mon = 0; - n = sscanf(s, "%d%c%3s%c%d", &yr, &sep1, month, &sep2, &day); - if ( n < 3 ) return 0; - } - break; - - case D_M_Y: - n = sscanf(s, "%d%c%d%c%d", &day, &sep1, &mon, &sep2, &yr); - if ( n < 3 ) - { - mon = 0; - n = sscanf(s, "%d%c%3s%c%d", &day, &sep1, month, &sep2, &yr); - if ( n < 3 ) return 0; - } - break; - - default: // M_D_Y - n = sscanf(s, "%d%c%d%c%d", &mon, &sep1, &day, &sep2, &yr); - if ( n < 3 ) - { - mon = 0; - n = sscanf(s, "%3s%c%d%c%d", month, &sep1, &day, &sep2, &yr); - if ( n < 3 ) return 0; - } - } - if (mon == 0) mon = datetime_findMonth(month); - *d = datetime_encodeDate(yr, mon, day); - } - if (*d == -DateDelta) return 0; - else return 1; -} - -//============================================================================= - -int datetime_strToTime(char* s, DateTime* t) - -// Input: s = time as string -// Output: t = encoded time, -// returns 1 if conversion successful, 0 if not -// Purpose: converts a string time to a DateTime value. -// Note: accepts time as hr:min:sec or as decimal hours. - -{ - int n, hr, min = 0, sec = 0; - char *endptr; - - // Attempt to read time as decimal hours - *t = strtod(s, &endptr); - if ( *endptr == 0 ) - { - *t /= 24.0; - return 1; - } - - // Read time in hr:min:sec format - *t = 0.0; - n = sscanf(s, "%d:%d:%d", &hr, &min, &sec); - if ( n == 0 ) return 0; - *t = datetime_encodeTime(hr, min, sec); - if ( (hr >= 0) && (min >= 0) && (sec >= 0) ) return 1; - else return 0; -} - -//============================================================================= - -void datetime_setDateFormat(int fmt) - -// Input: fmt = date format code -// Output: none -// Purpose: sets date format - -{ - if ( fmt >= Y_M_D && fmt <= M_D_Y) DateFormat = fmt; -} - -//============================================================================= - -DateTime datetime_addSeconds(DateTime date1, double seconds) - -// Input: date1 = an encoded date/time value -// seconds = number of seconds to add to date1 -// Output: returns updated value of date1 -// Purpose: adds a given number of seconds to a date/time. - -{ - double d = floor(date1); - int h, m, s; - datetime_decodeTime(date1, &h, &m, &s); - return d + (3600.0*h + 60.0*m + s + seconds)/SecsPerDay; -} - -//============================================================================= - -DateTime datetime_addDays(DateTime date1, DateTime date2) - -// Input: date1 = an encoded date/time value -// date2 = decimal days to be added to date1 -// Output: returns date1 + date2 -// Purpose: adds a given number of decimal days to a date/time. - -{ - double d1 = floor(date1); - double d2 = floor(date2); - int h1, m1, s1; - int h2, m2, s2; - datetime_decodeTime(date1, &h1, &m1, &s1); - datetime_decodeTime(date2, &h2, &m2, &s2); - return d1 + d2 + datetime_encodeTime(h1+h2, m1+m2, s1+s2); -} - -//============================================================================= - -long datetime_timeDiff(DateTime date1, DateTime date2) - -// Input: date1 = an encoded date/time value -// date2 = an encoded date/time value -// Output: returns date1 - date2 in seconds -// Purpose: finds number of seconds between two dates. - -{ - double d1 = floor(date1); - double d2 = floor(date2); - int h, m, s; - long s1, s2, secs; - datetime_decodeTime(date1, &h, &m, &s); - s1 = 3600*h + 60*m + s; - datetime_decodeTime(date2, &h, &m, &s); - s2 = 3600*h + 60*m + s; - secs = (int)(floor((d1 - d2)*SecsPerDay + 0.5)); - secs += (s1 - s2); - return secs; -} - -//============================================================================= - -int datetime_monthOfYear(DateTime date) - -// Input: date = an encoded date/time value -// Output: returns index of month of year (1..12) -// Purpose: finds month of year (Jan = 1 ...) for a given date. - -{ - int year, month, day; - datetime_decodeDate(date, &year, &month, &day); - return month; -} - -//============================================================================= - -int datetime_dayOfYear(DateTime date) - -// Input: date = an encoded date/time value -// Output: returns day of year (1..365) -// Purpose: finds day of year (Jan 1 = 1) for a given date. - -{ - int year, month, day; - DateTime startOfYear; - datetime_decodeDate(date, &year, &month, &day); - startOfYear = datetime_encodeDate(year, 1, 1); - return (int)(floor(date - startOfYear)) + 1; -} - -//============================================================================= - -int datetime_dayOfWeek(DateTime date) - -// Input: date = an encoded date/time value -// Output: returns index of day of week (1..7) -// Purpose: finds day of week (Sun = 1, ... Sat = 7) for a given date. - -{ - int t = (int)(floor(date)) + DateDelta; - return (t % 7) + 1; -} - -//============================================================================= - -int datetime_hourOfDay(DateTime date) - -// Input: date = an encoded date/time value -// Output: returns hour of day (0..23) -// Purpose: finds hour of day (0 = 12 AM, ..., 23 = 11 PM) for a given date. - -{ - int hour, min, sec; - datetime_decodeTime(date, &hour, &min, &sec); - return hour; -} - -//============================================================================= - -int datetime_daysPerMonth(int year, int month) - -// Input: year = year in which month falls -// month = month of year (1..12) -// Output: returns number of days in the month -// Purpose: finds number of days in a given month of a specified year. - -{ - if ( month < 1 || month > 12 ) return 0; - return DaysPerMonth[isLeapYear(year)][month-1]; -} - -//============================================================================= - -void datetime_getTimeStamp(int fmt, DateTime aDate, int stampSize, char* timeStamp) - -// Input: fmt = desired date format code -// aDate = a date/time value in decimal days -// stampSize = the number of bytes allocated for the time stamp -// Output: returns a time stamp string (e.g., Year-Month-Day Hr:Min:Sec) -// Purpose: Expresses a decimal day date by a time stamp. -{ - char dateStr[DATE_STR_SIZE]; - char timeStr[TIME_STR_SIZE]; - int oldDateFormat = DateFormat; - - if ( stampSize < TIME_STAMP_SIZE ) return; - datetime_setDateFormat(fmt); - datetime_dateToStr(aDate, dateStr); - DateFormat = oldDateFormat; - datetime_timeToStr(aDate, timeStr); - snprintf(timeStamp, stampSize, "%s %s", dateStr, timeStr); -} diff --git a/src/datetime.h b/src/datetime.h deleted file mode 100644 index 98b87b48e..000000000 --- a/src/datetime.h +++ /dev/null @@ -1,72 +0,0 @@ -//----------------------------------------------------------------------------- -// datetime.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// The DateTime type is used to store date and time values. It is -// equivalent to a double floating point type. -// -// The integral part of a DateTime value is the number of days that have -// passed since 12/31/1899. The fractional part of a DateTime value is the -// fraction of a 24 hour day that has elapsed. -// -// Update History -// ============== -// Build 5.1.011: -// - New getTimeStamp function added. -//----------------------------------------------------------------------------- - -#ifndef DATETIME_H -#define DATETIME_H - - -typedef double DateTime; - -#define Y_M_D 0 -#define M_D_Y 1 -#define D_M_Y 2 -#define NO_DATE -693594 // 1/1/0001 -#define DATE_STR_SIZE 12 -#define TIME_STR_SIZE 9 -#define TIME_STAMP_SIZE 21 - -// Functions for encoding a date or time value to a DateTime value -DateTime datetime_encodeDate(int year, int month, int day); -DateTime datetime_encodeTime(int hour, int minute, int second); - -// Functions for decoding a DateTime value to a date and time -void datetime_decodeDate(DateTime date, int* y, int* m, int* d); -void datetime_decodeTime(DateTime time, int* h, int* m, int* s); - -// Function for finding day of week for a date (1 = Sunday) -// month of year, days per month, and hour of day -int datetime_monthOfYear(DateTime date); -int datetime_dayOfYear(DateTime date); -int datetime_dayOfWeek(DateTime date); -int datetime_hourOfDay(DateTime date); -int datetime_daysPerMonth(int year, int month); - -// Functions for converting a DateTime value to a string -void datetime_dateToStr(DateTime date, char* s); -void datetime_timeToStr(DateTime time, char* s); -void datetime_getTimeStamp(int fmt, DateTime aDate, int stampSize, - char* timeStamp); - -// Functions for converting a string date or time to a DateTime value -int datetime_findMonth(char* s); -int datetime_strToDate(char* s, DateTime* d); -int datetime_strToTime(char* s, DateTime* t); - -// Function for setting date format -void datetime_setDateFormat(int fmt); - -// Functions for adding and subtracting dates -DateTime datetime_addSeconds(DateTime date1, double seconds); -DateTime datetime_addDays(DateTime date1, DateTime date2); -long datetime_timeDiff(DateTime date1, DateTime date2); - - -#endif //DATETIME_H diff --git a/src/dwflow.c b/src/dwflow.c deleted file mode 100644 index d6484fe57..000000000 --- a/src/dwflow.c +++ /dev/null @@ -1,684 +0,0 @@ -//----------------------------------------------------------------------------- -// dwflow.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 08/01/22 (Build 5.2.1) -// Author: L. Rossman -// M. Tryby (EPA) -// R. Dickinson (CDM) -// -// Solves the momentum equation for flow in a conduit under dynamic wave -// flow routing. -// -// Update History -// ============== -// Build 5.1.008: -// - Bug in finding if conduit was upstrm/dnstrm full was fixed. -// Build 5.1.012: -// - Modified uniform loss rate term of conduit momentum equation. -// 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. -// Build 5.2.1: -// - Implements the new option to skip checking for normal flow limitations. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include "headers.h" - -static const double MAXVELOCITY = 50.; // max. allowable velocity (ft/sec) - -static int getFlowClass(int link, double q, double h1, double h2, - double y1, double y2, double* criticalDepth, double* normalDepth, - double* fasnh); -static void findSurfArea(int link, double q, double length, double* h1, - double* h2, double* y1, double* y2); -static double findLocalLosses(int link, double a1, double a2, double aMid, - double q); - -static double getWidth(TXsect* xsect, double y); -static double getSlotWidth(TXsect* xsect, double y); -static double getArea(TXsect* xsect, double y, double wSlot); -static double getHydRad(TXsect* xsect, double y); - -static double checkNormalFlow(int j, double q, double y1, double y2, - double a1, double r1); - -//============================================================================= - -void dwflow_findConduitFlow(int j, int steps, double omega, double dt) -// -// Input: j = link index -// steps = number of iteration steps taken -// omega = under-relaxation parameter -// dt = time step (sec) -// Output: returns new flow value (cfs) -// Purpose: updates flow in conduit link by solving finite difference -// form of continuity and momentum equations. -// -{ - int k; // index of conduit - int n1, n2; // indexes of end nodes - double z1, z2; // upstream/downstream invert elev. (ft) - double h1, h2; // upstream/dounstream flow heads (ft) - double y1, y2; // upstream/downstream flow depths (ft) - double a1, a2; // upstream/downstream flow areas (ft2) - double r1; // upstream hyd. radius (ft) - double yMid, rMid, aMid; // mid-stream or avg. values of y, r, & a - double aWtd, rWtd; // upstream weighted area & hyd. radius - double qLast; // flow from previous iteration (cfs) - double qOld; // flow from previous time step (cfs) - double aOld; // area from previous time step (ft2) - double v; // velocity (ft/sec) - double rho; // upstream weighting factor - double sigma; // inertial damping factor - double length; // effective conduit length (ft) - double wSlot; // Preissmann slot width (ft) - double dq1, dq2, dq3, dq4, dq5, // terms in momentum eqn. - dq6; // term for evap and infil losses - double denom; // denominator of flow update formula - double q; // new flow value (cfs) - double barrels; // number of barrels in conduit - TXsect* xsect = &Link[j].xsect; // ptr. to conduit's cross section data - char isFull = FALSE; // TRUE if conduit flowing full - char isClosed = FALSE; // TRUE if conduit closed - - - - // --- adjust isClosed status by any control action - if ( Link[j].setting == 0 ) isClosed = TRUE; - - // --- get flow from last time step & previous iteration - k = Link[j].subIndex; - barrels = Conduit[k].barrels; - qOld = Link[j].oldFlow / barrels; - qLast = Conduit[k].q1; - Conduit[k].evapLossRate = 0.0; - Conduit[k].seepLossRate = 0.0; - - // --- get most current heads at upstream and downstream ends of conduit - n1 = Link[j].node1; - n2 = Link[j].node2; - z1 = Node[n1].invertElev + Link[j].offset1; - z2 = Node[n2].invertElev + Link[j].offset2; - h1 = Node[n1].newDepth + Node[n1].invertElev; - h2 = Node[n2].newDepth + Node[n2].invertElev; - h1 = MAX(h1, z1); - h2 = MAX(h2, z2); - - // --- get unadjusted upstream and downstream flow depths in conduit - // (flow depth = head in conduit - elev. of conduit invert) - y1 = h1 - z1; - y2 = h2 - z2; - y1 = MAX(y1, FUDGE); - y2 = MAX(y2, FUDGE); - - // --- flow depths can't exceed full depth of conduit if slot not used - if ( SurchargeMethod != SLOT ) - { - y1 = MIN(y1, xsect->yFull); - y2 = MIN(y2, xsect->yFull); - } - - // -- get area from solution at previous time step - aOld = Conduit[k].a2; - aOld = MAX(aOld, FUDGE); - - // --- use Courant-modified length instead of conduit's actual length - length = Conduit[k].modLength; - - // --- find surface area contributions to upstream and downstream nodes - // based on previous iteration's flow estimate - findSurfArea(j, qLast, length, &h1, &h2, &y1, &y2); - - // --- compute area at each end of conduit & hyd. radius at upstream end - wSlot = getSlotWidth(xsect, y1); - a1 = getArea(xsect, y1, wSlot); - r1 = getHydRad(xsect, y1); - wSlot = getSlotWidth(xsect, y2); - a2 = getArea(xsect, y2, wSlot); - - // --- compute area & hyd. radius at midpoint - yMid = 0.5 * (y1 + y2); - wSlot = getSlotWidth(xsect, yMid); - aMid = getArea(xsect, yMid, wSlot); - rMid = getHydRad(xsect, yMid); - - // --- alternate approach not currently used, but might produce better - // Bernoulli energy balance for steady flows - //aMid = (a1+a2)/2.0; - //rMid = (r1+getHydRad(xsect,y2))/2.0; - - // --- check if conduit is flowing full - if ( y1 >= xsect->yFull && - y2 >= xsect->yFull) isFull = TRUE; - - // --- set new flow to zero if conduit is dry or if flap gate is closed - if ( Link[j].flowClass == DRY || - Link[j].flowClass == UP_DRY || - Link[j].flowClass == DN_DRY || - isClosed || - aMid <= FUDGE ) - { - Conduit[k].a1 = 0.5 * (a1 + a2); - Conduit[k].q1 = 0.0;; - Conduit[k].q2 = 0.0; - Link[j].dqdh = GRAVITY * dt * aMid / length * barrels; - Link[j].froude = 0.0; - Link[j].newDepth = MIN(yMid, Link[j].xsect.yFull); - Link[j].newVolume = Conduit[k].a1 * link_getLength(j) * barrels; - Link[j].newFlow = 0.0; - return; - } - - // --- compute velocity from last flow estimate - v = qLast / aMid; - if ( fabs(v) > MAXVELOCITY ) v = MAXVELOCITY * SGN(qLast); - - // --- compute Froude No. - Link[j].froude = link_getFroude(j, v, yMid); - if ( Link[j].flowClass == SUBCRITICAL && - Link[j].froude > 1.0 ) Link[j].flowClass = SUPCRITICAL; - - // --- find inertial damping factor (sigma) - if ( Link[j].froude <= 0.5 ) sigma = 1.0; - else if ( Link[j].froude >= 1.0 ) sigma = 0.0; - else sigma = 2.0 * (1.0 - Link[j].froude); - - // --- get upstream-weighted area & hyd. radius based on damping factor - // (modified version of R. Dickinson's slope weighting) - rho = 1.0; - if ( !isFull && qLast > 0.0 && h1 >= h2 ) rho = sigma; - aWtd = a1 + (aMid - a1) * rho; - rWtd = r1 + (rMid - r1) * rho; - - // --- determine how much inertial damping to apply - if ( InertDamping == NO_DAMPING ) sigma = 1.0; - else if ( InertDamping == FULL_DAMPING ) sigma = 0.0; - - // --- use full inertial damping if closed conduit is surcharged - if ( isFull && !xsect_isOpen(xsect->type) ) sigma = 0.0; - - // --- compute terms of momentum eqn.: - // --- 1. friction slope term - if ( xsect->type == FORCE_MAIN && isFull ) - dq1 = dt * forcemain_getFricSlope(j, fabs(v), rMid); - else dq1 = dt * Conduit[k].roughFactor / pow(rWtd, 1.33333) * fabs(v); - - // --- 2. energy slope term - dq2 = dt * GRAVITY * aWtd * (h2 - h1) / length; - - // --- 3 & 4. inertial terms - dq3 = 0.0; - dq4 = 0.0; - if ( sigma > 0.0 ) - { - dq3 = 2.0 * v * (aMid - aOld) * sigma; - dq4 = dt * v * v * (a2 - a1) / length * sigma; - } - - // --- 5. local losses term - dq5 = 0.0; - if ( Conduit[k].hasLosses ) - { - dq5 = findLocalLosses(j, a1, a2, aMid, qLast) / 2.0 / length * dt; - } - - // --- 6. term for evap and seepage losses per unit length - dq6 = link_getLossRate(j, qLast) * 2.5 * dt * v / link_getLength(j); - - // --- combine terms to find new conduit flow - denom = 1.0 + dq1 + dq5; - q = (qOld - dq2 + dq3 + dq4 + dq6) / denom; - - // --- compute derivative of flow w.r.t. head - Link[j].dqdh = 1.0 / denom * GRAVITY * dt * aWtd / length * barrels; - - // --- check if any flow limitation applies - Link[j].inletControl = FALSE; - Link[j].normalFlow = FALSE; - if ( q > 0.0 ) - { - // --- check for inlet controlled culvert flow - if ( xsect->culvertCode > 0 && !isFull ) - q = culvert_getInflow(j, q, h1); - - // --- check for normal flow limitation based on surface slope & Fr - else if (NormalFlowLtd != NEITHER && y1 < Link[j].xsect.yFull && - ( Link[j].flowClass == SUBCRITICAL || - Link[j].flowClass == SUPCRITICAL )) - q = checkNormalFlow(j, q, y1, y2, a1, r1); - } - - // --- apply under-relaxation weighting between new & old flows; - // --- do not allow change in flow direction without first being zero - if ( steps > 0 ) - { - q = (1.0 - omega) * qLast + omega * q; - if ( q * qLast < 0.0 ) q = 0.001 * SGN(q); - } - - // --- check if user-supplied flow limit applies - if ( Link[j].qLimit > 0.0 ) - { - if ( fabs(q) > Link[j].qLimit ) q = SGN(q) * Link[j].qLimit; - } - - // --- check for reverse flow with closed flap gate - if ( link_setFlapGate(j, n1, n2, q) ) q = 0.0; - - // --- do not allow flow out of a dry node - // (as suggested by R. Dickinson) - if( q > FUDGE && Node[n1].newDepth <= FUDGE ) q = FUDGE; - if( q < -FUDGE && Node[n2].newDepth <= FUDGE ) q = -FUDGE; - - // --- save new values of area, flow, depth, & volume - Conduit[k].a1 = aMid; - Conduit[k].q1 = q; - Conduit[k].q2 = q; - Link[j].newDepth = MIN(yMid, xsect->yFull); - aMid = (a1 + a2) / 2.0; -// aMid = MIN(aMid, xsect->aFull); //Slot can have aMid > aFull - Conduit[k].fullState = link_getFullState(a1, a2, xsect->aFull); - Link[j].newVolume = aMid * link_getLength(j) * barrels; - Link[j].newFlow = q * barrels; -} - -//============================================================================= - -int getFlowClass(int j, double q, double h1, double h2, double y1, double y2, - double *yC, double *yN, double* fasnh) -// -// Input: j = conduit link index -// q = current conduit flow (cfs) -// h1 = head at upstream end of conduit (ft) -// h2 = head at downstream end of conduit (ft) -// y1 = upstream flow depth in conduit (ft) -// y2 = downstream flow depth in conduit (ft) -// yC = critical flow depth (ft) -// yN = normal flow depth (ft) -// fasnh = fraction between norm. & crit. depth -// Output: returns flow classification code -// Purpose: determines flow class for a conduit based on depths at each end. -// -{ - int n1, n2; // indexes of upstrm/downstrm nodes - int flowClass; // flow classification code - double ycMin, ycMax; // min/max critical depths (ft) - double z1, z2; // offsets of conduit inverts (ft) - - // --- get upstream & downstream node indexes - n1 = Link[j].node1; - n2 = Link[j].node2; - - // --- get upstream & downstream conduit invert offsets - z1 = Link[j].offset1; - z2 = Link[j].offset2; - - // --- base offset of an outfall conduit on outfall's depth - if ( Node[n1].type == OUTFALL ) z1 = MAX(0.0, (z1 - Node[n1].newDepth)); - if ( Node[n2].type == OUTFALL ) z2 = MAX(0.0, (z2 - Node[n2].newDepth)); - - // --- default class is SUBCRITICAL - flowClass = SUBCRITICAL; - *fasnh = 1.0; - - // --- case where both ends of conduit are wet - if ( y1 > FUDGE && y2 > FUDGE ) - { - if ( q < 0.0 ) - { - // --- upstream end at critical depth if flow depth is - // below conduit's critical depth and an upstream - // conduit offset exists - if ( z1 > 0.0 ) - { - *yN = link_getYnorm(j, fabs(q)); - *yC = link_getYcrit(j, fabs(q)); - ycMin = MIN(*yN, *yC); - if ( y1 < ycMin ) flowClass = UP_CRITICAL; - } - } - - // --- case of normal direction flow - else - { - // --- downstream end at smaller of critical and normal depth - // if downstream flow depth below this and a downstream - // conduit offset exists - if ( z2 > 0.0 ) - { - *yN = link_getYnorm(j, fabs(q)); - *yC = link_getYcrit(j, fabs(q)); - ycMin = MIN(*yN, *yC); - ycMax = MAX(*yN, *yC); - if ( y2 < ycMin ) flowClass = DN_CRITICAL; - else if ( y2 < ycMax ) - { - if ( ycMax - ycMin < FUDGE ) *fasnh = 0.0; - else *fasnh = (ycMax - y2) / (ycMax - ycMin); - } - } - } - } - - // --- case where no flow at either end of conduit - else if ( y1 <= FUDGE && y2 <= FUDGE ) flowClass = DRY; - - // --- case where downstream end of pipe is wet, upstream dry - else if ( y2 > FUDGE ) - { - // --- flow classification is UP_DRY if downstream head < - // invert of upstream end of conduit - if ( h2 < Node[n1].invertElev + Link[j].offset1 ) flowClass = UP_DRY; - - // --- otherwise, the downstream head will be >= upstream - // conduit invert creating a flow reversal and upstream end - // should be at critical depth, providing that an upstream - // offset exists (otherwise subcritical condition is maintained) - else if ( z1 > 0.0 ) - { - *yN = link_getYnorm(j, fabs(q)); - *yC = link_getYcrit(j, fabs(q)); - flowClass = UP_CRITICAL; - } - } - - // --- case where upstream end of pipe is wet, downstream dry - else - { - // --- flow classification is DN_DRY if upstream head < - // invert of downstream end of conduit - if ( h1 < Node[n2].invertElev + Link[j].offset2 ) flowClass = DN_DRY; - - // --- otherwise flow at downstream end should be at critical depth - // providing that a downstream offset exists (otherwise - // subcritical condition is maintained) - else if ( z2 > 0.0 ) - { - *yN = link_getYnorm(j, fabs(q)); - *yC = link_getYcrit(j, fabs(q)); - flowClass = DN_CRITICAL; - } - } - return flowClass; -} - -//============================================================================= - -void findSurfArea(int j, double q, double length, double* h1, double* h2, - double* y1, double* y2) -// -// Input: j = conduit link index -// q = current conduit flow (cfs) -// length = conduit length (ft) -// h1 = head at upstream end of conduit (ft) -// h2 = head at downstream end of conduit (ft) -// y1 = upstream flow depth (ft) -// y2 = downstream flow depth (ft) -// Output: updated values of h1, h2, y1, & y2; -// Purpose: assigns surface area of conduit to its up and downstream nodes. -// -{ - int n1, n2; // indexes of upstrm/downstrm nodes - double flowDepth1; // flow depth at upstrm end (ft) - double flowDepth2; // flow depth at downstrm end (ft) - double flowDepthMid; // flow depth at midpt. (ft) - double width1; // top width at upstrm end (ft) - double width2; // top width at downstrm end (ft) - double widthMid; // top width at midpt. (ft) - double surfArea1 = 0.0; // surface area at upstream node (ft2) - double surfArea2 = 0.0; // surface area st downstrm node (ft2) - double criticalDepth; // critical flow depth (ft) - double normalDepth; // normal flow depth (ft) - double fullDepth; // full depth (ft) - double fasnh = 1.0; // fraction between norm. & crit. depth - TXsect* xsect = &Link[j].xsect; // pointer to cross-section data - - // --- get node indexes & current flow depths - n1 = Link[j].node1; - n2 = Link[j].node2; - flowDepth1 = *y1; - flowDepth2 = *y2; - - normalDepth = (flowDepth1 + flowDepth2) / 2.0; - criticalDepth = normalDepth; - - // --- find conduit's flow classification - fullDepth = xsect->yFull; - if (flowDepth1 >= fullDepth && flowDepth2 >= fullDepth) - { - Link[j].flowClass = SUBCRITICAL; - } - else Link[j].flowClass = getFlowClass(j, q, *h1, *h2, *y1, *y2, - &criticalDepth, &normalDepth, &fasnh); - - // --- add conduit's surface area to its end nodes depending on flow class - switch ( Link[j].flowClass ) - { - case SUBCRITICAL: - flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); - if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; - width1 = getWidth(xsect, flowDepth1); - width2 = getWidth(xsect, flowDepth2); - widthMid = getWidth(xsect, flowDepthMid); - surfArea1 = (width1 + widthMid) * length / 4.; - surfArea2 = (widthMid + width2) * length / 4. * fasnh; - break; - - case UP_CRITICAL: - flowDepth1 = criticalDepth; - if ( normalDepth < criticalDepth ) flowDepth1 = normalDepth; - flowDepth1 = MAX(flowDepth1, FUDGE); - *h1 = Node[n1].invertElev + Link[j].offset1 + flowDepth1; - flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); - if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; - width2 = getWidth(xsect, flowDepth2); - widthMid = getWidth(xsect, flowDepthMid); - surfArea2 = (widthMid + width2) * length * 0.5; - break; - - case DN_CRITICAL: - flowDepth2 = criticalDepth; - if ( normalDepth < criticalDepth ) flowDepth2 = normalDepth; - flowDepth2 = MAX(flowDepth2, FUDGE); - *h2 = Node[n2].invertElev + Link[j].offset2 + flowDepth2; - width1 = getWidth(xsect, flowDepth1); - flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); - if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; - widthMid = getWidth(xsect, flowDepthMid); - surfArea1 = (width1 + widthMid) * length * 0.5; - break; - - case UP_DRY: - flowDepth1 = FUDGE; - flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); - if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; - width1 = getWidth(xsect, flowDepth1); - width2 = getWidth(xsect, flowDepth2); - widthMid = getWidth(xsect, flowDepthMid); - - // --- assign avg. surface area of downstream half of conduit - // to the downstream node - surfArea2 = (widthMid + width2) * length / 4.; - - // --- if there is no free-fall at upstream end, assign the - // upstream node the avg. surface area of the upstream half - if ( Link[j].offset1 <= 0.0 ) - { - surfArea1 = (width1 + widthMid) * length / 4.; - } - break; - - case DN_DRY: - flowDepth2 = FUDGE; - flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); - if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; - width1 = getWidth(xsect, flowDepth1); - width2 = getWidth(xsect, flowDepth2); - widthMid = getWidth(xsect, flowDepthMid); - - // --- assign avg. surface area of upstream half of conduit - // to the upstream node - surfArea1 = (widthMid + width1) * length / 4.; - - // --- if there is no free-fall at downstream end, assign the - // downstream node the avg. surface area of the downstream half - if ( Link[j].offset2 <= 0.0 ) - { - surfArea2 = (width2 + widthMid) * length / 4.; - } - break; - - case DRY: - surfArea1 = FUDGE * length / 2.0; - surfArea2 = surfArea1; - break; - } - Link[j].surfArea1 = surfArea1; - Link[j].surfArea2 = surfArea2; - *y1 = flowDepth1; - *y2 = flowDepth2; -} - -//============================================================================= - -double findLocalLosses(int j, double a1, double a2, double aMid, double q) -// -// Input: j = link index -// a1 = upstream area (ft2) -// a2 = downstream area (ft2) -// aMid = midpoint area (ft2) -// q = flow rate (cfs) -// Output: returns local losses (ft/sec) -// Purpose: computes local losses term of momentum equation. -// -{ - double losses = 0.0; - q = fabs(q); - if ( a1 > FUDGE ) losses += Link[j].cLossInlet * (q/a1); - if ( a2 > FUDGE ) losses += Link[j].cLossOutlet * (q/a2); - if ( aMid > FUDGE ) losses += Link[j].cLossAvg * (q/aMid); - return losses; -} - -//============================================================================= - -double getSlotWidth(TXsect* xsect, double y) -{ - double yNorm = y / xsect->yFull; - - // --- return 0.0 if slot surcharge method not used - if (SurchargeMethod != SLOT || xsect_isOpen(xsect->type) || - yNorm < CrownCutoff) return 0.0; - - // --- for depth > 1.78 * pipe depth, slot width = 1% of max. width - if (yNorm > 1.78) return 0.01 * xsect->wMax; - - // --- otherwise use the Sjoberg formula - return xsect->wMax * 0.5423 * exp(-pow(yNorm, 2.4)); -} - -//============================================================================= - -double getWidth(TXsect* xsect, double y) -// -// Input: xsect = ptr. to conduit cross section -// y = flow depth (ft) -// Output: returns top width (ft) -// Purpose: computes top width of flow surface in conduit. -// -{ - double wSlot = getSlotWidth(xsect, y); - if (wSlot > 0.0) return wSlot; - if (y / xsect->yFull >= CrownCutoff && !xsect_isOpen(xsect->type)) - y = CrownCutoff * xsect->yFull; - return xsect_getWofY(xsect, y); -} - -//============================================================================= - -double getArea(TXsect* xsect, double y, double wSlot) -// -// Input: xsect = ptr. to conduit cross section -// y = flow depth (ft) -// Output: returns flow area (ft2) -// Purpose: computes area of flow cross-section in a conduit. -// -{ - if ( y >= xsect->yFull ) return xsect->aFull + (y - xsect->yFull) * wSlot; - return xsect_getAofY(xsect, y); -} - -//============================================================================= - -double getHydRad(TXsect* xsect, double y) -// -// Input: xsect = ptr. to conduit cross section -// y = flow depth (ft) -// Output: returns hydraulic radius (ft) -// Purpose: computes hydraulic radius of flow cross-section in a conduit. -// -{ - if (y >= xsect->yFull) return xsect->rFull; - return xsect_getRofY(xsect, y); -} - -//============================================================================= - -double checkNormalFlow(int j, double q, double y1, double y2, double a1, - double r1) -// -// Input: j = link index -// q = link flow found from dynamic wave equations (cfs) -// y1 = flow depth at upstream end (ft) -// y2 = flow depth at downstream end (ft) -// a1 = flow area at upstream end (ft2) -// r1 = hyd. radius at upstream end (ft) -// Output: returns modifed flow in link (cfs) -// Purpose: checks if flow in link should be replaced by normal flow. -// -{ - int check = FALSE; - int k = Link[j].subIndex; - int n1 = Link[j].node1; - int n2 = Link[j].node2; - int hasOutfall = (Node[n1].type == OUTFALL || Node[n2].type == OUTFALL); - double qNorm; - double f1; - - // --- check if water surface slope < conduit slope - if ( NormalFlowLtd == SLOPE || NormalFlowLtd == BOTH || hasOutfall ) - { - if ( y1 < y2) check = TRUE; - } - - // --- check if Fr >= 1.0 at upstream end of conduit - if ( !check && (NormalFlowLtd == FROUDE || NormalFlowLtd == BOTH) && - !hasOutfall ) - { - if ( y1 > FUDGE && y2 > FUDGE ) - { - f1 = link_getFroude(j, q/a1, y1); - if ( f1 >= 1.0 ) check = TRUE; - } - } - - // --- check if normal flow < dynamic flow - if ( check ) - { - qNorm = Conduit[k].beta * a1 * pow(r1, 2./3.); - if ( qNorm < q ) - { - Link[j].normalFlow = TRUE; - return qNorm; - } - } - return q; -} diff --git a/src/dynwave.c b/src/dynwave.c deleted file mode 100644 index ac302e849..000000000 --- a/src/dynwave.c +++ /dev/null @@ -1,908 +0,0 @@ -//----------------------------------------------------------------------------- -// dynwave.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// M. Tryby (EPA) -// R. Dickinson (CDM) -// -// Dynamic wave flow routing functions. -// -// This module solves the dynamic wave flow routing equations using -// Picard Iterations (i.e., a method of successive approximations) -// to solve the explicit form of the continuity and momentum equations -// for conduits. -// -// Update History -// ============== -// Build 5.1.002: -// - Only non-ponded nodal surface area is saved for use in -// surcharge algorithm. -// Build 5.1.007: -// - Node losses added to node outflow variable instead of treated -// as a separate item when computing change in node flow volume. -// Build 5.1.008: -// - Module-specific constants moved here from project.c. -// - Support added for user-specified minimum variable time step. -// - Node crown elevations found here instead of in flowrout.c module. -// - OpenMP use to parallelize findLinkFlows() & findNodeDepths(). -// - Bug in finding complete list of capacity limited links fixed. -// Build 5.1.011: -// - Added test for failed memory allocation. -// - Fixed illegal array index bug for Ideal Pumps. -// Build 5.1.013: -// - Include omp.h protected against lack of compiler support for OpenMP. -// - SurchargeMethod option used to decide how node surcharging is handled. -// - 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. -// Build 5.1.015: -// - Roll back the 5.1.014 change for conduit losses in updateNodeFlows(). -// Build 5.2.0: -// - Support added for reporting most frequent non-converging links. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include "headers.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -static const double MINTIMESTEP = 0.001; // min. time step (sec) -static const double OMEGA = 0.5; // under-relaxation parameter -static const double DEFAULT_SURFAREA = 12.566; // Min. nodal surface area (~4 ft diam.) -static const double DEFAULT_HEADTOL = 0.005; // Default head tolerance (ft) -static const double EXTRAN_CROWN_CUTOFF = 0.96; // crown cutoff for EXTRAN -static const double SLOT_CROWN_CUTOFF = 0.985257; // crown cutoff for SLOT -static const int DEFAULT_MAXTRIALS = 8; // Max. trials per time step - - -//----------------------------------------------------------------------------- -// Data Structures -//----------------------------------------------------------------------------- -typedef struct -{ - char converged; // TRUE if iterations for a node done - double newSurfArea; // current surface area (ft2) - double oldSurfArea; // previous surface area (ft2) - double sumdqdh; // sum of dqdh from adjoining links - double dYdT; // change in depth w.r.t. time (ft/sec) -} TXnode; - -//----------------------------------------------------------------------------- -// Shared Variables -//----------------------------------------------------------------------------- -static double VariableStep; // size of variable time step (sec) -static TXnode* Xnode; // extended nodal information - -static double Omega; // actual under-relaxation parameter -static int Steps; // number of Picard iterations - -//----------------------------------------------------------------------------- -// Function declarations -//----------------------------------------------------------------------------- -static void initRoutingStep(void); -static void initNodeStates(void); -static void findBypassedLinks(); -static void findLimitedLinks(); - -static void findLinkFlows(double dt); -static int isTrueConduit(int link); -static void findNonConduitFlow(int link, double dt); -static void findNonConduitSurfArea(int link); -static double getModPumpFlow(int link, double q, double dt); -static void updateNodeFlows(int link); -static void updateConvergenceStats(); - -static int findNodeDepths(double dt); -static void setNodeDepth(int node, double dt); -static double getFloodedDepth(int node, int canPond, double dV, double yNew, - double yMax, double dt); - -static double getVariableStep(double maxStep); -static double getLinkStep(double tMin, int *minLink); -static double getNodeStep(double tMin, int *minNode); - -//============================================================================= - -void dynwave_init() -// -// Input: none -// Output: none -// Purpose: initializes dynamic wave routing method. -// -{ - int i, j; - double z; - - VariableStep = 0.0; - Xnode = (TXnode *) calloc(Nobjects[NODE], sizeof(TXnode)); - if ( Xnode == NULL ) - { - report_writeErrorMsg(ERR_MEMORY, - " Not enough memory for dynamic wave routing."); - return; - } - - // --- initialize node surface areas & crown elev. - for (i = 0; i < Nobjects[NODE]; i++ ) - { - Xnode[i].newSurfArea = 0.0; - Xnode[i].oldSurfArea = 0.0; - Node[i].crownElev = Node[i].invertElev; - } - - // --- initialize links & update node crown elevations - for (i = 0; i < Nobjects[LINK]; i++) - { - j = Link[i].node1; - z = Node[j].invertElev + Link[i].offset1 + Link[i].xsect.yFull; - Node[j].crownElev = MAX(Node[j].crownElev, z); - j = Link[i].node2; - z = Node[j].invertElev + Link[i].offset2 + Link[i].xsect.yFull; - Node[j].crownElev = MAX(Node[j].crownElev, z); - Link[i].flowClass = DRY; - Link[i].dqdh = 0.0; - } - - // --- set crown cutoff for finding top width of closed conduits - if ( SurchargeMethod == SLOT ) CrownCutoff = SLOT_CROWN_CUTOFF; - else CrownCutoff = EXTRAN_CROWN_CUTOFF; -} - -//============================================================================= - -void dynwave_close() -// -// Input: none -// Output: none -// Purpose: frees memory allocated for dynamic wave routing method. -// -{ - FREE(Xnode); -} - -//============================================================================= - -void dynwave_validate() -// -// Input: none -// Output: none -// Purpose: adjusts dynamic wave routing options. -// -{ - if ( MinRouteStep > RouteStep ) MinRouteStep = RouteStep; - if ( MinRouteStep < MINTIMESTEP ) MinRouteStep = MINTIMESTEP; - if ( MinSurfArea == 0.0 ) MinSurfArea = DEFAULT_SURFAREA; - else MinSurfArea /= UCF(LENGTH) * UCF(LENGTH); - if ( HeadTol == 0.0 ) HeadTol = DEFAULT_HEADTOL; - else HeadTol /= UCF(LENGTH); - if ( MaxTrials == 0 ) MaxTrials = DEFAULT_MAXTRIALS; -} - -//============================================================================= - -double dynwave_getRoutingStep(double fixedStep) -// -// Input: fixedStep = user-supplied fixed time step (sec) -// Output: returns routing time step (sec) -// Purpose: computes variable routing time step if applicable. -// -{ - // --- use user-supplied fixed step if variable step option turned off - // or if its smaller than the min. allowable variable time step - if ( CourantFactor == 0.0 ) return fixedStep; - if ( fixedStep < MINTIMESTEP ) return fixedStep; - - // --- at start of simulation (when current variable step is zero) - // use the minimum allowable time step - if ( VariableStep == 0.0 ) - { - VariableStep = MinRouteStep; - } - - // --- otherwise compute variable step based on current flow solution - else VariableStep = getVariableStep(fixedStep); - - // --- adjust step to be a multiple of a millisecond - VariableStep = floor(1000.0 * VariableStep) / 1000.0; - return VariableStep; -} - -//============================================================================= - -int dynwave_execute(double tStep) -// -// Input: links = array of topo sorted links indexes -// tStep = time step (sec) -// Output: returns number of iterations used -// Purpose: routes flows through drainage network over current time step. -// -{ - int converged; - - // --- initialize - if ( ErrorCode ) return 0; - Steps = 0; - converged = FALSE; - Omega = OMEGA; - initRoutingStep(); - - // --- keep iterating until convergence - while ( Steps < MaxTrials ) - { - // --- execute a routing step & check for nodal convergence - initNodeStates(); - findLinkFlows(tStep); - converged = findNodeDepths(tStep); - Steps++; - if ( Steps > 1 ) - { - if ( converged ) break; - - // --- check if link calculations can be skipped in next step - findBypassedLinks(); - } - } - if ( !converged ) updateConvergenceStats(); - - // --- identify any capacity-limited conduits - findLimitedLinks(); - return Steps; -} - -//============================================================================= - -void updateConvergenceStats() -{ - int i; - NonConvergeCount++; - for (i = 0; i < Nobjects[NODE]; i++) - stats_updateConvergenceStats(i, Xnode[i].converged); -} - -//============================================================================= - -void initRoutingStep() -{ - int i; - for (i = 0; i < Nobjects[NODE]; i++) - { - Xnode[i].converged = FALSE; - Xnode[i].dYdT = 0.0; - } - for (i = 0; i < Nobjects[LINK]; i++) - { - Link[i].bypassed = FALSE; - Link[i].surfArea1 = 0.0; - Link[i].surfArea2 = 0.0; - } - - // --- a2 preserves conduit area from solution at last time step - for ( i = 0; i < Nlinks[CONDUIT]; i++) Conduit[i].a2 = Conduit[i].a1; -} - -//============================================================================= - -void initNodeStates() -// -// Input: none -// Output: none -// Purpose: initializes node's surface area, inflow & outflow -// -{ - int i; - - for (i = 0; i < Nobjects[NODE]; i++) - { - // --- initialize nodal surface area - if ( AllowPonding ) - { - Xnode[i].newSurfArea = node_getPondedArea(i, Node[i].newDepth); - } - else - { - Xnode[i].newSurfArea = node_getSurfArea(i, Node[i].newDepth); - } - - // --- initialize nodal inflow & outflow - Node[i].inflow = 0.0; - Node[i].outflow = Node[i].losses; - if ( Node[i].newLatFlow >= 0.0 ) - { - Node[i].inflow += Node[i].newLatFlow; - } - else - { - Node[i].outflow -= Node[i].newLatFlow; - } - Xnode[i].sumdqdh = 0.0; - } -} - -//============================================================================= - -void findBypassedLinks() -{ - int i; - for (i = 0; i < Nobjects[LINK]; i++) - { - if ( Xnode[Link[i].node1].converged && - Xnode[Link[i].node2].converged ) - Link[i].bypassed = TRUE; - else Link[i].bypassed = FALSE; - } -} - -//============================================================================= - -void findLimitedLinks() -// -// Input: none -// Output: none -// Purpose: determines if a conduit link is capacity limited. -// -{ - int j, n1, n2, k; - double h1, h2; - - for (j = 0; j < Nobjects[LINK]; j++) - { - // ---- check only non-dummy conduit links - if ( !isTrueConduit(j) ) continue; - - // --- check that upstream end is full - k = Link[j].subIndex; - Conduit[k].capacityLimited = FALSE; - if ( Conduit[k].a1 >= Link[j].xsect.aFull ) - { - // --- check if HGL slope > conduit slope - n1 = Link[j].node1; - n2 = Link[j].node2; - h1 = Node[n1].newDepth + Node[n1].invertElev; - h2 = Node[n2].newDepth + Node[n2].invertElev; - if ( (h1 - h2) > fabs(Conduit[k].slope) * Conduit[k].length ) - Conduit[k].capacityLimited = TRUE; - } - } -} - -//============================================================================= - -void findLinkFlows(double dt) -{ - int i; - - // --- find new flow in each non-dummy conduit -#pragma omp parallel num_threads(NumThreads) -{ - #pragma omp for - for ( i = 0; i < Nobjects[LINK]; i++) - { - if ( isTrueConduit(i) && !Link[i].bypassed ) - dwflow_findConduitFlow(i, Steps, Omega, dt); - } -} - - // --- update inflow/outflows for nodes attached to non-dummy conduits - for ( i = 0; i < Nobjects[LINK]; i++) - { - if ( isTrueConduit(i) ) updateNodeFlows(i); - } - - // --- find new flows for all dummy conduits, pumps & regulators - for ( i = 0; i < Nobjects[LINK]; i++) - { - if ( !isTrueConduit(i) ) - { - if ( !Link[i].bypassed ) findNonConduitFlow(i, dt); - updateNodeFlows(i); - } - } -} - -//============================================================================= - -int isTrueConduit(int j) -{ - return ( Link[j].type == CONDUIT && Link[j].xsect.type != DUMMY ); -} - -//============================================================================= - -void findNonConduitFlow(int i, double dt) -// -// Input: i = link index -// dt = time step (sec) -// Output: none -// Purpose: finds new flow in a non-conduit-type link -// -{ - double qLast; // previous link flow (cfs) - double qNew; // new link flow (cfs) - - // --- get link flow from last iteration - qLast = Link[i].newFlow; - Link[i].dqdh = 0.0; - - // --- get new inflow to link from its upstream node - // (link_getInflow returns 0 if flap gate closed or pump is offline) - qNew = link_getInflow(i); - if ( Link[i].type == PUMP ) qNew = getModPumpFlow(i, qNew, dt); - - // --- find surface area at each end of link - findNonConduitSurfArea(i); - - // --- apply under-relaxation with flow from previous iteration; - // --- do not allow flow to change direction without first being 0 - if ( Steps > 0 && Link[i].type != PUMP ) - { - qNew = (1.0 - Omega) * qLast + Omega * qNew; - if ( qNew * qLast < 0.0 ) qNew = 0.001 * SGN(qNew); - } - Link[i].newFlow = qNew; -} - -//============================================================================= - -double getModPumpFlow(int i, double q, double dt) -// -// Input: i = link index -// q = pump flow from pump curve (cfs) -// dt = time step (sec) -// Output: returns modified pump flow rate (cfs) -// Purpose: modifies pump curve pumping rate depending on amount of water -// available at pump's inlet node. -// -{ - int j = Link[i].node1; // pump's inlet node index - int k = Link[i].subIndex; // pump's index - double newNetInflow; // inflow - outflow rate (cfs) - double netFlowVolume; // inflow - outflow volume (ft3) - double y; // node depth (ft) - - if ( q == 0.0 ) return q; - - // --- case where inlet node is a storage node: - // prevent node volume from going negative - if ( Node[j].type == STORAGE ) return node_getMaxOutflow(j, q, dt); - - // --- case where inlet is a non-storage node - switch ( Pump[k].type ) - { - // --- for Type1 pump, a volume is computed for inlet node, - // so make sure it doesn't go negative - case TYPE1_PUMP: - return node_getMaxOutflow(j, q, dt); - - // --- for other types of pumps, if pumping rate would make depth - // at upstream node negative, then set pumping rate = inflow - case TYPE2_PUMP: - case TYPE4_PUMP: - case TYPE3_PUMP: - newNetInflow = Node[j].inflow - Node[j].outflow - q; - netFlowVolume = 0.5 * (Node[j].oldNetInflow + newNetInflow ) * dt; - y = Node[j].oldDepth + netFlowVolume / Xnode[j].newSurfArea; - if ( y <= 0.0 ) return Node[j].inflow; - } - return q; -} - -//============================================================================= - -void findNonConduitSurfArea(int i) -// -// Input: i = link index -// Output: none -// Purpose: finds the surface area contributed by a non-conduit -// link to its upstream and downstream nodes. -// -{ - if ( Link[i].type == ORIFICE ) - { - Link[i].surfArea1 = Orifice[Link[i].subIndex].surfArea / 2.; - } - - // --- no surface area for weirs to maintain SWMM 4 compatibility - else Link[i].surfArea1 = 0.0; - - Link[i].surfArea2 = Link[i].surfArea1; - if ( Link[i].flowClass == UP_CRITICAL || - Node[Link[i].node1].type == STORAGE ) Link[i].surfArea1 = 0.0; - if ( Link[i].flowClass == DN_CRITICAL || - Node[Link[i].node2].type == STORAGE ) Link[i].surfArea2 = 0.0; -} - -//============================================================================= - -void updateNodeFlows(int i) -// -// Input: i = link index -// q = link flow rate (cfs) -// Output: none -// Purpose: updates cumulative inflow & outflow at link's end nodes. -// -{ - int k; - int barrels = 1; - int n1 = Link[i].node1; - int n2 = Link[i].node2; - double q = Link[i].newFlow; - double uniformLossRate = 0.0; - - // --- compute any uniform seepage loss from a conduit - if ( Link[i].type == CONDUIT ) - { - k = Link[i].subIndex; - uniformLossRate = Conduit[k].evapLossRate + Conduit[k].seepLossRate; - barrels = Conduit[k].barrels; - uniformLossRate *= barrels; - } - - // --- update total inflow & outflow at upstream/downstream nodes - if ( q >= 0.0 ) - { - Node[n1].outflow += q + uniformLossRate; - Node[n2].inflow += q; - } - else - { - Node[n1].inflow -= q; - Node[n2].outflow -= q - uniformLossRate; - } - - // --- add surf. area contributions to upstream/downstream nodes - Xnode[Link[i].node1].newSurfArea += Link[i].surfArea1 * barrels; - Xnode[Link[i].node2].newSurfArea += Link[i].surfArea2 * barrels; - - // --- update summed value of dqdh at each end node - Xnode[Link[i].node1].sumdqdh += Link[i].dqdh; - if ( Link[i].type == PUMP ) - { - k = Link[i].subIndex; - if ( Pump[k].type != TYPE4_PUMP ) - { - Xnode[n2].sumdqdh += Link[i].dqdh; - } - } - else Xnode[n2].sumdqdh += Link[i].dqdh; -} - -//============================================================================= - -int findNodeDepths(double dt) -// -// Input: dt = time step (sec) -// Output: returns TRUE if depth change at all non-Outfall nodes is -// within the convergence tolerance and FALSE otherwise -// Purpose: finds new depth at all nodes and checks if convergence achieved. -// -{ - int i; - double yOld; // previous node depth (ft) - - // --- compute outfall depths based on flow in connecting link - for ( i = 0; i < Nobjects[LINK]; i++ ) link_setOutfallDepth(i); - - // --- compute new depth for all non-outfall nodes and determine if - // depth change from previous iteration is below tolerance -#pragma omp parallel num_threads(NumThreads) -{ - #pragma omp for private(yOld) - for ( i = 0; i < Nobjects[NODE]; i++ ) - { - if ( Node[i].type == OUTFALL ) continue; - yOld = Node[i].newDepth; - setNodeDepth(i, dt); - Xnode[i].converged = TRUE; - if ( fabs(yOld - Node[i].newDepth) > HeadTol ) - { - Xnode[i].converged = FALSE; - } - } -} - - // --- return FALSE if any non-Outfall node failed to converge - for (i = 0; i < Nobjects[NODE]; i++) - { - if ( Node[i].type == OUTFALL ) continue; - if (Xnode[i].converged == FALSE) return FALSE; - } - return TRUE; -} - -//============================================================================= - -void setNodeDepth(int i, double dt) -// -// Input: i = node index -// dt = time step (sec) -// Output: none -// Purpose: sets depth at non-outfall node after current time step. -// -{ - int canPond; // TRUE if node can pond overflows - int isPonded; // TRUE if node is currently ponded - int isSurcharged = FALSE; // TRUE if node is surcharged - double dQ; // inflow minus outflow at node (cfs) - double dV; // change in node volume (ft3) - double dy; // change in node depth (ft) - double yMax; // max. depth at node (ft) - double yOld; // node depth at previous time step (ft) - double yLast; // previous node depth (ft) - double yNew; // new node depth (ft) - double yCrown; // depth to node crown (ft) - double surfArea; // node surface area (ft2) - double denom; // denominator term - double corr; // correction factor - double f; // relative surcharge depth - - // --- see if node can pond water above it - canPond = (AllowPonding && Node[i].pondedArea > 0.0); - isPonded = (canPond && Node[i].newDepth > Node[i].fullDepth); - - // --- initialize values - yCrown = Node[i].crownElev - Node[i].invertElev; - yOld = Node[i].oldDepth; - yLast = Node[i].newDepth; - Node[i].overflow = 0.0; - surfArea = Xnode[i].newSurfArea; - surfArea = MAX(surfArea, MinSurfArea); - - // --- determine average net flow volume into node over the time step - dQ = Node[i].inflow - Node[i].outflow; - dV = 0.5 * (Node[i].oldNetInflow + dQ) * dt; - - - // --- determine if node is EXTRAN surcharged - if (SurchargeMethod == EXTRAN) - { - // --- ponded nodes don't surcharge - if (isPonded) isSurcharged = FALSE; - - // --- closed storage units that are full are in surcharge - else if (Node[i].type == STORAGE) - { - isSurcharged = (Node[i].surDepth > 0.0 && - yLast > Node[i].fullDepth); - } - - // --- surcharge occurs when node depth exceeds top of its highest link - else isSurcharged = (yCrown > 0.0 && yLast > yCrown); - } - - // --- if node not surcharged, base depth change on surface area - if (!isSurcharged) - { - dy = dV / surfArea; - yNew = yOld + dy; - - // --- save non-ponded surface area for use in surcharge algorithm - if ( !isPonded ) Xnode[i].oldSurfArea = surfArea; - - // --- apply under-relaxation to new depth estimate - if ( Steps > 0 ) - { - yNew = (1.0 - Omega) * yLast + Omega * yNew; - } - - // --- don't allow a ponded node to drop much below full depth - if ( isPonded && yNew < Node[i].fullDepth ) - yNew = Node[i].fullDepth - FUDGE; - } - - // --- if node surcharged, base depth change on dqdh - // NOTE: depth change is w.r.t depth from previous - // iteration; also, do not apply under-relaxation. - else - { - // --- apply correction factor for upstream terminal nodes - corr = 1.0; - if ( Node[i].degree < 0 ) corr = 0.6; - - // --- allow surface area from last non-surcharged condition - // to influence dqdh if depth close to crown depth - denom = Xnode[i].sumdqdh; - if ( yLast < 1.25 * yCrown ) - { - f = (yLast - yCrown) / yCrown; - denom += (Xnode[i].oldSurfArea/dt - - Xnode[i].sumdqdh) * exp(-15.0 * f); - } - - // --- compute new estimate of node depth - if ( denom == 0.0 ) dy = 0.0; - else dy = corr * dQ / denom; - yNew = yLast + dy; - if ( yNew < yCrown ) yNew = yCrown - FUDGE; - - // --- don't allow a newly ponded node to rise much above full depth - if ( canPond && yNew > Node[i].fullDepth ) - yNew = Node[i].fullDepth + FUDGE; - } - - // --- depth cannot be negative - if ( yNew < 0 ) yNew = 0.0; - - // --- determine max. non-flooded depth - yMax = Node[i].fullDepth; - if ( canPond == FALSE ) yMax += Node[i].surDepth; - - // --- find flooded depth & volume - if ( yNew > yMax ) - { - yNew = getFloodedDepth(i, canPond, dV, yNew, yMax, dt); - } - else Node[i].newVolume = node_getVolume(i, yNew); - - // --- compute change in depth w.r.t. time - Xnode[i].dYdT = fabs(yNew - yOld) / dt; - - // --- save new depth for node - Node[i].newDepth = yNew; -} - -//============================================================================= - -double getFloodedDepth(int i, int canPond, double dV, double yNew, - double yMax, double dt) -// -// Input: i = node index -// canPond = TRUE if water can pond over node -// isPonded = TRUE if water is currently ponded -// dV = change in volume over time step (ft3) -// yNew = current depth at node (ft) -// yMax = max. depth at node before ponding (ft) -// dt = time step (sec) -// Output: returns depth at node when flooded (ft) -// Purpose: computes depth, volume and overflow for a flooded node. -// -{ - if ( canPond == FALSE ) - { - Node[i].overflow = dV / dt; - Node[i].newVolume = Node[i].fullVolume; - yNew = yMax; - } - else - { - Node[i].newVolume = MAX((Node[i].oldVolume+dV), Node[i].fullVolume); - Node[i].overflow = (Node[i].newVolume - - MAX(Node[i].oldVolume, Node[i].fullVolume)) / dt; - } - if ( Node[i].overflow < FUDGE ) Node[i].overflow = 0.0; - return yNew; - -} - -//============================================================================= - -double getVariableStep(double maxStep) -// -// Input: maxStep = user-supplied max. time step (sec) -// Output: returns time step (sec) -// Purpose: finds time step that satisfies stability criterion but -// is no greater than the user-supplied max. time step. -// -{ - int minLink = -1; // index of link w/ min. time step - int minNode = -1; // index of node w/ min. time step - double tMin; // allowable time step (sec) - double tMinLink; // allowable time step for links (sec) - double tMinNode; // allowable time step for nodes (sec) - - // --- find stable time step for links & then nodes - tMin = maxStep; - tMinLink = getLinkStep(tMin, &minLink); - tMinNode = getNodeStep(tMinLink, &minNode); - - // --- use smaller of the link and node time step - tMin = tMinLink; - if ( tMinNode < tMin ) - { - tMin = tMinNode ; - minLink = -1; - } - - // --- update count of times the minimum node or link was critical - stats_updateCriticalTimeCount(minNode, minLink); - - // --- don't let time step go below an absolute minimum - if ( tMin < MinRouteStep ) tMin = MinRouteStep; - return tMin; -} - -//============================================================================= - -double getLinkStep(double tMin, int *minLink) -// -// Input: tMin = critical time step found so far (sec) -// Output: minLink = index of link with critical time step; -// returns critical time step (sec) -// Purpose: finds critical time step for conduits based on Courant criterion. -// -{ - int i; // link index - int k; // conduit index - double q; // conduit flow (cfs) - double t; // time step (sec) - double tLink = tMin; // critical link time step (sec) - - // --- examine each conduit link - for ( i = 0; i < Nobjects[LINK]; i++ ) - { - if ( Link[i].type == CONDUIT ) - { - // --- skip conduits with negligible flow, area or Fr - k = Link[i].subIndex; - q = fabs(Link[i].newFlow) / Conduit[k].barrels; - if ( q <= FUDGE - || Conduit[k].a1 <= FUDGE - || Link[i].froude <= 0.01 - ) continue; - - // --- compute time step to satisfy Courant condition - t = Link[i].newVolume / Conduit[k].barrels / q; - t = t * Conduit[k].modLength / link_getLength(i); - t = t * Link[i].froude / (1.0 + Link[i].froude) * CourantFactor; - - // --- update critical link time step - if ( t < tLink ) - { - tLink = t; - *minLink = i; - } - } - } - return tLink; -} - -//============================================================================= - -double getNodeStep(double tMin, int *minNode) -// -// Input: tMin = critical time step found so far (sec) -// Output: minNode = index of node with critical time step; -// returns critical time step (sec) -// Purpose: finds critical time step for nodes based on max. allowable -// projected change in depth. -// -{ - int i; // node index - double maxDepth; // max. depth allowed at node (ft) - double dYdT; // change in depth per unit time (ft/sec) - double t1; // time needed to reach depth limit (sec) - double tNode = tMin; // critical node time step (sec) - - // --- find smallest time so that estimated change in nodal depth - // does not exceed safety factor * maxdepth - for ( i = 0; i < Nobjects[NODE]; i++ ) - { - // --- see if node can be skipped - if ( Node[i].type == OUTFALL ) continue; - if ( Node[i].newDepth <= FUDGE) continue; - if ( Node[i].newDepth + FUDGE >= - Node[i].crownElev - Node[i].invertElev ) continue; - - // --- define max. allowable depth change using crown elevation - maxDepth = (Node[i].crownElev - Node[i].invertElev) * 0.25; - if ( maxDepth < FUDGE ) continue; - dYdT = Xnode[i].dYdT; - if (dYdT < FUDGE ) continue; - - // --- compute time to reach max. depth & compare with critical time - t1 = maxDepth / dYdT; - if ( t1 < tNode ) - { - tNode = t1; - *minNode = i; - } - } - return tNode; -} diff --git a/src/enums.h b/src/enums.h deleted file mode 100644 index c260e4150..000000000 --- a/src/enums.h +++ /dev/null @@ -1,500 +0,0 @@ -//----------------------------------------------------------------------------- -// enums.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 06/01/22 (Build 5.2.1) -// Author: L. Rossman -// -// Enumerated constants -// -// Update History -// ============== -// Build 5.1.004: -// - IGNORE_RDII for the ignore RDII option added. -// Build 5.1.007: -// - s_GWF for [GWF] input file section added. -// - s_ADJUST for [ADJUSTMENTS] input file section added. -// Build 5.1.008: -// - Enumerations for fullness state of a conduit added. -// - NUM_THREADS added for number of parallel threads option. -// - Runoff flow categories added to represent mass balance components. -// Build 5.1.010: -// - New ROADWAY_WEIR type of weir added. -// - Potential evapotranspiration (PET) added as a system output variable. -// Build 5.1.011: -// - s_EVENT added to InputSectionType enumeration. -// Build 5.1.013: -// - SURCHARGE_METHOD and RULE_STEP options added. -// - WEIR_CURVE added as a curve type. -// Build 5.2.0: -// - Support added for Streets and Inlets. -// - Support added for variable speed pumps. -// - Support added for analytical storage shapes. -// Build 5.2.1: -// - Adds a NEITHER option to the NormalFlowType enumeration. -//----------------------------------------------------------------------------- - -#ifndef ENUMS_H -#define ENUMS_H - - -//------------------------------------- -// Names of major object types -//------------------------------------- - enum ObjectType { - GAGE, // rain gage - SUBCATCH, // subcatchment - NODE, // conveyance system node - LINK, // conveyance system link - POLLUT, // pollutant - LANDUSE, // land use category - TIMEPATTERN, // dry weather flow time pattern - CURVE, // generic table of values - TSERIES, // generic time series of values - CONTROL, // conveyance system control rules - TRANSECT, // irregular channel cross-section - AQUIFER, // groundwater aquifer - UNITHYD, // RDII unit hydrograph - SNOWMELT, // snowmelt parameter set - SHAPE, // custom conduit shape - LID, // LID treatment units - STREET, // street cross section - INLET, // street inlet design - MAX_OBJ_TYPES}; - -//------------------------------------- -// Names of Node sub-types -//------------------------------------- - #define MAX_NODE_TYPES 5 - enum NodeType { - JUNCTION, - OUTFALL, - STORAGE, - DIVIDER}; - -//------------------------------------- -// Names of Link sub-types -//------------------------------------- - #define MAX_LINK_TYPES 5 - enum LinkType { - CONDUIT, - PUMP, - ORIFICE, - WEIR, - OUTLET}; - -//------------------------------------- -// File types -//------------------------------------- - enum FileType { - RAINFALL_FILE, // rainfall file - RUNOFF_FILE, // runoff file - HOTSTART_FILE, // hotstart file - RDII_FILE, // RDII file - INFLOWS_FILE, // inflows interface file - OUTFLOWS_FILE}; // outflows interface file - -//------------------------------------- -// File usage types -//------------------------------------- - enum FileUsageType { - NO_FILE, // no file usage - SCRATCH_FILE, // use temporary scratch file - USE_FILE, // use previously saved file - SAVE_FILE}; // save file currently in use - -//------------------------------------- -// Rain gage data types -//------------------------------------- - enum GageDataType { - RAIN_TSERIES, // rainfall from user-supplied time series - RAIN_FILE}; // rainfall from external file - -//------------------------------------- -// Cross section shape types -//------------------------------------- - enum XsectType { - DUMMY, // 0 - CIRCULAR, // 1 closed - FILLED_CIRCULAR, // 2 closed - RECT_CLOSED, // 3 closed - RECT_OPEN, // 4 - TRAPEZOIDAL, // 5 - TRIANGULAR, // 6 - PARABOLIC, // 7 - POWERFUNC, // 8 - RECT_TRIANG, // 9 - RECT_ROUND, // 10 - MOD_BASKET, // 11 - HORIZ_ELLIPSE, // 12 closed - VERT_ELLIPSE, // 13 closed - ARCH, // 14 closed - EGGSHAPED, // 15 closed - HORSESHOE, // 16 closed - GOTHIC, // 17 closed - CATENARY, // 18 closed - SEMIELLIPTICAL, // 19 closed - BASKETHANDLE, // 20 closed - SEMICIRCULAR, // 21 closed - IRREGULAR, // 22 - CUSTOM, // 23 closed - FORCE_MAIN, // 24 closed - STREET_XSECT}; // 25 - -//------------------------------------- -// Measurement units types -//------------------------------------- - enum UnitsType { - US, // US units - SI}; // SI (metric) units - - enum FlowUnitsType { - CFS, // cubic feet per second - GPM, // gallons per minute - MGD, // million gallons per day - CMS, // cubic meters per second - LPS, // liters per second - MLD}; // million liters per day - - enum ConcUnitsType { - MG, // Milligrams / L - UG, // Micrograms / L - COUNT}; // Counts / L - -//-------------------------------------- -// Quantities requiring unit conversions -//-------------------------------------- - enum ConversionType { - RAINFALL, - RAINDEPTH, - EVAPRATE, - LENGTH, - LANDAREA, - VOLUME, - WINDSPEED, - TEMPERATURE, - MASS, - GWFLOW, - FLOW}; // Flow must always be listed last - -//------------------------------------- -// Computed subcatchment quantities -//------------------------------------- - #define MAX_SUBCATCH_RESULTS 9 - enum SubcatchResultType { - SUBCATCH_RAINFALL, // rainfall intensity - SUBCATCH_SNOWDEPTH, // snow depth - SUBCATCH_EVAP, // evap loss - SUBCATCH_INFIL, // infil loss - SUBCATCH_RUNOFF, // runoff flow rate - SUBCATCH_GW_FLOW, // groundwater flow rate to node - SUBCATCH_GW_ELEV, // elevation of saturated gw table - SUBCATCH_SOIL_MOIST, // soil moisture - SUBCATCH_WASHOFF}; // pollutant washoff concentration - -//------------------------------------- -// Computed node quantities -//------------------------------------- - #define MAX_NODE_RESULTS 7 - enum NodeResultType { - NODE_DEPTH, // water depth above invert - NODE_HEAD, // hydraulic head - NODE_VOLUME, // volume stored & ponded - NODE_LATFLOW, // lateral inflow rate - NODE_INFLOW, // total inflow rate - NODE_OVERFLOW, // overflow rate - NODE_QUAL}; // concentration of each pollutant - -//------------------------------------- -// Computed link quantities -//------------------------------------- - #define MAX_LINK_RESULTS 6 - enum LinkResultType { - LINK_FLOW, // flow rate - LINK_DEPTH, // flow depth - LINK_VELOCITY, // flow velocity - LINK_VOLUME, // link volume - LINK_CAPACITY, // ratio of area to full area - LINK_QUAL}; // concentration of each pollutant - -//------------------------------------- -// System-wide quantities -//------------------------------------- -#define MAX_SYS_RESULTS 15 -enum SysFlowType { - SYS_TEMPERATURE, // air temperature - SYS_RAINFALL, // rainfall intensity - SYS_SNOWDEPTH, // snow depth - SYS_INFIL, // infil - SYS_RUNOFF, // runoff flow - SYS_DWFLOW, // dry weather inflow - SYS_GWFLOW, // ground water inflow - SYS_IIFLOW, // RDII inflow - SYS_EXFLOW, // external inflow - SYS_INFLOW, // total lateral inflow - SYS_FLOODING, // flooding outflow - SYS_OUTFLOW, // outfall outflow - SYS_STORAGE, // storage volume - SYS_EVAP, // evaporation - SYS_PET}; // potential ET - -//------------------------------------- -// Conduit flow classifications -//------------------------------------- - enum FlowClassType { - DRY, // dry conduit - UP_DRY, // upstream end is dry - DN_DRY, // downstream end is dry - SUBCRITICAL, // sub-critical flow - SUPCRITICAL, // super-critical flow - UP_CRITICAL, // free-fall at upstream end - DN_CRITICAL, // free-fall at downstream end - MAX_FLOW_CLASSES, // number of distinct flow classes - UP_FULL, // upstream end is full - DN_FULL, // downstream end is full - ALL_FULL}; // completely full - -//------------------------ -// Runoff flow categories -//------------------------ -enum RunoffFlowType { - RUNOFF_RAINFALL, // rainfall - RUNOFF_EVAP, // evaporation - RUNOFF_INFIL, // infiltration - RUNOFF_RUNOFF, // runoff - RUNOFF_DRAINS, // LID drain flow - RUNOFF_RUNON}; // runon from outfalls - -//------------------------------------- -// Surface pollutant loading categories -//------------------------------------- - enum LoadingType { - BUILDUP_LOAD, // pollutant buildup load - DEPOSITION_LOAD, // rainfall deposition load - SWEEPING_LOAD, // load removed by sweeping - BMP_REMOVAL_LOAD, // load removed by BMPs - INFIL_LOAD, // runon load removed by infiltration - RUNOFF_LOAD, // load removed by runoff - FINAL_LOAD}; // load remaining on surface - -//------------------------------------- -// Input data options -//------------------------------------- - enum RainfallType { - RAINFALL_INTENSITY, // rainfall expressed as intensity - RAINFALL_VOLUME, // rainfall expressed as volume - CUMULATIVE_RAINFALL}; // rainfall expressed as cumulative volume - - enum TempType { - NO_TEMP, // no temperature data supplied - TSERIES_TEMP, // temperatures come from time series - FILE_TEMP}; // temperatures come from file - -enum WindType { - MONTHLY_WIND, // wind speed varies by month - FILE_WIND}; // wind speed comes from file - - enum EvapType { - CONSTANT_EVAP, // constant evaporation rate - MONTHLY_EVAP, // evaporation rate varies by month - TIMESERIES_EVAP, // evaporation supplied by time series - TEMPERATURE_EVAP, // evaporation from daily temperature - FILE_EVAP, // evaporation comes from file - RECOVERY, // soil recovery pattern - DRYONLY}; // evap. allowed only in dry periods - - enum NormalizerType { - PER_AREA, // buildup is per unit of area - PER_CURB}; // buildup is per unit of curb length - - enum BuildupType { - NO_BUILDUP, // no buildup - POWER_BUILDUP, // power function buildup equation - EXPON_BUILDUP, // exponential function buildup equation - SATUR_BUILDUP, // saturation function buildup equation - EXTERNAL_BUILDUP}; // external time series buildup - - enum WashoffType { - NO_WASHOFF, // no washoff - EXPON_WASHOFF, // exponential washoff equation - RATING_WASHOFF, // rating curve washoff equation - EMC_WASHOFF}; // event mean concentration washoff - -enum SubAreaType { - IMPERV0, // impervious w/o depression storage - IMPERV1, // impervious w/ depression storage - PERV}; // pervious - - enum RunoffRoutingType { - TO_OUTLET, // perv & imperv runoff goes to outlet - TO_IMPERV, // perv runoff goes to imperv area - TO_PERV}; // imperv runoff goes to perv subarea - - enum RouteModelType { - NO_ROUTING, // no routing - SF, // steady flow model - KW, // kinematic wave model - EKW, // extended kin. wave model - DW}; // dynamic wave model - - enum ForceMainType { - H_W, // Hazen-Williams eqn. - D_W}; // Darcy-Weisbach eqn. - - enum OffsetType { - DEPTH_OFFSET, // offset measured as depth - ELEV_OFFSET}; // offset measured as elevation - - enum KinWaveMethodType { - NORMAL, // normal method - MODIFIED}; // modified method - -enum CompatibilityType { - SWMM5, // SWMM 5 weighting for area & hyd. radius - SWMM3, // SWMM 3 weighting - SWMM4}; // SWMM 4 weighting - - enum NormalFlowType { - SLOPE, // based on slope only - FROUDE, // based on Fr only - BOTH, // based on slope & Fr - NEITHER}; - - enum InertialDampingType { - NO_DAMPING, // no inertial damping - PARTIAL_DAMPING, // partial damping - FULL_DAMPING}; // full damping - - 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 - WET_WEATHER_INFLOW, // computed runoff inflow - GROUNDWATER_INFLOW, // computed groundwater inflow - RDII_INFLOW, // computed I&I inflow - FLOW_INFLOW, // inflow parameter is flow - CONCEN_INFLOW, // inflow parameter is pollutant concen. - MASS_INFLOW}; // inflow parameter is pollutant mass - - enum PatternType { - MONTHLY_PATTERN, // DWF multipliers for each month - DAILY_PATTERN, // DWF multipliers for each day of week - HOURLY_PATTERN, // DWF multipliers for each hour of day - WEEKEND_PATTERN}; // hourly multipliers for week end days - - enum OutfallType { - FREE_OUTFALL, // critical depth outfall condition - NORMAL_OUTFALL, // normal flow depth outfall condition - FIXED_OUTFALL, // fixed depth outfall condition - TIDAL_OUTFALL, // variable tidal stage outfall condition - TIMESERIES_OUTFALL}; // variable time series outfall depth - - enum StorageType { - TABULAR, // area v. depth from table - FUNCTIONAL, // area v. depth from power function - CYLINDRICAL, // area v. depth from elliptical cylinder - CONICAL, // area v. depth from elliptical cone - PARABOLOID, // area v. depth from elliptical paraboloid - PYRAMIDAL}; // area v. depth from rectangular pyramid - - enum ReactorType { - CSTR, // completely mixed reactor - PLUG}; // plug flow reactor - - enum TreatmentType { - REMOVAL, // treatment stated as a removal - CONCEN}; // treatment stated as effluent concen. - - enum DividerType { - CUTOFF_DIVIDER, // diverted flow is excess of cutoff flow - TABULAR_DIVIDER, // table of diverted flow v. inflow - WEIR_DIVIDER, // diverted flow proportional to excess flow - OVERFLOW_DIVIDER}; // diverted flow is flow > full conduit flow - - enum PumpCurveType { - TYPE1_PUMP, // flow varies stepwise with wet well volume - TYPE2_PUMP, // flow varies stepwise with inlet depth - TYPE3_PUMP, // flow varies with head delivered - TYPE4_PUMP, // flow varies with inlet depth - TYPE5_PUMP, // variable speed version of TYPE3 pump - IDEAL_PUMP}; // outflow equals inflow - - enum OrificeType { - SIDE_ORIFICE, // side orifice - BOTTOM_ORIFICE}; // bottom orifice - - enum WeirType { - TRANSVERSE_WEIR, // transverse weir - SIDEFLOW_WEIR, // side flow weir - VNOTCH_WEIR, // V-notch (triangular) weir - TRAPEZOIDAL_WEIR, // trapezoidal weir - ROADWAY_WEIR}; // FHWA HDS-5 roadway weir - - enum CurveType { - STORAGE_CURVE, // surf. area v. depth for storage node - DIVERSION_CURVE, // diverted flow v. inflow for divider node - TIDAL_CURVE, // water elev. v. hour of day for outfall - RATING_CURVE, // flow rate v. head for outlet link - CONTROL_CURVE, // control setting v. controller variable - SHAPE_CURVE, // width v. depth for custom x-section - WEIR_CURVE, // discharge coeff. v. head for weir - PUMP1_CURVE, // flow v. wet well volume for pump - PUMP2_CURVE, // flow v. depth for pump (discrete) - PUMP3_CURVE, // flow v. head for pump (continuous) - PUMP4_CURVE, // flow v. depth for pump (continuous) - PUMP5_CURVE}; // variable speed version of TYPE3 pump - - enum NodeInletType { - NO_INLET, - BYPASS, - CAPTURE - }; - - enum InputSectionType { - s_TITLE, s_OPTION, s_FILE, s_RAINGAGE, - s_TEMP, s_EVAP, s_SUBCATCH, s_SUBAREA, - s_INFIL, s_AQUIFER, s_GROUNDWATER, s_SNOWMELT, - s_JUNCTION, s_OUTFALL, s_STORAGE, s_DIVIDER, - s_CONDUIT, s_PUMP, s_ORIFICE, s_WEIR, - s_OUTLET, s_XSECTION, s_TRANSECT, s_LOSSES, - s_CONTROL, s_POLLUTANT, s_LANDUSE, s_BUILDUP, - s_WASHOFF, s_COVERAGE, s_INFLOW, s_DWF, - s_PATTERN, s_RDII, s_UNITHYD, s_LOADING, - s_TREATMENT, s_CURVE, s_TIMESERIES, s_REPORT, - s_COORDINATE, s_VERTICES, s_POLYGON, s_LABEL, - s_SYMBOL, s_BACKDROP, s_TAG, s_PROFILE, - s_MAP, s_LID_CONTROL, s_LID_USAGE, s_GWF, - s_ADJUST, s_EVENT, s_STREET, s_INLET_USAGE, - s_INLET}; - - 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, - 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}; - -enum NoYesType { - NO, - YES}; - -enum NoneAllType { - NONE, - ALL, - SOME}; - - -#endif //ENUMS_H diff --git a/src/error.c b/src/error.c deleted file mode 100644 index 626950b6f..000000000 --- a/src/error.c +++ /dev/null @@ -1,49 +0,0 @@ -//----------------------------------------------------------------------------- -// error.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Error messages -// -// Update History -// ============== -// Build 5.1.008: -// - Text of Error 217 for control rules modified. -// Build 5.1.010: -// - Text of Error 318 for rainfall data files modified. -// Build 5.1.015: -// - Added new Error 140 for storage nodes. -// Build 5.2.0: -// - Re-designed error message system. -// - Added new Error 235 for invalid infiltration parameters. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include "error.h" - -char ErrString[256]; - -char* error_getMsg(int errCode, char* msg) -{ - switch (errCode) - { - -#define ERR(code,string) case code: strcpy(msg, string); break; -#include "error.txt" -#undef ERR - - default: - strcpy(msg, ""); - } - return (msg); -}; - -int error_setInpError(int errcode, char* s) -{ - strcpy(ErrString, s); - return errcode; -} diff --git a/src/error.h b/src/error.h deleted file mode 100644 index fedf0847e..000000000 --- a/src/error.h +++ /dev/null @@ -1,182 +0,0 @@ -//----------------------------------------------------------------------------- -// error.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Error codes -// -//----------------------------------------------------------------------------- - -#ifndef ERROR_H -#define ERROR_H - -enum ErrorType { - -// ... Runtime Errors - ERR_NONE = 0, - ERR_MEMORY = 101, - ERR_KINWAVE = 103, - ERR_ODE_SOLVER = 105, - ERR_TIMESTEP = 107, - -// ... Subcatchment/Aquifer Errors - ERR_SUBCATCH_OUTLET = 108, - ERR_AQUIFER_PARAMS = 109, - ERR_GROUND_ELEV = 110, - -// ... Conduit/Pump Errors - ERR_LENGTH = 111, - ERR_ELEV_DROP = 112, - ERR_ROUGHNESS = 113, - ERR_BARRELS = 114, - ERR_SLOPE = 115, - ERR_NO_XSECT = 117, - ERR_XSECT = 119, - ERR_NO_CURVE = 121, - ERR_PUMP_LIMITS = 122, - -// ... Topology Errors - ERR_LOOP = 131, - ERR_MULTI_OUTLET = 133, - ERR_DUMMY_LINK = 134, - -// ... Node Errors - ERR_DIVIDER = 135, - ERR_DIVIDER_LINK = 136, - ERR_WEIR_DIVIDER = 137, - ERR_NODE_DEPTH = 138, - ERR_REGULATOR = 139, - ERR_STORAGE_VOLUME = 140, - ERR_OUTFALL = 141, - ERR_REGULATOR_SHAPE = 143, - ERR_NO_OUTLETS = 145, - -// ... RDII Errors - ERR_UNITHYD_TIMES = 151, - ERR_UNITHYD_RATIOS = 153, - ERR_RDII_AREA = 155, - -// ... Rain Gage Errors - ERR_RAIN_FILE_CONFLICT = 156, - ERR_RAIN_GAGE_FORMAT = 157, - ERR_RAIN_GAGE_TSERIES = 158, - ERR_RAIN_GAGE_INTERVAL = 159, - -// ... Treatment Function Error - ERR_CYCLIC_TREATMENT = 161, - -// ... Curve/Time Series Errors - ERR_CURVE_SEQUENCE = 171, - ERR_TIMESERIES_SEQUENCE = 173, - -// ... Snowmelt Errors - ERR_SNOWMELT_PARAMS = 181, - ERR_SNOWPACK_PARAMS = 182, - -// ... LID Errors - ERR_LID_TYPE = 183, - ERR_LID_LAYER = 184, - ERR_LID_PARAMS = 185, - ERR_LID_AREAS = 187, - ERR_LID_CAPTURE_AREA = 188, - -// ... Simulation Date/Time Errors - ERR_START_DATE = 191, - ERR_REPORT_DATE = 193, - ERR_REPORT_STEP = 195, - -// ... Input Parser Errors - ERR_INPUT = 200, - ERR_LINE_LENGTH = 201, - ERR_ITEMS = 203, - ERR_KEYWORD = 205, - ERR_DUP_NAME = 207, - ERR_NAME = 209, - ERR_NUMBER = 211, - ERR_DATETIME = 213, - ERR_RULE = 217, - ERR_TRANSECT_UNKNOWN = 219, - ERR_TRANSECT_SEQUENCE = 221, - ERR_TRANSECT_TOO_FEW = 223, - ERR_TRANSECT_TOO_MANY = 225, - ERR_TRANSECT_MANNING = 227, - ERR_TRANSECT_OVERBANK = 229, - ERR_TRANSECT_NO_DEPTH = 231, - ERR_MATH_EXPR = 233, - ERR_INFIL_PARAMS = 235, - -// ... File Name/Opening Errors - ERR_FILE_NAME = 301, - ERR_INP_FILE = 303, - ERR_RPT_FILE = 305, - ERR_OUT_FILE = 307, - ERR_OUT_SIZE = 308, - ERR_OUT_WRITE = 309, - ERR_OUT_READ = 311, - -// ... Rain File Errors - ERR_RAIN_FILE_SCRATCH = 313, - ERR_RAIN_FILE_OPEN = 315, - ERR_RAIN_FILE_DATA = 317, - ERR_RAIN_FILE_SEQUENCE = 318, - ERR_RAIN_FILE_FORMAT = 319, - ERR_RAIN_IFACE_FORMAT = 320, - ERR_RAIN_FILE_GAGE = 321, - -// ... Runoff File Errors - ERR_RUNOFF_FILE_OPEN = 323, - ERR_RUNOFF_FILE_FORMAT = 325, - ERR_RUNOFF_FILE_END = 327, - ERR_RUNOFF_FILE_READ = 329, - -// ... Hotstart File Errors - ERR_HOTSTART_FILE_OPEN = 331, - ERR_HOTSTART_FILE_FORMAT = 333, - ERR_HOTSTART_FILE_READ = 335, - -// ... Climate File Errors - ERR_NO_CLIMATE_FILE = 336, - ERR_CLIMATE_FILE_OPEN = 337, - ERR_CLIMATE_FILE_READ = 338, - ERR_CLIMATE_END_OF_FILE = 339, - -// ... RDII File Errors - ERR_RDII_FILE_SCRATCH = 341, - ERR_RDII_FILE_OPEN = 343, - ERR_RDII_FILE_FORMAT = 345, - -// ... Routing File Errors - ERR_ROUTING_FILE_OPEN = 351, - ERR_ROUTING_FILE_FORMAT = 353, - ERR_ROUTING_FILE_NOMATCH = 355, - ERR_ROUTING_FILE_NAMES = 357, - -// ... Time Series File Errors - ERR_TABLE_FILE_OPEN = 361, - ERR_TABLE_FILE_READ = 363, - -// ... Runtime Errors - ERR_SYSTEM = 500, - -// ... API Errors - ERR_API_NOT_OPEN = 501, - ERR_API_NOT_STARTED = 502, - ERR_API_NOT_ENDED = 503, - ERR_API_OBJECT_TYPE = 504, - ERR_API_OBJECT_INDEX = 505, - ERR_API_OBJECT_NAME = 506, - ERR_API_PROPERTY_TYPE = 507, - ERR_API_PROPERTY_VALUE = 508, - ERR_API_TIME_PERIOD = 509, - -// ... Additional Errors - MAXERRMSG = 1000 -}; - -char* error_getMsg(int i, char* msg); -int error_setInpError(int errcode, char* s); - -#endif //ERROR_H diff --git a/src/error.txt b/src/error.txt deleted file mode 100644 index 30d45a783..000000000 --- a/src/error.txt +++ /dev/null @@ -1,135 +0,0 @@ -// SWMM 5.2 Error Messages - -ERR(101,"\n ERROR 101: memory allocation error.") -ERR(103,"\n ERROR 103: cannot solve KW equations for Link %s.") -ERR(105,"\n ERROR 105: cannot open ODE solver.") -ERR(107,"\n ERROR 107: cannot compute a valid time step.") - -ERR(108,"\n ERROR 108: ambiguous outlet ID name for Subcatchment %s.") -ERR(109,"\n ERROR 109: invalid parameter values for Aquifer %s.") -ERR(110,"\n ERROR 110: ground elevation is below water table for Subcatchment %s.") - -ERR(111,"\n ERROR 111: invalid length for Conduit %s.") -ERR(112,"\n ERROR 112: elevation drop exceeds length for Conduit %s.") -ERR(113,"\n ERROR 113: invalid roughness for Conduit %s.") -ERR(114,"\n ERROR 114: invalid number of barrels for Conduit %s.") -ERR(115,"\n ERROR 115: adverse slope for Conduit %s.") -ERR(117,"\n ERROR 117: no cross section defined for Link %s.") -ERR(119,"\n ERROR 119: invalid cross section for Link %s.") -ERR(121,"\n ERROR 121: missing or invalid pump curve assigned to Pump %s.") -ERR(122,"\n ERROR 122: startup depth not higher than shutoff depth for Pump %s.") - -ERR(131,"\n ERROR 131: the following links form cyclic loops in the drainage system:") -ERR(133,"\n ERROR 133: Node %s has more than one outlet link.") -ERR(134,"\n ERROR 134: Node %s has illegal DUMMY link connections.") - -ERR(135,"\n ERROR 135: Divider %s does not have two outlet links.") -ERR(136,"\n ERROR 136: Divider %s has invalid diversion link.") -ERR(137,"\n ERROR 137: Weir Divider %s has invalid parameters.") -ERR(138,"\n ERROR 138: Node %s has initial depth greater than maximum depth.") -ERR(139,"\n ERROR 139: Regulator %s is the outlet of a non-storage node.") -ERR(140,"\n ERROR 140: Storage node %s has negative volume at full depth.") -ERR(141,"\n ERROR 141: Outfall %s has more than 1 inlet link or an outlet link.") -ERR(143,"\n ERROR 143: Regulator %s has invalid cross-section shape.") -ERR(145,"\n ERROR 145: Drainage system has no acceptable outlet nodes.") - -ERR(151,"\n ERROR 151: a Unit Hydrograph in set %s has invalid time base.") -ERR(153,"\n ERROR 153: a Unit Hydrograph in set %s has invalid response ratios.") -ERR(155,"\n ERROR 155: invalid sewer area for RDII at node %s.") - -ERR(156,"\n ERROR 156: ambiguous station ID for Rain Gage %s.") -ERR(157,"\n ERROR 157: inconsistent rainfall format for Rain Gage %s.") -ERR(158,"\n ERROR 158: time series for Rain Gage %s is also used by another object.") -ERR(159,"\n ERROR 159: recording interval greater than time series interval for Rain Gage %s.") - -ERR(161,"\n ERROR 161: cyclic dependency in treatment functions at node %s.") - -ERR(171,"\n ERROR 171: Curve %s has invalid or out of sequence data.") -ERR(173,"\n ERROR 173: Time Series %s has its data out of sequence.") - -ERR(181,"\n ERROR 181: invalid Snow Melt Climatology parameters.") -ERR(182,"\n ERROR 182: invalid parameters for Snow Pack %s.") - -ERR(183,"\n ERROR 183: no type specified for LID %s.") -ERR(184,"\n ERROR 184: missing layer for LID %s.") -ERR(185,"\n ERROR 185: invalid parameter value for LID %s.") -ERR(187,"\n ERROR 187: LID area exceeds total area for Subcatchment %s.") -ERR(188,"\n ERROR 188: LID capture area exceeds total impervious area for Subcatchment %s.") - -ERR(191,"\n ERROR 191: simulation start date comes after ending date.") -ERR(193,"\n ERROR 193: report start date comes after ending date.") -ERR(195,"\n ERROR 195: reporting time step or duration is less than routing time step.") - -ERR(200,"\n ERROR 200: one or more errors in input file.") -ERR(201,"\n ERROR 201: too many characters in input line ") -ERR(203,"\n ERROR 203: too few items ") -ERR(205,"\n ERROR 205: invalid keyword %s ") -ERR(207,"\n ERROR 207: duplicate ID name %s ") -ERR(209,"\n ERROR 209: undefined object %s ") -ERR(211,"\n ERROR 211: invalid number %s ") -ERR(213,"\n ERROR 213: invalid date/time %s ") -ERR(217,"\n ERROR 217: control rule clause invalid or out of sequence ") -ERR(219,"\n ERROR 219: data provided for unidentified transect ") -ERR(221,"\n ERROR 221: transect station out of sequence ") -ERR(223,"\n ERROR 223: Transect %s has too few stations.") -ERR(225,"\n ERROR 225: Transect %s has too many stations.") -ERR(227,"\n ERROR 227: Transect %s has no Manning's N.") -ERR(229,"\n ERROR 229: Transect %s has invalid overbank locations.") -ERR(231,"\n ERROR 231: Transect %s has no depth.") -ERR(233,"\n ERROR 233: invalid math expression ") -ERR(235,"\n ERROR 235: invalid infiltration parameters ") - -ERR(301,"\n ERROR 301: files share same names.") -ERR(303,"\n ERROR 303: cannot open input file.") -ERR(305,"\n ERROR 305: cannot open report file.") -ERR(307,"\n ERROR 307: cannot open binary results file.") -ERR(308,"\n ERROR 308: amount of output produced will exceed maximum file size.") - -ERR(309,"\n ERROR 309: error writing to binary results file.") -ERR(311,"\n ERROR 311: error reading from binary results file.") - -ERR(313,"\n ERROR 313: cannot open scratch rainfall interface file.") -ERR(315,"\n ERROR 315: cannot open rainfall interface file %s.") -ERR(317,"\n ERROR 317: cannot open rainfall data file %s.") -ERR(318,"\n ERROR 318: the following line is out of sequence in rainfall data file %s.") -ERR(319,"\n ERROR 319: unknown format for rainfall data file %s.") -ERR(320,"\n ERROR 320: invalid format for rainfall interface file.") -ERR(321,"\n ERROR 321: no data in rainfall interface file for gage %s.") - -ERR(323,"\n ERROR 323: cannot open runoff interface file %s.") -ERR(325,"\n ERROR 325: incompatible data found in runoff interface file.") -ERR(327,"\n ERROR 327: attempting to read beyond end of runoff interface file.") -ERR(329,"\n ERROR 329: error in reading from runoff interface file.") - -ERR(331,"\n ERROR 331: cannot open hot start interface file %s.") -ERR(333,"\n ERROR 333: incompatible data found in hot start interface file.") -ERR(335,"\n ERROR 335: error in reading from hot start interface file.") - -ERR(336,"\n ERROR 336: no climate file specified for evaporation and/or wind speed.") -ERR(337,"\n ERROR 337: cannot open climate file %s.") -ERR(338,"\n ERROR 338: error in reading from climate file %s.") -ERR(339,"\n ERROR 339: attempt to read beyond end of climate file %s.") - -ERR(341,"\n ERROR 341: cannot open scratch RDII interface file.") -ERR(343,"\n ERROR 343: cannot open RDII interface file %s.") -ERR(345,"\n ERROR 345: invalid format for RDII interface file.") - -ERR(351,"\n ERROR 351: cannot open routing interface file %s.") -ERR(353,"\n ERROR 353: invalid format for routing interface file %s.") -ERR(355,"\n ERROR 355: mis-matched names in routing interface file %s.") -ERR(357,"\n ERROR 357: inflows and outflows interface files have same name.") - -ERR(361,"\n ERROR 361: could not open external file used for Time Series %s.") -ERR(363,"\n ERROR 363: invalid data in external file used for Time Series %s.") - -// API Error Keys -ERR(500,"\n ERROR 500: System exception thrown.") -ERR(501,"\n API Error 501: project not opened.") -ERR(502,"\n API Error 502: simulation not started.") -ERR(503,"\n API Error 503: simulation not ended.") -ERR(504,"\n API Error 504: invalid object type.") -ERR(505,"\n API Error 505: invalid object index.") -ERR(506,"\n API Error 506: invalid object name.") -ERR(507,"\n API Error 507: invalid property type.") -ERR(508,"\n API Error 508: invalid property value.") -ERR(509,"\n API Error 509: invalid time period.") diff --git a/src/exfil.c b/src/exfil.c deleted file mode 100644 index f1cb2adf6..000000000 --- a/src/exfil.c +++ /dev/null @@ -1,252 +0,0 @@ -//----------------------------------------------------------------------------- -// exfil.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Storage unit exfiltration functions. -// -// Update History -// ============== -// Build 5.1.008: -// - Monthly conductivity adjustment applied to exfiltration rate. -// Build 5.1.010: -// - New modified Green-Ampt infiltration option used. -// Build 5.1.011: -// - Fixed units conversion error for storage units with surface area curves. -// Build 5.2.0: -// - Support added for analytical storage shapes. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include "headers.h" -#include "infil.h" -#include "exfil.h" - -static int createStorageExfil(int k, double x[]); - -//============================================================================= - -int exfil_readStorageParams(int k, char* tok[], int ntoks, int n) -// -// Input: k = storage unit index -// tok[] = array of string tokens -// ntoks = number of tokens -// n = last token processed -// Output: returns an error code -// Purpose: reads a storage unit's exfiltration parameters from a -// tokenized line of input. -// -{ - int i; - double x[3]; //suction head, Ksat, IMDmax - - // --- read Ksat if it's the only remaining token - if ( ntoks == n+1 ) - { - if ( ! getDouble(tok[n], &x[1]) ) - return error_setInpError(ERR_NUMBER, tok[n]); - x[0] = 0.0; - x[2] = 0.0; - } - - // --- otherwise read Green-Ampt infiltration parameters from input tokens - else if ( ntoks < n + 3 ) return error_setInpError(ERR_ITEMS, ""); - else for (i = 0; i < 3; i++) - { - if ( ! getDouble(tok[n+i], &x[i]) ) - return error_setInpError(ERR_NUMBER, tok[n+i]); - } - - // --- no exfiltration if Ksat is 0 - if ( x[1] == 0.0 ) return 0; - - // --- create an exfiltration object - return createStorageExfil(k, x); -} - -//============================================================================= - -void exfil_initState(int k) -// -// Input: k = storage unit index -// Output: none -// Purpose: initializes the state of a storage unit's exfiltration object. -// -{ - int i; - double a, alast, d; - TTable* aCurve; - TExfil* exfil = Storage[k].exfil; - - // --- initialize exfiltration object - if ( exfil != NULL ) - { - // --- initialize the Green-Ampt infil. parameters - grnampt_initState(exfil->btmExfil); - grnampt_initState(exfil->bankExfil); - - switch (Storage[k].shape) - { - // --- shape given by a Storage Curve - case TABULAR: - i = Storage[k].aCurve; - exfil->btmArea = 0.0; - exfil->bankMinDepth = 0.0; - exfil->bankMaxDepth = 0.0; - exfil->bankMaxArea = 0.0; - if ( i >= 0 ) - { - // --- get bottom area - aCurve = &Curve[i]; - Storage[k].exfil->btmArea = table_lookupEx(aCurve, 0.0); - - // --- find min/max bank depths and max. bank area - table_getFirstEntry(aCurve, &d, &a); - alast = a; - while ( table_getNextEntry(aCurve, &d, &a) ) - { - if ( a < alast ) break; - else if ( a > alast ) - { - exfil->bankMaxArea = a; - exfil->bankMaxDepth = d; - } - else if ( exfil->bankMaxArea == 0.0 ) - exfil->bankMinDepth = d; - else break; - alast = a; - } - - // --- convert from user units to internal units - exfil->btmArea /= UCF(LENGTH) * UCF(LENGTH); - exfil->bankMaxArea /= UCF(LENGTH) * UCF(LENGTH); - exfil->bankMinDepth /= UCF(LENGTH); - exfil->bankMaxDepth /= UCF(LENGTH); - } - break; - - // --- functional storage shape curve - case FUNCTIONAL: - exfil->btmArea = Storage[k].a0; - if ( Storage[k].a2 == 0.0 ) - exfil->btmArea +=Storage[k].a1; - exfil->bankMinDepth = 0.0; - exfil->bankMaxDepth = BIG; - exfil->bankMaxArea = BIG; - break; - - // --- cylindrical, conical & prismatic shapes - case CYLINDRICAL: - case CONICAL: - case PYRAMIDAL: - exfil->btmArea = Storage[k].a0; - exfil->bankMinDepth = 0.0; - exfil->bankMaxDepth = BIG; - exfil->bankMaxArea = BIG; - break; - } - } -} - -//============================================================================= - -double exfil_getLoss(TExfil* exfil, double tStep, double depth, double area) -// -// Input: exfil = ptr. to a storage exfiltration object -// tStep = time step (sec) -// depth = water depth (ft) -// area = surface area (ft2) -// Output: returns exfiltration rate out of storage unit (cfs) -// Purpose: computes rate of water exfiltrated from a storage node into -// the soil beneath it. -// -{ - double exfilRate = 0.0; - - // --- find infiltration through bottom of unit - if ( exfil->btmExfil->IMDmax == 0.0 ) - { - exfilRate = exfil->btmExfil->Ks * Adjust.hydconFactor; - } - else exfilRate = grnampt_getInfil(exfil->btmExfil, tStep, 0.0, depth, - MOD_GREEN_AMPT); - exfilRate *= exfil->btmArea; - - // --- find infiltration through sloped banks - if ( depth > exfil->bankMinDepth ) - { - // --- get area of banks - area = MIN(area, exfil->bankMaxArea) - exfil->btmArea; - if ( area > 0.0 ) - { - // --- if infil. rate not a function of depth - if ( exfil->btmExfil->IMDmax == 0.0 ) - { - exfilRate += area * exfil->btmExfil->Ks * Adjust.hydconFactor; - } - - // --- infil. rate depends on depth above bank - else - { - // --- case where water depth is above the point where the - // storage curve no longer has increasing area with depth - if ( depth > exfil->bankMaxDepth ) - { - depth = depth - exfil->bankMaxDepth + - (exfil->bankMaxDepth - exfil->bankMinDepth) / 2.0; - } - - // --- case where water depth is below top of bank - else depth = (depth - exfil->bankMinDepth) / 2.0; - - // --- use Green-Ampt function for bank infiltration - exfilRate += area * grnampt_getInfil(exfil->bankExfil, - tStep, 0.0, depth, MOD_GREEN_AMPT); - } - } - } - return exfilRate; -} - -//============================================================================= - -int createStorageExfil(int k, double x[]) -// -// Input: k = index of storage unit node -// x = array of Green-Ampt infiltration parameters -// Output: returns an error code. -// Purpose: creates an exfiltration object for a storage node. -// -// Note: the exfiltration object is freed in project.c. -// -{ - TExfil* exfil; - - // --- create an exfiltration object for the storage node - exfil = Storage[k].exfil; - if ( exfil == NULL ) - { - exfil = (TExfil *) malloc(sizeof(TExfil)); - if ( exfil == NULL ) return error_setInpError(ERR_MEMORY, ""); - Storage[k].exfil = exfil; - - // --- create Green-Ampt infiltration objects for the bottom & banks - exfil->btmExfil = NULL; - exfil->bankExfil = NULL; - exfil->btmExfil = (TGrnAmpt *) malloc(sizeof(TGrnAmpt)); - if ( exfil->btmExfil == NULL ) return error_setInpError(ERR_MEMORY, ""); - exfil->bankExfil = (TGrnAmpt *) malloc(sizeof(TGrnAmpt)); - if ( exfil->bankExfil == NULL ) return error_setInpError(ERR_MEMORY, ""); - } - - // --- initialize the Green-Ampt parameters - if ( !grnampt_setParams(exfil->btmExfil, x) ) - return error_setInpError(ERR_NUMBER, ""); - grnampt_setParams(exfil->bankExfil, x); - return 0; -} diff --git a/src/exfil.h b/src/exfil.h deleted file mode 100644 index 8da4da302..000000000 --- a/src/exfil.h +++ /dev/null @@ -1,35 +0,0 @@ -//----------------------------------------------------------------------------- -// exfil.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Public interface for exfiltration functions. -//----------------------------------------------------------------------------- - -#ifndef EXFIL_H -#define EXFIL_H - -//---------------------------- -// EXFILTRATION OBJECT -//---------------------------- -typedef struct -{ - TGrnAmpt* btmExfil; - TGrnAmpt* bankExfil; - double btmArea; - double bankMinDepth; - double bankMaxDepth; - double bankMaxArea; -} TExfil; - -//----------------------------------------------------------------------------- -// Exfiltration Methods -//----------------------------------------------------------------------------- -int exfil_readStorageParams(int k, char* tok[], int ntoks, int n); -void exfil_initState(int k); -double exfil_getLoss(TExfil* exfil, double tStep, double depth, double area); - -#endif diff --git a/src/findroot.c b/src/findroot.c deleted file mode 100644 index d6056f658..000000000 --- a/src/findroot.c +++ /dev/null @@ -1,138 +0,0 @@ -//----------------------------------------------------------------------------- -// findroot.c -// -// Finds solution of func(x) = 0 using either the Newton-Raphson -// method or Ridder's Method. -// Based on code from Numerical Recipes in C (Cambridge University -// Press, 1992). -// -// Date: 11/19/13 -// Author: L. Rossman -//----------------------------------------------------------------------------- - -#include -#include "findroot.h" - -#define SIGN(a,b) ((b) >= 0.0 ? fabs(a) : -fabs(a)) -#define MAXIT 60 - - -int findroot_Newton(double x1, double x2, double* rts, double xacc, - void (*func) (double x, double* f, double* df, void* p), - void* p) -// -// Using a combination of Newton-Raphson and bisection, find the root of a -// function func bracketed between x1 and x2. The root, returned in rts, -// will be refined until its accuracy is known within +/-xacc. func is a -// user-supplied routine, that returns both the function value and the first -// derivative of the function. p is a pointer to any auxilary data structure -// that func may require. It can be NULL if not needed. The function returns -// the number of function evaluations used or 0 if the maximum allowed -// iterations were exceeded. -// -// NOTES: -// 1. The calling program must insure that the signs of func(x1) and func(x2) -// are not the same, otherwise x1 and x2 do not bracket the root. -// 2. If func(x1) > func(x2) then the order of x1 and x2 should be -// switched in the call to Newton. -// -{ - int j, n = 0; - double df, dx, dxold, f, x; - double temp, xhi, xlo; - - // Initialize the "stepsize before last" and the last step. - x = *rts; - xlo = x1; - xhi = x2; - dxold = fabs(x2-x1); - dx = dxold; - func(x, &f, &df, p); - n++; - - // Loop over allowed iterations. - for (j=1; j<=MAXIT; j++) - { - // Bisect if Newton out of range or not decreasing fast enough. - if ( ( ( (x-xhi)*df-f)*((x-xlo)*df-f) >= 0.0 - || (fabs(2.0*f) > fabs(dxold*df) ) ) ) - { - dxold = dx; - dx = 0.5*(xhi-xlo); - x = xlo + dx; - if ( xlo == x ) break; - } - - // Newton step acceptable. Take it. - else - { - dxold = dx; - dx = f/df; - temp = x; - x -= dx; - if ( temp == x ) break; - } - - // Convergence criterion. - if ( fabs(dx) < xacc ) break; - - // Evaluate function. Maintain bracket on the root. - func(x, &f, &df, p); - n++; - if ( f < 0.0 ) xlo = x; - else xhi = x; - } - *rts = x; - if ( n <= MAXIT) return n; - else return 0; -}; - - -double findroot_Ridder(double x1, double x2, double xacc, - double (*func)(double, void* p), void* p) -{ - int j; - double ans, fhi, flo, fm, fnew, s, xhi, xlo, xm, xnew; - - flo = func(x1, p); - fhi = func(x2, p); - if ( flo == 0.0 ) return x1; - if ( fhi == 0.0 ) return x2; - ans = 0.5*(x1+x2); - if ( (flo > 0.0 && fhi < 0.0) || (flo < 0.0 && fhi > 0.0) ) - { - xlo = x1; - xhi = x2; - for (j=1; j<=MAXIT; j++) { - xm = 0.5*(xlo + xhi); - fm = func(xm, p); - s = sqrt( fm*fm - flo*fhi ); - if (s == 0.0) return ans; - xnew = xm + (xm-xlo)*( (flo >= fhi ? 1.0 : -1.0)*fm/s ); - if ( fabs(xnew - ans) <= xacc ) break; - ans = xnew; - fnew = func(ans, p); - if ( SIGN(fm, fnew) != fm) - { - xlo = xm; - flo = fm; - xhi = ans; - fhi = fnew; - } - else if ( SIGN(flo, fnew) != flo ) - { - xhi = ans; - fhi = fnew; - } - else if ( SIGN(fhi, fnew) != fhi) - { - xlo = ans; - flo = fnew; - } - else return ans; - if ( fabs(xhi - xlo) <= xacc ) return ans; - } - return ans; - } - return -1.e20; -} diff --git a/src/findroot.h b/src/findroot.h deleted file mode 100644 index 3b54c6456..000000000 --- a/src/findroot.h +++ /dev/null @@ -1,18 +0,0 @@ -//----------------------------------------------------------------------------- -// findroot.h -// -// Header file for root finding method contained in findroot.c -// -// Last modified on 11/19/13. -//----------------------------------------------------------------------------- - -#ifndef FINDROOT_H -#define FINDROOT_H - -int findroot_Newton(double x1, double x2, double* rts, double xacc, - void (*func) (double x, double* f, double* df, void* p), - void* p); -double findroot_Ridder(double x1, double x2, double xacc, - double (*func)(double, void* p), void* p); - -#endif //FINDROOT_H diff --git a/src/flowrout.c b/src/flowrout.c deleted file mode 100644 index f296c3fb0..000000000 --- a/src/flowrout.c +++ /dev/null @@ -1,800 +0,0 @@ -//----------------------------------------------------------------------------- -// flowrout.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 05/02/22 (Build 5.2.1) -// Author: L. Rossman -// M. Tryby (EPA) -// -// Flow routing functions. -// -// Update History -// ============== -// Build 5.1.007: -// - updateStorageState() modified in response to node outflow being -// initialized with current evap & seepage losses in routing_execute(). -// Build 5.1.008: -// - Determination of node crown elevations moved to dynwave.c. -// - Support added for new way of recording conduit's fullness state. -// Build 5.1.012: -// - Overflow computed in updateStorageState() must be non-negative. -// - Terminal storage nodes now updated corectly. -// Build 5.1.014: -// - Arguments to function link_getLossRate changed. -// Build 5.2.0: -// - Correction made to updating state of terminal storage nodes. -// Build 5.2.1: -// - For storage routing, after convergence the reported depth is now -// based on the last volume found rather than the next trial depth. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include "headers.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -static const double OMEGA = 0.55; // under-relaxation parameter -static const int MAXITER = 10; // max. iterations for storage updating -static const double STOPTOL = 0.005; // storage updating stopping tolerance - -//----------------------------------------------------------------------------- -// External functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// flowrout_init (called by routing_open) -// flowrout_close (called by routing_close) -// flowrout_getRoutingStep (called routing_getRoutingStep) -// flowrout_execute (called routing_execute) - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static void initLinkDepths(void); -static void initNodeDepths(void); -static void initNodes(void); -static void initLinks(int routingModel); -static void validateTreeLayout(void); -static void validateGeneralLayout(void); -static void updateStorageState(int i, int j, int links[], double dt); -static double getStorageOutflow(int node, int j, int links[], double dt); -static double getLinkInflow(int link, double dt); -static void setNewNodeState(int node, double dt); -static void setNewLinkState(int link); -static void updateNodeDepth(int node, double y); -static int steadyflow_execute(int link, double* qin, double* qout, - double tStep); - - -//============================================================================= - -void flowrout_init(int routingModel) -// -// Input: routingModel = routing model code -// Output: none -// Purpose: initializes flow routing system. -// -{ - // --- initialize for dynamic wave routing - if ( routingModel == DW ) - { - // --- check for valid conveyance network layout - validateGeneralLayout(); - dynwave_init(); - - // --- initialize node & link depths if not using a hotstart file - if ( Fhotstart1.mode == NO_FILE ) - { - initNodeDepths(); - initLinkDepths(); - } - } - - // --- validate network layout for kinematic wave routing - else validateTreeLayout(); - - // --- initialize node & link volumes - initNodes(); - initLinks(routingModel); -} - -//============================================================================= - -void flowrout_close(int routingModel) -// -// Input: routingModel = routing method code -// Output: none -// Purpose: closes down routing method used. -// -{ - if ( routingModel == DW ) dynwave_close(); -} - -//============================================================================= - -double flowrout_getRoutingStep(int routingModel, double fixedStep) -// -// Input: routingModel = type of routing method used -// fixedStep = user-assigned max. routing step (sec) -// Output: returns adjusted value of routing time step (sec) -// Purpose: finds variable time step for dynamic wave routing. -// -{ - if ( routingModel == DW ) - { - return dynwave_getRoutingStep(fixedStep); - } - return fixedStep; -} - -//============================================================================= - -int flowrout_execute(int links[], int routingModel, double tStep) -// -// Input: links = array of link indexes in topo-sorted order (per routing model) -// routingModel = type of routing method used -// tStep = routing time step (sec) -// Output: returns number of computational steps taken -// Purpose: routes flow through conveyance network over current time step. -// -{ - int i, j; - int n1; // upstream node of link - double qin; // link inflow (cfs) - double qout; // link outflow (cfs) - double steps; // computational step count - - // --- set overflows to drain any ponded water - if ( ErrorCode ) return 0; - for (j = 0; j < Nobjects[NODE]; j++) - { - Node[j].updated = FALSE; - Node[j].overflow = 0.0; - if ( Node[j].type != STORAGE - && Node[j].newVolume > Node[j].fullVolume ) - { - Node[j].overflow = (Node[j].newVolume - Node[j].fullVolume)/tStep; - } - } - - // --- execute dynamic wave routing if called for - if ( routingModel == DW ) - { - return dynwave_execute(tStep); - } - - // --- otherwise examine each link, moving from upstream to downstream - steps = 0.0; - for (i = 0; i < Nobjects[LINK]; i++) - { - // --- see if upstream node is a storage unit whose state needs updating - j = links[i]; - n1 = Link[j].node1; - if ( Node[n1].type == STORAGE ) updateStorageState(n1, i, links, tStep); - - // --- retrieve inflow at upstream end of link - qin = getLinkInflow(j, tStep); - - // --- route flow through link - if ( routingModel == SF ) - steps += steadyflow_execute(j, &qin, &qout, tStep); - else - steps += kinwave_execute(j, &qin, &qout, tStep); - Link[j].newFlow = qout; - - // adjust outflow at upstream node and inflow at downstream node - Node[ Link[j].node1 ].outflow += qin; - Node[ Link[j].node2 ].inflow += qout; - } - if ( Nobjects[LINK] > 0 ) steps /= Nobjects[LINK]; - - // --- update state of each non-updated node and link - for ( j=0; j 2 ) - { - report_writeErrorMsg(ERR_DIVIDER, Node[j].ID); - } - break; - - // --- outfalls cannot have any outlet links - case OUTFALL: - if ( Node[j].degree > 0 ) - { - report_writeErrorMsg(ERR_OUTFALL, Node[j].ID); - } - break; - - // --- storage nodes can have multiple outlets - case STORAGE: break; - - // --- all other nodes allowed only one outlet link - default: - if ( Node[j].degree > 1 ) - { - report_writeErrorMsg(ERR_MULTI_OUTLET, Node[j].ID); - } - } - } - - // --- check links - for (j=0; j 1 ) - { - report_writeErrorMsg(ERR_DUMMY_LINK, Node[i].ID); - } - } - } - - // --- check each node to see if it qualifies as an outlet node - // (meaning that degree = 0) - for ( i = 0; i < Nobjects[NODE]; i++ ) - { - // --- if node is of type Outfall, check that it has only 1 - // connecting link (which can either be an outflow or inflow link) - if ( Node[i].type == OUTFALL ) - { - if ( Node[i].degree + (int)Node[i].inflow > 1 ) - { - report_writeErrorMsg(ERR_OUTFALL, Node[i].ID); - } - else outletCount++; - } - } - if ( outletCount == 0 ) report_writeErrorMsg(ERR_NO_OUTLETS, ""); - - // --- reset node inflows back to zero - for ( i = 0; i < Nobjects[NODE]; i++ ) - { - if ( Node[i].inflow == 0.0 ) Node[i].degree = -Node[i].degree; - Node[i].inflow = 0.0; - } -} - -//============================================================================= - -void initNodeDepths(void) -// -// Input: none -// Output: none -// Purpose: sets initial depth at nodes for Dynamic Wave flow routing. -// -{ - int i; // link or node index - int n; // node index - double y; // node water depth (ft) - - // --- use Node[].inflow as a temporary accumulator for depth in - // connecting links and Node[].outflow as a temporary counter - // for the number of connecting links - for (i = 0; i < Nobjects[NODE]; i++) - { - Node[i].inflow = 0.0; - Node[i].outflow = 0.0; - } - - // --- total up flow depths in all connecting links into nodes - for (i = 0; i < Nobjects[LINK]; i++) - { - if ( Link[i].newDepth > FUDGE ) y = Link[i].newDepth + Link[i].offset1; - else y = 0.0; - n = Link[i].node1; - Node[n].inflow += y; - Node[n].outflow += 1.0; - n = Link[i].node2; - Node[n].inflow += y; - Node[n].outflow += 1.0; - } - - // --- if no user-supplied depth then set initial depth at non-storage/ - // non-outfall nodes to average of depths in connecting links - for ( i = 0; i < Nobjects[NODE]; i++ ) - { - if ( Node[i].type == OUTFALL ) continue; - if ( Node[i].type == STORAGE ) continue; - if ( Node[i].initDepth > 0.0 ) continue; - if ( Node[i].outflow > 0.0 ) - { - Node[i].newDepth = Node[i].inflow / Node[i].outflow; - } - } - - // --- compute initial depths at all outfall nodes - for ( i = 0; i < Nobjects[LINK]; i++ ) link_setOutfallDepth(i); -} - -//============================================================================= - -void initLinkDepths() -// -// Input: none -// Output: none -// Purpose: sets initial flow depths in conduits under Dyn. Wave routing. -// -{ - int i; // link index - double y, y1, y2; // depths (ft) - - // --- examine each link - for (i = 0; i < Nobjects[LINK]; i++) - { - // --- examine each conduit - if ( Link[i].type == CONDUIT ) - { - // --- skip conduits with user-assigned initial flows - // (their depths have already been set to normal depth) - if ( Link[i].q0 != 0.0 ) continue; - - // --- set depth to average of depths at end nodes - y1 = Node[Link[i].node1].newDepth - Link[i].offset1; - y1 = MAX(y1, 0.0); - y1 = MIN(y1, Link[i].xsect.yFull); - y2 = Node[Link[i].node2].newDepth - Link[i].offset2; - y2 = MAX(y2, 0.0); - y2 = MIN(y2, Link[i].xsect.yFull); - y = 0.5 * (y1 + y2); - y = MAX(y, FUDGE); - Link[i].newDepth = y; - } - } -} - -//============================================================================= - -void initNodes() -// -// Input: none -// Output: none -// Purpose: sets initial inflow/outflow and volume for each node -// -{ - int i; - - for ( i = 0; i < Nobjects[NODE]; i++ ) - { - // --- initialize node inflow and outflow - Node[i].inflow = Node[i].newLatFlow; - Node[i].outflow = 0.0; - - // --- initialize node volume - Node[i].newVolume = 0.0; - if ( AllowPonding && - Node[i].pondedArea > 0.0 && - Node[i].newDepth > Node[i].fullDepth ) - { - Node[i].newVolume = Node[i].fullVolume + - (Node[i].newDepth - Node[i].fullDepth) * - Node[i].pondedArea; - } - else Node[i].newVolume = node_getVolume(i, Node[i].newDepth); - } - - // --- update nodal inflow/outflow at ends of each link - // (needed for Steady Flow & Kin. Wave routing) - for ( i = 0; i < Nobjects[LINK]; i++ ) - { - if ( Link[i].newFlow >= 0.0 ) - { - Node[Link[i].node1].outflow += Link[i].newFlow; - Node[Link[i].node2].inflow += Link[i].newFlow; - } - else - { - Node[Link[i].node1].inflow -= Link[i].newFlow; - Node[Link[i].node2].outflow -= Link[i].newFlow; - } - } -} - -//============================================================================= - -void initLinks(int routingModel) -// -// Input: none -// Output: none -// Purpose: sets initial upstream/downstream conditions in links. -// -{ - int i; // link index - int k; // conduit or pump index - - // --- examine each link - for ( i = 0; i < Nobjects[LINK]; i++ ) - { - if ( routingModel == SF) Link[i].newFlow = 0.0; - - // --- otherwise if link is a conduit - else if ( Link[i].type == CONDUIT ) - { - // --- assign initial flow to both ends of conduit - k = Link[i].subIndex; - Conduit[k].q1 = Link[i].newFlow / Conduit[k].barrels; - Conduit[k].q2 = Conduit[k].q1; - - // --- find areas based on initial flow depth - Conduit[k].a1 = xsect_getAofY(&Link[i].xsect, Link[i].newDepth); - Conduit[k].a2 = Conduit[k].a1; - - // --- compute initial volume from area - { - Link[i].newVolume = Conduit[k].a1 * link_getLength(i) * - Conduit[k].barrels; - } - Link[i].oldVolume = Link[i].newVolume; - } - } -} - -//============================================================================= - -double getLinkInflow(int j, double dt) -// -// Input: j = link index -// dt = routing time step (sec) -// Output: returns link inflow (cfs) -// Purpose: finds flow into upstream end of link at current time step under -// Steady or Kin. Wave routing. -// -{ - int n1 = Link[j].node1; - double q; - if ( Link[j].type == CONDUIT || - Link[j].type == PUMP || - Node[n1].type == STORAGE ) q = link_getInflow(j); - else q = 0.0; - return node_getMaxOutflow(n1, q, dt); -} - -//============================================================================= - -void updateStorageState(int i, int j, int links[], double dt) -// -// Input: i = index of storage node -// j = current position in links array -// links = array of topo-sorted link indexes -// dt = routing time step (sec) -// Output: none -// Purpose: updates depth and volume of a storage node using successive -// approximation with under-relaxation for Steady or Kin. Wave -// routing. -// -{ - int iter; // iteration counter - int stopped; // TRUE when iterations stop - double vFixed; // fixed terms of flow balance eqn. - double v2; // new volume estimate (ft3) - double d1; // initial value of storage depth (ft) - double d2; // updated value of storage depth (ft) - - // --- see if storage node needs updating - if ( Node[i].type != STORAGE ) return; - if ( Node[i].updated ) return; - - // --- compute terms of flow balance eqn. - // v2 = v1 + (inflow - outflow)*dt - // that do not depend on storage depth at end of time step - vFixed = Node[i].oldVolume + - 0.5 * (Node[i].oldNetInflow + Node[i].inflow - - Node[i].outflow) * dt; - d1 = Node[i].newDepth; - - // --- iterate finding outflow (which depends on depth) and subsequent - // new volume and depth until negligible depth change occurs - iter = 1; - stopped = FALSE; - while ( iter < MAXITER && !stopped ) - { - // --- find new volume from flow balance eqn. - v2 = vFixed - 0.5 * getStorageOutflow(i, j, links, dt) * dt; - - // --- limit volume to full volume if no ponding - // and compute overflow rate - v2 = MAX(0.0, v2); - Node[i].overflow = 0.0; - if ( v2 > Node[i].fullVolume ) - { - Node[i].overflow = (v2 - MAX(Node[i].oldVolume, - Node[i].fullVolume)) / dt; - if ( Node[i].overflow < FUDGE ) Node[i].overflow = 0.0; - if ( !AllowPonding || Node[i].pondedArea == 0.0 ) - v2 = Node[i].fullVolume; - } - - // --- update node's volume & depth - Node[i].newVolume = v2; - d2 = node_getDepth(i, v2); - Node[i].newDepth = d2; - - // --- use under-relaxation to estimate new depth value - // and stop if close enough to previous value - d2 = (1.0 - OMEGA)*d1 + OMEGA*d2; - if ( fabs(d2 - d1) <= STOPTOL ) stopped = TRUE; - - // --- update old depth with new value and continue to iterate - d1 = d2; - iter++; - } - - // --- mark node as being updated - Node[i].updated = TRUE; -} - -//============================================================================= - -double getStorageOutflow(int i, int j, int links[], double dt) -// -// Input: i = index of storage node -// j = current position in links array -// links = array of topo-sorted link indexes -// dt = routing time step (sec) -// Output: returns total outflow from storage node (cfs) -// Purpose: computes total flow released from a storage node. -// -{ - int k, m; - double outflow = 0.0; - - for (k = j; k < Nobjects[LINK]; k++) - { - m = links[k]; - if ( Link[m].node1 != i ) break; - outflow += getLinkInflow(m, dt); - } - return outflow; -} - -//============================================================================= - -void setNewNodeState(int j, double dt) -// -// Input: j = node index -// dt = time step (sec) -// Output: none -// Purpose: updates state of node after current time step -// for Steady Flow or Kinematic Wave flow routing. -// -{ - int canPond; // TRUE if ponding can occur at node - double newNetInflow; // inflow - outflow at node (cfs) - - // --- update terminal storage nodes - if ( Node[j].type == STORAGE ) - { - if ( Node[j].updated == FALSE ) - updateStorageState(j, Nobjects[LINK], NULL, dt); - return; - } - - // --- update stored volume - newNetInflow = Node[j].inflow - Node[j].outflow - Node[j].losses; - Node[j].newVolume = Node[j].oldVolume + newNetInflow * dt; - if ( Node[j].newVolume < FUDGE ) Node[j].newVolume = 0.0; - - // --- determine any overflow lost from system - Node[j].overflow = 0.0; - canPond = (AllowPonding && Node[j].pondedArea > 0.0); - if ( Node[j].newVolume > Node[j].fullVolume ) - { - Node[j].overflow = (Node[j].newVolume - MAX(Node[j].oldVolume, - Node[j].fullVolume)) / dt; - if ( Node[j].overflow < FUDGE ) Node[j].overflow = 0.0; - if ( !canPond ) Node[j].newVolume = Node[j].fullVolume; - } - - // --- compute a depth from volume - // (depths at upstream nodes are subsequently adjusted in - // setNewLinkState to reflect depths in connected conduit) - Node[j].newDepth = node_getDepth(j, Node[j].newVolume); -} - -//============================================================================= - -void setNewLinkState(int j) -// -// Input: j = link index -// Output: none -// Purpose: updates state of link after current time step under -// Steady Flow or Kinematic Wave flow routing -// -{ - int k; - double a, y1, y2; - - Link[j].newDepth = 0.0; - Link[j].newVolume = 0.0; - - if ( Link[j].type == CONDUIT ) - { - // --- find avg. depth from entry/exit conditions - k = Link[j].subIndex; - a = 0.5 * (Conduit[k].a1 + Conduit[k].a2); - Link[j].newVolume = a * link_getLength(j) * Conduit[k].barrels; - y1 = xsect_getYofA(&Link[j].xsect, Conduit[k].a1); - y2 = xsect_getYofA(&Link[j].xsect, Conduit[k].a2); - Link[j].newDepth = 0.5 * (y1 + y2); - - // --- update depths at end nodes - updateNodeDepth(Link[j].node1, y1 + Link[j].offset1); - updateNodeDepth(Link[j].node2, y2 + Link[j].offset2); - - // --- check if capacity limited - if ( Conduit[k].a1 >= Link[j].xsect.aFull ) - { - Conduit[k].capacityLimited = TRUE; - Conduit[k].fullState = ALL_FULL; - } - else - { - Conduit[k].capacityLimited = FALSE; - Conduit[k].fullState = 0; - } - } -} - -//============================================================================= - -void updateNodeDepth(int i, double y) -// -// Input: i = node index -// y = flow depth (ft) -// Output: none -// Purpose: updates water depth at a node with a possibly higher value. -// -{ - // --- storage nodes were updated elsewhere - if ( Node[i].type == STORAGE ) return; - - // --- if non-outfall node is flooded, then use full depth - if ( Node[i].type != OUTFALL && Node[i].degree > 0 && - Node[i].overflow > 0.0 ) y = Node[i].fullDepth; - - // --- if current new depth below y - if ( Node[i].newDepth < y ) - { - // --- update new depth - Node[i].newDepth = y; - - // --- depth cannot exceed full depth (if value exists) - if ( Node[i].fullDepth > 0.0 && y > Node[i].fullDepth ) - { - Node[i].newDepth = Node[i].fullDepth; - } - } -} - -//============================================================================= - -int steadyflow_execute(int j, double* qin, double* qout, double tStep) -// -// Input: j = link index -// qin = inflow to link (cfs) -// tStep = time step (sec) -// Output: qin = adjusted inflow to link (limited by flow capacity) (cfs) -// qout = link's outflow (cfs) -// returns 1 if successful -// Purpose: performs steady flow routing through a single link. -// -{ - int k; - double s; - double q; - - // --- use Manning eqn. to compute flow area for conduits - if ( Link[j].type == CONDUIT ) - { - k = Link[j].subIndex; - q = (*qin) / Conduit[k].barrels; - if ( Link[j].xsect.type == DUMMY ) Conduit[k].a1 = 0.0; - else - { - // --- adjust flow for evap and infil losses - q -= link_getLossRate(j, q); - - // --- flow can't exceed full flow - if ( q > Link[j].qFull ) - { - q = Link[j].qFull; - Conduit[k].a1 = Link[j].xsect.aFull; - (*qin) = q * Conduit[k].barrels; - } - - // --- infer flow area from flow rate - else - { - s = q / Conduit[k].beta; - Conduit[k].a1 = xsect_getAofS(&Link[j].xsect, s); - } - } - Conduit[k].a2 = Conduit[k].a1; - - Conduit[k].q1Old = Conduit[k].q1; - Conduit[k].q2Old = Conduit[k].q2; - - Conduit[k].q1 = q; - Conduit[k].q2 = q; - (*qout) = q * Conduit[k].barrels; - } - else (*qout) = (*qin); - return 1; -} - -//============================================================================= diff --git a/src/forcmain.c b/src/forcmain.c deleted file mode 100644 index a6314bbce..000000000 --- a/src/forcmain.c +++ /dev/null @@ -1,157 +0,0 @@ -//----------------------------------------------------------------------------- -// forcemain.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Special Non-Manning Force Main functions -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include "headers.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -static const double VISCOS = 1.1E-5; // Kinematic viscosity of water - // @ 20 deg C (sq ft/sec) - -//----------------------------------------------------------------------------- -// External functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// forcemain_getEquivN -// forcemain_getRoughFactor -// forcemain_getFricSlope - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static double forcemain_getFricFactor(double e, double hrad, double re); -static double forcemain_getReynolds(double v, double hrad); - -//============================================================================= - -double forcemain_getEquivN(int j, int k) -// -// Input: j = link index -// k = conduit index -// Output: returns an equivalent Manning's n for a force main -// Purpose: computes a Mannng's n that results in the same normal flow -// value for a force main flowing full under fully turbulent -// conditions using either the Hazen-Williams or Dary-Weisbach -// flow equations. -// -{ - TXsect xsect = Link[j].xsect; - double f; - double d = xsect.yFull; - switch ( ForceMainEqn ) - { - case H_W: - return 1.067 / xsect.rBot * pow(d/Conduit[k].slope, 0.04); - case D_W: - f = forcemain_getFricFactor(xsect.rBot, d/4.0, 1.0e12); - return sqrt(f/185.0) * pow(d, (1./6.)); - } - return Conduit[k].roughness; -} - -//============================================================================= - -double forcemain_getRoughFactor(int j, double lengthFactor) -// -// Input: j = link index -// lengthFactor = factor by which a pipe will be artifically lengthened -// Output: returns a roughness adjustment factor for a force main -// Purpose: computes an adjustment factor for a force main that compensates for -// any artificial lengthening the pipe may have received. -// -{ - TXsect xsect = Link[j].xsect; - double r; - switch ( ForceMainEqn ) - { - case H_W: - r = 1.318*xsect.rBot*pow(lengthFactor, 0.54); - return GRAVITY / pow(r, 1.852); - case D_W: - return 1.0/8.0/lengthFactor; - } - return 0.0; -} - -//============================================================================= - -double forcemain_getFricSlope(int j, double v, double hrad) -// -// Input: j = link index -// v = flow velocity (ft/sec) -// hrad = hydraulic radius (ft) -// Output: returns a force main pipe's friction slope -// Purpose: computes the headloss per unit length used in dynamic wave -// flow routing for a pressurized force main using either the -// Hazen-Williams or Darcy-Weisbach flow equations. -// Note: the pipe's roughness factor was saved in xsect.sBot in -// conduit_validate() in LINK.C. -// -{ - double re, f; - TXsect xsect = Link[j].xsect; - switch ( ForceMainEqn ) - { - case H_W: - return xsect.sBot * pow(v, 0.852) / pow(hrad, 1.1667); - case D_W: - re = forcemain_getReynolds(v, hrad); - f = forcemain_getFricFactor(xsect.rBot, hrad, re); - return f * xsect.sBot * v / hrad; - } - return 0.0; -} - -//============================================================================= - -double forcemain_getReynolds(double v, double hrad) -// -// Input: v = flow velocity (ft/sec) -// hrad = hydraulic radius (ft) -// Output: returns a flow's Reynolds Number -// Purpose: computes a flow's Reynolds Number -// -{ - return 4.0 * hrad * v / VISCOS; -} - -//============================================================================= - -double forcemain_getFricFactor(double e, double hrad, double re) -// -// Input: e = roughness height (ft) -// hrad = hydraulic radius (ft) -// re = Reynolds number -// Output: returns a Darcy-Weisbach friction factor -// Purpose: computes the Darcy-Weisbach friction factor for a force main -// using the Swamee and Jain approximation to the Colebrook-White -// equation. -// -{ - double f; - if ( re < 10.0 ) re = 10.0; - if ( re <= 2000.0 ) f = 64.0 / re; - else if ( re < 4000.0 ) - { - f = forcemain_getFricFactor(e, hrad, 4000.0); - f = 0.032 + (f - 0.032) * ( re - 2000.0) / 2000.0; - } - else - { - f = e/3.7/(4.0*hrad); - if ( re < 1.0e10 ) f += 5.74/pow(re, 0.9); - f = log10(f); - f = 0.25 / f / f; - } - return f; -} diff --git a/src/funcs.h b/src/funcs.h deleted file mode 100644 index 6f9782723..000000000 --- a/src/funcs.h +++ /dev/null @@ -1,547 +0,0 @@ -//----------------------------------------------------------------------------- -// funcs.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// M. Tryby (EPA) -// -// Global interfacing functions. -// -// Update History -// ============== -// Build 5.1.007: -// - climate_readAdjustments() added. -// Build 5.1.008: -// - Function list was re-ordered and blank lines added for readability. -// - Pollutant buildup/washoff functions for the new surfqual.c module added. -// - Several other functions added, re-named or have modified arguments. -// Build 5.1.010: -// - New roadway_getInflow() function added. -// Build 5.1.013: -// - Additional arguments added to function stats_updateSubcatchStats. -// Build 5.1.014: -// - Arguments to link_getLossRate function changed. -// Build 5.2.0: -// - Support added for Streets and Inlets. -// - Support added for reporting most frequent non-converging links. -// - Support added for named variables & math expressions in control rules. -// - Support added for tracking a gage's prior n-hour rainfall total. -// - Refactored external inflow code. -//----------------------------------------------------------------------------- - -#ifndef FUNCS_H -#define FUNCS_H - -//----------------------------------------------------------------------------- -// Project Methods -//----------------------------------------------------------------------------- -void project_open(const char *f1, const char *f2, const char *f3); -void project_close(void); - -void project_readInput(void); -int project_readOption(char* s1, char* s2); -void project_validate(void); -int project_init(void); - -int project_addObject(int type, char* id, int n); -int project_findObject(int type, const char* id); -char* project_findID(int type, char* id); - -double** project_createMatrix(int nrows, int ncols); -void project_freeMatrix(double** m); - -//----------------------------------------------------------------------------- -// Input Reader Methods -//----------------------------------------------------------------------------- -int input_countObjects(void); -int input_readData(void); - -//----------------------------------------------------------------------------- -// Report Writer Methods -//----------------------------------------------------------------------------- -int report_readOptions(char* tok[], int ntoks); - -void report_writeLine(const char* line); -void report_writeSysTime(void); -void report_writeLogo(void); -void report_writeTitle(void); -void report_writeOptions(void); -void report_writeReport(void); - -void report_writeRainStats(int gage, TRainStats* rainStats); -void report_writeRdiiStats(double totalRain, double totalRdii); - -void report_writeControlActionsHeading(void); -void report_writeControlAction(DateTime aDate, char* linkID, double value, - char* ruleID); - -void report_writeRunoffError(TRunoffTotals* totals, double area); -void report_writeLoadingError(TLoadingTotals* totals); -void report_writeGwaterError(TGwaterTotals* totals, double area); -void report_writeFlowError(TRoutingTotals* totals); -void report_writeQualError(TRoutingTotals* totals); - -void report_writeMaxStats(TMaxStats massBalErrs[], TMaxStats CourantCrit[], - int nMaxStats); -void report_writeMaxFlowTurns(TMaxStats flowTurns[], int nMaxStats); -void report_writeNonconvergedStats(TMaxStats maxNonconverged[], - int nMaxStats); -void report_writeTimeStepStats(TTimeStepStats* timeStepStats); - -void report_writeErrorMsg(int code, char* msg); -void report_writeErrorCode(void); -void report_writeInputErrorMsg(int k, int sect, char* line, long lineCount); -void report_writeWarningMsg(char* msg, char* id); -void report_writeTseriesErrorMsg(int code, TTable *tseries); - -void inputrpt_writeInput(void); -void statsrpt_writeReport(void); - -//----------------------------------------------------------------------------- -// Temperature/Evaporation Methods -//----------------------------------------------------------------------------- -int climate_readParams(char* tok[], int ntoks); -int climate_readEvapParams(char* tok[], int ntoks); -int climate_readAdjustments(char* tok[], int ntoks); -void climate_validate(void); -void climate_openFile(void); -void climate_initState(void); -void climate_setState(DateTime aDate); -DateTime climate_getNextEvapDate(void); - -//----------------------------------------------------------------------------- -// Rainfall Processing Methods -//----------------------------------------------------------------------------- -void rain_open(void); -void rain_close(void); - -//----------------------------------------------------------------------------- -// Snowmelt Processing Methods -//----------------------------------------------------------------------------- -int snow_readMeltParams(char* tok[], int ntoks); -int snow_createSnowpack(int subcacth, int snowIndex); - -void snow_validateSnowmelt(int snowIndex); -void snow_initSnowpack(int subcatch); -void snow_initSnowmelt(int snowIndex); - -void snow_getState(int subcatch, int subArea, double x[]); -void snow_setState(int subcatch, int subArea, double x[]); - -void snow_setMeltCoeffs(int snowIndex, double season); -void snow_plowSnow(int subcatch, double tStep); -double snow_getSnowMelt(int subcatch, double rainfall, double snowfall, - double tStep, double netPrecip[]); -double snow_getSnowCover(int subcatch); - -//----------------------------------------------------------------------------- -// Runoff Analyzer Methods -//----------------------------------------------------------------------------- -int runoff_open(void); -void runoff_execute(void); -void runoff_close(void); - -//----------------------------------------------------------------------------- -// Conveyance System Routing Methods -//----------------------------------------------------------------------------- -int routing_open(void); -double routing_getRoutingStep(int routingModel, double fixedStep); -void routing_execute(int routingModel, double routingStep); -void routing_close(int routingModel); - -//----------------------------------------------------------------------------- -// Output Filer Methods -//----------------------------------------------------------------------------- -int output_open(void); -void output_end(void); -void output_close(void); -void output_saveResults(double reportTime); -void output_updateAvgResults(void); -void output_readDateTime(long period, DateTime *aDate); -void output_readSubcatchResults(long period, int index); -void output_readNodeResults(int long, int index); -void output_readLinkResults(int long, int index); - -//----------------------------------------------------------------------------- -// Groundwater Methods -//----------------------------------------------------------------------------- -int gwater_readAquiferParams(int aquifer, char* tok[], int ntoks); -int gwater_readGroundwaterParams(char* tok[], int ntoks); -int gwater_readFlowExpression(char* tok[], int ntoks); -void gwater_deleteFlowExpression(int subcatch); - -void gwater_validateAquifer(int aquifer); -void gwater_validate(int subcatch); - -void gwater_initState(int subcatch); -void gwater_getState(int subcatch, double x[]); -void gwater_setState(int subcatch, double x[]); - -void gwater_getGroundwater(int subcatch, double evap, double infil, - double tStep); -double gwater_getVolume(int subcatch); - -//----------------------------------------------------------------------------- -// RDII Methods -//----------------------------------------------------------------------------- -int rdii_readRdiiInflow(char* tok[], int ntoks); -void rdii_deleteRdiiInflow(int node); -void rdii_initUnitHyd(int unitHyd); -int rdii_readUnitHydParams(char* tok[], int ntoks); -void rdii_openRdii(void); -void rdii_closeRdii(void); -int rdii_getNumRdiiFlows(DateTime aDate); -void rdii_getRdiiFlow(int index, int* node, double* q); - -//----------------------------------------------------------------------------- -// Landuse Methods -//----------------------------------------------------------------------------- -int landuse_readParams(int landuse, char* tok[], int ntoks); -int landuse_readPollutParams(int pollut, char* tok[], int ntoks); -int landuse_readBuildupParams(char* tok[], int ntoks); -int landuse_readWashoffParams(char* tok[], int ntoks); - -void landuse_getInitBuildup(TLandFactor* landFactor, double* initBuildup, - double area, double curb); -double landuse_getBuildup(int landuse, int pollut, double area, double curb, - double buildup, double tStep); - -double landuse_getWashoffLoad(int landuse, int p, double area, - TLandFactor landFactor[], double runoff, double vOutflow); -double landuse_getAvgBmpEffic(int j, int p); -double landuse_getCoPollutLoad(int p, double washoff[]); - -//----------------------------------------------------------------------------- -// Flow/Quality Routing Methods -//----------------------------------------------------------------------------- -void flowrout_init(int routingModel); -void flowrout_close(int routingModel); -double flowrout_getRoutingStep(int routingModel, double fixedStep); -int flowrout_execute(int links[], int routingModel, double tStep); - -void toposort_sortLinks(int links[]); -int kinwave_execute(int link, double* qin, double* qout, double tStep); - -void dynwave_validate(void); -void dynwave_init(void); -void dynwave_close(void); -double dynwave_getRoutingStep(double fixedStep); -int dynwave_execute(double tStep); -void dwflow_findConduitFlow(int j, int steps, double omega, double dt); - -void qualrout_init(void); -void qualrout_execute(double tStep); - -//----------------------------------------------------------------------------- -// Treatment Methods -//----------------------------------------------------------------------------- -int treatmnt_open(void); -void treatmnt_close(void); -int treatmnt_readExpression(char* tok[], int ntoks); -void treatmnt_delete(int node); -void treatmnt_treat(int node, double q, double v, double tStep); -void treatmnt_setInflow(double qIn, double wIn[]); - -//----------------------------------------------------------------------------- -// Mass Balance Methods -//----------------------------------------------------------------------------- -int massbal_open(void); -void massbal_close(void); -void massbal_report(void); - -void massbal_updateRunoffTotals(int type, double v); -void massbal_updateLoadingTotals(int type, int pollut, double w); -void massbal_updateGwaterTotals(double vInfil, double vUpperEvap, - double vLowerEvap, double vLowerPerc, double vGwater); -void massbal_updateRoutingTotals(double tStep); - - -void massbal_initTimeStepTotals(void); -void massbal_addInflowFlow(int type, double q); -void massbal_addInflowQual(int type, int pollut, double w); -void massbal_addOutflowFlow(double q, int isFlooded); -void massbal_addOutflowQual(int pollut, double mass, int isFlooded); -void massbal_addNodeLosses(double evapLoss, double infilLoss); -void massbal_addLinkLosses(double evapLoss, double infilLoss); -void massbal_addReactedMass(int pollut, double mass); -void massbal_addSeepageLoss(int pollut, double seepLoss); -void massbal_addToFinalStorage(int pollut, double mass); -double massbal_getStepFlowError(void); -double massbal_getRunoffError(void); -double massbal_getFlowError(void); - -//----------------------------------------------------------------------------- -// Simulation Statistics Methods -//----------------------------------------------------------------------------- -int stats_open(void); -void stats_close(void); -void stats_report(void); - -void stats_updateCriticalTimeCount(int node, int link); -void stats_updateFlowStats(double tStep, DateTime aDate); -void stats_updateTimeStepStats(double tStep, int trialsCount, int steadyState); - -void stats_updateSubcatchStats(int subcatch, double rainVol, - double runonVol, double evapVol, double infilVol, - double impervVol, double pervVol, double runoffVol, double runoff); -void stats_updateGwaterStats(int j, double infil, double evap, - double latFlow, double deepFlow, double theta, double waterTable, - double tStep); -void stats_updateMaxRunoff(void); -void stats_updateMaxNodeDepth(int node, double depth); -void stats_updateConvergenceStats(int node, int converged); - - -//----------------------------------------------------------------------------- -// Raingage Methods -//----------------------------------------------------------------------------- -int gage_readParams(int gage, char* tok[], int ntoks); -void gage_validate(int gage); -void gage_initState(int gage); -void gage_setState(int gage, DateTime aDate); -double gage_getPrecip(int gage, double *rainfall, double *snowfall); -void gage_setReportRainfall(int gage, DateTime aDate); -DateTime gage_getNextRainDate(int gage, DateTime aDate); -void gage_updatePastRain(int j, int tStep); -double gage_getPastRain(int gage, int hrs); - -//----------------------------------------------------------------------------- -// Subcatchment Methods -//----------------------------------------------------------------------------- -int subcatch_readParams(int subcatch, char* tok[], int ntoks); -int subcatch_readSubareaParams(char* tok[], int ntoks); -int subcatch_readLanduseParams(char* tok[], int ntoks); -int subcatch_readInitBuildup(char* tok[], int ntoks); - -void subcatch_validate(int subcatch); -void subcatch_initState(int subcatch); -void subcatch_setOldState(int subcatch); - -double subcatch_getFracPerv(int subcatch); -double subcatch_getStorage(int subcatch); -double subcatch_getDepth(int subcatch); - -void subcatch_getRunon(int subcatch); -void subcatch_addRunonFlow(int subcatch, double flow); -double subcatch_getRunoff(int subcatch, double tStep); - -double subcatch_getWtdOutflow(int subcatch, double wt); -void subcatch_getResults(int subcatch, double wt, float x[]); - -//----------------------------------------------------------------------------- -// Surface Pollutant Buildup/Washoff Methods -//----------------------------------------------------------------------------- -void surfqual_initState(int subcatch); -void surfqual_getWashoff(int subcatch, double runoff, double tStep); -void surfqual_getBuildup(int subcatch, double tStep); -void surfqual_sweepBuildup(int subcatch, DateTime aDate); -double surfqual_getWtdWashoff(int subcatch, int pollut, double wt); - -//----------------------------------------------------------------------------- -// Conveyance System Node Methods -//----------------------------------------------------------------------------- -int node_readParams(int node, int type, int subIndex, char* tok[], int ntoks); -void node_validate(int node); - -void node_initState(int node); -void node_initFlows(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); - -double node_getSurfArea(int node, double depth); -double node_getDepth(int node, double volume); -double node_getVolume(int node, double depth); -double node_getPondedArea(int node, double depth); - -double node_getOutflow(int node, int link); -double node_getLosses(int node, double tStep); -double node_getMaxOutflow(int node, double q, double tStep); -double node_getSystemOutflow(int node, int *isFlooded); -void node_getResults(int node, double wt, float x[]); - -//----------------------------------------------------------------------------- -// Conveyance System Inflow Methods -//----------------------------------------------------------------------------- -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); - -void inflow_initDwfInflow(TDwfInflow* inflow); -void inflow_initDwfPattern(int pattern); - -double inflow_getExtInflow(TExtInflow* inflow, DateTime aDate); -double inflow_getDwfInflow(TDwfInflow* inflow, int m, int d, int h); - -void inflow_deleteExtInflows(int node); -void inflow_deleteDwfInflows(int node); - -//----------------------------------------------------------------------------- -// Routing Interface File Methods -//----------------------------------------------------------------------------- -int iface_readFileParams(char* tok[], int ntoks); -void iface_openRoutingFiles(void); -void iface_closeRoutingFiles(void); -int iface_getNumIfaceNodes(DateTime aDate); -int iface_getIfaceNode(int index); -double iface_getIfaceFlow(int index); -double iface_getIfaceQual(int index, int pollut); -void iface_saveOutletResults(DateTime reportDate, FILE* file); - -//----------------------------------------------------------------------------- -// Hot Start File Methods -//----------------------------------------------------------------------------- -int hotstart_open(void); -void hotstart_close(void); - -//----------------------------------------------------------------------------- -// Conveyance System Link Methods -//----------------------------------------------------------------------------- -int link_readParams(int link, int type, int subIndex, char* tok[], int ntoks); -int link_readXsectParams(char* tok[], int ntoks); -int link_readLossParams(char* tok[], int ntoks); - -void link_validate(int link); -void link_initState(int link); -void link_setOldHydState(int link); -void link_setOldQualState(int link); - -void link_setTargetSetting(int j); -void link_setSetting(int j, double tstep); -int link_setFlapGate(int link, int n1, int n2, double q); - -double link_getInflow(int link); -void link_setOutfallDepth(int link); -double link_getLength(int link); -double link_getYcrit(int link, double q); -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); -char link_getFullState(double a1, double a2, double aFull); - -void link_getResults(int link, double wt, float x[]); - -//----------------------------------------------------------------------------- -// Link Cross-Section Methods -//----------------------------------------------------------------------------- -int xsect_isOpen(int type); -int xsect_setParams(TXsect *xsect, int type, double p[], double ucf); -void xsect_setIrregXsectParams(TXsect *xsect); -void xsect_setCustomXsectParams(TXsect *xsect); -void xsect_setStreetXsectParams(TXsect *xsect); -double xsect_getAmax(TXsect* xsect); - -double xsect_getSofA(TXsect* xsect, double area); -double xsect_getYofA(TXsect* xsect, double area); -double xsect_getRofA(TXsect* xsect, double area); -double xsect_getAofS(TXsect* xsect, double sFactor); -double xsect_getdSdA(TXsect* xsect, double area); -double xsect_getAofY(TXsect* xsect, double y); -double xsect_getRofY(TXsect* xsect, double y); -double xsect_getWofY(TXsect* xsect, double y); -double xsect_getYcrit(TXsect* xsect, double q); - -//----------------------------------------------------------------------------- -// Culvert/Roadway Methods -//----------------------------------------------------------------------------- -double culvert_getInflow(int link, double q, double h); -double roadway_getInflow(int link, double dir, double hcrest, double h1, - double h2); - -//----------------------------------------------------------------------------- -// Force Main Methods -//----------------------------------------------------------------------------- -double forcemain_getEquivN(int j, int k); -double forcemain_getRoughFactor(int j, double lengthFactor); -double forcemain_getFricSlope(int j, double v, double hrad); - -//----------------------------------------------------------------------------- -// Cross-Section Transect Methods -//----------------------------------------------------------------------------- -int transect_create(int n); -void transect_delete(void); -int transect_readParams(int* count, char* tok[], int ntoks); -void transect_validate(int j); -void transect_createStreetTransect(TStreet* street); - -//----------------------------------------------------------------------------- -// Street Cross-Section Methods -//----------------------------------------------------------------------------- -int street_create(int nStreets); -void street_delete(); -int street_readParams(char* tok[], int ntoks); -double street_getExtentFilled(int link); - -//----------------------------------------------------------------------------- -// Custom Shape Cross-Section Methods -//----------------------------------------------------------------------------- -int shape_validate(TShape *shape, TTable *curve); - -//----------------------------------------------------------------------------- -// Control Rule Methods -//----------------------------------------------------------------------------- -int controls_create(int n); -void controls_delete(void); -void controls_init(void); -void controls_addToCount(char* s); -int controls_addVariable(char* tok[], int ntoks); -int controls_addExpression(char* tok[], int ntoks); -int controls_addRuleClause(int rule, int keyword, char* Tok[], int nTokens); -int controls_evaluate(DateTime currentTime, DateTime elapsedTime, - double tStep); - -//----------------------------------------------------------------------------- -// Table & Time Series Methods -//----------------------------------------------------------------------------- -int table_readCurve(char* tok[], int ntoks); -int table_readTimeseries(char* tok[], int ntoks); - -int table_addEntry(TTable* table, double x, double y); -int table_getFirstEntry(TTable* table, double* x, double* y); -int table_getNextEntry(TTable* table, double* x, double* y); -void table_deleteEntries(TTable* table); - -void table_init(TTable* table); -int table_validate(TTable* table); - -double table_lookup(TTable* table, double x); -double table_lookupEx(TTable* table, double x); -double table_intervalLookup(TTable* table, double x); -double table_inverseLookup(TTable* table, double y); - -double table_getSlope(TTable *table, double x); -double table_getMaxY(TTable *table, double x); -double table_getStorageVolume(TTable* table, double x); -double table_getStorageDepth(TTable* table, double v); - -void table_tseriesInit(TTable *table); -double table_tseriesLookup(TTable* table, double t, char extend); - -//----------------------------------------------------------------------------- -// Utility Methods -//----------------------------------------------------------------------------- -double UCF(int quantity); // units conversion factor -int getInt(char *s, int *y); // get integer from string -int getFloat(char *s, float *y); // get float from string -int getDouble(char *s, double *y); // get double from string -char* getTempFileName(char *s); // get temporary file name -int findmatch(char *s, char *keyword[]); // search for matching keyword -int match(char *str, char *substr); // true if substr matches part of str -int strcomp(const char *s1, const char *s2); // case insensitive string compare -size_t sstrncpy(char *dest, const char *src, - size_t n); // safe string copy -size_t sstrcat(char* dest, const char* src, - size_t destsize); // safe string concatenation -void writecon(const 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); -char* addAbsolutePath(char *fname); // add full path to a file name - -#endif //FUNCS_H diff --git a/src/gage.c b/src/gage.c deleted file mode 100644 index 031cc017c..000000000 --- a/src/gage.c +++ /dev/null @@ -1,705 +0,0 @@ -//----------------------------------------------------------------------------- -// gage.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Rain gage functions. -// -// Update History -// ============== -// Build 5.1.007: -// - Support for monthly rainfall adjustments added. -// Build 5.1.013: -// - Validation no longer performed on unused gages. -// Build 5.2.0: -// - Support added for tracking a gage's prior n-hour rainfall total. -// - Support added for relative file names. -// - Support added for setting rainfall through API call. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include "headers.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -const double OneSecond = 1.1574074e-5; - -//----------------------------------------------------------------------------- -// External functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// gage_readParams (called by input_readLine) -// gage_validate (called by project_validate) -// gage_initState (called by project_init) -// gage_setState (called by runoff_execute & getRainfall in rdii.c) -// gage_getPrecip (called by subcatch_getRunoff) -// gage_getNextRainDate (called by runoff_getTimeStep) -// gage_updatePastRain (called by runoff_execute) -// gage_getPastRain (called by getRainValue in controls.c) -// gage_setReportRainfall (called by output_saveSubcatchResults) - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static int readGageSeriesFormat(char* tok[], int ntoks, double x[]); -static int readGageFileFormat(char* tok[], int ntoks, double x[]); -static int getFirstRainfall(int gage); -static int getNextRainfall(int gage); -static double convertRainfall(int gage, double rain); -static void initPastRain(int gage); - -//============================================================================= - -int gage_readParams(int j, char* tok[], int ntoks) -// -// Input: j = rain gage index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads rain gage parameters from a line of input data -// -// Data formats are: -// Name RainType RecdFreq SCF TIMESERIES SeriesName -// Name RainType RecdFreq SCF FILE FileName Station Units StartDate -// -{ - int k, err; - char *id; - char fname[MAXFNAME+1]; - char staID[MAXMSG+1]; - double x[7]; - - // --- check that gage exists - if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); - id = project_findID(GAGE, tok[0]); - if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); - - // --- assign default parameter values - x[0] = -1.0; // No time series index - x[1] = 1.0; // Rain type is volume - x[2] = 3600.0; // Recording freq. is 3600 sec - x[3] = 1.0; // Snow catch deficiency factor - x[4] = NO_DATE; // Default is no start/end date - x[5] = NO_DATE; - x[6] = 0.0; // US units - fname[0] = '\0'; - staID[0] = '\0'; - - if ( ntoks < 5 ) return error_setInpError(ERR_ITEMS, ""); - k = findmatch(tok[4], GageDataWords); - if ( k == RAIN_TSERIES ) - { - err = readGageSeriesFormat(tok, ntoks, x); - } - else if ( k == RAIN_FILE ) - { - if ( ntoks < 8 ) return error_setInpError(ERR_ITEMS, ""); - sstrncpy(fname, tok[5], MAXFNAME); - sstrncpy(staID, tok[6], MAXMSG); - err = readGageFileFormat(tok, ntoks, x); - } - else return error_setInpError(ERR_KEYWORD, tok[4]); - - // --- save parameters to rain gage object - if ( err > 0 ) return err; - Gage[j].ID = id; - Gage[j].tSeries = (int)x[0]; - Gage[j].rainType = (int)x[1]; - Gage[j].rainInterval = (int)x[2]; - Gage[j].snowFactor = x[3]; - Gage[j].rainUnits = (int)x[6]; - if ( Gage[j].tSeries >= 0 ) Gage[j].dataSource = RAIN_TSERIES; - else Gage[j].dataSource = RAIN_FILE; - if ( Gage[j].dataSource == RAIN_FILE ) - { - sstrncpy(Gage[j].fname, addAbsolutePath(fname), MAXFNAME); - sstrncpy(Gage[j].staID, staID, MAXMSG); - Gage[j].startFileDate = x[4]; - Gage[j].endFileDate = x[5]; - } - Gage[j].unitsFactor = 1.0; - Gage[j].coGage = -1; - Gage[j].isUsed = FALSE; - return 0; -} - -//============================================================================= - -int readGageSeriesFormat(char* tok[], int ntoks, double x[]) -{ - int m, ts; - DateTime aTime; - - if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); - - // --- determine type of rain data - m = findmatch(tok[1], RainTypeWords); - if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); - x[1] = (double)m; - - // --- get data time interval & convert to seconds - if ( getDouble(tok[2], &x[2]) ) x[2] = floor(x[2]*3600 + 0.5); - else if ( datetime_strToTime(tok[2], &aTime) ) - { - x[2] = floor(aTime*SECperDAY + 0.5); - } - else return error_setInpError(ERR_DATETIME, tok[2]); - if ( x[2] <= 0.0 ) return error_setInpError(ERR_DATETIME, tok[2]); - - // --- get snow catch deficiency factor - if ( !getDouble(tok[3], &x[3]) ) - return error_setInpError(ERR_DATETIME, tok[3]);; - - // --- get time series index - ts = project_findObject(TSERIES, tok[5]); - if ( ts < 0 ) return error_setInpError(ERR_NAME, tok[5]); - x[0] = (double)ts; - sstrncpy(tok[2], "", 0); - return 0; -} - -//============================================================================= - -int readGageFileFormat(char* tok[], int ntoks, double x[]) -{ - int m, u; - DateTime aDate; - DateTime aTime; - - // --- determine type of rain data - m = findmatch(tok[1], RainTypeWords); - if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); - x[1] = (double)m; - - // --- get data time interval & convert to seconds - if ( getDouble(tok[2], &x[2]) ) x[2] *= 3600; - else if ( datetime_strToTime(tok[2], &aTime) ) - { - x[2] = floor(aTime*SECperDAY + 0.5); - } - else return error_setInpError(ERR_DATETIME, tok[2]); - if ( x[2] <= 0.0 ) return error_setInpError(ERR_DATETIME, tok[2]); - - // --- get snow catch deficiency factor - if ( !getDouble(tok[3], &x[3]) ) - return error_setInpError(ERR_NUMBER, tok[3]); - - // --- get rain depth units - u = findmatch(tok[7], RainUnitsWords); - if ( u < 0 ) return error_setInpError(ERR_KEYWORD, tok[7]); - x[6] = (double)u; - - // --- get start date (if present) - if ( ntoks > 8 && *tok[8] != '*') - { - if ( !datetime_strToDate(tok[8], &aDate) ) - return error_setInpError(ERR_DATETIME, tok[8]); - x[4] = (float) aDate; - } - return 0; -} - -//============================================================================= - -void gage_validate(int j) -// -// Input: j = rain gage index -// Output: none -// Purpose: checks for valid rain gage parameters -// -// NOTE: assumes that any time series used by a rain gage has been -// previously validated. -// -{ - int i, k; - int gageInterval; - - // --- for gage with time series data: - if ( Gage[j].dataSource == RAIN_TSERIES ) - { - // --- no validation for an unused gage - if ( !Gage[j].isUsed ) return; - - // --- see if gage uses same time series as another gage - k = Gage[j].tSeries; - for (i=0; i= 0 ) - { - report_writeErrorMsg(ERR_RAIN_GAGE_TSERIES, Gage[j].ID); - } - gageInterval = (int)(floor(Tseries[k].dxMin*SECperDAY + 0.5)); - if ( gageInterval > 0 && Gage[j].rainInterval > gageInterval ) - { - report_writeErrorMsg(ERR_RAIN_GAGE_INTERVAL, Gage[j].ID); - } - if ( Gage[j].rainInterval < gageInterval ) - { - report_writeWarningMsg(WARN09, Gage[j].ID); - } - if ( Gage[j].rainInterval < WetStep ) - { - report_writeWarningMsg(WARN01, Gage[j].ID); - WetStep = Gage[j].rainInterval; - } - } -} - -//============================================================================= - -void gage_initState(int j) -// -// Input: j = rain gage index -// Output: none -// Purpose: initializes state of rain gage. -// -{ - // --- initialize actual and reported rainfall - Gage[j].rainfall = 0.0; - Gage[j].apiRainfall = MISSING; - Gage[j].reportRainfall = 0.0; - if ( IgnoreRainfall ) return; - - // --- for gage with file data: - if ( Gage[j].dataSource == RAIN_FILE ) - { - // --- set current file position to start of period of record - Gage[j].currentFilePos = Gage[j].startFilePos; - - // --- assign units conversion factor - // (rain depths on interface file are in inches) - if ( UnitSystem == SI ) Gage[j].unitsFactor = MMperINCH; - } - - // --- get first & next rainfall values - if ( getFirstRainfall(j) ) - { - // --- find date at end of starting rain interval - Gage[j].endDate = datetime_addSeconds( - Gage[j].startDate, Gage[j].rainInterval); - - // --- if rainfall record begins after start of simulation, - if ( Gage[j].startDate > StartDateTime ) - { - // --- make next rainfall date the start of the rain record - Gage[j].nextDate = Gage[j].startDate; - Gage[j].nextRainfall = Gage[j].rainfall; - - // --- make start of current rain interval the simulation start - Gage[j].startDate = StartDateTime; - Gage[j].endDate = Gage[j].nextDate; - Gage[j].rainfall = 0.0; - } - - // --- otherwise find next recorded rainfall - else if ( !getNextRainfall(j) ) Gage[j].nextDate = NO_DATE; - } - else Gage[j].startDate = NO_DATE; - initPastRain(j); -} - -//============================================================================= - -void gage_setState(int j, DateTime t) -// -// Input: j = rain gage index -// t = a calendar date/time -// Output: none -// Purpose: updates state of rain gage for specified date. -// -{ - // --- return if gage not used by any subcatchment - if ( Gage[j].isUsed == FALSE ) return; - - // --- set rainfall to zero if disabled - if ( IgnoreRainfall ) - { - Gage[j].rainfall = 0.0; - return; - } - - // --- use rainfall from co-gage (gage with lower index that uses - // same rainfall time series or file) if it exists - if ( Gage[j].coGage >= 0) - { - Gage[j].rainfall = Gage[Gage[j].coGage].rainfall; - return; - } - - // --- use rainfall supplied by API function call - // (where constant ZERO (1.e-10) is used for 0 rainfall) - if (Gage[j].apiRainfall != MISSING) - { - Gage[j].rainfall = Gage[j].apiRainfall; - return; - } - - // --- otherwise march through rainfall record until date t is bracketed - t += OneSecond; - for (;;) - { - // --- no rainfall if no interval start date - if ( Gage[j].startDate == NO_DATE ) - { - Gage[j].rainfall = 0.0; - return; - } - - // --- no rainfall if time is before interval start date - if ( t < Gage[j].startDate ) - { - Gage[j].rainfall = 0.0; - return; - } - - // --- use current rainfall if time is before interval end date - if ( t < Gage[j].endDate ) - { - return; - } - - // --- no rainfall if t >= interval end date & no next interval exists - if ( Gage[j].nextDate == NO_DATE ) - { - Gage[j].rainfall = 0.0; - return; - } - - // --- no rainfall if t > interval end date & < next interval date - if ( t < Gage[j].nextDate ) - { - Gage[j].rainfall = 0.0; - return; - } - - // --- otherwise update next rainfall interval date - Gage[j].startDate = Gage[j].nextDate; - Gage[j].endDate = datetime_addSeconds(Gage[j].startDate, - Gage[j].rainInterval); - Gage[j].rainfall = Gage[j].nextRainfall; - if ( !getNextRainfall(j) ) Gage[j].nextDate = NO_DATE; - } -} - -//============================================================================= - -void initPastRain(int j) -{ - // --- initialize past hourly rain accumulation - int i; - for (i = 0; i <= MAXPASTRAIN; i++) - Gage[j].pastRain[i] = 0.0; - Gage[j].pastInterval = 0; -} - -//============================================================================= - -void gage_updatePastRain(int j, int tStep) -// -// Input: j = rain gage index -// tStep = current runoff time step (sec) -// Output: none -// Purpose: updates past MAXPASTRAIN hourly rain totals. -// -// Note: pastRain[0] is past rain volume prior to 1 hour, -// pastRain[n] is past rain volume after n hours, -// pastInterval is time since last hour was reached. -{ - int i, t; - double r; - - // --- current rainfall intensity (in/sec or mm/sec) - r = Gage[j].rainfall / 3600.; - - // --- process each hourly interval of current time step - while (tStep > 0) - { - // --- time for most recent rainfall interval to reach 1 hr - t = 3600 - Gage[j].pastInterval; - - // --- remaining time step is greater than this time - if (tStep > t) - { - // --- add current rain to most recent interval - Gage[j].pastRain[0] += t * r; - - // --- shift all prior hourly rain amounts by 1 hour - for (i = MAXPASTRAIN; i > 0; i-- ) - Gage[j].pastRain[i] = Gage[j].pastRain[i-1]; - - // --- begin a new most recent interval - Gage[j].pastInterval = 0; - Gage[j].pastRain[0] = 0.0; - tStep -= t; - } - // --- time to reach 1 hr in most recent interval is greater - // than remaining time step so update most recent interval - else - { - Gage[j].pastRain[0] += tStep * r; - Gage[j].pastInterval += tStep; - tStep = 0; - } - } -} - -//============================================================================= - -double gage_getPastRain(int j, int n) -// -// Input: j = rain gage index -// n = number of hours prior to current date -// Output: cumulative rain volume (inches or mm) in last n hours -// Purpose: retrieves rainfall total over some previous number of hours. -// -{ - int i; - double result = 0.0; - if (n < 1 || n > MAXPASTRAIN) return 0.0; - for (i = 1; i <= n; i++) - result += Gage[j].pastRain[i]; - return result; -} - -//============================================================================= - -DateTime gage_getNextRainDate(int j, DateTime aDate) -// -// Input: j = rain gage index -// aDate = calendar date/time -// Output: next date with rainfall occurring -// Purpose: finds the next date from specified date when rainfall occurs. -// -{ - if ( Gage[j].isUsed == FALSE ) return aDate; - aDate += OneSecond; - if ( aDate < Gage[j].startDate ) return Gage[j].startDate; - if ( aDate < Gage[j].endDate ) return Gage[j].endDate; - return Gage[j].nextDate; -} - -//============================================================================= - -double gage_getPrecip(int j, double *rainfall, double *snowfall) -// -// Input: j = rain gage index -// Output: rainfall = rainfall rate (ft/sec) -// snowfall = snow fall rate (ft/sec) -// returns total precipitation (ft/sec) -// Purpose: determines whether gage's recorded rainfall is rain or snow. -// -{ - *rainfall = 0.0; - *snowfall = 0.0; - if ( !IgnoreSnowmelt && Temp.ta <= Snow.snotmp ) - { - *snowfall = Gage[j].rainfall * Gage[j].snowFactor / UCF(RAINFALL); - } - else *rainfall = Gage[j].rainfall / UCF(RAINFALL); - return (*rainfall) + (*snowfall); -} - -//============================================================================= - -void gage_setReportRainfall(int j, DateTime reportDate) -// -// Input: j = rain gage index -// reportDate = date/time value of current reporting time -// Output: none -// Purpose: sets the rainfall value reported at the current reporting time. -// -{ - double result; - - // --- use value from co-gage if it exists - if ( Gage[j].coGage >= 0) - { - Gage[j].reportRainfall = Gage[Gage[j].coGage].reportRainfall; - return; - } - - // --- rainfall set by API call - if (Gage[j].apiRainfall != MISSING) - { - Gage[j].reportRainfall = Gage[j].apiRainfall; - return; - } - - // --- otherwise increase reporting time by 1 second to avoid - // roundoff problems - reportDate += OneSecond; - - // --- use current rainfall if report date/time is before end - // of current rain interval - if ( reportDate < Gage[j].endDate ) result = Gage[j].rainfall; - - // --- use 0.0 if report date/time is before start of next rain interval - else if ( reportDate < Gage[j].nextDate ) result = 0.0; - - // --- otherwise report date/time falls right on end of current rain - // interval and start of next interval so use next interval's rainfall - else result = Gage[j].nextRainfall; - Gage[j].reportRainfall = result; -} - -//============================================================================= - -int getFirstRainfall(int j) -// -// Input: j = rain gage index -// Output: returns TRUE if successful -// Purpose: positions rainfall record to date with first rainfall. -// -{ - int k; // time series index - float vFirst; // first rain volume (ft or m) - double rFirst; // first rain intensity (in/hr or mm/hr) - - // --- assign default values to date & rainfall - Gage[j].startDate = NO_DATE; - Gage[j].rainfall = 0.0; - - // --- initialize internal cumulative rainfall value - Gage[j].rainAccum = 0; - - // --- use rain interface file if applicable - if ( Gage[j].dataSource == RAIN_FILE ) - { - if ( Frain.file && Gage[j].endFilePos > Gage[j].startFilePos ) - { - // --- retrieve 1st date & rainfall volume from file - fseek(Frain.file, Gage[j].startFilePos, SEEK_SET); - fread(&Gage[j].startDate, sizeof(DateTime), 1, Frain.file); - fread(&vFirst, sizeof(float), 1, Frain.file); - Gage[j].currentFilePos = ftell(Frain.file); - - // --- convert rainfall to intensity - Gage[j].rainfall = convertRainfall(j, (double)vFirst); - return 1; - } - return 0; - } - - // --- otherwise access user-supplied rainfall time series - else - { - k = Gage[j].tSeries; - if ( k >= 0 ) - { - // --- retrieve first rainfall value from time series - if ( table_getFirstEntry(&Tseries[k], &Gage[j].startDate, - &rFirst) ) - { - // --- convert rainfall to intensity - Gage[j].rainfall = convertRainfall(j, rFirst); - return 1; - } - } - return 0; - } -} - -//============================================================================= - -int getNextRainfall(int j) -// -// Input: j = rain gage index -// Output: returns 1 if successful; 0 if not -// Purpose: positions rainfall record to date with next non-zero rainfall -// while updating the gage's next rain intensity value. -// -// Note: zero rainfall values explicitly entered into a rain file or -// time series are skipped over so that a proper accounting of -// wet and dry periods can be maintained. -// -{ - int k; // time series index - float vNext; // next rain volume (ft or m) - double rNext; // next rain intensity (in/hr or mm/hr) - - Gage[j].nextRainfall = 0.0; - do - { - if ( Gage[j].dataSource == RAIN_FILE ) - { - if ( Frain.file && Gage[j].currentFilePos < Gage[j].endFilePos ) - { - fseek(Frain.file, Gage[j].currentFilePos, SEEK_SET); - fread(&Gage[j].nextDate, sizeof(DateTime), 1, Frain.file); - fread(&vNext, sizeof(float), 1, Frain.file); - Gage[j].currentFilePos = ftell(Frain.file); - rNext = convertRainfall(j, (double)vNext); - } - else return 0; - } - - else - { - k = Gage[j].tSeries; - if ( k >= 0 ) - { - if ( !table_getNextEntry(&Tseries[k], - &Gage[j].nextDate, &rNext) ) return 0; - rNext = convertRainfall(j, rNext); - } - else return 0; - } - } while (rNext == 0.0); - Gage[j].nextRainfall = rNext; - return 1; -} - -//============================================================================= - -double convertRainfall(int j, double r) -// -// Input: j = rain gage index -// r = rainfall value (user units) -// Output: returns rainfall intensity (user units) -// Purpose: converts rainfall value to an intensity (depth per hour). -// -{ - double r1; - switch ( Gage[j].rainType ) - { - case RAINFALL_INTENSITY: - r1 = r; - break; - - case RAINFALL_VOLUME: - r1 = r / Gage[j].rainInterval * 3600.0; - break; - - case CUMULATIVE_RAINFALL: - if ( r < Gage[j].rainAccum ) - r1 = r / Gage[j].rainInterval * 3600.0; - else r1 = (r - Gage[j].rainAccum) / Gage[j].rainInterval * 3600.0; - Gage[j].rainAccum = r; - break; - - default: r1 = r; - } - return r1 * Gage[j].unitsFactor * Adjust.rainFactor; -} - -//============================================================================= diff --git a/src/globals.h b/src/globals.h deleted file mode 100644 index 70f2431aa..000000000 --- a/src/globals.h +++ /dev/null @@ -1,172 +0,0 @@ -//----------------------------------------------------------------------------- -// globals.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Global Variables -// -// Update History -// ============== -// Build 5.1.004: -// - Ignore RDII option added. -// Build 5.1.007: -// - Monthly climate variable adjustments added. -// Build 5.1.008: -// - Number of parallel threads for dynamic wave routing added. -// - Minimum dynamic wave routing variable time step added. -// Build 5.1.011: -// - Changed WarningCode to Warnings (# warnings issued) -// - Added error message text as a variable. -// - Added elapsed simulation time (in decimal days) variable. -// - Added variables associated with detailed routing events. -// Build 5.1.012: -// - InSteadyState variable made local to routing_execute in routing.c. -// Build 5.1.013: -// - CrownCutoff and RuleStep added as analysis option variables. -// Build 5.1.015: -// - Fixes bug in summary statistics when Report Start date > Start Date. -// Build 5.2.0: -// - Support for relative file names added. -//----------------------------------------------------------------------------- - -#ifndef GLOBALS_H -#define GLOBALS_H - - -EXTERN TFile - Finp, // Input file - Fout, // Output file - Frpt, // Report file - Fclimate, // Climate file - Frain, // Rainfall file - Frunoff, // Runoff file - Frdii, // RDII inflow file - Fhotstart1, // Hot start input file - Fhotstart2, // Hot start output file - Finflows, // Inflows routing file - Foutflows; // Outflows routing file - -EXTERN long - Nperiods, // Number of reporting periods - TotalStepCount, // Total routing steps used - ReportStepCount, // Reporting routing steps used - NonConvergeCount; // Number of non-converging steps - -EXTERN char - Msg[MAXMSG+1], // Text of output message - ErrorMsg[MAXMSG+1], // Text of error message - Title[MAXTITLE][MAXMSG+1],// Project title - TempDir[MAXFNAME+1], // Temporary file directory - InpDir[MAXFNAME+1]; // Input file directory - -EXTERN TRptFlags - RptFlags; // Reporting options - -EXTERN int - Nobjects[MAX_OBJ_TYPES], // Number of each object type - Nnodes[MAX_NODE_TYPES], // Number of each node sub-type - Nlinks[MAX_LINK_TYPES], // Number of each link sub-type - UnitSystem, // Unit system - FlowUnits, // Flow units - InfilModel, // Infiltration method - RouteModel, // Flow routing method - ForceMainEqn, // Flow equation for force mains - LinkOffsets, // Link offset convention - SurchargeMethod, // EXTRAN or SLOT method - AllowPonding, // Allow water to pond at nodes - InertDamping, // Degree of inertial damping - NormalFlowLtd, // Normal flow limited - SlopeWeighting, // Use slope weighting - Compatibility, // SWMM 5/3/4 compatibility - SkipSteadyState, // Skip over steady state periods - IgnoreRainfall, // Ignore rainfall/runoff - IgnoreRDII, // Ignore RDII - IgnoreSnowmelt, // Ignore snowmelt - IgnoreGwater, // Ignore groundwater - IgnoreRouting, // Ignore flow routing - IgnoreQuality, // Ignore water quality - ErrorCode, // Error code number - Warnings, // Number of warning messages - WetStep, // Runoff wet time step (sec) - DryStep, // Runoff dry time step (sec) - ReportStep, // Reporting time step (sec) - RuleStep, // Rule evaluation time step (sec) - SweepStart, // Day of year when sweeping starts - SweepEnd, // Day of year when sweeping ends - MaxTrials, // Max. trials for DW routing - NumThreads, // Number of parallel threads used - NumEvents; // Number of detailed events - -EXTERN double - RouteStep, // Routing time step (sec) - MinRouteStep, // Minimum variable time step (sec) - LengtheningStep, // Time step for lengthening (sec) - StartDryDays, // Antecedent dry days - CourantFactor, // Courant time step factor - MinSurfArea, // Minimum nodal surface area - MinSlope, // Minimum conduit slope - RunoffError, // Runoff continuity error - GwaterError, // Groundwater continuity error - FlowError, // Flow routing error - QualError, // Quality routing error - HeadTol, // DW routing head tolerance (ft) - SysFlowTol, // Tolerance for steady system flow - LatFlowTol, // Tolerance for steady nodal inflow - CrownCutoff; // Fractional pipe crown cutoff - -EXTERN DateTime - StartDate, // Starting date - StartTime, // Starting time - StartDateTime, // Starting Date+Time - EndDate, // Ending date - EndTime, // Ending time - EndDateTime, // Ending Date+Time - ReportStartDate, // Report start date - ReportStartTime, // Report start time - ReportStart; // Report start Date+Time - -EXTERN double - ReportTime, // Current reporting time (msec) - OldRunoffTime, // Previous runoff time (msec) - NewRunoffTime, // Current runoff time (msec) - OldRoutingTime, // Previous routing time (msec) - NewRoutingTime, // Current routing time (msec) - TotalDuration, // Simulation duration (msec) - ElapsedTime; // Current elapsed time (days) - -EXTERN TTemp Temp; // Temperature data -EXTERN TEvap Evap; // Evaporation data -EXTERN TWind Wind; // Wind speed data -EXTERN TSnow Snow; // Snow melt data -EXTERN TAdjust Adjust; // Climate adjustments - -EXTERN TSnowmelt* Snowmelt; // Array of snow melt objects -EXTERN TGage* Gage; // Array of rain gages -EXTERN TSubcatch* Subcatch; // Array of subcatchments -EXTERN TAquifer* Aquifer; // Array of groundwater aquifers -EXTERN TUnitHyd* UnitHyd; // Array of unit hydrographs -EXTERN TNode* Node; // Array of nodes -EXTERN TOutfall* Outfall; // Array of outfall nodes -EXTERN TDivider* Divider; // Array of divider nodes -EXTERN TStorage* Storage; // Array of storage nodes -EXTERN TLink* Link; // Array of links -EXTERN TConduit* Conduit; // Array of conduit links -EXTERN TPump* Pump; // Array of pump links -EXTERN TOrifice* Orifice; // Array of orifice links -EXTERN TWeir* Weir; // Array of weir links -EXTERN TOutlet* Outlet; // Array of outlet device links -EXTERN TPollut* Pollut; // Array of pollutants -EXTERN TLanduse* Landuse; // Array of landuses -EXTERN TPattern* Pattern; // Array of time patterns -EXTERN TTable* Curve; // Array of curve tables -EXTERN TTable* Tseries; // Array of time series tables -EXTERN TTransect* Transect; // Array of transect data -EXTERN TStreet* Street; // Array of defined Street cross-sections -EXTERN TShape* Shape; // Array of custom conduit shapes -EXTERN TEvent* Event; // Array of routing events - - -#endif //GLOBALS_H diff --git a/src/gwater.c b/src/gwater.c deleted file mode 100644 index 48db9de17..000000000 --- a/src/gwater.c +++ /dev/null @@ -1,872 +0,0 @@ -//----------------------------------------------------------------------------- -// gwater.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Groundwater functions. -// -// Update History -// ============== -// Build 5.1.007: -// - User-supplied function for deep GW seepage flow added. -// - New variable names for use in user-supplied GW flow equations added. -// Build 5.1.008: -// - More variable names for user-supplied GW flow equations added. -// - Subcatchment area made into a shared variable. -// - Evaporation loss initialized to 0. -// - Support for collecting GW statistics added. -// Build 5.1.010: -// - Unsaturated hydraulic conductivity added to GW flow equation variables. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include -#include "headers.h" -#include "odesolve.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -static const double GWTOL = 0.0001; // ODE solver tolerance -static const double XTOL = 0.001; // tolerance for moisture & depth - -enum GWstates {THETA, // moisture content of upper GW zone - LOWERDEPTH}; // depth of lower saturated GW zone - -enum GWvariables { - gwvHGW, // water table height (ft) - gwvHSW, // surface water height (ft) - gwvHCB, // channel bottom height (ft) - gwvHGS, // ground surface height (ft) - gwvKS, // sat. hyd. condutivity (ft/s) - gwvK, // unsat. hyd. conductivity (ft/s) - gwvTHETA, // upper zone moisture content - gwvPHI, // soil porosity - gwvFI, // surface infiltration (ft/s) - gwvFU, // uper zone percolation rate (ft/s) - gwvA, // subcatchment area (ft2) - gwvMAX}; - -// Names of GW variables that can be used in GW outflow expression -static char* GWVarWords[] = {"HGW", "HSW", "HCB", "HGS", "KS", "K", - "THETA", "PHI", "FI", "FU", "A", NULL}; - -//----------------------------------------------------------------------------- -// Shared variables -//----------------------------------------------------------------------------- -// NOTE: all flux rates are in ft/sec, all depths are in ft. -static double Area; // subcatchment area (ft2) -static double Infil; // infiltration rate from surface -static double MaxEvap; // max. evaporation rate -static double AvailEvap; // available evaporation rate -static double UpperEvap; // evaporation rate from upper GW zone -static double LowerEvap; // evaporation rate from lower GW zone -static double UpperPerc; // percolation rate from upper to lower zone -static double LowerLoss; // loss rate from lower GW zone -static double GWFlow; // flow rate from lower zone to conveyance node -static double MaxUpperPerc; // upper limit on UpperPerc -static double MaxGWFlowPos; // upper limit on GWFlow when its positve -static double MaxGWFlowNeg; // upper limit on GWFlow when its negative -static double FracPerv; // fraction of surface that is pervious -static double TotalDepth; // total depth of GW aquifer -static double Theta; // moisture content of upper zone -static double HydCon; // unsaturated hydraulic conductivity (ft/s) -static double Hgw; // ht. of saturated zone -static double Hstar; // ht. from aquifer bottom to node invert -static double Hsw; // ht. from aquifer bottom to water surface -static double Tstep; // current time step (sec) -static TAquifer A; // aquifer being analyzed -static TGroundwater* GW; // groundwater object being analyzed -static MathExpr* LatFlowExpr; // user-supplied lateral GW flow expression -static MathExpr* DeepFlowExpr; // user-supplied deep GW flow expression - -//----------------------------------------------------------------------------- -// External Functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// gwater_readAquiferParams (called by input_readLine) -// gwater_readGroundwaterParams (called by input_readLine) -// gwater_readFlowExpression (called by input_readLine) -// gwater_deleteFlowExpression (called by deleteObjects in project.c) -// gwater_validateAquifer (called by swmm_open) -// gwater_validate (called by subcatch_validate) -// gwater_initState (called by subcatch_initState) -// gwater_getVolume (called by massbal_open & massbal_getGwaterError) -// gwater_getGroundwater (called by getSubareaRunoff in subcatch.c) -// gwater_getState (called by saveRunoff in hotstart.c) -// gwater_setState (called by readRunoff in hotstart.c) - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static void getDxDt(double t, double* x, double* dxdt); -static void getFluxes(double upperVolume, double lowerDepth); -static void getEvapRates(double theta, double upperDepth); -static double getUpperPerc(double theta, double upperDepth); -static double getGWFlow(double lowerDepth); -static void updateMassBal(double area, double tStep); - -// Used to process custom GW outflow equations -static int getVariableIndex(char* s); -static double getVariableValue(int varIndex); - -//============================================================================= - -int gwater_readAquiferParams(int j, char* tok[], int ntoks) -// -// Input: j = aquifer index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns error message -// Purpose: reads aquifer parameter values from line of input data -// -// Data line contains following parameters: -// ID, porosity, wiltingPoint, fieldCapacity, conductivity, -// conductSlope, tensionSlope, upperEvapFraction, lowerEvapDepth, -// gwRecession, bottomElev, waterTableElev, upperMoisture -// (evapPattern) -// -{ - int i, p; - double x[12]; - char *id; - - // --- check that aquifer exists - if ( ntoks < 13 ) return error_setInpError(ERR_ITEMS, ""); - id = project_findID(AQUIFER, tok[0]); - if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); - - // --- read remaining tokens as numbers - for (i = 0; i < 11; i++) x[i] = 0.0; - for (i = 1; i < 13; i++) - { - if ( ! getDouble(tok[i], &x[i-1]) ) - return error_setInpError(ERR_NUMBER, tok[i]); - } - - // --- read upper evap pattern if present - p = -1; - if ( ntoks > 13 ) - { - p = project_findObject(TIMEPATTERN, tok[13]); - if ( p < 0 ) return error_setInpError(ERR_NAME, tok[13]); - } - - // --- assign parameters to aquifer object - Aquifer[j].ID = id; - Aquifer[j].porosity = x[0]; - Aquifer[j].wiltingPoint = x[1]; - Aquifer[j].fieldCapacity = x[2]; - Aquifer[j].conductivity = x[3] / UCF(RAINFALL); - Aquifer[j].conductSlope = x[4]; - Aquifer[j].tensionSlope = x[5] / UCF(LENGTH); - Aquifer[j].upperEvapFrac = x[6]; - Aquifer[j].lowerEvapDepth = x[7] / UCF(LENGTH); - Aquifer[j].lowerLossCoeff = x[8] / UCF(RAINFALL); - Aquifer[j].bottomElev = x[9] / UCF(LENGTH); - Aquifer[j].waterTableElev = x[10] / UCF(LENGTH); - Aquifer[j].upperMoisture = x[11]; - Aquifer[j].upperEvapPat = p; - return 0; -} - -//============================================================================= - -int gwater_readGroundwaterParams(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// Purpose: reads groundwater inflow parameters for a subcatchment from -// a line of input data. -// -// Data format is: -// subcatch aquifer node surfElev a1 b1 a2 b2 a3 fixedDepth + -// (nodeElev bottomElev waterTableElev upperMoisture ) -// -{ - int i, j, k, m, n; - double x[11]; - TGroundwater* gw; - - // --- check that specified subcatchment, aquifer & node exist - if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); - j = project_findObject(SUBCATCH, tok[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); - - // --- check for enough tokens - if ( ntoks < 11 ) return error_setInpError(ERR_ITEMS, ""); - - // --- check that specified aquifer and node exists - k = project_findObject(AQUIFER, tok[1]); - if ( k < 0 ) return error_setInpError(ERR_NAME, tok[1]); - n = project_findObject(NODE, tok[2]); - if ( n < 0 ) return error_setInpError(ERR_NAME, tok[2]); - - // -- read in the flow parameters - for ( i = 0; i < 7; i++ ) - { - if ( ! getDouble(tok[i+3], &x[i]) ) - return error_setInpError(ERR_NUMBER, tok[i+3]); - } - - // --- read in optional depth parameters - for ( i = 7; i < 11; i++) - { - x[i] = MISSING; - m = i + 3; - if ( ntoks > m && *tok[m] != '*' ) - { - if (! getDouble(tok[m], &x[i]) ) - return error_setInpError(ERR_NUMBER, tok[m]); - if ( i < 10 ) x[i] /= UCF(LENGTH); - } - } - - // --- create a groundwater flow object - if ( !Subcatch[j].groundwater ) - { - gw = (TGroundwater *) malloc(sizeof(TGroundwater)); - if ( !gw ) return error_setInpError(ERR_MEMORY, ""); - Subcatch[j].groundwater = gw; - } - else gw = Subcatch[j].groundwater; - - // --- populate the groundwater flow object with its parameters - gw->aquifer = k; - gw->node = n; - gw->surfElev = x[0] / UCF(LENGTH); - gw->a1 = x[1]; - gw->b1 = x[2]; - gw->a2 = x[3]; - gw->b2 = x[4]; - gw->a3 = x[5]; - gw->fixedDepth = x[6] / UCF(LENGTH); - gw->nodeElev = x[7]; //already converted to ft. - gw->bottomElev = x[8]; - gw->waterTableElev = x[9]; - gw->upperMoisture = x[10]; - return 0; -} - -//============================================================================= - -int gwater_readFlowExpression(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// Purpose: reads mathematical expression for lateral or deep groundwater -// flow for a subcatchment from a line of input data. -// -// Format is: subcatch LATERAL/DEEP -// where subcatch is the ID of the subcatchment, LATERAL is for lateral -// GW flow, DEEP is for deep GW flow and is any well-formed math -// expression. -// -{ - int i, j, k; - char exprStr[MAXLINE+1]; - MathExpr* expr; - - // --- return if too few tokens - if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); - - // --- check that subcatchment exists - j = project_findObject(SUBCATCH, tok[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); - - // --- check if expression is for lateral or deep GW flow - k = 1; - if ( match(tok[1], "LAT") ) k = 1; - else if ( match(tok[1], "DEEP") ) k = 2; - else return error_setInpError(ERR_KEYWORD, tok[1]); - - // --- concatenate remaining tokens into a single string - sstrncpy(exprStr, tok[2], MAXLINE); - for ( i = 3; i < ntoks; i++) - { - sstrcat(exprStr, " ", MAXLINE+1); - sstrcat(exprStr, tok[i], MAXLINE+1); - } - - // --- delete any previous flow eqn. - if ( k == 1 ) mathexpr_delete(Subcatch[j].gwLatFlowExpr); - else mathexpr_delete(Subcatch[j].gwDeepFlowExpr); - - // --- create a parsed expression tree from the string expr - // (getVariableIndex is the function that converts a GW - // variable's name into an index number) - expr = mathexpr_create(exprStr, getVariableIndex); - if ( expr == NULL ) return error_setInpError(ERR_MATH_EXPR, ""); - - // --- save expression tree with the subcatchment - if ( k == 1 ) Subcatch[j].gwLatFlowExpr = expr; - else Subcatch[j].gwDeepFlowExpr = expr; - return 0; -} - -//============================================================================= - -void gwater_deleteFlowExpression(int j) -// -// Input: j = subcatchment index -// Output: none -// Purpose: deletes a subcatchment's custom groundwater flow expressions. -// -{ - mathexpr_delete(Subcatch[j].gwLatFlowExpr); - mathexpr_delete(Subcatch[j].gwDeepFlowExpr); -} - -//============================================================================= - -void gwater_validateAquifer(int j) -// -// Input: j = aquifer index -// Output: none -// Purpose: validates groundwater aquifer properties . -// -{ - int p; - - if ( Aquifer[j].porosity <= 0.0 - || Aquifer[j].fieldCapacity >= Aquifer[j].porosity - || Aquifer[j].wiltingPoint >= Aquifer[j].fieldCapacity - || Aquifer[j].conductivity <= 0.0 - || Aquifer[j].conductSlope < 0.0 - || Aquifer[j].tensionSlope < 0.0 - || Aquifer[j].upperEvapFrac < 0.0 - || Aquifer[j].lowerEvapDepth < 0.0 - || Aquifer[j].waterTableElev < Aquifer[j].bottomElev - || Aquifer[j].upperMoisture > Aquifer[j].porosity - || Aquifer[j].upperMoisture < Aquifer[j].wiltingPoint ) - report_writeErrorMsg(ERR_AQUIFER_PARAMS, Aquifer[j].ID); - - p = Aquifer[j].upperEvapPat; - if ( p >= 0 && Pattern[p].type != MONTHLY_PATTERN ) - { - report_writeErrorMsg(ERR_AQUIFER_PARAMS, Aquifer[j].ID); - } -} - -//============================================================================= - -void gwater_validate(int j) -{ - TAquifer a; // Aquifer data structure - TGroundwater* gw; // Groundwater data structure - - gw = Subcatch[j].groundwater; - if ( gw ) - { - a = Aquifer[gw->aquifer]; - - // ... use aquifer values for missing groundwater parameters - if ( gw->bottomElev == MISSING ) gw->bottomElev = a.bottomElev; - if ( gw->waterTableElev == MISSING ) gw->waterTableElev = a.waterTableElev; - if ( gw->upperMoisture == MISSING ) gw->upperMoisture = a.upperMoisture; - - // ... ground elevation can't be below water table elevation - if ( gw->surfElev < gw->waterTableElev ) - report_writeErrorMsg(ERR_GROUND_ELEV, Subcatch[j].ID); - } -} - -//============================================================================= - -void gwater_initState(int j) -// -// Input: j = subcatchment index -// Output: none -// Purpose: initializes state of subcatchment's groundwater. -// -{ - TAquifer a; // Aquifer data structure - TGroundwater* gw; // Groundwater data structure - - gw = Subcatch[j].groundwater; - if ( gw ) - { - a = Aquifer[gw->aquifer]; - - // ... initial moisture content - gw->theta = gw->upperMoisture; - if ( gw->theta >= a.porosity ) - { - gw->theta = a.porosity - XTOL; - } - - // ... initial depth of lower (saturated) zone - gw->lowerDepth = gw->waterTableElev - gw->bottomElev; - if ( gw->lowerDepth >= gw->surfElev - gw->bottomElev ) - { - gw->lowerDepth = gw->surfElev - gw->bottomElev - XTOL; - } - - // ... initial lateral groundwater outflow - gw->oldFlow = 0.0; - gw->newFlow = 0.0; - gw->evapLoss = 0.0; - - // ... initial available infiltration volume into upper zone - gw->maxInfilVol = (gw->surfElev - gw->waterTableElev) * - (a.porosity - gw->theta) / - subcatch_getFracPerv(j); - } -} - -//============================================================================= - -void gwater_getState(int j, double x[]) -// -// Input: j = subcatchment index -// Output: x[] = array of groundwater state variables -// Purpose: retrieves state of subcatchment's groundwater. -// -{ - TGroundwater* gw = Subcatch[j].groundwater; - x[0] = gw->theta; - x[1] = gw->bottomElev + gw->lowerDepth; - x[2] = gw->newFlow; - x[3] = gw->maxInfilVol; -} - -//============================================================================= - -void gwater_setState(int j, double x[]) -// -// Input: j = subcatchment index -// x[] = array of groundwater state variables -// Purpose: assigns values to a subcatchment's groundwater state. -// -{ - TGroundwater* gw = Subcatch[j].groundwater; - if ( gw == NULL ) return; - gw->theta = x[0]; - gw->lowerDepth = x[1] - gw->bottomElev; - gw->oldFlow = x[2]; - if ( x[3] != MISSING ) gw->maxInfilVol = x[3]; -} - -//============================================================================= - -double gwater_getVolume(int j) -// -// Input: j = subcatchment index -// Output: returns total volume of groundwater in ft/ft2 -// Purpose: finds volume of groundwater stored in upper & lower zones -// -{ - TAquifer a; - TGroundwater* gw; - double upperDepth; - gw = Subcatch[j].groundwater; - if ( gw == NULL ) return 0.0; - a = Aquifer[gw->aquifer]; - upperDepth = gw->surfElev - gw->bottomElev - gw->lowerDepth; - return (upperDepth * gw->theta) + (gw->lowerDepth * a.porosity); -} - -//============================================================================= - -void gwater_getGroundwater(int j, double evap, double infil, double tStep) -// -// Purpose: computes groundwater flow from subcatchment during current time step. -// Input: j = subcatchment index -// evap = pervious surface evaporation volume consumed (ft3) -// infil = surface infiltration volume (ft3) -// tStep = time step (sec) -// Output: none -// -{ - int n; // node exchanging groundwater - double x[2]; // upper moisture content & lower depth - double vUpper; // upper vol. available for percolation - double nodeFlow; // max. possible GW flow from node - - // --- save subcatchment's groundwater and aquifer objects to - // shared variables - GW = Subcatch[j].groundwater; - if ( GW == NULL ) return; - LatFlowExpr = Subcatch[j].gwLatFlowExpr; - DeepFlowExpr = Subcatch[j].gwDeepFlowExpr; - A = Aquifer[GW->aquifer]; - - // --- get fraction of total area that is pervious - FracPerv = subcatch_getFracPerv(j); - if ( FracPerv <= 0.0 ) return; - Area = Subcatch[j].area; - - // --- convert infiltration volume (ft3) to equivalent rate - // over entire GW (subcatchment) area - infil = infil / Area / tStep; - Infil = infil; - Tstep = tStep; - - // --- convert pervious surface evaporation already exerted (ft3) - // to equivalent rate over entire GW (subcatchment) area - evap = evap / Area / tStep; - - // --- convert max. surface evap rate (ft/sec) to a rate - // that applies to GW evap (GW evap can only occur - // through the pervious land surface area) - MaxEvap = Evap.rate * FracPerv; - - // --- available subsurface evaporation is difference between max. - // rate and pervious surface evap already exerted - AvailEvap = MAX((MaxEvap - evap), 0.0); - - // --- save total depth & outlet node properties to shared variables - TotalDepth = GW->surfElev - GW->bottomElev; - if ( TotalDepth <= 0.0 ) return; - n = GW->node; - - // --- establish min. water table height above aquifer bottom at which - // GW flow can occur (override node's invert if a value was provided - // in the GW object) - if ( GW->nodeElev != MISSING ) Hstar = GW->nodeElev - GW->bottomElev; - else Hstar = Node[n].invertElev - GW->bottomElev; - - // --- establish surface water height (relative to aquifer bottom) - // for drainage system node connected to the GW aquifer - if ( GW->fixedDepth > 0.0 ) - { - Hsw = GW->fixedDepth + Node[n].invertElev - GW->bottomElev; - } - else Hsw = Node[n].newDepth + Node[n].invertElev - GW->bottomElev; - - // --- store state variables (upper zone moisture content, lower zone - // depth) in work vector x - x[THETA] = GW->theta; - x[LOWERDEPTH] = GW->lowerDepth; - - // --- set limit on percolation rate from upper to lower GW zone - vUpper = (TotalDepth - x[LOWERDEPTH]) * (x[THETA] - A.fieldCapacity); - vUpper = MAX(0.0, vUpper); - MaxUpperPerc = vUpper / tStep; - - // --- set limit on GW flow out of aquifer based on volume of lower zone - MaxGWFlowPos = x[LOWERDEPTH]*A.porosity / tStep; - - // --- set limit on GW flow into aquifer from drainage system node - // based on min. of capacity of upper zone and drainage system - // inflow to the node - MaxGWFlowNeg = (TotalDepth - x[LOWERDEPTH]) * (A.porosity - x[THETA]) - / tStep; - nodeFlow = (Node[n].inflow + Node[n].newVolume/tStep) / Area; - MaxGWFlowNeg = -MIN(MaxGWFlowNeg, nodeFlow); - - // --- integrate eqns. for d(Theta)/dt and d(LowerDepth)/dt - // NOTE: ODE solver must have been initialized previously - odesolve_integrate(x, 2, 0, tStep, GWTOL, tStep, getDxDt); - - // --- keep state variables within allowable bounds - x[THETA] = MAX(x[THETA], A.wiltingPoint); - if ( x[THETA] >= A.porosity ) - { - x[THETA] = A.porosity - XTOL; - x[LOWERDEPTH] = TotalDepth - XTOL; - } - x[LOWERDEPTH] = MAX(x[LOWERDEPTH], 0.0); - if ( x[LOWERDEPTH] >= TotalDepth ) - { - x[LOWERDEPTH] = TotalDepth - XTOL; - } - - // --- save new values of state values - GW->theta = x[THETA]; - GW->lowerDepth = x[LOWERDEPTH]; - getFluxes(GW->theta, GW->lowerDepth); - GW->oldFlow = GW->newFlow; - GW->newFlow = GWFlow; - GW->evapLoss = UpperEvap + LowerEvap; - - //--- find max. infiltration volume (as depth over - // the pervious portion of the subcatchment) - // that upper zone can support in next time step - GW->maxInfilVol = (TotalDepth - x[LOWERDEPTH]) * - (A.porosity - x[THETA]) / FracPerv; - - // --- update GW mass balance - updateMassBal(Area, tStep); - - // --- update GW statistics - stats_updateGwaterStats(j, infil, GW->evapLoss, GWFlow, LowerLoss, - GW->theta, GW->lowerDepth + GW->bottomElev, tStep); -} - -//============================================================================= - -void updateMassBal(double area, double tStep) -// -// Input: area = subcatchment area (ft2) -// tStep = time step (sec) -// Output: none -// Purpose: updates GW mass balance with volumes of water fluxes. -// -{ - double vInfil; // infiltration volume - double vUpperEvap; // upper zone evap. volume - double vLowerEvap; // lower zone evap. volume - double vLowerPerc; // lower zone deep perc. volume - double vGwater; // volume of exchanged groundwater - double ft2sec = area * tStep; - - vInfil = Infil * ft2sec; - vUpperEvap = UpperEvap * ft2sec; - vLowerEvap = LowerEvap * ft2sec; - vLowerPerc = LowerLoss * ft2sec; - vGwater = 0.5 * (GW->oldFlow + GW->newFlow) * ft2sec; - massbal_updateGwaterTotals(vInfil, vUpperEvap, vLowerEvap, vLowerPerc, - vGwater); -} - -//============================================================================= - -void getFluxes(double theta, double lowerDepth) -// -// Input: upperVolume = vol. depth of upper zone (ft) -// upperDepth = depth of upper zone (ft) -// Output: none -// Purpose: computes water fluxes into/out of upper/lower GW zones. -// -{ - double upperDepth; - - // --- find upper zone depth - lowerDepth = MAX(lowerDepth, 0.0); - lowerDepth = MIN(lowerDepth, TotalDepth); - upperDepth = TotalDepth - lowerDepth; - - // --- save lower depth and theta to global variables - Hgw = lowerDepth; - Theta = theta; - - // --- find evaporation rate from both zones - getEvapRates(theta, upperDepth); - - // --- find percolation rate from upper to lower zone - UpperPerc = getUpperPerc(theta, upperDepth); - UpperPerc = MIN(UpperPerc, MaxUpperPerc); - - // --- find loss rate to deep GW - if ( DeepFlowExpr != NULL ) - LowerLoss = mathexpr_eval(DeepFlowExpr, getVariableValue) / - UCF(RAINFALL); - else - LowerLoss = A.lowerLossCoeff * lowerDepth / TotalDepth; - LowerLoss = MIN(LowerLoss, lowerDepth/Tstep); - - // --- find GW flow rate from lower zone to drainage system node - GWFlow = getGWFlow(lowerDepth); - if ( LatFlowExpr != NULL ) - { - GWFlow += mathexpr_eval(LatFlowExpr, getVariableValue) / UCF(GWFLOW); - } - if ( GWFlow >= 0.0 ) GWFlow = MIN(GWFlow, MaxGWFlowPos); - else GWFlow = MAX(GWFlow, MaxGWFlowNeg); -} - -//============================================================================= - -void getDxDt(double t, double* x, double* dxdt) -// -// Input: t = current time (not used) -// x = array of state variables -// Output: dxdt = array of time derivatives of state variables -// Purpose: computes time derivatives of upper moisture content -// and lower depth. -// -{ - double qUpper; // inflow - outflow for upper zone (ft/sec) - double qLower; // inflow - outflow for lower zone (ft/sec) - double denom; - - getFluxes(x[THETA], x[LOWERDEPTH]); - qUpper = Infil - UpperEvap - UpperPerc; - qLower = UpperPerc - LowerLoss - LowerEvap - GWFlow; - - // --- d(upper zone moisture)/dt = (net upper zone flow) / - // (upper zone depth) - denom = TotalDepth - x[LOWERDEPTH]; - if (denom > 0.0) - dxdt[THETA] = qUpper / denom; - else - dxdt[THETA] = 0.0; - - // --- d(lower zone depth)/dt = (net lower zone flow) / - // (upper zone moisture deficit) - denom = A.porosity - x[THETA]; - if (denom > 0.0) - dxdt[LOWERDEPTH] = qLower / denom; - else - dxdt[LOWERDEPTH] = 0.0; -} - -//============================================================================= - -void getEvapRates(double theta, double upperDepth) -// -// Input: theta = moisture content of upper zone -// upperDepth = depth of upper zone (ft) -// Output: none -// Purpose: computes evapotranspiration out of upper & lower zones. -// -{ - int p, month; - double f; - double lowerFrac, upperFrac; - - // --- no GW evaporation when infiltration is occurring - UpperEvap = 0.0; - LowerEvap = 0.0; - if ( Infil > 0.0 ) return; - - // --- get monthly-adjusted upper zone evap fraction - upperFrac = A.upperEvapFrac; - f = 1.0; - p = A.upperEvapPat; - if ( p >= 0 ) - { - month = datetime_monthOfYear(getDateTime(NewRunoffTime)); - f = Pattern[p].factor[month-1]; - } - upperFrac *= f; - - // --- upper zone evaporation requires that soil moisture - // be above the wilting point - if ( theta > A.wiltingPoint ) - { - // --- actual evap is upper zone fraction applied to max. potential - // rate, limited by the available rate after any surface evap - UpperEvap = upperFrac * MaxEvap; - UpperEvap = MIN(UpperEvap, AvailEvap); - } - - // --- check if lower zone evaporation is possible - if ( A.lowerEvapDepth > 0.0 ) - { - // --- find the fraction of the lower evaporation depth that - // extends into the saturated lower zone - lowerFrac = (A.lowerEvapDepth - upperDepth) / A.lowerEvapDepth; - lowerFrac = MAX(0.0, lowerFrac); - lowerFrac = MIN(lowerFrac, 1.0); - - // --- make the lower zone evap rate proportional to this fraction - // and the evap not used in the upper zone - LowerEvap = lowerFrac * (1.0 - upperFrac) * MaxEvap; - LowerEvap = MIN(LowerEvap, (AvailEvap - UpperEvap)); - } -} - -//============================================================================= - -double getUpperPerc(double theta, double upperDepth) -// -// Input: theta = moisture content of upper zone -// upperDepth = depth of upper zone (ft) -// Output: returns percolation rate (ft/sec) -// Purpose: finds percolation rate from upper to lower zone. -// -{ - double delta; // unfilled water content of upper zone - double dhdz; // avg. change in head with depth - double hydcon; // unsaturated hydraulic conductivity - - // --- no perc. from upper zone if no depth or moisture content too low - if ( upperDepth <= 0.0 || theta <= A.fieldCapacity ) return 0.0; - - // --- compute hyd. conductivity as function of moisture content - delta = theta - A.porosity; - hydcon = A.conductivity * exp(delta * A.conductSlope); - - // --- compute integral of dh/dz term - delta = theta - A.fieldCapacity; - dhdz = 1.0 + A.tensionSlope * 2.0 * delta / upperDepth; - - // --- compute upper zone percolation rate - HydCon = hydcon; - return hydcon * dhdz; -} - -//============================================================================= - -double getGWFlow(double lowerDepth) -// -// Input: lowerDepth = depth of lower zone (ft) -// Output: returns groundwater flow rate (ft/sec) -// Purpose: finds groundwater outflow from lower saturated zone. -// -{ - double q, t1, t2, t3; - - // --- water table must be above Hstar for flow to occur - if ( lowerDepth <= Hstar ) return 0.0; - - // --- compute groundwater component of flow - if ( GW->b1 == 0.0 ) t1 = GW->a1; - else t1 = GW->a1 * pow( (lowerDepth - Hstar)*UCF(LENGTH), GW->b1); - - // --- compute surface water component of flow - if ( GW->b2 == 0.0 ) t2 = GW->a2; - else if (Hsw > Hstar) - { - t2 = GW->a2 * pow( (Hsw - Hstar)*UCF(LENGTH), GW->b2); - } - else t2 = 0.0; - - // --- compute groundwater/surface water interaction term - t3 = GW->a3 * lowerDepth * Hsw * UCF(LENGTH) * UCF(LENGTH); - - // --- compute total groundwater flow - q = (t1 - t2 + t3) / UCF(GWFLOW); - if ( q < 0.0 && GW->a3 != 0.0 ) q = 0.0; - return q; -} - -//============================================================================= - -int getVariableIndex(char* s) -// -// Input: s = name of a groundwater variable -// Output: returns index of groundwater variable -// Purpose: finds position of GW variable in list of GW variable names. -// -{ - int k; - - k = findmatch(s, GWVarWords); - if ( k >= 0 ) return k; - return -1; -} - -//============================================================================= - -double getVariableValue(int varIndex) -// -// Input: varIndex = index of a GW variable -// Output: returns current value of GW variable -// Purpose: finds current value of a GW variable. -// -{ - switch (varIndex) - { - case gwvHGW: return Hgw * UCF(LENGTH); - case gwvHSW: return Hsw * UCF(LENGTH); - case gwvHCB: return Hstar * UCF(LENGTH); - case gwvHGS: return TotalDepth * UCF(LENGTH); - case gwvKS: return A.conductivity * UCF(RAINFALL); - case gwvK: return HydCon * UCF(RAINFALL); - case gwvTHETA:return Theta; - case gwvPHI: return A.porosity; - case gwvFI: return Infil * UCF(RAINFALL); - case gwvFU: return UpperPerc * UCF(RAINFALL); - case gwvA: return Area * UCF(LANDAREA); - default: return 0.0; - } -} diff --git a/src/hash.c b/src/hash.c deleted file mode 100644 index 4a73e6ccd..000000000 --- a/src/hash.c +++ /dev/null @@ -1,117 +0,0 @@ -//----------------------------------------------------------------------------- -// hash.c -// -// Implementation of a simple Hash Table for string storage & retrieval -// CASE INSENSITIVE -// -// Written by L. Rossman -// Last Updated on 6/19/03 -// -// The hash table data structure (HTable) is defined in "hash.h". -// Interface Functions: -// HTcreate() - creates a hash table -// HTinsert() - inserts a string & its index value into a hash table -// HTfind() - retrieves the index value of a string from a table -// HTfree() - frees a hash table -//----------------------------------------------------------------------------- - -#include -#include -#include "hash.h" -#define UCHAR(x) (((x) >= 'a' && (x) <= 'z') ? ((x)&~32) : (x)) - -/* Case-insensitive comparison of strings s1 and s2 */ -int samestr(const char *s1, const char *s2) -{ - int i; - for (i=0; UCHAR(s1[i]) == UCHAR(s2[i]); i++) - if (!s1[i+1] && !s2[i+1]) return(1); - return(0); -} /* End of samestr */ - -/* Use Fletcher's checksum to compute 2-byte hash of string */ -unsigned int hash(const char *str) -{ - unsigned int sum1= 0, check1; - unsigned long sum2= 0L; - while( '\0' != *str ) - { - sum1 += UCHAR(*str); - str++; - if ( 255 <= sum1 ) sum1 -= 255; - sum2 += sum1; - } - check1= sum2; - check1 %= 255; - check1= 255 - (sum1+check1) % 255; - sum1= 255 - (sum1+check1) % 255; - return( ( ( check1 << 8 ) | sum1 ) % HTMAXSIZE); -} - -HTtable *HTcreate() -{ - int i; - HTtable *ht = (HTtable *) calloc(HTMAXSIZE, sizeof(HTtable)); - if (ht != NULL) for (i=0; i= HTMAXSIZE ) return(0); - entry = (struct HTentry *) malloc(sizeof(struct HTentry)); - if (entry == NULL) return(0); - entry->key = key; - entry->data = data; - entry->next = ht[i]; - ht[i] = entry; - return(1); -} - -int HTfind(HTtable *ht, const char *key) -{ - unsigned int i = hash(key); - struct HTentry *entry; - if ( i >= HTMAXSIZE ) return(NOTFOUND); - entry = ht[i]; - while (entry != NULL) - { - if ( samestr(entry->key,key) ) return(entry->data); - entry = entry->next; - } - return(NOTFOUND); -} - -char *HTfindKey(HTtable *ht, const char *key) -{ - unsigned int i = hash(key); - struct HTentry *entry; - if ( i >= HTMAXSIZE ) return(NULL); - entry = ht[i]; - while (entry != NULL) - { - if ( samestr(entry->key,key) ) return(entry->key); - entry = entry->next; - } - return(NULL); -} - -void HTfree(HTtable *ht) -{ - struct HTentry *entry, - *nextentry; - int i; - for (i=0; inext; - free(entry); - entry = nextentry; - } - } - free(ht); -} diff --git a/src/hash.h b/src/hash.h deleted file mode 100644 index ba42be00e..000000000 --- a/src/hash.h +++ /dev/null @@ -1,30 +0,0 @@ -//----------------------------------------------------------------------------- -// hash.h -// -// Header file for Hash Table module hash.c. -//----------------------------------------------------------------------------- - -#ifndef HASH_H -#define HASH_H - - -#define HTMAXSIZE 1999 -#define NOTFOUND -1 - -struct HTentry -{ - char *key; - int data; - struct HTentry *next; -}; - -typedef struct HTentry *HTtable; - -HTtable* HTcreate(void); -int HTinsert(HTtable *, char *, int); -int HTfind(HTtable *, const char *); -char* HTfindKey(HTtable *, const char *); -void HTfree(HTtable *); - - -#endif //HASH_H diff --git a/src/headers.h b/src/headers.h deleted file mode 100644 index 87139b060..000000000 --- a/src/headers.h +++ /dev/null @@ -1,20 +0,0 @@ -//----------------------------------------------------------------------------- -// headers.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Header files included in most SWMM5 modules. -// -// DO NOT CHANGE THE ORDER OF THE #INCLUDE STATEMENTS -//----------------------------------------------------------------------------- -#include "macros.h" -#include "objects.h" -#define EXTERN extern -#include "globals.h" -#include "funcs.h" -#include "error.h" -#include "text.h" -#include "keywords.h" diff --git a/src/hotstart.c b/src/hotstart.c deleted file mode 100644 index 69f7abcc9..000000000 --- a/src/hotstart.c +++ /dev/null @@ -1,544 +0,0 @@ -//----------------------------------------------------------------------------- -// hotstart.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Hot Start file functions. -// -// A SWMM hot start file contains the state of a SWMM project after -// a simulation has been run, allowing it to be used to initialize -// a subsequent simulation that picks up where the previous run ended. -// -// An abridged version of the hot start file (version 2) is available -// that contains only variables that appear in the binary output file -// (groundwater upper moisture and water table elevation, node depth, -// lateral inflow, and quality, and link flow, depth, setting and quality). -// -// When reading a previously saved hot start file checks are made to -// insure the the current SWMM project has the same number of major -// components (subcatchments, land uses, nodes, links, and pollutants) -// and unit system as does the hot start file. No test is made to -// insure that these components are of the same sub-type and maintain -// the same order as when the hot start file was created. -// -// Update History -// ============== -// Build 5.1.008: -// - Storage node hydraulic residence time (HRT) was added to the file. -// - Link control settings are now applied when reading a hot start file. -// - Runoff read from file assigned to newRunoff property instead of oldRunoff. -// - Array indexing bug when reading snowpack state from file fixed. -// Build 5.1.011: -// - Link control setting bug when reading a hot start file fixed. -// Build 5.1.015: -// - Support added for multiple infiltration methods within a project. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include -#include -#include "headers.h" - -//----------------------------------------------------------------------------- -// Local Variables -//----------------------------------------------------------------------------- -static int fileVersion; - -//----------------------------------------------------------------------------- -// External functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// hotstart_open (called by swmm_start in swmm5.c) -// hotstart_close (called by swmm_end in swmm5.c) - -//----------------------------------------------------------------------------- -// Function declarations -//----------------------------------------------------------------------------- -static int openHotstartFile1(void); -static int openHotstartFile2(void); -static void readRunoff(void); -static void saveRunoff(void); -static void readRouting(void); -static void saveRouting(void); -static int readFloat(float *x, FILE* f); -static int readDouble(double* x, FILE* f); - -//============================================================================= - -int hotstart_open() -{ - // --- open hot start files - if ( !openHotstartFile1() ) return FALSE; //input hot start file - if ( !openHotstartFile2() ) return FALSE; //output hot start file - return TRUE; -} - -//============================================================================= - -void hotstart_close() -{ - if ( Fhotstart2.file ) - { - saveRunoff(); - saveRouting(); - fclose(Fhotstart2.file); - } -} - -//============================================================================= - -int openHotstartFile1() -// -// Input: none -// Output: none -// Purpose: opens a previously saved routing hotstart file. -// -{ - int nSubcatch; - int nNodes; - int nLinks; - int nPollut; - int nLandUses; - int flowUnits; - char fStamp[] = "SWMM5-HOTSTART"; - char fileStamp[] = "SWMM5-HOTSTART"; - char fStampx[] = "SWMM5-HOTSTARTx"; - char fileStamp2[] = "SWMM5-HOTSTART2"; - char fileStamp3[] = "SWMM5-HOTSTART3"; - char fileStamp4[] = "SWMM5-HOTSTART4"; - - // --- try to open the file - if ( Fhotstart1.mode != USE_FILE ) return TRUE; - if ( (Fhotstart1.file = fopen(Fhotstart1.name, "r+b")) == NULL) - { - report_writeErrorMsg(ERR_HOTSTART_FILE_OPEN, Fhotstart1.name); - return FALSE; - } - - // --- check that file contains proper header records - fread(fStampx, sizeof(char), strlen(fileStamp2), Fhotstart1.file); - if ( strcmp(fStampx, fileStamp4) == 0 ) fileVersion = 4; - else if ( strcmp(fStampx, fileStamp3) == 0 ) fileVersion = 3; - else if ( strcmp(fStampx, fileStamp2) == 0 ) fileVersion = 2; - else - { - rewind(Fhotstart1.file); - fread(fStamp, sizeof(char), strlen(fileStamp), Fhotstart1.file); - if ( strcmp(fStamp, fileStamp) != 0 ) - { - report_writeErrorMsg(ERR_HOTSTART_FILE_FORMAT, ""); - return FALSE; - } - fileVersion = 1; - } - - nSubcatch = -1; - nNodes = -1; - nLinks = -1; - nPollut = -1; - nLandUses = -1; - flowUnits = -1; - if ( fileVersion >= 2 ) - { - fread(&nSubcatch, sizeof(int), 1, Fhotstart1.file); - } - else nSubcatch = Nobjects[SUBCATCH]; - if ( fileVersion >= 3 ) - { - fread(&nLandUses, sizeof(int), 1, Fhotstart1.file); - } - else nLandUses = Nobjects[LANDUSE]; - fread(&nNodes, sizeof(int), 1, Fhotstart1.file); - fread(&nLinks, sizeof(int), 1, Fhotstart1.file); - fread(&nPollut, sizeof(int), 1, Fhotstart1.file); - fread(&flowUnits, sizeof(int), 1, Fhotstart1.file); - if ( nSubcatch != Nobjects[SUBCATCH] - || nLandUses != Nobjects[LANDUSE] - || nNodes != Nobjects[NODE] - || nLinks != Nobjects[LINK] - || nPollut != Nobjects[POLLUT] - || flowUnits != FlowUnits ) - { - report_writeErrorMsg(ERR_HOTSTART_FILE_FORMAT, ""); - return FALSE; - } - - // --- read contents of the file and close it - if ( fileVersion >= 3 ) readRunoff(); - readRouting(); - fclose(Fhotstart1.file); - if ( ErrorCode ) return FALSE; - else return TRUE; -} - -//============================================================================= - -int openHotstartFile2() -// -// Input: none -// Output: none -// Purpose: opens a new routing hotstart file to save results to. -// -{ - int nSubcatch; - int nLandUses; - int nNodes; - int nLinks; - int nPollut; - int flowUnits; - char fileStamp[] = "SWMM5-HOTSTART4"; - - // --- try to open file - if ( Fhotstart2.mode != SAVE_FILE ) return TRUE; - if ( (Fhotstart2.file = fopen(Fhotstart2.name, "w+b")) == NULL) - { - report_writeErrorMsg(ERR_HOTSTART_FILE_OPEN, Fhotstart2.name); - return FALSE; - } - - // --- write file stamp & number of objects to file - nSubcatch = Nobjects[SUBCATCH]; - nLandUses = Nobjects[LANDUSE]; - nNodes = Nobjects[NODE]; - nLinks = Nobjects[LINK]; - nPollut = Nobjects[POLLUT]; - flowUnits = FlowUnits; - fwrite(fileStamp, sizeof(char), strlen(fileStamp), Fhotstart2.file); - fwrite(&nSubcatch, sizeof(int), 1, Fhotstart2.file); - fwrite(&nLandUses, sizeof(int), 1, Fhotstart2.file); - fwrite(&nNodes, sizeof(int), 1, Fhotstart2.file); - fwrite(&nLinks, sizeof(int), 1, Fhotstart2.file); - fwrite(&nPollut, sizeof(int), 1, Fhotstart2.file); - fwrite(&flowUnits, sizeof(int), 1, Fhotstart2.file); - return TRUE; -} - -//============================================================================= - -void saveRouting() -// -// Input: none -// Output: none -// Purpose: saves current state of all nodes and links to hotstart file. -// -{ - int i, j; - float x[3]; - - for (i = 0; i < Nobjects[NODE]; i++) - { - x[0] = (float)Node[i].newDepth; - x[1] = (float)Node[i].newLatFlow; - fwrite(x, sizeof(float), 2, Fhotstart2.file); - - if ( Node[i].type == STORAGE ) - { - j = Node[i].subIndex; - x[0] = (float)Storage[j].hrt; - fwrite(&x[0], sizeof(float), 1, Fhotstart2.file); - } - - for (j = 0; j < Nobjects[POLLUT]; j++) - { - x[0] = (float)Node[i].newQual[j]; - fwrite(&x[0], sizeof(float), 1, Fhotstart2.file); - } - } - for (i = 0; i < Nobjects[LINK]; i++) - { - x[0] = (float)Link[i].newFlow; - x[1] = (float)Link[i].newDepth; - x[2] = (float)Link[i].setting; - fwrite(x, sizeof(float), 3, Fhotstart2.file); - for (j = 0; j < Nobjects[POLLUT]; j++) - { - x[0] = (float)Link[i].newQual[j]; - fwrite(&x[0], sizeof(float), 1, Fhotstart2.file); - } - } -} - -//============================================================================= - -void readRouting() -// -// Input: none -// Output: none -// Purpose: reads initial state of all nodes, links and groundwater objects -// from hotstart file. -// -{ - int i, j; - float x; - double xgw[4]; - FILE* f = Fhotstart1.file; - - // --- for file format 2, assign GW moisture content and lower depth - if ( fileVersion == 2 ) - { - // --- flow and available upper zone volume not used - xgw[2] = 0.0; - xgw[3] = MISSING; - for (i = 0; i < Nobjects[SUBCATCH]; i++) - { - // --- read moisture content and water table elevation as floats - if ( !readFloat(&x, f) ) return; - xgw[0] = x; - if ( !readFloat(&x, f) ) return; - xgw[1] = x; - - // --- set GW state - if ( Subcatch[i].groundwater != NULL ) gwater_setState(i, xgw); - } - } - - // --- read node states - for (i = 0; i < Nobjects[NODE]; i++) - { - if ( !readFloat(&x, f) ) return; - Node[i].newDepth = x; - if ( !readFloat(&x, f) ) return; - Node[i].newLatFlow = x; - - if ( fileVersion >= 4 && Node[i].type == STORAGE ) - { - if ( !readFloat(&x, f) ) return; - j = Node[i].subIndex; - Storage[j].hrt = x; - } - - for (j = 0; j < Nobjects[POLLUT]; j++) - { - if ( !readFloat(&x, f) ) return; - Node[i].newQual[j] = x; - } - - // --- read in zeros here for backwards compatibility - if ( fileVersion <= 2 ) - { - for (j = 0; j < Nobjects[POLLUT]; j++) - { - if ( !readFloat(&x, f) ) return; - } - } - } - - // --- read link states - for (i = 0; i < Nobjects[LINK]; i++) - { - if ( !readFloat(&x, f) ) return; - Link[i].newFlow = x; - if ( !readFloat(&x, f) ) return; - Link[i].newDepth = x; - if ( !readFloat(&x, f) ) return; - Link[i].setting = x; - - // --- set link's target setting to saved setting - Link[i].targetSetting = x; - link_setTargetSetting(i); - link_setSetting(i, 0.0); - - for (j = 0; j < Nobjects[POLLUT]; j++) - { - if ( !readFloat(&x, f) ) return; - Link[i].newQual[j] = x; - } - - } -} - -//============================================================================= - -void saveRunoff(void) -// -// Input: none -// Output: none -// Purpose: saves current state of all subcatchments to hotstart file. -// -{ - int i, j, k; - double x[6]; - FILE* f = Fhotstart2.file; - - for (i = 0; i < Nobjects[SUBCATCH]; i++) - { - // Ponded depths for each sub-area & total runoff (4 elements) - for (j = 0; j < 3; j++) x[j] = Subcatch[i].subArea[j].depth; - x[3] = Subcatch[i].newRunoff; - fwrite(x, sizeof(double), 4, f); - - // Infiltration state (max. of 6 elements) - for (j=0; j<6; j++) x[j] = 0.0; - infil_getState(i, x); - fwrite(x, sizeof(double), 6, f); - - // Groundwater state (4 elements) - if ( Subcatch[i].groundwater != NULL ) - { - gwater_getState(i, x); - fwrite(x, sizeof(double), 4, f); - } - - // Snowpack state (5 elements for each of 3 snow surfaces) - if ( Subcatch[i].snowpack != NULL ) - { - for (j=0; j<3; j++) - { - snow_getState(i, j, x); - fwrite(x, sizeof(double), 5, f); - } - } - - // Water quality - if ( Nobjects[POLLUT] > 0 ) - { - // Runoff quality - for (j=0; j 0 ) - { - // Runoff quality - for (j=0; j -#include -#include "headers.h" - -//----------------------------------------------------------------------------- -// Imported variables -//----------------------------------------------------------------------------- -extern double Qcf[]; // flow units conversion factors - // (see swmm5.c) - -//----------------------------------------------------------------------------- -// Shared variables -//----------------------------------------------------------------------------- -static int IfaceFlowUnits; // flow units for routing interface file -static int IfaceStep; // interface file time step (sec) -static int NumIfacePolluts; // number of pollutants in interface file -static int* IfacePolluts; // indexes of interface file pollutants -static int NumIfaceNodes; // number of nodes on interface file -static int* IfaceNodes; // indexes of nodes on interface file -static double** OldIfaceValues; // interface flows & WQ at previous time -static double** NewIfaceValues; // interface flows & WQ at next time -static double IfaceFrac; // fraction of interface file time step -static DateTime OldIfaceDate; // previous date of interface values -static DateTime NewIfaceDate; // next date of interface values - -//----------------------------------------------------------------------------- -// External Functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// iface_readFileParams (called by input_readLine) -// iface_openRoutingFiles (called by routing_open) -// iface_closeRoutingFiles (called by routing_close) -// iface_getNumIfaceNodes (called by addIfaceInflows in routing.c) -// iface_getIfaceNode (called by addIfaceInflows in routing.c) -// iface_getIfaceFlow (called by addIfaceInflows in routing.c) -// iface_getIfaceQual (called by addIfaceInflows in routing.c) -// iface_saveOutletResults (called by output_saveResults) - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static void openFileForOutput(void); -static void openFileForInput(void); -static int getIfaceFilePolluts(void); -static int getIfaceFileNodes(void); -static void setOldIfaceValues(void); -static void readNewIfaceValues(void); -static int isOutletNode(int node); - -//============================================================================= - -int iface_readFileParams(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads interface file information from a line of input data. -// -// Data format is: -// USE/SAVE FileType FileName -// -{ - char k; - int j; - char fname[MAXFNAME+1]; - - // --- determine file disposition and type - if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); - k = (char)findmatch(tok[0], FileModeWords); - if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[0]); - j = findmatch(tok[1], FileTypeWords); - if ( j < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); - if ( ntoks < 3 ) return 0; - sstrncpy(fname, tok[2], MAXFNAME); - - // --- process file name - switch ( j ) - { - case RAINFALL_FILE: - Frain.mode = k; - sstrncpy(Frain.name, addAbsolutePath(fname), MAXFNAME); - break; - - case RUNOFF_FILE: - Frunoff.mode = k; - sstrncpy(Frunoff.name, addAbsolutePath(fname), MAXFNAME); - break; - - case HOTSTART_FILE: - if ( k == USE_FILE ) - { - Fhotstart1.mode = k; - sstrncpy(Fhotstart1.name, addAbsolutePath(fname), MAXFNAME); - } - else if ( k == SAVE_FILE ) - { - Fhotstart2.mode = k; - sstrncpy(Fhotstart2.name, addAbsolutePath(fname), MAXFNAME); - } - break; - - case RDII_FILE: - Frdii.mode = k; - sstrncpy(Frdii.name, fname, MAXFNAME); - break; - - case INFLOWS_FILE: - if ( k != USE_FILE ) return error_setInpError(ERR_ITEMS, ""); - Finflows.mode = k; - sstrncpy(Finflows.name, addAbsolutePath(fname), MAXFNAME); - break; - - case OUTFLOWS_FILE: - if ( k != SAVE_FILE ) return error_setInpError(ERR_ITEMS, ""); - Foutflows.mode = k; - sstrncpy(Foutflows.name, addAbsolutePath(fname), MAXFNAME); - break; - } - return 0; -} - -//============================================================================= - -void iface_openRoutingFiles() -// -// Input: none -// Output: none -// Purpose: opens routing interface files. -// -{ - // --- initialize shared variables - NumIfacePolluts = 0; - IfacePolluts = NULL; - NumIfaceNodes = 0; - IfaceNodes = NULL; - OldIfaceValues = NULL; - NewIfaceValues = NULL; - - // --- check that inflows & outflows files are not the same - if ( Foutflows.mode != NO_FILE && Finflows.mode != NO_FILE ) - { - if ( strcomp(Foutflows.name, Finflows.name) ) - { - report_writeErrorMsg(ERR_ROUTING_FILE_NAMES, ""); - return; - } - } - - // --- open the file for reading or writing - if ( Foutflows.mode == SAVE_FILE ) openFileForOutput(); - if ( Finflows.mode == USE_FILE ) openFileForInput(); -} - -//============================================================================= - -void iface_closeRoutingFiles() -// -// Input: none -// Output: none -// Purpose: closes routing interface files. -// -{ - FREE(IfacePolluts); - FREE(IfaceNodes); - if ( OldIfaceValues != NULL ) project_freeMatrix(OldIfaceValues); - if ( NewIfaceValues != NULL ) project_freeMatrix(NewIfaceValues); - if ( Finflows.file ) fclose(Finflows.file); - if ( Foutflows.file ) fclose(Foutflows.file); -} - -//============================================================================= - -int iface_getNumIfaceNodes(DateTime currentDate) -// -// Input: currentDate = current date/time -// Output: returns number of interface nodes if data exists or -// 0 otherwise -// Purpose: reads inflow data from interface file at current date. -// -{ - // --- return 0 if file begins after current date - if ( OldIfaceDate > currentDate ) return 0; - - // --- keep updating new interface values until current date bracketed - while ( NewIfaceDate < currentDate && NewIfaceDate != NO_DATE ) - { - setOldIfaceValues(); - readNewIfaceValues(); - } - - // --- return 0 if no data available - if ( NewIfaceDate == NO_DATE ) return 0; - - // --- find fraction current date is bewteen old & new interface dates - IfaceFrac = (currentDate - OldIfaceDate) / (NewIfaceDate - OldIfaceDate); - IfaceFrac = MAX(0.0, IfaceFrac); - IfaceFrac = MIN(IfaceFrac, 1.0); - - // --- return number of interface nodes - return NumIfaceNodes; -} - -//============================================================================= - -int iface_getIfaceNode(int index) -// -// Input: index = interface file node index -// Output: returns project node index -// Purpose: finds index of project node associated with interface node index -// -{ - if ( index >= 0 && index < NumIfaceNodes ) return IfaceNodes[index]; - else return -1; -} - -//============================================================================= - -double iface_getIfaceFlow(int index) -// -// Input: index = interface file node index -// Output: returns inflow to node -// Purpose: finds interface flow for particular node index. -// -{ - double q1, q2; - - if ( index >= 0 && index < NumIfaceNodes ) - { - // --- interpolate flow between old and new values - q1 = OldIfaceValues[index][0]; - q2 = NewIfaceValues[index][0]; - return (1.0 - IfaceFrac)*q1 + IfaceFrac*q2; - } - else return 0.0; -} - -//============================================================================= - -double iface_getIfaceQual(int index, int pollut) -// -// Input: index = index of node on interface file -// pollut = index of pollutant on interface file -// Output: returns inflow pollutant concentration -// Purpose: finds interface concentration for particular node index & pollutant. -// -{ - int j; - double c1, c2; - - if ( index >= 0 && index < NumIfaceNodes ) - { - // --- find index of pollut on interface file - j = IfacePolluts[pollut]; - if ( j < 0 ) return 0.0; - - // --- interpolate flow between old and new values - // (remember that 1st col. of values matrix is for flow) - c1 = OldIfaceValues[index][j+1]; - c2 = NewIfaceValues[index][j+1]; - return (1.0 - IfaceFrac)*c1 + IfaceFrac*c2; - } - else return 0.0; -} - -//============================================================================= - -void iface_saveOutletResults(DateTime reportDate, FILE* file) -// -// Input: reportDate = reporting date/time -// file = ptr. to interface file -// Output: none -// Purpose: saves system outflows to routing interface file. -// -{ - int i, p, yr, mon, day, hr, min, sec; - char theDate[26]; - datetime_decodeDate(reportDate, &yr, &mon, &day); - datetime_decodeTime(reportDate, &hr, &min, &sec); - snprintf(theDate, 26, " %04d %02d %02d %02d %02d %02d ", - yr, mon, day, hr, min, sec); - for (i=0; i 0 ) - { - report_writeErrorMsg(err, Finflows.name); - return; - } - - // --- match nodes in file with those in project - err = getIfaceFileNodes(); - if ( err > 0 ) - { - report_writeErrorMsg(err, Finflows.name); - return; - } - - // --- create matrices for old & new interface flows & WQ values - OldIfaceValues = project_createMatrix(NumIfaceNodes, - 1+NumIfacePolluts); - NewIfaceValues = project_createMatrix(NumIfaceNodes, - 1+NumIfacePolluts); - if ( OldIfaceValues == NULL || NewIfaceValues == NULL ) - { - report_writeErrorMsg(ERR_MEMORY, ""); - return; - } - - // --- read in new interface flows & WQ values - readNewIfaceValues(); - OldIfaceDate = NewIfaceDate; -} - -//============================================================================= - -int getIfaceFilePolluts() -// -// Input: none -// Output: returns an error code -// Purpose: reads names of pollutants saved on the inflows interface file. -// -{ - int i, j; - char line[MAXLINE+1]; // line from inflows interface file - char s1[MAXLINE+1]; // general string variable - char s2[MAXLINE+1]; - - // --- read number of pollutants (minus FLOW) - fgets(line, MAXLINE, Finflows.file); - NumIfacePolluts = -1; - if (sscanf(line, "%d", &NumIfacePolluts)) - NumIfacePolluts--; - if ( NumIfacePolluts < 0 ) return ERR_ROUTING_FILE_FORMAT; - - // --- read flow units - fgets(line, MAXLINE, Finflows.file); - if (sscanf(line, "%s %s", s1, s2) < 2) - return ERR_ROUTING_FILE_FORMAT; - if ( !strcomp(s1, "FLOW") ) - return ERR_ROUTING_FILE_FORMAT; - IfaceFlowUnits = findmatch(s2, FlowUnitWords); - if ( IfaceFlowUnits < 0 ) - return ERR_ROUTING_FILE_FORMAT; - - // --- allocate memory for pollutant index array - if ( Nobjects[POLLUT] > 0 ) - { - IfacePolluts = (int *) calloc(Nobjects[POLLUT], sizeof(int)); - if ( !IfacePolluts ) return ERR_MEMORY; - for (i=0; i 0 && Nobjects[POLLUT] > 0 ) - { - // --- check each pollutant name on file with project's pollutants - for (i=0; i -#include -#include "headers.h" -#include "infil.h" - -//----------------------------------------------------------------------------- -// Local Variables -//----------------------------------------------------------------------------- -typedef union TInfil { - THorton horton; - TGrnAmpt grnAmpt; - TCurveNum curveNum; -} TInfil; -TInfil *Infil; - -static double Fumax; // saturated water volume in upper soil zone (ft) -static double InfilFactor; - -//----------------------------------------------------------------------------- -// External Functions (declared in infil.h) -//----------------------------------------------------------------------------- -// infil_create (called by createObjects in project.c) -// infil_delete (called by deleteObjects in project.c) -// infil_readParams (called by input_readLine) -// infil_initState (called by subcatch_initState) -// infil_getState (called by writeRunoffFile in hotstart.c) -// infil_setState (called by readRunoffFile in hotstart.c) -// infil_getInfil (called by getSubareaRunoff in subcatch.c) - -// Called locally and by storage node methods in node.c -// grnampt_setParams -// grnampt_initState -// grnampt_getInfil - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static int horton_setParams(THorton *infil, double p[]); -static void horton_initState(THorton *infil); -static void horton_getState(THorton *infil, double x[]); -static void horton_setState(THorton *infil, double x[]); -static double horton_getInfil(THorton *infil, double tstep, double irate, - double depth); -static double modHorton_getInfil(THorton *infil, double tstep, double irate, - double depth); - -static void grnampt_getState(TGrnAmpt *infil, double x[]); -static void grnampt_setState(TGrnAmpt *infil, double x[]); -static double grnampt_getUnsatInfil(TGrnAmpt *infil, double tstep, - double irate, double depth, int modelType); -static double grnampt_getSatInfil(TGrnAmpt *infil, double tstep, - double irate, double depth); -static double grnampt_getF2(double f1, double c1, double ks, double ts); - -static int curvenum_setParams(TCurveNum *infil, double p[]); -static void curvenum_initState(TCurveNum *infil); -static void curvenum_getState(TCurveNum *infil, double x[]); -static void curvenum_setState(TCurveNum *infil, double x[]); -static double curvenum_getInfil(TCurveNum *infil, double tstep, double irate, - double depth); - -//============================================================================= - -void infil_create(int n) -// -// Purpose: creates an array of infiltration objects. -// Input: n = number of subcatchments -// Output: none -// -{ - Infil = (TInfil *) calloc(n, sizeof(TInfil)); - if (Infil == NULL) ErrorCode = ERR_MEMORY; - InfilFactor = 1.0; - return; -} - -//============================================================================= - -void infil_delete() -// -// Purpose: deletes infiltration objects associated with subcatchments -// Input: none -// Output: none -// -{ - FREE(Infil); -} - -//============================================================================= - -int infil_readParams(int m, char* tok[], int ntoks) -// -// Input: m = default infiltration model -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: sets infiltration parameters from a line of input data. -// -// Format of data line is: -// subcatch p1 p2 ... (infilMethod) -{ - int i, j, n, status; - double x[5]; - - // --- check that subcatchment exists - j = project_findObject(SUBCATCH, tok[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); - - // --- check for infiltration method keyword is last token - i = findmatch(tok[ntoks-1], InfilModelWords); - if ( i >= 0 ) - { - m = i; - --ntoks; - } - - // --- number of input tokens depends on infiltration model m - if ( m == HORTON ) n = 5; - else if ( m == MOD_HORTON ) n = 5; - else if ( m == GREEN_AMPT ) n = 4; - else if ( m == MOD_GREEN_AMPT ) n = 4; - else if ( m == CURVE_NUMBER ) n = 4; - else return 0; - - if ( ntoks < n ) return error_setInpError(ERR_ITEMS, ""); - - // --- parse numerical values from tokens - for (i = 0; i < 5; i++) x[i] = 0.0; - for (i = 1; i < n; i++) - { - if (!getDouble(tok[i], &x[i - 1])) - return error_setInpError(ERR_NUMBER, tok[i]); - } - - // --- special case for Horton infil. - last parameter is optional - if ( (m == HORTON || m == MOD_HORTON) && ntoks > n ) - { - if ( ! getDouble(tok[n], &x[n-1]) ) - return error_setInpError(ERR_NUMBER, tok[n]); - } - - // --- assign parameter values to infil, infilModel object - Subcatch[j].infil = j; - Subcatch[j].infilModel = m; - switch (m) - { - case HORTON: - case MOD_HORTON: status = horton_setParams(&Infil[j].horton, x); - break; - case GREEN_AMPT: - case MOD_GREEN_AMPT: - status = grnampt_setParams(&Infil[j].grnAmpt, x); - break; - case CURVE_NUMBER: status = curvenum_setParams(&Infil[j].curveNum, x); - break; - default: status = TRUE; - } - if ( !status ) return error_setInpError(ERR_INFIL_PARAMS, ""); - return 0; -} - -//============================================================================= - -void infil_initState(int j) -// -// Input: j = subcatchment index -// Output: none -// Purpose: initializes state of infiltration for a subcatchment. -// -{ - switch (Subcatch[j].infilModel) - { - case HORTON: - case MOD_HORTON: horton_initState(&Infil[j].horton); break; - case GREEN_AMPT: - case MOD_GREEN_AMPT: - grnampt_initState(&Infil[j].grnAmpt); break; - case CURVE_NUMBER: curvenum_initState(&Infil[j].curveNum); break; - } -} - -//============================================================================= - -void infil_getState(int j, double x[]) -// -// Input: j = subcatchment index -// Output: x = subcatchment's infiltration state -// Purpose: retrieves the current infiltration state for a subcatchment. -// -{ - switch (Subcatch[j].infilModel) - { - case HORTON: - case MOD_HORTON: horton_getState(&Infil[j].horton, x); break; - case GREEN_AMPT: - case MOD_GREEN_AMPT: - grnampt_getState(&Infil[j].grnAmpt, x); break; - case CURVE_NUMBER: curvenum_getState(&Infil[j].curveNum, x); break; - } -} - -//============================================================================= - -void infil_setState(int j, double x[]) -// -// Input: j = subcatchment index -// m = infiltration method code -// Output: none -// Purpose: sets the current infiltration state for a subcatchment. -// -{ - switch (Subcatch[j].infilModel) - { - case HORTON: - case MOD_HORTON: horton_setState(&Infil[j].horton, x); break; - case GREEN_AMPT: - case MOD_GREEN_AMPT: - grnampt_setState(&Infil[j].grnAmpt, x); break; - case CURVE_NUMBER: curvenum_setState(&Infil[j].curveNum, x); break; - } -} - -//============================================================================= - -void infil_setInfilFactor(int j) -// -// Input: j = subcatchment index -// Output: none -// Purpose: assigns a value to the infiltration adjustment factor. -{ - int m; - int p; - - // ... set factor to the global conductivity adjustment factor - InfilFactor = Adjust.hydconFactor; - - // ... override global factor with subcatchment's adjustment if assigned - if (j >= 0) - { - p = Subcatch[j].infilPattern; - if (p >= 0 && Pattern[p].type == MONTHLY_PATTERN) - { - m = datetime_monthOfYear(getDateTime(OldRunoffTime)) - 1; - InfilFactor = Pattern[p].factor[m]; - } - } -} - -//============================================================================= - -double infil_getInfil(int j, double tstep, double rainfall, - double runon, double depth) -// -// Input: j = subcatchment index -// tstep = runoff time step (sec) -// rainfall = rainfall rate (ft/sec) -// runon = runon rate from other sub-areas or subcatchments (ft/sec) -// depth = depth of surface water on subcatchment (ft) -// Output: returns infiltration rate (ft/sec) -// Purpose: computes infiltration rate depending on infiltration method. -// -{ - switch (Subcatch[j].infilModel) - { - case HORTON: - return horton_getInfil(&Infil[j].horton, tstep, rainfall+runon, depth); - - case MOD_HORTON: - return modHorton_getInfil(&Infil[j].horton, tstep, rainfall+runon, - depth); - - case GREEN_AMPT: - case MOD_GREEN_AMPT: - return grnampt_getInfil(&Infil[j].grnAmpt, tstep, rainfall+runon, depth, - Subcatch[j].infilModel); - - case CURVE_NUMBER: - depth += runon * tstep; - return curvenum_getInfil(&Infil[j].curveNum, tstep, rainfall, depth); - - default: - return 0.0; - } -} - -//============================================================================= - -int horton_setParams(THorton *infil, double p[]) -// -// Input: infil = ptr. to Horton infiltration object -// p[] = array of parameter values -// Output: returns TRUE if parameters are valid, FALSE otherwise -// Purpose: assigns Horton infiltration parameters to a subcatchment. -// -{ - int k; - for (k = 0; k < 5; k++) if ( p[k] < 0.0 ) return FALSE; - - // --- max. & min. infil rates (ft/sec) - infil->f0 = p[0] / UCF(RAINFALL); - infil->fmin = p[1] / UCF(RAINFALL); - - // --- convert decay const. to 1/sec - infil->decay = p[2] / 3600.; - - // --- convert drying time (days) to a regeneration const. (1/sec) - // assuming that former is time to reach 98% dry along an - // exponential drying curve - if (p[3] == 0.0 ) p[3] = TINY; - infil->regen = -log(1.0-0.98) / p[3] / SECperDAY; - - // --- optional max. infil. capacity (ft) (p[4] = 0 if no value supplied) - infil->Fmax = p[4] / UCF(RAINDEPTH); - if ( infil->f0 < infil->fmin ) return FALSE; - return TRUE; -} - -//============================================================================= - -void horton_initState(THorton *infil) -// -// Input: infil = ptr. to Horton infiltration object -// Output: none -// Purpose: initializes time on Horton infiltration curve for a subcatchment. -// -{ - infil->tp = 0.0; - infil->Fe = 0.0; -} - -//============================================================================= - -void horton_getState(THorton *infil, double x[]) -{ - x[0] = infil->tp; - x[1] = infil->Fe; -} - -void horton_setState(THorton *infil, double x[]) -{ - infil->tp = x[0]; - infil->Fe = x[1]; -} - -//============================================================================= - -double horton_getInfil(THorton *infil, double tstep, double irate, double depth) -// -// Input: infil = ptr. to Horton infiltration object -// tstep = runoff time step (sec), -// irate = net "rainfall" rate (ft/sec), -// = rainfall + snowmelt + runon - evaporation -// depth = depth of ponded water (ft). -// Output: returns infiltration rate (ft/sec) -// Purpose: computes Horton infiltration for a subcatchment. -// -{ - // --- assign local variables - int iter; - double fa, fp = 0.0; - double Fp, F1, t1, tlim, ex, kt; - double FF, FF1, r; - double f0 = infil->f0 * InfilFactor; - double fmin = infil->fmin * InfilFactor; - double Fmax = infil->Fmax; - double tp = infil->tp; - double df = f0 - fmin; - double kd = infil->decay; - double kr = infil->regen * Evap.recoveryFactor; - - // --- special cases of no infil. or constant infil - if ( df < 0.0 || kd < 0.0 || kr < 0.0 ) return 0.0; - if ( df == 0.0 || kd == 0.0 ) - { - fp = f0; - fa = irate + depth / tstep; - if ( fp > fa ) fp = fa; - return MAX(0.0, fp); - } - - // --- compute water available for infiltration - fa = irate + depth / tstep; - - // --- case where there is water to infiltrate - if ( fa > ZERO ) - { - // --- compute average infil. rate over time step - t1 = tp + tstep; // future cumul. time - tlim = 16.0 / kd; // for tp >= tlim, f = fmin - if ( tp >= tlim ) - { - Fp = fmin * tp + df / kd; - F1 = Fp + fmin * tstep; - } - else - { - Fp = fmin * tp + df / kd * (1.0 - exp(-kd * tp)); - F1 = fmin * t1 + df / kd * (1.0 - exp(-kd * t1)); - } - fp = (F1 - Fp) / tstep; - fp = MAX(fp, fmin); - - // --- limit infil rate to available infil - if ( fp > fa ) fp = fa; - - // --- if fp on flat portion of curve then increase tp by tstep - if ( t1 > tlim ) tp = t1; - - // --- if infil < available capacity then increase tp by tstep - else if ( fp < fa ) tp = t1; - - // --- if infil limited by available capacity then - // solve F(tp) - F1 = 0 using Newton-Raphson method - else - { - F1 = Fp + fp * tstep; - tp = tp + tstep / 2.0; - for ( iter=1; iter<=20; iter++ ) - { - kt = MIN( 60.0, kd*tp ); - ex = exp(-kt); - FF = fmin * tp + df / kd * (1.0 - ex) - F1; - FF1 = fmin + df * ex; - r = FF / FF1; - tp = tp - r; - if ( fabs(r) <= 0.001 * tstep ) break; - } - } - - // --- limit cumulative infiltration to Fmax - if ( Fmax > 0.0 ) - { - if ( infil->Fe + fp * tstep > Fmax ) - fp = (Fmax - infil->Fe) / tstep; - fp = MAX(fp, 0.0); - infil->Fe += fp * tstep; - } - } - - // --- case where infil. capacity is regenerating; update tp. - else if (kr > 0.0) - { - r = exp(-kr * tstep); - tp = 1.0 - exp(-kd * tp); - tp = -log(1.0 - r*tp) / kd; - - // reduction in cumulative infiltration - if ( Fmax > 0.0 ) - { - infil->Fe = fmin*tp + (df/kd)*(1.0 - exp(-kd*tp)); - } - } - infil->tp = tp; - return fp; -} - -//============================================================================= - -double modHorton_getInfil(THorton *infil, double tstep, double irate, - double depth) -// -// Input: infil = ptr. to Horton infiltration object -// tstep = runoff time step (sec), -// irate = net "rainfall" rate (ft/sec), -// = rainfall + snowmelt + runon -// depth = depth of ponded water (ft). -// Output: returns infiltration rate (ft/sec) -// Purpose: computes modified Horton infiltration for a subcatchment. -// -{ - // --- assign local variables - double f = 0.0; - double fp, fa; - double f0 = infil->f0 * InfilFactor; - double fmin = infil->fmin * InfilFactor; - double df = f0 - fmin; - double kd = infil->decay; - double kr = infil->regen * Evap.recoveryFactor; - - // --- special cases of no or constant infiltration - if ( df < 0.0 || kd < 0.0 || kr < 0.0 ) return 0.0; - if ( df == 0.0 || kd == 0.0 ) - { - fp = f0; - fa = irate + depth / tstep; - if ( fp > fa ) fp = fa; - return MAX(0.0, fp); - } - - // --- compute water available for infiltration - fa = irate + depth / tstep; - - // --- case where there is water to infiltrate - if ( fa > ZERO ) - { - // --- saturated condition - if ( infil->Fmax > 0.0 && infil->Fe >= infil->Fmax ) return 0.0; - - // --- potential infiltration - fp = f0 - kd * infil->Fe; - fp = MAX(fp, fmin); - - // --- actual infiltration - f = MIN(fa, fp); - - // --- new cumulative infiltration minus seepage - infil->Fe += MAX((f - fmin), 0.0) * tstep; - if ( infil->Fmax > 0.0 ) infil->Fe = MAX(infil->Fe, infil->Fmax); - } - - // --- reduce cumulative infiltration for dry condition - else if (kr > 0.0) - { - infil->Fe *= exp(-kr * tstep); - infil->Fe = MAX(infil->Fe, 0.0); - } - return f; -} - -//============================================================================= - -void grnampt_getParams(int j, double p[]) -// -// Input: j = subcatchment index -// p[] = array of parameter values -// Output: none -// Purpose: retrieves Green-Ampt infiltration parameters for a subcatchment. -// -{ - p[0] = Infil[j].grnAmpt.S * UCF(RAINDEPTH); // Capillary suction head (ft) - p[1] = Infil[j].grnAmpt.Ks * UCF(RAINFALL); // Sat. hyd. conductivity (ft/sec) - p[2] = Infil[j].grnAmpt.IMDmax; // Max. init. moisture deficit -} - -//============================================================================= - -int grnampt_setParams(TGrnAmpt *infil, double p[]) -// -// Input: infil = ptr. to Green-Ampt infiltration object -// p[] = array of parameter values -// Output: returns TRUE if parameters are valid, FALSE otherwise -// Purpose: assigns Green-Ampt infiltration parameters to a subcatchment. -// -{ - double ksat; // sat. hyd. conductivity in in/hr - - if ( p[0] < 0.0 || p[1] <= 0.0 || p[2] < 0.0 || p[2] > 1.0) return FALSE; - infil->S = p[0] / UCF(RAINDEPTH); // Capillary suction head (ft) - infil->Ks = p[1] / UCF(RAINFALL); // Sat. hyd. conductivity (ft/sec) - infil->IMDmax = p[2]; // Max. init. moisture deficit - - // --- find depth of upper soil zone (ft) using Mein's eqn. - ksat = infil->Ks * 12. * 3600.; - infil->Lu = 4.0 * sqrt(ksat) / 12.; - return TRUE; -} - -//============================================================================= - -void grnampt_initState(TGrnAmpt *infil) -// -// Input: infil = ptr. to Green-Ampt infiltration object -// Output: none -// Purpose: initializes state of Green-Ampt infiltration for a subcatchment. -// -{ - if (infil == NULL) return; - infil->IMD = infil->IMDmax; - infil->Fu = 0.0; - infil->F = 0.0; - infil->Sat = FALSE; - infil->T = 0.0; -} - -void grnampt_getState(TGrnAmpt *infil, double x[]) -{ - x[0] = infil->IMD; - x[1] = infil->F; - x[2] = infil->Fu; - x[3] = infil->Sat; - x[4] = infil->T; -} - -void grnampt_setState(TGrnAmpt *infil, double x[]) -{ - infil->IMD = x[0]; - infil->F = x[1]; - infil->Fu = x[2]; - infil->Sat = (char)x[3]; - infil->T = x[4]; -} - -//============================================================================= - -double grnampt_getInfil(TGrnAmpt *infil, double tstep, double irate, - double depth, int modelType) -// -// Input: infil = ptr. to Green-Ampt infiltration object -// tstep = time step (sec), -// irate = net "rainfall" rate to upper zone (ft/sec); -// = rainfall + snowmelt + runon, -// does not include ponded water (added on below) -// depth = depth of ponded water (ft) -// modelType = either GREEN_AMPT or MOD_GREEN_AMPT -// Output: returns infiltration rate (ft/sec) -// Purpose: computes Green-Ampt infiltration for a subcatchment -// or a storage node. -// -{ - // --- find saturated upper soil zone water volume - Fumax = infil->IMDmax * infil->Lu * sqrt(InfilFactor); - - // --- reduce time until next event - infil->T -= tstep; - - // --- use different procedures depending on upper soil zone saturation - if ( infil->Sat ) return grnampt_getSatInfil(infil, tstep, irate, depth); - else return grnampt_getUnsatInfil(infil, tstep, irate, depth, modelType); -} - -//============================================================================= - -double grnampt_getUnsatInfil(TGrnAmpt *infil, double tstep, double irate, - double depth, int modelType) -// -// Input: infil = ptr. to Green-Ampt infiltration object -// tstep = runoff time step (sec), -// irate = net "rainfall" rate to upper zone (ft/sec); -// = rainfall + snowmelt + runon, -// does not include ponded water (added on below) -// depth = depth of ponded water (ft) -// modelType = either GREEN_AMPT or MOD_GREEN_AMPT -// Output: returns infiltration rate (ft/sec) -// Purpose: computes Green-Ampt infiltration when upper soil zone is -// unsaturated. -// -{ - double ia, c1, F2, dF, Fs, kr, ts; - double ks = infil->Ks * InfilFactor; - double lu = infil->Lu * sqrt(InfilFactor); - - // --- get available infiltration rate (rainfall + ponded water) - ia = irate + depth / tstep; - if ( ia < ZERO ) ia = 0.0; - - // --- no rainfall so recover upper zone moisture - if ( ia == 0.0 ) - { - if ( infil->Fu <= 0.0 ) return 0.0; - kr = lu / 90000.0 * Evap.recoveryFactor; - dF = kr * Fumax * tstep; - infil->F -= dF; - infil->Fu -= dF; - if ( infil->Fu <= 0.0 ) - { - infil->Fu = 0.0; - infil->F = 0.0; - infil->IMD = infil->IMDmax; - return 0.0; - } - - // --- if new wet event begins then reset IMD & F - if ( infil->T <= 0.0 ) - { - infil->IMD = (Fumax - infil->Fu) / lu; - infil->F = 0.0; - } - return 0.0; - } - - // --- rainfall does not exceed Ksat - if ( ia <= ks ) - { - dF = ia * tstep; - infil->F += dF; - infil->Fu += dF; - infil->Fu = MIN(infil->Fu, Fumax); - if ( modelType == GREEN_AMPT && infil->T <= 0.0 ) - { - infil->IMD = (Fumax - infil->Fu) / lu; - infil->F = 0.0; - } - return ia; - } - - // --- rainfall exceeds Ksat; renew time to drain upper zone - infil->T = 5400.0 / lu / Evap.recoveryFactor; - - // --- find volume needed to saturate surface layer - Fs = ks * (infil->S + depth) * infil->IMD / (ia - ks); - - // --- surface layer already saturated - if ( infil->F > Fs ) - { - infil->Sat = TRUE; - return grnampt_getSatInfil(infil, tstep, irate, depth); - } - - // --- surface layer remains unsaturated - if ( infil->F + ia*tstep < Fs ) - { - dF = ia * tstep; - infil->F += dF; - infil->Fu += dF; - infil->Fu = MIN(infil->Fu, Fumax); - return ia; - } - - // --- surface layer becomes saturated during time step; - // --- compute portion of tstep when saturated - ts = tstep - (Fs - infil->F) / ia; - if ( ts <= 0.0 ) ts = 0.0; - - // --- compute new total volume infiltrated - c1 = (infil->S + depth) * infil->IMD; - F2 = grnampt_getF2(Fs, c1, ks, ts); - if ( F2 > Fs + ia*ts ) F2 = Fs + ia*ts; - - // --- compute infiltration rate - dF = F2 - infil->F; - infil->F = F2; - infil->Fu += dF; - infil->Fu = MIN(infil->Fu, Fumax); - infil->Sat = TRUE; - return dF / tstep; -} - -//============================================================================= - -double grnampt_getSatInfil(TGrnAmpt *infil, double tstep, double irate, - double depth) -// -// Input: infil = ptr. to Green-Ampt infiltration object -// tstep = runoff time step (sec), -// irate = net "rainfall" rate to upper zone (ft/sec); -// = rainfall + snowmelt + runon, -// does not include ponded water (added on below) -// depth = depth of ponded water (ft). -// Output: returns infiltration rate (ft/sec) -// Purpose: computes Green-Ampt infiltration when upper soil zone is -// saturated. -// -{ - double ia, c1, dF, F2; - double ks = infil->Ks * InfilFactor; - double lu = infil->Lu * sqrt(InfilFactor); - - // --- get available infiltration rate (rainfall + ponded water) - ia = irate + depth / tstep; - if ( ia < ZERO ) return 0.0; - - // --- re-set new event recovery time - infil->T = 5400.0 / lu / Evap.recoveryFactor; - - // --- solve G-A equation for new cumulative infiltration volume (F2) - c1 = (infil->S + depth) * infil->IMD; - F2 = grnampt_getF2(infil->F, c1, ks, tstep); - dF = F2 - infil->F; - - // --- all available water infiltrates -- set saturated state to false - if ( dF > ia * tstep ) - { - dF = ia * tstep; - infil->Sat = FALSE; - } - - // --- update total infiltration and upper zone moisture deficit - infil->F += dF; - infil->Fu += dF; - infil->Fu = MIN(infil->Fu, Fumax); - return dF / tstep; -} - -//============================================================================= - -double grnampt_getF2(double f1, double c1, double ks, double ts) -// -// Input: f1 = old infiltration volume (ft) -// c1 = head * moisture deficit (ft) -// ks = sat. hyd. conductivity (ft/sec) -// ts = time step (sec) -// Output: returns infiltration volume at end of time step (ft) -// Purpose: computes new infiltration volume over a time step -// using Green-Ampt formula for saturated upper soil zone. -// -{ - int i; - double f2 = f1; - double f2min; - double df2; - double c2; - - // --- find min. infil. volume - f2min = f1 + ks * ts; - - // --- use min. infil. volume for 0 moisture deficit - if ( c1 == 0.0 ) return f2min; - - // --- use direct form of G-A equation for small time steps - // and c1/f1 < 100 - if ( ts < 10.0 && f1 > 0.01 * c1 ) - { - f2 = f1 + ks * (1.0 + c1/f1) * ts; - return MAX(f2, f2min); - } - - // --- use Newton-Raphson method to solve integrated G-A equation - // (convergence limit reduced from that used in previous releases) - c2 = c1 * log(f1 + c1) - ks * ts; - for ( i = 1; i <= 20; i++ ) - { - df2 = (f2 - f1 - c1 * log(f2 + c1) + c2) / (1.0 - c1 / (f2 + c1) ); - if ( fabs(df2) < 0.00001 ) - { - return MAX(f2, f2min); - } - f2 -= df2; - } - return f2min; -} - -//============================================================================= - -int curvenum_setParams(TCurveNum *infil, double p[]) -// -// Input: infil = ptr. to Curve Number infiltration object -// p[] = array of parameter values -// Output: returns TRUE if parameters are valid, FALSE otherwise -// Purpose: assigns Curve Number infiltration parameters to a subcatchment. -// -{ - - // --- convert Curve Number to max. infil. capacity - if ( p[0] < 10.0 ) p[0] = 10.0; - if ( p[0] > 99.0 ) p[0] = 99.0; - infil->Smax = (1000.0 / p[0] - 10.0) / 12.0; - if ( infil->Smax < 0.0 ) return FALSE; - - // --- convert drying time (days) to a regeneration const. (1/sec) - if ( p[2] > 0.0 ) infil->regen = 1.0 / (p[2] * SECperDAY); - else return FALSE; - - // --- compute inter-event time from regeneration const. as in Green-Ampt - infil->Tmax = 0.06 / infil->regen; - - return TRUE; -} - -//============================================================================= - -void curvenum_initState(TCurveNum *infil) -// -// Input: infil = ptr. to Curve Number infiltration object -// Output: none -// Purpose: initializes state of Curve Number infiltration for a subcatchment. -// -{ - infil->S = infil->Smax; - infil->P = 0.0; - infil->F = 0.0; - infil->T = 0.0; - infil->Se = infil->Smax; - infil->f = 0.0; -} - -void curvenum_getState(TCurveNum *infil, double x[]) -{ - x[0] = infil->S; - x[1] = infil->P; - x[2] = infil->F; - x[3] = infil->T; - x[4] = infil->Se; - x[5] = infil->f; -} - -void curvenum_setState(TCurveNum *infil, double x[]) -{ - infil->S = x[0]; - infil->P = x[1]; - infil->F = x[2]; - infil->T = x[3]; - infil->Se = x[4]; - infil->f = x[5]; -} - -//============================================================================= - -double curvenum_getInfil(TCurveNum *infil, double tstep, double irate, - double depth) -// -// Input: infil = ptr. to Curve Number infiltration object -// tstep = runoff time step (sec), -// irate = rainfall rate (ft/sec); -// depth = depth of runon + ponded water (ft) -// Output: returns infiltration rate (ft/sec) -// Purpose: computes infiltration rate using the Curve Number method. -// Note: this function treats runon from other subcatchments as part -// of the ponded depth and not as an effective rainfall rate. -{ - double F1; // new cumulative infiltration (ft) - double f1 = 0.0; // new infiltration rate (ft/sec) - double fa = irate + depth/tstep; // max. available infil. rate (ft/sec) - - // --- case where there is rainfall - if ( irate > ZERO ) - { - // --- check if new rain event - if ( infil->T >= infil->Tmax ) - { - infil->P = 0.0; - infil->F = 0.0; - infil->f = 0.0; - infil->Se = infil->S; - } - infil->T = 0.0; - - // --- update cumulative precip. - infil->P += irate * tstep; - - // --- find potential new cumulative infiltration - F1 = infil->P * (1.0 - infil->P / (infil->P + infil->Se)); - - // --- compute potential infiltration rate - f1 = (F1 - infil->F) / tstep; - if ( f1 < 0.0 || infil->S <= 0.0 ) f1 = 0.0; - - } - - // --- case of no rainfall - else - { - // --- if there is ponded water then use previous infil. rate - if ( depth > MIN_TOTAL_DEPTH && infil->S > 0.0 ) - { - f1 = infil->f; - if ( f1*tstep > infil->S ) f1 = infil->S / tstep; - } - - // --- otherwise update inter-event time - else infil->T += tstep; - } - - // --- if there is some infiltration - if ( f1 > 0.0 ) - { - // --- limit infil. rate to max. available rate - f1 = MIN(f1, fa); - f1 = MAX(f1, 0.0); - - // --- update actual cumulative infiltration - infil->F += f1 * tstep; - - // --- reduce infil. capacity if a regen. constant was supplied - if ( infil->regen > 0.0 ) - { - infil->S -= f1 * tstep; - if ( infil->S < 0.0 ) infil->S = 0.0; - } - } - - // --- otherwise regenerate infil. capacity - else - { - infil->S += infil->regen * infil->Smax * tstep * Evap.recoveryFactor; - if ( infil->S > infil->Smax ) infil->S = infil->Smax; - } - infil->f = f1; - return f1; -} diff --git a/src/infil.h b/src/infil.h deleted file mode 100644 index f5ddf31aa..000000000 --- a/src/infil.h +++ /dev/null @@ -1,112 +0,0 @@ -//----------------------------------------------------------------------------- -// infil.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Public interface for infiltration functions. -// -// Update History -// ============== -// Build 5.1.010: -// - New Modified Green Ampt infiltration option added. -// Build 5.1.013: -// - New function infil_setInfilFactor() added. -// Build 5.1.015: -// - Support added for multiple infiltration methods within a project. -//----------------------------------------------------------------------------- - -#ifndef INFIL_H -#define INFIL_H - -//--------------------- -// Enumerated Constants -//--------------------- -enum InfilType { - HORTON, // Horton infiltration - MOD_HORTON, // Modified Horton infiltration - GREEN_AMPT, // Green-Ampt infiltration - MOD_GREEN_AMPT, // Modified Green-Ampt infiltration - CURVE_NUMBER}; // SCS Curve Number infiltration - -//--------------------- -// Horton Infiltration -//--------------------- -typedef struct -{ - double f0; // initial infil. rate (ft/sec) - double fmin; // minimum infil. rate (ft/sec) - double Fmax; // maximum total infiltration (ft); - double decay; // decay coeff. of infil. rate (1/sec) - double regen; // regeneration coeff. of infil. rate (1/sec) - //----------------------------- - double tp; // present time on infiltration curve (sec) - double Fe; // cumulative infiltration (ft) -} THorton; - - -//------------------------- -// Green-Ampt Infiltration -//------------------------- -typedef struct -{ - double S; // avg. capillary suction (ft) - double Ks; // saturated conductivity (ft/sec) - double IMDmax; // max. soil moisture deficit (ft/ft) - //----------------------------- - double IMD; // current initial soil moisture deficit - double F; // current cumulative infiltrated volume (ft) - double Fu; // current upper zone infiltrated volume (ft) - double Lu; // depth of upper soil zone (ft) - double T; // time until start of next rain event (sec) - char Sat; // saturation flag -} TGrnAmpt; - - -//-------------------------- -// Curve Number Infiltration -//-------------------------- -typedef struct -{ - double Smax; // max. infiltration capacity (ft) - double regen; // infil. capacity regeneration constant (1/sec) - double Tmax; // maximum inter-event time (sec) - //----------------------------- - double S; // current infiltration capacity (ft) - double F; // current cumulative infiltration (ft) - double P; // current cumulative precipitation (ft) - double T; // current inter-event time (sec) - double Se; // current event infiltration capacity (ft) - double f; // previous infiltration rate (ft/sec) - -} TCurveNum; - -//----------------------------------------------------------------------------- -// Exported Variables -//----------------------------------------------------------------------------- -extern THorton* HortInfil; -extern TGrnAmpt* GAInfil; -extern TCurveNum* CNInfil; - -//----------------------------------------------------------------------------- -// Infiltration Methods -//----------------------------------------------------------------------------- -void infil_create(int n); -void infil_delete(void); -int infil_readParams(int m, char* tok[], int ntoks); -void infil_initState(int j); -void infil_getState(int j, double x[]); -void infil_setState(int j, double x[]); -void infil_setInfilFactor(int j); -double infil_getInfil(int area, double tstep, double rainfall, double runon, - double depth); - -void grnampt_getParams(int j, double p[]); -int grnampt_setParams(TGrnAmpt *infil, double p[]); -void grnampt_initState(TGrnAmpt *infil); -double grnampt_getInfil(TGrnAmpt *infil, double tstep, double irate, - double depth, int modelType); - -#endif diff --git a/src/inflow.c b/src/inflow.c deleted file mode 100644 index 7cb5e250f..000000000 --- a/src/inflow.c +++ /dev/null @@ -1,484 +0,0 @@ -//----------------------------------------------------------------------------- -// inflow.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Manages any Direct External or Dry Weather Flow inflows -// that have been assigned to nodes of the drainage system. -// -// Update History -// ============== -// Build 5.2.0: -// - Removed references to unused extIfaceInflow member of ExtInflow struct. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include "headers.h" - -//----------------------------------------------------------------------------- -// External Functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// inflow_initDwfPattern (called createObjects in project.c) -// inflow_readExtInflow (called by input_readLine) -// inflow_readDwfInflow (called by input_readLine) -// inflow_deleteExtInflows (called by deleteObjects in project.c) -// inflow_deleteDwfInflows (called by deleteObjects in project.c) -// inflow_getExtInflow (called by addExternalInflows in routing.c) -// inflow_setExtInflow (called by setNodeInflow in swmm5.c) -// inflow_getDwfInflow (called by addDryWeatherInflows in routing.c) - -//----------------------------------------------------------------------------- -// Local Functions -//----------------------------------------------------------------------------- -double getPatternFactor(int p, int month, int day, int hour); - - -int inflow_readExtInflow(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error message -// Purpose: reads parameters of a direct external inflow from a line of input. -// -// Formats of data line are: -// nodeID FLOW tSeriesID (FLOW 1.0 scaleFactor baseline basePat) -// nodeID pollutID tSeriesID (CONCEN/MASS unitsFactor scaleFactor baseline basePat) -// -{ - int j; // object index - int param; // FLOW (-1) or pollutant index - int type = CONCEN_INFLOW; // FLOW, CONCEN or MASS inflow - int tseries = -1; // time series index - int basePat = -1; // baseline pattern - double cf = 1.0; // units conversion factor - double sf = 1.0; // scaling factor - double baseline = 0.0; // baseline value - - // --- find index of node receiving the inflow - if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); - j = project_findObject(NODE, tok[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); - - // --- find index of inflow pollutant or use -1 for FLOW - param = project_findObject(POLLUT, tok[1]); - if ( param < 0 ) - { - if ( match(tok[1], w_FLOW) ) param = -1; - else return error_setInpError(ERR_NAME, tok[1]); - } - - // --- find index of inflow time series (if supplied) in data base - if ( strlen(tok[2]) > 0 ) - { - tseries = project_findObject(TSERIES, tok[2]); - if ( tseries < 0 ) return error_setInpError(ERR_NAME, tok[2]); - Tseries[tseries].refersTo = EXTERNAL_INFLOW; - } - - // --- assign type & cf values for a FLOW inflow - if (param == -1) - { - type = FLOW_INFLOW; - cf = 1.0/UCF(FLOW); - } - - // --- do the same for a pollutant inflow - if ( ntoks >= 4 && param > -1) - { - if ( match(tok[3], w_CONCEN) ) type = CONCEN_INFLOW; - else if ( match(tok[3], w_MASS) ) type = MASS_INFLOW; - else return error_setInpError(ERR_KEYWORD, tok[3]); - if ( ntoks >= 5 && type == MASS_INFLOW ) - { - if ( ! getDouble(tok[4], &cf) ) - { - return error_setInpError(ERR_NUMBER, tok[4]); - } - if ( cf <= 0.0 ) return error_setInpError(ERR_NUMBER, tok[4]); - } - } - - // --- get sf and baseline values - if ( ntoks >= 6 ) - { - if ( ! getDouble(tok[5], &sf) ) - { - return error_setInpError(ERR_NUMBER, tok[5]); - } - } - if ( ntoks >= 7 ) - { - if ( ! getDouble(tok[6], &baseline) ) - { - return error_setInpError(ERR_NUMBER, tok[6]); - } - } - - // --- get baseline time pattern - if ( ntoks >= 8 ) - { - basePat = project_findObject(TIMEPATTERN, tok[7]); - if ( basePat < 0 ) return error_setInpError(ERR_NAME, tok[7]); - } - - // --- include LperFT3 term in conversion factor for MASS_INFLOW - if ( type == MASS_INFLOW ) cf /= LperFT3; - - return(inflow_setExtInflow(j, param, type, tseries, basePat, - cf, baseline, sf)); -} - -//============================================================================= - -int inflow_setExtInflow(int j, int param, int type, int tseries, int basePat, - double cf, double baseline, double sf) -// Purpose: This function assigns property values to the inflow object -// Inputs: j = Node index -// param = FLOW (-1) or pollutant index -// type = FLOW, CONCEN or MASS inflow -// tSeries = time series index -// basePat = baseline pattern -// cf = units conversion factor -// baseline = baseline inflow value -// sf = scaling factor -// Return: returns Error Code - -{ - TExtInflow* inflow; // external inflow object - - // --- check if an external inflow object for this constituent already exists - inflow = Node[j].extInflow; - while ( inflow ) - { - if ( inflow->param == param ) break; - inflow = inflow->next; - } - - // --- if it doesn't exist, then create it - if ( inflow == NULL ) - { - inflow = (TExtInflow *) malloc(sizeof(TExtInflow)); - if ( inflow == NULL ) - { - return error_setInpError(ERR_MEMORY, ""); - } - inflow->next = Node[j].extInflow; - Node[j].extInflow = inflow; - } - - // --- assign property values to the inflow object - inflow->param = param; - inflow->type = type; - inflow->tSeries = tseries; - inflow->cFactor = cf; - inflow->sFactor = sf; - inflow->baseline = baseline; - inflow->basePat = basePat; - return 0; -} - -//============================================================================= - -void inflow_deleteExtInflows(int j) -// -// Input: j = node index -// Output: none -// Purpose: deletes all time series inflow data for a node. -// -{ - TExtInflow* inflow1; - TExtInflow* inflow2; - inflow1 = Node[j].extInflow; - while ( inflow1 ) - { - inflow2 = inflow1->next; - free(inflow1); - inflow1 = inflow2; - } -} - -//============================================================================= - -double inflow_getExtInflow(TExtInflow* inflow, DateTime aDate) -// -// Input: inflow = external inflow data structure -// aDate = current simulation date/time -// Output: returns current value of external inflow parameter -// Purpose: retrieves the value of an external inflow at a specific -// date and time. -// -{ - int month, day, hour; - int p = inflow->basePat; // baseline pattern - int k = inflow->tSeries; // time series index - double cf = inflow->cFactor; // units conversion factor - double sf = inflow->sFactor; // scaling factor - double blv = inflow->baseline; // baseline value - double tsv = 0.0; // time series value - - if ( p >= 0 ) - { - month = datetime_monthOfYear(aDate) - 1; - day = datetime_dayOfWeek(aDate) - 1; - hour = datetime_hourOfDay(aDate); - blv *= getPatternFactor(p, month, day, hour); - } - if ( k >= 0 ) tsv = table_tseriesLookup(&Tseries[k], aDate, FALSE) * sf; - return cf * (tsv + blv); -} - -//============================================================================= - -int inflow_readDwfInflow(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error message -// Purpose: reads dry weather inflow parameters from line of input data. -// -// Format of data line is: -// nodeID FLOW/pollutID avgValue (pattern1 pattern2 ... pattern4) -// -{ - int i; - int j; // node index - int k; // pollutant index (-1 for flow) - int m; // time pattern index - int pats[4]; // time pattern index array - double x; // avg. DWF value - TDwfInflow* inflow; // dry weather flow inflow object - - // --- find index of node receiving the inflow - if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); - j = project_findObject(NODE, tok[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); - - // --- find index of inflow pollutant (-1 for FLOW) - k = project_findObject(POLLUT, tok[1]); - if ( k < 0 ) - { - if ( match(tok[1], w_FLOW) ) k = -1; - else return error_setInpError(ERR_NAME, tok[1]); - } - - // --- get avg. value of DWF inflow - if ( !getDouble(tok[2], &x) ) - return error_setInpError(ERR_NUMBER, tok[2]); - if ( k == -1 ) x /= UCF(FLOW); - - // --- get time patterns assigned to the inflow - for (i=0; i<4; i++) pats[i] = -1; - for (i=3; i<7; i++) - { - if ( i >= ntoks ) break; - if ( strlen(tok[i]) == 0 ) continue; - m = project_findObject(TIMEPATTERN, tok[i]); - if ( m < 0 ) return error_setInpError(ERR_NAME, tok[i]); - pats[i-3] = m; - } - - // --- check if inflow for this constituent already exists - inflow = Node[j].dwfInflow; - while ( inflow ) - { - if ( inflow->param == k ) break; - inflow = inflow->next; - } - - // --- if it doesn't exist, then create it - if ( inflow == NULL ) - { - inflow = (TDwfInflow *) malloc(sizeof(TDwfInflow)); - if ( inflow == NULL ) return error_setInpError(ERR_MEMORY, ""); - inflow->next = Node[j].dwfInflow; - Node[j].dwfInflow = inflow; - } - - // --- assign property values to the inflow object - inflow->param = k; - inflow->avgValue = x; - for (i=0; i<4; i++) inflow->patterns[i] = pats[i]; - return 0; -} - -//============================================================================= - -void inflow_deleteDwfInflows(int j) -// -// Input: j = node index -// Output: none -// Purpose: deletes all dry weather inflow data for a node. -// -{ - TDwfInflow* inflow1; - TDwfInflow* inflow2; - inflow1 = Node[j].dwfInflow; - while ( inflow1 ) - { - inflow2 = inflow1->next; - free(inflow1); - inflow1 = inflow2; - } -} - -//============================================================================= - -void inflow_initDwfInflow(TDwfInflow* inflow) -// -// Input: inflow = dry weather inflow data structure -// Output: none -// Purpose: initialzes a dry weather inflow by ordering its time patterns. -// -// This function sorts the user-supplied time patterns for a dry weather -// inflow in the order of the PatternType enumeration (monthly, daily, -// weekday hourly, weekend hourly) to help speed up pattern processing. -// -{ - int i, p; - int tmpPattern[4]; // index of each type of DWF pattern - - // --- assume no patterns were supplied - for (i=0; i<4; i++) tmpPattern[i] = -1; - - // --- assign supplied patterns to proper position (by type) in tmpPattern - for (i=0; i<4; i++) - { - p = inflow->patterns[i]; - if ( p >= 0 ) tmpPattern[Pattern[p].type] = p; - } - - // --- re-fill inflow pattern array by pattern type - for (i=0; i<4; i++) inflow->patterns[i] = tmpPattern[i]; -} - -//============================================================================= - -double inflow_getDwfInflow(TDwfInflow* inflow, int month, int day, int hour) -// -// Input: inflow = dry weather inflow data structure -// month = current month of year of simulation -// day = current day of week of simulation -// hour = current hour of day of simulation -// Output: returns value of dry weather inflow parameter -// Purpose: computes dry weather inflow value at a specific point in time. -// -{ - int p1, p2; // pattern index - double f = 1.0; // pattern factor - - p1 = inflow->patterns[MONTHLY_PATTERN]; - if ( p1 >= 0 ) f *= getPatternFactor(p1, month, day, hour); - p1 = inflow->patterns[DAILY_PATTERN]; - if ( p1 >= 0 ) f *= getPatternFactor(p1, month, day, hour); - p1 = inflow->patterns[HOURLY_PATTERN]; - p2 = inflow->patterns[WEEKEND_PATTERN]; - if ( p2 >= 0 ) - { - if ( day == 0 || day == 6 ) - f *= getPatternFactor(p2, month, day, hour); - else if ( p1 >= 0 ) - f *= getPatternFactor(p1, month, day, hour); - } - else if ( p1 >= 0 ) f *= getPatternFactor(p1, month, day, hour); - return f * inflow->avgValue; - -} - -//============================================================================= - -void inflow_initDwfPattern(int j) -// -// Input: j = time pattern index -// Output: none -// Purpose: initialzes a dry weather inflow time pattern. -// -{ - int i; - for (i=0; i<24; i++) Pattern[j].factor[i] = 1.0; - Pattern[j].count = 0; - Pattern[j].type = -1; - Pattern[j].ID = NULL; -} - -//============================================================================= - -int inflow_readDwfPattern(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error message -// Purpose: reads values of a time pattern from a line of input data. -// -// Format of data line is: -// patternID patternType value(1) value(2) ... -// patternID value(n) value(n+1) .... (for continuation lines) -{ - int i, j, k, n = 1; - - // --- check for minimum number of tokens - if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); - - // --- check that pattern exists in database - j = project_findObject(TIMEPATTERN, tok[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); - - // --- check if this is first line of pattern - // (ID pointer will not have been assigned yet) - if ( Pattern[j].ID == NULL ) - { - // --- assign ID pointer & pattern type - Pattern[j].ID = project_findID(TIMEPATTERN, tok[0]); - k = findmatch(tok[1], PatternTypeWords); - if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); - Pattern[j].type = k; - n = 2; - } - - // --- start reading pattern factors from rest of line - while ( ntoks > n && Pattern[j].count < 24 ) - { - i = Pattern[j].count; - if ( !getDouble(tok[n], &Pattern[j].factor[i]) ) - return error_setInpError(ERR_NUMBER, tok[n]); - Pattern[j].count++; - n++; - } - return 0; -} - -//============================================================================= - -double getPatternFactor(int p, int month, int day, int hour) -// -// Input: p = time pattern index -// month = current month of year of simulation -// day = current day of week of simulation -// hour = current hour of day of simulation -// Output: returns value of a time pattern multiplier -// Purpose: computes time pattern multiplier for a specific point in time. -{ - switch ( Pattern[p].type ) - { - case MONTHLY_PATTERN: - if ( month >= 0 && month < 12 ) return Pattern[p].factor[month]; - break; - case DAILY_PATTERN: - if ( day >= 0 && day < 7 ) return Pattern[p].factor[day]; - break; - case HOURLY_PATTERN: - if ( hour >= 0 && hour < 24 ) return Pattern[p].factor[hour]; - break; - case WEEKEND_PATTERN: - if ( day == 0 || day == 6 ) - { - if ( hour >= 0 && hour < 24 ) return Pattern[p].factor[hour]; - } - break; - } - return 1.0; -} diff --git a/src/inlet.c b/src/inlet.c deleted file mode 100644 index 4defe127e..000000000 --- a/src/inlet.c +++ /dev/null @@ -1,1955 +0,0 @@ -//----------------------------------------------------------------------------- -// inlet.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 10/08/22 (Build 5.2.2) -// Author: L. Rossman -// -// Street/Channel Inlet Functions -// -// Computes capture efficiency of inlets placed in Street conduits -// or Rectangular/Trapezoidal channels using FHWA HEC-22 methods (see -// Brown, S.A. et al., Urban Drainage Design Manual, Federal Highway -// Administration Hydraulic Engineering Circular No. 22, 3rd Edition, -// FHWA-NHI-10-009, August 2013). -// -// Build 5.2.1: -// - Substitutes the constant BIG for HUGE. -// Build 5.2.2: -// - Additional statistics added to Street Flow Summary table. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include -#include "headers.h" - -// Grate inlet -typedef struct -{ - int type; // type of grate used - double length; // length (parallel to flow) (ft) - double width; // width (perpendicular to flow) (ft) - double fracOpenArea; // fraction of grate area that is open - double splashVeloc; // splash-over velocity (ft/s) -} TGrateInlet; - -// Slotted drain inlet -typedef struct -{ - double length; // length (parallel to flow) (ft) - double width; // width (perpendicular to flow) (ft) -} TSlottedInlet; - -// Curb opening inlet -typedef struct -{ - double length; // length of curb opening (ft) - double height; // height of curb opening (ft) - int throatAngle; // type of throat angle -} TCurbInlet; - -// Custom inlet -typedef struct -{ - int onGradeCurve; // flow diversion curve index - int onSagCurve; // flow rating curve index -} TCustomInlet; - -// Inlet design object -typedef struct -{ - char * ID; // name assigned to inlet design - int type; // type of inlet used (grate, curb, etc) - TGrateInlet grateInlet; // length = 0 if not used - TSlottedInlet slottedInlet; // length = 0 if not used - TCurbInlet curbInlet; // length = 0 if not used - int customCurve; // curve index = -1 if not used -} TInletDesign; - - -// Inlet performance statistics -typedef struct -{ - int flowPeriods; // # periods with approach flow - int capturePeriods; // # periods with captured flow - int backflowPeriods; // # periods with backflow - double peakFlow; // peak flow seen by inlet (cfs) - double peakFlowCapture; // capture efficiency at peak flow - double avgFlowCapture; // average capture efficiency - double bypassFreq; // frequency of bypass flow -} TInletStats; - -// Inlet list object -struct TInlet -{ - int linkIndex; // index of conduit link with the inlet - int designIndex; // index of inlet's design - int nodeIndex; // index of node receiving captured flow - int numInlets; // # inlets on each side of street or in channel - int placement; // whether inlet is on-grade or on-sag - double clogFactor; // fractional degree of inlet clogging - double flowLimit; // inlet flow restriction (cfs) - double localDepress; // local gutter depression (ft) - double localWidth; // local depression width (ft) - - double flowFactor; // flow = flowFactor * (flow spread)^2.67 - double flowCapture; // captured flow rate (cfs) - double backflow; // backflow from capture node (cfs) - double backflowRatio; // inlet backflow / capture node overflow - TInletStats stats; // inlet performance statistics - TInlet * nextInlet; // next inlet in list -}; - -// Shared inlet variables -TInletDesign * InletDesigns; // array of available inlet designs -int InletDesignCount; // number of inlet designs -int UsesInlets; // TRUE if project uses inlets - -//----------------------------------------------------------------------------- -// Enumerations -//----------------------------------------------------------------------------- - -enum InletType { - GRATE_INLET, CURB_INLET, COMBO_INLET, SLOTTED_INLET, - DROP_GRATE_INLET, DROP_CURB_INLET, CUSTOM_INLET -}; - -enum GrateType { - P50, P50x100, P30, CURVED_VANE, TILT_BAR_45, - TILT_BAR_30, RETICULINE, GENERIC -}; - -enum InletPlacementType { AUTOMATIC, ON_GRADE, ON_SAG }; - -enum ThroatAngleType { HORIZONTAL_THROAT, INCLINED_THROAT, VERTICAL_THROAT }; - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -static char* InletTypeWords[] = - {"GRATE", "CURB", "", "SLOTTED", "DROP_GRATE", "DROP_CURB", "CUSTOM", NULL}; - -static char* GrateTypeWords[] = - {"P_BAR-50", "P_BAR-50x100", "P_BAR-30", "CURVED_VANE", "TILT_BAR-45", "TILT_BAR-30", - "RETICULINE", "GENERIC", NULL}; - -static char* ThroatAngleWords[] = - {"HORIZONTAL", "INCLINED", "VERTICAL", NULL}; - -static char *PlacementTypeWords[] = - {"AUTOMATIC", "ON_GRADE", "ON_SAG"}; - -// Coefficients for cubic polynomials fitted to Splash Over Velocity v. -// Grate Length curves in Chart 5B of HEC-22 manual taken from Denver -// UDFCD manual. -static const double SplashCoeffs[][4] = { - {2.22, 4.03, 0.65, 0.06}, //P_BAR-50 - {0.74, 2.44, 0.27, 0.02}, //P_BAR-50x100 - {1.76, 3.12, 0.45, 0.03}, //P_BAR-30 - {0.30, 4.85, 1.31, 0.15}, //Curved_Vane - {0.99, 2.64, 0.36, 0.03}, //Tilt_Bar-45 - {0.51, 2.34, 0.2, 0.01}, //Tilt_Bar-30 - {0.28, 2.28, 0.18, 0.01}}; //Reticuline - -// Grate opening ratios (Chart 9B of HEC-22 manual) -static const double GrateOpeningRatios[] = { - 0.90, //P_BAR-50 - 0.80, //P_BAR-50x100 - 0.60, //P_BAR-30 - 0.35, //Curved_Vane - 0.17, //Tilt_Bar-45 (assumed) - 0.34, //Tilt_Bar-30 - 0.80, //Reticuline - 1.00}; //Generic - -//----------------------------------------------------------------------------- -// Imported Variables -//----------------------------------------------------------------------------- -extern TLinkStats* LinkStats; // defined in STATS.C -extern TNodeStats* NodeStats; // defined in STATS.C - -//----------------------------------------------------------------------------- -// Local Shared Variables -//----------------------------------------------------------------------------- -// Variables as named in the HEC-22 manual. -static double Sx; // street cross slope -static double SL; // conduit longitudinal slope -static double Sw; // gutter + cross slope -static double a; // street gutter depression (ft) -static double W; // street gutter width (ft) -static double T; // top width of flow spread (ft) -static double n; // Manning's roughness coeff. - -// Additional variables -static int Nsides; // 1- or 2-sided street -static double Tcrown; // distance from street curb to crown (ft) -static double Beta; // = 1.486 * sqrt(SL) / n -static double Qfactor; // factor f in Izzard's eqn. Q = f*T^2.67 -static TXsect* xsect; // cross-section data of inlet's conduit -static double* InletFlow; // captured inlet flow received by each node -static TInlet* FirstInlet; // head of list of deployed inlets - -//----------------------------------------------------------------------------- -// External functions (declared in inlet.h) -//----------------------------------------------------------------------------- -// inlet_create called by createObjects in project.c -// inlet_delete called by deleteObjects in project.c -// inlet_readDesignParams called by parseLine in input.c -// inlet_readUsageParams called by parseLine in input.c -// inlet_validate called by project_validate -// inlet_findCapturedFlows called by routing_execute -// inlet_adjustQualInflows called by routing_execute -// inlet_adjustQualOutflows called by routing execute -// inlet_writeStatsReport called by statsrpt_writeReport -// inlet_capturedFlow called by findLinkMassFlow in qualrout.c - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static int readGrateInletParams(int inletIndex, char* tok[], int ntoks); -static int readCurbInletParams(int inletIndex, char* tok[], int ntoks); -static int readSlottedInletParams(int inletIndex, char* tok[], int ntoks); -static int readCustomInletParams(int inletIndex, char* tok[], int ntoks); - -static void initInletStats(TInlet* inlet); -static void updateInletStats(TInlet* inlet, double q); -static void writeStreetStatsHeader(); -static void writeStreetStats(int link); - -static void getBackflowRatios(); -static double getInletArea(TInlet* inlet); - -static int getInletPlacement(TInlet* inlet, int node); -static void getConduitGeometry(TInlet* inlet); -static double getFlowSpread(double flow); -static double getEo(double slopeRatio, double spread, double gutterWidth); - -static double getCustomCapturedFlow(TInlet* inlet, double flow, double depth); -static double getOnGradeCapturedFlow(TInlet* inlet, double flow, double depth); -static double getOnGradeInletCapture(int inletIndex, double flow, double depth); -static double getGrateInletCapture(int inletIndex, double flow); -static double getCurbInletCapture(double flow, double length); - -static double getGutterFlowRatio(double gutterWidth); -static double getGutterAreaRatio(double grateWidth, double area); -static double getSplashOverVelocity(int grateType, double grateLength); - -static double getOnSagCapturedFlow(TInlet* inlet, double flow, double depth); -static double getOnSagInletCapture(int inletIndex, double depth); -static void findOnSagGrateFlows(int inletIndex, double depth, - double *weirFlow, double *orificeFlow); -static void findOnSagCurbFlows(int inletIndex, double depth, - double openingLength, double *weirFlow, - double *orificeFlow); -static double getCurbOrificeFlow(double flowDepth, double openingHeight, - double openingLength, int throatAngle); -static double getOnSagSlottedFlow(int inletIndex, double depth); - -//============================================================================= - -int inlet_create(int numInlets) -// -// Input: numInlets = number of inlet designs to create -// Output: none -// Purpose: creats a collection of inlet designs. -// -{ - int i; - - InletDesigns = NULL; - InletFlow = NULL; - InletDesignCount = 0; - UsesInlets = FALSE; - FirstInlet = NULL; - InletDesigns = (TInletDesign *)calloc(numInlets, sizeof(TInletDesign)); - if (InletDesigns == NULL) return ERR_MEMORY; - InletDesignCount = numInlets; - - InletFlow = (double *)calloc(Nobjects[NODE], sizeof(double)); - if (InletFlow == NULL) return ERR_MEMORY; - - for (i = 0; i < InletDesignCount; i++) - { - InletDesigns[i].customCurve = -1; - InletDesigns[i].curbInlet.length = 0.0; - InletDesigns[i].grateInlet.length = 0.0; - InletDesigns[i].slottedInlet.length = 0.0; - InletDesigns[i].type = CUSTOM_INLET; - } - return 0; -} - -//============================================================================= - -void inlet_delete() -// -// Input: none -// Output: none -// Purpose: frees all memory allocated for inlet analysis. -// -{ - TInlet* inlet = FirstInlet; - TInlet* nextInlet; - while (inlet) - { - nextInlet = inlet->nextInlet; - free(inlet); - inlet = nextInlet; - } - FirstInlet = NULL; - FREE(InletFlow); - FREE(InletDesigns); -} - -//============================================================================= - -int inlet_readDesignParams(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: extracts a set of inlet design parameters from a tokenized line -// of the [INLETS] section of a SWMM input file. -// -// Format of input line is: -// ID GRATE Length Width GrateType (OpenArea) (SplashVeloc) -// ID CURB Length Height (ThroatType) -// ID SLOTTED Length Width -// ID DROP_GRATE Length Width GrateType (OpenArea) (SplashVeloc) -// ID DROP_CURB Length Height -// ID CUSTOM CurveID -// -{ - int i; - - // --- check for minimum number of tokens - if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); - - // --- check that design ID already registered in project - i = project_findObject(INLET, tok[0]); - if ( i < 0 ) return error_setInpError(ERR_NAME, tok[0]); - InletDesigns[i].ID = project_findID(INLET, tok[0]); - - // --- retrieve type of inlet design - InletDesigns[i].type = findmatch(tok[1], InletTypeWords); - - // --- read inlet's design parameters - switch (InletDesigns[i].type) - { - case GRATE_INLET: - case DROP_GRATE_INLET: - return readGrateInletParams(i, tok, ntoks); - case CURB_INLET: - case DROP_CURB_INLET: - return readCurbInletParams(i, tok, ntoks); - case SLOTTED_INLET: - return readSlottedInletParams(i, tok, ntoks); - case CUSTOM_INLET: - return readCustomInletParams(i, tok, ntoks); - default: return error_setInpError(ERR_KEYWORD, tok[1]); - } - return 0; -} -//============================================================================= - -int inlet_readUsageParams(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: extracts inlet usage parameters from a tokenized line -// of the [INLET_USAGE] section of a SWMM input file. -// -// Format of input line is: -// linkID inletID nodeID (#Inlets %Clog Qmax aLocal wLocal placement) -// where -// linkID = ID name of link containing the inlet -// inletID = ID name of inlet design being used -// nodeID = ID name of node receiving captured flow -// #Inlets = number of identical inlets used (default = 1) -// %Clog = percent that inlet is clogged -// Qmax = maximum flow that inlet can capture (default = 0 (no limit)) -// aLocal = local gutter depression (ft or m) (default = 0) -// wLocal = width of local gutter depression (ft or m) (default = 0) -// placement = ON_GRADE, ON_SAG, or AUTO (the default) -// -{ - int linkIndex, designIndex, nodeIndex, numInlets = 1; - int placement = AUTOMATIC; - double flowLimit = 0.0, pctClogged = 0.0; - double aLocal = 0.0, wLocal = 0.0; - TInlet* inlet; - - // --- check that inlet's link exists - if (ntoks < 3) return error_setInpError(ERR_ITEMS, ""); - linkIndex = project_findObject(LINK, tok[0]); - if (linkIndex < 0) return error_setInpError(ERR_NAME, tok[0]); - - // --- check that inlet design type exists - designIndex = project_findObject(INLET, tok[1]); - if (designIndex < 0) return error_setInpError(ERR_NAME, tok[1]); - - // --- check that receiving node exists - nodeIndex = project_findObject(NODE, tok[2]); - if (nodeIndex < 0) return error_setInpError(ERR_NAME, tok[2]); - - // --- get number of inlets - if (ntoks > 3) - if (!getInt(tok[3], &numInlets) || numInlets < 1) - return error_setInpError(ERR_NUMBER, tok[3]); - - // --- get flow limit & percent clogged - if (ntoks > 4) - { - if (!getDouble(tok[4], &pctClogged) || pctClogged < 0.0 - || pctClogged > 99.) - return error_setInpError(ERR_NUMBER, tok[4]); - } - if (ntoks > 5) - if (!getDouble(tok[5], &flowLimit) || flowLimit < 0.0) - return error_setInpError(ERR_NUMBER, tok[5]); - - // --- get local depression parameters - if (ntoks > 6) - if (!getDouble(tok[6], &aLocal) || aLocal < 0.0) - return error_setInpError(ERR_NUMBER, tok[6]); - if (ntoks > 7) - if (!getDouble(tok[7], &wLocal) || wLocal < 0.0) - return error_setInpError(ERR_NUMBER, tok[7]); - - // --- get inlet placement - if (ntoks > 8) - { - placement = findmatch(tok[8], PlacementTypeWords); - if (placement < 0) return error_setInpError(ERR_KEYWORD, tok[8]); - } - - // --- create an inlet usage object for the link - inlet = Link[linkIndex].inlet; - if (inlet == NULL) - { - inlet = (TInlet *)malloc(sizeof(TInlet)); - if (!inlet) return error_setInpError(ERR_MEMORY, ""); - Link[linkIndex].inlet = inlet; - inlet->nextInlet = FirstInlet; - FirstInlet = inlet; - } - - // --- save inlet usage parameters - inlet->linkIndex = linkIndex; - inlet->designIndex = designIndex; - inlet->nodeIndex = nodeIndex; - inlet->numInlets = numInlets; - inlet->placement = placement; - inlet->clogFactor = 1.0 - (pctClogged / 100.); - inlet->flowLimit = flowLimit / UCF(FLOW); - inlet->localDepress = aLocal / UCF(LENGTH); - inlet->localWidth = wLocal / UCF(LENGTH); - inlet->flowFactor = 0.0; - inlet->backflowRatio = 0.0; - initInletStats(inlet); - UsesInlets = TRUE; - return 0; -} - -//============================================================================= - -void inlet_validate() -// -// Input: none -// Output: none -// Purpose: checks that inlets have been assigned to conduits with proper -// cross section shapes and counts the number of inlets that each -// node receives either bypased or captured flow from. -// -{ - int i, j, inletType, inletValid; - TInlet* inlet; - TInlet* prevInlet; - - // --- traverse the list of inlets placed in conduits - if (!UsesInlets) return; - prevInlet = FirstInlet; - inlet = FirstInlet; - while (inlet) - { - // --- check that inlet's conduit can accept the inlet's type - inletValid = FALSE; - i = inlet->linkIndex; - xsect = &Link[i].xsect; - inletType = InletDesigns[inlet->designIndex].type; - if (inletType == CUSTOM_INLET) - { - j = InletDesigns[inlet->designIndex].customCurve; - if (j >= 0) - { - if (Curve[j].curveType == DIVERSION_CURVE || - Curve[j].curveType == RATING_CURVE) - inletValid = TRUE; - } - } - else if ((xsect->type == TRAPEZOIDAL || xsect->type == RECT_OPEN) && - (inletType == DROP_GRATE_INLET || - inletType == DROP_CURB_INLET)) - inletValid = TRUE; - else if (xsect->type == STREET_XSECT && - inletType != DROP_GRATE_INLET && - inletType != DROP_CURB_INLET) - inletValid = TRUE; - - // --- if inlet placement is valid then - if (inletValid) - { - // --- record that receptor node has inlets - Node[Link[i].node2].inlet = BYPASS; - Node[inlet->nodeIndex].inlet = CAPTURE; - - // --- initialize inlet's backflow - inlet->backflow = 0.0; - - // --- compute street inlet's flow factor for Izzard's eqn. - // (used in Q = flowFactor * Spread^2.67 equation) - getConduitGeometry(inlet); - inlet->flowFactor = (0.56/n) * pow(SL,0.5) * pow(Sx,1.67); - - // --- save reference to current inlet & continue to next inlet - prevInlet = inlet; - inlet = inlet->nextInlet; - } - - // --- if inlet placement is not valid then issue a warning message - // and remove the inlet from the conduit - else - { - report_writeWarningMsg(WARN12, Link[i].ID); - if (inlet == FirstInlet) - { - FirstInlet = inlet->nextInlet; - prevInlet = FirstInlet; - free(inlet); - inlet = FirstInlet; - } - else - { - prevInlet->nextInlet = inlet->nextInlet; - free(inlet); - inlet = prevInlet->nextInlet; - } - Link[i].inlet = NULL; - } - } - - // --- determine how capture node's overflow is split between its inlets - getBackflowRatios(); -} - -//============================================================================= - -void inlet_findCapturedFlows(double tStep) -// -// Input: tStep = current flow routing time step (sec) -// Output: none -// Purpose: computes flow captured by each inlet and adjusts the -// lateral flows of the inlet's bypass and capture nodes accordingly. -// -// This function is called after regular lateral flows to all nodes have been -// set but before a flow routing step has been taken. -{ - int i, j, m, placement; - double q; - TInlet *inlet; - - // --- For non-DW routing find conduit flow into each node - // (used to limit max. amount of on-sag capture) - if (!UsesInlets) return; - memset(InletFlow, 0, Nobjects[NODE]*sizeof(double)); - if (RouteModel != DW) - { - for (j = 0; j < Nobjects[NODE]; j++) - Node[j].inflow = MAX(0., Node[j].newLatFlow); - for (i = 0; i < Nobjects[LINK]; i++) - Node[Link[i].node2].inflow += MAX(0.0, Link[i].newFlow); - } - - // --- loop through each inlet - for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) - { - // --- identify indexes of inlet's bypass (j) and capture (m) nodes - i = inlet->linkIndex; - j = Link[i].node2; - m = inlet->nodeIndex; - - // --- get inlet's placement (ON_GRADE or ON_SAG) - placement = getInletPlacement(inlet, j); - - // --- find flow captured by a Custom inlet - if (InletDesigns[inlet->designIndex].type == CUSTOM_INLET) - { - q = fabs(Link[i].newFlow); - inlet->flowCapture = getCustomCapturedFlow(inlet, q, Node[j].newDepth); - } - - // --- find flow captured by on-grade inlet - else if (placement == ON_GRADE) - { - q = fabs(Link[i].newFlow); - inlet->flowCapture = getOnGradeCapturedFlow(inlet, q, Node[j].newDepth); - } - - // --- find flow captured by on-sag inlet - else - { - q = Node[j].inflow; - inlet->flowCapture = getOnSagCapturedFlow(inlet, q, Node[j].newDepth); - } - if (fabs(inlet->flowCapture) < FUDGE) inlet->flowCapture = 0.0; - - // --- add to total flow captured by inlet's node - InletFlow[j] += inlet->flowCapture; - - // --- capture node's overflow becomes inlet's backflow - inlet->backflow = Node[m].overflow * inlet->backflowRatio; - if (fabs(inlet->backflow) < FUDGE) inlet->backflow = 0.0; - } - - // --- make second pass through each inlet - for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) - { - // --- identify indexes of inlet's bypass (j) and capture (m) nodes - i = inlet->linkIndex; - j = Link[i].node2; - m = inlet->nodeIndex; - - // --- for on-sag placement under non-DW routing, captured flow - // is limited to inlet's share of bypass node's inflow plus - // any stored volume - if (RouteModel != DW && getInletPlacement(inlet, j) == ON_SAG) - { - q = Node[j].newVolume / tStep; - q += MAX(Node[j].inflow, 0.0); - if (InletFlow[j] > q) - inlet->flowCapture *= q / InletFlow[j]; - } - - // --- adjust lateral flows at bypass and capture nodes - // (subtract captured flow from bypass node, add it to capture - // node, and add any backflow to bypass node) - Node[j].newLatFlow -= (inlet->flowCapture - inlet->backflow); - Node[m].newLatFlow += inlet->flowCapture; - - // --- update inlet's performance if reporting has begun - if (getDateTime(NewRoutingTime) > ReportStart) - updateInletStats(inlet, fabs(Link[i].newFlow)); - } -} - -//============================================================================= - -void inlet_adjustQualInflows() -// -// Input: none -// Output: none -// Purpose: adjusts accumulated flow rates and pollutant mass inflows at each -// inlet's bypass and capture nodes after a flow routing step has -// been taken prior to a quality routing step. -// -{ - int i, j, m, p; - double qNet; - TInlet* inlet; - - if (!UsesInlets) return; - if (IgnoreQuality || Nobjects[POLLUT] == 0) return; - for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) - { - // --- identify indexes of inlet's bypass (j) and capture (m) nodes - i = inlet->linkIndex; - j = Link[i].node2; - m = inlet->nodeIndex; - - // --- there's a net flow from the bypass to the capture node - qNet = inlet->flowCapture - inlet->backflow; - if (qNet > 0.0) - { - // --- add net capture flow to capture node's accumulated flow - // inflow for quality routing - Node[m].qualInflow += qNet; - - // --- and do the same for pollutant mass flows - // (Node[m].newQual is the mass inflow accumulator for node m) - for (p = 0; p < Nobjects[POLLUT]; p++) - Node[m].newQual[p] += qNet * Node[j].oldQual[p]; - } - - // --- there's a net backflow from the capture to the bypass node - else - { - // --- add the backflow flow rate and pollutant mass flow to the - // bypass node's accumulated flow and pollutant mass inflow - qNet = -qNet; - Node[j].qualInflow += qNet; - for (p = 0; p < Nobjects[POLLUT]; p++) - Node[j].newQual[p] += qNet * Node[m].oldQual[p]; - } - } -} - -//============================================================================= - -void inlet_adjustQualOutflows() -// -// Input: none -// Output: none -// Purpose: adjusts mass balance totals after a complete routing step has been -// taken so as not to treat inlet transfer flows as system outflows. -// -{ - int j, p; - double q, w; - TInlet* inlet; - - // --- these variables, declared in massbal.c, accumulate system-wide flow and - // pollutant mass fluxes over a time step to use in mass balances - extern TRoutingTotals StepFlowTotals; - extern TRoutingTotals* StepQualTotals; - - // --- examine each node - for (j = 0; j < Nobjects[NODE]; j++) - { - // --- node receives captured flow from an inlet - if (Node[j].inlet == CAPTURE) - { - // --- node also has an overflow (e.g., it's a surcharged sewer node) - q = Node[j].overflow; - if (q > 0.0) - { - // --- remove overflow from system flooding total since it does - // not leave the system (it is sent to inlet's bypass node) - StepFlowTotals.flooding -= q; - - // --- also remove pollutant overflow mass from system totals - if (!IgnoreQuality) - for (p = 0; p < Nobjects[POLLUT]; p++) - { - w = q * Node[j].newQual[p]; - StepQualTotals[p].flooding -= w; - } - } - } - } - - // --- for WQ analysis, examine each inlet's bypass node - if (!IgnoreQuality && Nobjects[POLLUT] > 0) - { - for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) - { - j = Link[inlet->linkIndex].node2; - - // --- inlet has net positive flow capture leading to - // node having a net negative lateral inflow - q = inlet->flowCapture - inlet->backflow; - if (q > 0.0 && Node[j].newLatFlow < 0.0) - - // --- remove the pollutant mass in the captured flow from - // the system totals since it does not leave the system - // (it is sent to the inlet's capture node) - for (p = 0; p < Nobjects[POLLUT]; p++) - { - w = q * Node[j].newQual[p]; - StepQualTotals[p].outflow -= w; - } - } - } -} - -//============================================================================= - -void inlet_writeStatsReport() -// -// Input: none -// Output: none -// Purpose: writes table of street & inlet flow statistics to SWMM's report file. -// -{ - int j, header = FALSE; - - if (Nobjects[STREET] == 0) return; - for (j = 0; j < Nobjects[LINK]; j++) - { - if (Link[j].xsect.type == STREET_XSECT) - { - if (!header) - { - writeStreetStatsHeader(); - header = TRUE; - } - writeStreetStats(j); - } - } - report_writeLine(""); -} - -//============================================================================= - -double inlet_capturedFlow(int i) -// -// Input: i = a link index -// Output: returns captured flow rate (cfs) -// Purpose: gets the current flow captured by an inlet. -// -{ - if (Link[i].inlet) return Link[i].inlet->flowCapture; - return 0.0; -} - -//============================================================================= - -int readGrateInletParams(int i, char* tok[], int ntoks) -{ -// -// Input: i = inlet index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: extracts a grate's inlet parameters from a set of string tokens. -// - int grateType; - double width, length, areaRatio = 0.0, vSplash = 0.0; - - // --- check for enough tokens - if (ntoks < 5) return error_setInpError(ERR_ITEMS, ""); - - // --- retrieve length & width - if (!getDouble(tok[2], &length) || length <= 0.0) - return error_setInpError(ERR_NUMBER, tok[2]); - if (!getDouble(tok[3], &width) || width <= 0.0) - return error_setInpError(ERR_NUMBER, tok[3]); - - // --- retrieve grate type - grateType = findmatch(tok[4], GrateTypeWords); - if (grateType < 0) return error_setInpError(ERR_KEYWORD, tok[4]); - - // --- only read open area & splash velocity for GENERIC type grate - if (grateType == GENERIC) - { - if (ntoks < 6) return error_setInpError(ERR_ITEMS, ""); - if (!getDouble(tok[5], &areaRatio) || areaRatio <= 0.0 - || areaRatio > 1.0) return error_setInpError(ERR_NUMBER, tok[5]); - if (ntoks > 6) - { - if (!getDouble(tok[6], &vSplash) || vSplash < 0.0) - return error_setInpError(ERR_NUMBER, tok[6]); - } - } - - // --- save grate inlet parameters - InletDesigns[i].grateInlet.length = length / UCF(LENGTH); - InletDesigns[i].grateInlet.width = width / UCF(LENGTH); - InletDesigns[i].grateInlet.type = grateType; - InletDesigns[i].grateInlet.fracOpenArea = areaRatio; - InletDesigns[i].grateInlet.splashVeloc = vSplash / UCF(LENGTH); - - // --- check if grate is part of a combo inlet - if (InletDesigns[i].type == GRATE_INLET && - InletDesigns[i].curbInlet.length > 0.0) - InletDesigns[i].type = COMBO_INLET; - return 0; -} - -//============================================================================= - -int readCurbInletParams(int i, char* tok[], int ntoks) -// -// Input: i = inlet index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: extracts curb opening inlet parameters from a set of string tokens. -// -{ - int throatAngle; - double height, length; - - // --- check for enough tokens - if (ntoks < 4) return error_setInpError(ERR_ITEMS, ""); - - // --- retrieve length & width of opening - if (!getDouble(tok[2], &length) || length <= 0.0) - return error_setInpError(ERR_NUMBER, tok[2]); - if (!getDouble(tok[3], &height) || height <= 0.0) - return error_setInpError(ERR_NUMBER, tok[3]); - - // --- retrieve type of throat angle for curb inlet - throatAngle = VERTICAL_THROAT; - if (InletDesigns[i].type == CURB_INLET && ntoks > 4) - { - throatAngle = findmatch(tok[4], ThroatAngleWords); - if (throatAngle < 0) return error_setInpError(ERR_KEYWORD, tok[4]); - } - - // ---- save curb opening inlet parameters - InletDesigns[i].curbInlet.length = length / UCF(LENGTH); - InletDesigns[i].curbInlet.height = height / UCF(LENGTH); - InletDesigns[i].curbInlet.throatAngle = throatAngle; - - // --- check if curb inlet is part of a combo inlet - if (InletDesigns[i].type == CURB_INLET && - InletDesigns[i].grateInlet.length > 0.0) - InletDesigns[i].type = COMBO_INLET; - return 0; -} - -//============================================================================= - -int readSlottedInletParams(int i, char* tok[], int ntoks) -// -// Input: i = inlet index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: extracts slotted drain inlet parameters from a set of string tokens. -// -{ - double width, length; - - // --- check for enough tokens - if (ntoks < 4) return error_setInpError(ERR_ITEMS, ""); - - // --- retrieve length and width - if (!getDouble(tok[2], &length) || length <= 0.0) - return error_setInpError(ERR_NUMBER, tok[2]); - if (!getDouble(tok[3], &width) || width <= 0.0) - return error_setInpError(ERR_NUMBER, tok[3]); - - // --- save slotted inlet parameters - InletDesigns[i].slottedInlet.length = length / UCF(LENGTH); - InletDesigns[i].slottedInlet.width = width / UCF(LENGTH); - return 0; -} - -//============================================================================= - -int readCustomInletParams(int i, char* tok[], int ntoks) -// -// Input: i = inlet index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: extracts custom inlet parameters from a set of string tokens. -// -{ - int c; // capture curve index - - if (ntoks < 3) return error_setInpError(ERR_ITEMS, ""); - else - { - c = project_findObject(CURVE, tok[2]); - if (c < 0) return error_setInpError(ERR_NAME, tok[2]); - } - InletDesigns[i].customCurve = c; - return 0; -} - -//============================================================================= - -void initInletStats(TInlet* inlet) -// -// Input: inlet = an inlet object placed in a conduit link -// Output: none -// Purpose: initializes the performance statistics of an inlet. -// -{ - if (inlet) - { - inlet->flowCapture = 0.0; - inlet->backflow = 0.0; - inlet->stats.flowPeriods = 0; - inlet->stats.capturePeriods = 0; - inlet->stats.backflowPeriods = 0; - inlet->stats.peakFlow = 0.0; - inlet->stats.peakFlowCapture = 0; - inlet->stats.avgFlowCapture = 0; - inlet->stats.bypassFreq = 0; - } -} - -//============================================================================= - -void updateInletStats(TInlet* inlet, double q) -// -// Input: inlet = an inlet object placed in a conduit link -// q = inlet's approach flow (cfs) -// Output: none -// Purpose: updates the performance statistics of an inlet. -// -{ - double qCapture = inlet->flowCapture, - qBackflow = inlet->backflow, - qNet = qCapture - qBackflow, - qBypass = q - qNet, - fCapture = 0.0; - - // --- check for no flow condition - if (q < MIN_RUNOFF_FLOW && qBackflow <= 0.0) return; - inlet->stats.flowPeriods++; - - // --- there is positive net flow from inlet to capture node - if (qNet > 0.0) - { - inlet->stats.capturePeriods++; - fCapture = qNet / q; - fCapture = MIN(fCapture, 1.0); - inlet->stats.avgFlowCapture += fCapture; - if (qBypass > MIN_RUNOFF_FLOW) inlet->stats.bypassFreq++; - } - - // --- otherwise inlet receives backflow from capture node - else inlet->stats.backflowPeriods++; - - // --- update peak flow stats - if (q > inlet->stats.peakFlow) - { - inlet->stats.peakFlow = q; - inlet->stats.peakFlowCapture = fCapture * 100.0; - } -} - -//============================================================================= - -void writeStreetStatsHeader() -// -// Input: none -// Output: none -// Purpose: writes column headers for Street Flow Summary table to SWMM's report file. -// -{ - report_writeLine(""); - report_writeLine("*******************"); - report_writeLine("Street Flow Summary"); - report_writeLine("*******************"); - report_writeLine(""); - fprintf(Frpt.file, -"\n ---------------------------------------------------------------------------------------------------------------------------------------" -"\n Peak Avg. Bypass Back Peak Peak" -"\n Peak Maximum Maximum Flow Flow Flow Flow Capture Bypass" -"\n Flow Spread Depth Inlet Inlet Inlet Capture Capture Freq Freq / Inlet Flow"); - if (UnitSystem == US) fprintf(Frpt.file, -"\n Street Conduit %3s ft ft Design Location Count Pcnt Pcnt Pcnt Pcnt %3s %3s", - FlowUnitWords[FlowUnits], FlowUnitWords[FlowUnits], FlowUnitWords[FlowUnits]); - else fprintf(Frpt.file, -"\n Street Conduit %3s m m Design Location Pcnt Pcnt Pcnt Pcnt %3s %3s", - FlowUnitWords[FlowUnits], FlowUnitWords[FlowUnits], FlowUnitWords[FlowUnits]); - fprintf(Frpt.file, -"\n ---------------------------------------------------------------------------------------------------------------------------------------"); -} - -//============================================================================= - -void writeStreetStats(int link) -// -// Input: link = index of a conduit link containing an inlet -// Output: none -// Purpose: writes flow statistics for a Street conduit and its inlet to -// SWMM's report file. -// -{ - int k, t, placement; - double maxSpread, maxDepth, maxFlow; - double fp, cp, afc = 0.0, bpf = 0.0; - TInlet* inlet; - - // --- retrieve street parameters - k = Link[link].subIndex; - t = Link[link].xsect.transect; - inlet = Link[link].inlet; - - // --- get recorded max flow and depth - maxFlow = LinkStats[link].maxFlow; - maxDepth = LinkStats[link].maxDepth; - - // --- SWMM's spread (flow width) at max depth - maxSpread = xsect_getWofY(&Link[link].xsect, maxDepth) / Street[t].sides; - maxSpread = MIN(maxSpread, Street[t].width); -/* - // HEC-22's spread based on max flow (doesn't account for backwater) - Sx = Street[t].slope; - a = Street[t].gutterDepression; - W = Street[t].gutterWidth; - n = Street[t].roughness; - Qfactor = (0.56 / n) * sqrt(Conduit[k].slope) * pow(Sx, 1.67); - maxSpread = getFlowSpread(maxFlow / Street[t].sides); - maxSpread = MIN(maxSpread, Street[t].width); -*/ - // --- write street stats - fprintf(Frpt.file, "\n %-16s", Link[link].ID); - fprintf(Frpt.file, " %9.3f", maxFlow * UCF(FLOW)); - fprintf(Frpt.file, " %9.3f", maxSpread * UCF(LENGTH)); - fprintf(Frpt.file, " %9.3f", maxDepth * UCF(LENGTH)); - - // --- write inlet stats - if (inlet) - { - fprintf(Frpt.file, " %-16s", InletDesigns[inlet->designIndex].ID); - placement = getInletPlacement(inlet, Link[inlet->linkIndex].node2); - if (placement == ON_GRADE) - fprintf(Frpt.file, " ON-GRADE"); - else - fprintf(Frpt.file, " ON-SAG "); - fprintf(Frpt.file, " %5d", inlet->numInlets); - fp = inlet->stats.flowPeriods / 100.0; - if (fp > 0.0) - { - cp = inlet->stats.capturePeriods / 100.0; - fprintf(Frpt.file, " %7.2f", inlet->stats.peakFlowCapture); - if (cp > 0.0) - { - afc = inlet->stats.avgFlowCapture / cp; - bpf = inlet->stats.bypassFreq / cp; - } - fprintf(Frpt.file, " %7.2f", afc); - fprintf(Frpt.file, " %7.2f", bpf); - fprintf(Frpt.file, " %7.2f", inlet->stats.backflowPeriods / fp); - fprintf(Frpt.file, " %7.2f", (maxFlow / Street[t].sides) * UCF(FLOW) * - 0.01 * inlet->stats.peakFlowCapture / inlet->numInlets); - fprintf(Frpt.file, " %7.2f", maxFlow * UCF(FLOW) * 0.01 * - (100.0 - inlet->stats.peakFlowCapture)); - } - } -} - -//============================================================================= - -int getInletPlacement(TInlet* inlet, int j) -// -// Input: inlet = an inlet object placed in a conduit link -// j = index of inlet's bypass node -// Output: returns type of inlet placement -// Purpose: determines actual placement for an inlet with AUTOMATIC placement. -// -{ - if (inlet->placement == AUTOMATIC) - { - if (Node[j].degree > 0) return ON_GRADE; - else return ON_SAG; - } - else return inlet->placement; -} - -//============================================================================= - -void getConduitGeometry(TInlet* inlet) -// -// Input: inlet = an inlet object placed in a conduit link -// Output: none -// Purpose: assigns properties of an inlet's conduit to -// module-level shared variables used by other functions. -// -{ - int linkIndex = inlet->linkIndex; - int t, k = Link[linkIndex].subIndex; - - SL = Conduit[k].slope; // longitudinal slope - Beta = Conduit[k].beta; // 1.486 * sqrt(SL) / n - xsect = &Link[linkIndex].xsect; - - // --- if conduit has a Street cross section - if (xsect->type == STREET_XSECT) - { - t = xsect->transect; - Sx = Street[t].slope; // street cross slope - a = Street[t].gutterDepression; // gutter depression - W = Street[t].gutterWidth; // gutter width - n = Street[t].roughness; // street roughness - Nsides = Street[t].sides; // 1 or 2 sided street - Tcrown = Street[t].width; // distance from curb to crown - Qfactor = inlet->flowFactor; // factor used in Izzard's eqn. - - // --- add inlet's local depression to street's continuous depression - if (inlet && inlet->localDepress * inlet->localWidth > 0) - { - a += inlet->localDepress; // inlet depression - W = inlet->localWidth; // inlet depressed width - } - - // --- slope of depressed gutter section - if (W * a > 0.0) Sw = Sx + a / W; - else Sw = Sx; - } - - // --- conduit has rectangular or trapezoidal cross section - else - { - a = 0.0; - W = 0.0; - n = Conduit[k].roughness; - Nsides = 1; - Sx = 0.01; - Sw = Sx; - } -} - -//============================================================================= - -double getFlowSpread(double Q) -// -// Input: Q = conduit flow rate (cfs) -// Output: returns width of flow spread (ft) -// Purpose: computes width of flow spread across a Street cross section using -// HEC-22 equations derived from Izzard's form of the Manning eqn. -// -{ - int iter; - double f, f1, Sr, Ts1, Ts2, Tw, Qs, Eo; - - f = Qfactor; // = (0.56/n) * SL^0.5 * Sx^1.67 - - // --- no depressed curb - if (a == 0.0) - { - Ts1 = pow(Q / f, 0.375); //HEC-22 Eq(4-2) - } - else - { - // --- check if spread is within curb width - f1 = f * pow((a / W) / Sx, 1.67); - Tw = pow(Q / f1, 0.375); //HEC-22 Eq(4-2) - if (Tw <= W) Ts1 = Tw; - else - { - // --- spread extends beyond curb width - Sr = (Sx + a / W) / Sx; - iter = 1; - Ts1 = pow(Q / f, 0.375) - W; - if (Ts1 <= 0) Ts1 = Tw - W; - while (iter < 11) - { - Eo = getEo(Sr, Ts1, W); - Qs = (1.0 - Eo) * Q; //HEC-22 Eq(4-6) - Ts2 = pow(Qs / f, 0.375); //HEC-22 Eq(4-2) - if (fabs(Ts2 - Ts1) < 0.01) break; - Ts1 = Ts2; - iter++; - } - Ts1 = Ts2 + W; - } - } - return MIN(Ts1, Tcrown); -} - -//============================================================================= - -double getEo(double Sr, double Ts, double w) -// -// Input: Sr = ratio of gutter slope to street cross slope -// Ts = amount of flow spread outside of gutter width (ft) -// w = gutter width (ft) -// Output: returns ratio of gutter flow to total flow in street cross section -// Purpose: solves HEC-22 Eq. (4-4) for Eo with Ts/w substituted for -// (T/w) - 1 where Ts = T - w. -// -{ - double x; - x = Sr / (Ts / w); - x = pow((1.0 + x), 2.67) - 1.0; - x = 1.0 + Sr / x; - return 1.0 / x; -} - -//============================================================================= - -double getOnGradeCapturedFlow(TInlet* inlet, double q, double d) -// -// Input: inlet = an inlet object placed in a conduit link -// q = flow in link prior to any inlet capture (cfs) -// d = flow depth seen by inlet (ft) -// Output: returns flow captured by the inlet (cfs) -// Purpose: computes flow captured by an inlet placed on-grade. -// -// An inlet object placed in a conduit can have multiple inlets of -// the same type distributed along the conduit's length that all -// send their captured flow to the same sewer node. This function -// finds the total captured flow as each individual inlet is analyzed -// sequentially, where its approach flow has been reduced by the -// amount of flow captured by prior inlets. -{ - int i, - linkIndex; // index of link containing inlets - double qApproach, // single inlet's approach flow (cfs) - qc, // single inlet's captured flow (cfs) - qCaptured, // total flow captured by link's inlets (cfs) - qBypassed, // total flow bypassed by link's inlets (cfs) - qMax; // max. flow that a single inlet can capture (cfs) - - if (inlet->numInlets == 0) return 0.0; - linkIndex = inlet->linkIndex; - - // --- check that link has flow - qApproach = q; - if (qApproach < MIN_RUNOFF_FLOW) return 0.0; - - // --- store conduit geometry in shared variables - getConduitGeometry(inlet); - - // --- adjust flow for 2-sided street - qApproach /= Nsides; - qBypassed = qApproach; - qCaptured = 0.0; - - // --- set limit on max. flow captured per inlet - qMax = BIG; - if (inlet->flowLimit > 0.0) qMax = inlet->flowLimit; - - // --- evaluate each inlet - for (i = 1; i <= inlet->numInlets; i++) - { - qc = getOnGradeInletCapture(inlet->designIndex, qBypassed, d) * - inlet->clogFactor; - qc = MIN(qc, qMax); - qc = MIN(qc, qBypassed); - qCaptured += qc; - qBypassed -= qc; - if (qBypassed < MIN_RUNOFF_FLOW) break; - } - return qCaptured *= Nsides; -} - -//============================================================================= - -double getOnGradeInletCapture(int i, double Q, double d) -// -// Input: i = an InletDesigns index -// Q = flow rate seen by inlet (cfs) -// d = flow depth seen by inlet (ft) -// Output: returns captured flow rate (cfs) -// Purpose: finds the flow captured by a single on-grade inlet. -// -{ - double Q1 = Q, Qc = 0.0, Lsweep = 0.0, Lcurb = 0.0, Lgrate = 0.0; - - // --- drop curb inlet (in non-Street conduit) only operates in on sag mode - if (InletDesigns[i].type == DROP_CURB_INLET) - { - Qc = getOnSagInletCapture(i, d); - return MIN(Qc, Q); - } - - // --- drop grate inlet (in non-Street conduit) - if (InletDesigns[i].type == DROP_GRATE_INLET) - { - Qc = getGrateInletCapture(i, Q); - return MIN(Qc, Q); - } - - // --- Remaining inlet types apply to Street conduits - - // --- find flow spread - T = getFlowSpread(Q); - - // --- slotted inlet (behaves as a curb opening inlet per HEC-22) - if (InletDesigns[i].type == SLOTTED_INLET) - { - Qc = getCurbInletCapture(Q, InletDesigns[i].slottedInlet.length); - return MIN(Qc, Q); - } - - Lcurb = InletDesigns[i].curbInlet.length; - Lgrate = InletDesigns[i].grateInlet.length; - - // --- curb opening inlet - if (Lcurb > 0.0) - { - Lsweep = Lcurb - Lgrate; - if (Lsweep > 0.0) - { - Qc = getCurbInletCapture(Q1, Lsweep); - Q1 -= Qc; - } - } - - // --- grate inlet - if (Lgrate > 0.0 && Q1 > 0.0) - { - if (Q1 != Q) T = getFlowSpread(Q1); - Qc += getGrateInletCapture(i, Q1); - } - return Qc; -} - -//============================================================================= - -double getGrateInletCapture(int i, double Q) -// -// Input: i = inlet type index -// Q = flow rate seen by inlet (cfs) -// Output: returns captured flow rate (cfs) -// Purpose: finds the flow captured by an on-grade grate inlet. -// -{ - int grateType; - double Lg, // grate length (ft) - Wg, // grate width (ft) - A, // total cross section flow area (ft2) - Y, // flow depth (ft) - Eo, // ratio of gutter to total flow - V, // flow velocity (ft/s) - Vo, // splash-over velocity (ft/s) - Qo = Q, // flow over street area (cfs) - Rf = 1.0, // ratio of intercepted to total frontal flow - Rs = 0.0; // ratio of intercepted to total side flow - -// xsect, a, W, & Sx were from getConduitGeometry(). T was from getFlowSpread(). - - Lg = InletDesigns[i].grateInlet.length; - Wg = InletDesigns[i].grateInlet.width; - - // --- flow ratio for drop inlet - if (xsect->type == TRAPEZOIDAL || xsect->type == RECT_OPEN) - { - A = xsect_getAofS(xsect, Q / Beta); - Y = xsect_getYofA(xsect, A); - T = xsect_getWofY(xsect, Y); - Eo = Beta * pow(Y*Wg, 1.67) / pow(Wg + 2*Y, 0.67) / Q; - if (Wg > 0.99*xsect->yBot && xsect->type == TRAPEZOIDAL && xsect->sBot > 0.0) - { - Wg = xsect->yBot; - Sx = 1.0 / xsect->sBot; - } - } - - // --- flow ratio & area for conventional street gutter - else if (a == 0.0) - { - A = T * T * Sx / 2.0; - Eo = getGutterFlowRatio(Wg); // flow ratio based on grate width - if (T >= Tcrown) Qo = Qfactor * pow(Tcrown, 2.67); - } - - // --- flow ratio & area for composite street gutter - else - { - // --- spread confined to gutter - if (T <= W) A = T * T * Sw / 2.0; - - // --- spread beyond gutter width - else A = (T * T * Sx + a * W) / 2.0; - - // flow ratio based on gutter width corrected for grate width - Eo = getGutterFlowRatio(W); - if (Eo < 1.0) - { - if (T >= Tcrown) - Qo = Qfactor * pow(Tcrown, 2.67) / (1.0 - Eo); - Eo = Eo * getGutterAreaRatio(Wg, A); //HEC-22 Eq(4-20a) - } - } - - // --- flow and splash-over velocities - V = Qo / A; - grateType = InletDesigns[i].grateInlet.type; - if (grateType < 0 || grateType == GENERIC) - Vo = InletDesigns[i].grateInlet.splashVeloc; - else - Vo = getSplashOverVelocity(grateType, Lg); - - // --- frontal flow capture efficiency - if (V > Vo) Rf = 1.0 - 0.09 * (V - Vo); //HEC-22 Eq(4-18) - - // --- side flow capture efficiency - if (Eo < 1.0) - { - Rs = 1.0 / (1.0 + (0.15 * pow(V, 1.8) / - Sx / pow(Lg, 2.3))); //HEC-22 Eq(4-19) - } - - // --- return total flow captured - return Q * (Rf * Eo + Rs * (1.0 - Eo)); //HEC-22 Eq(4-21) -} - -//============================================================================= - -double getCurbInletCapture(double Q, double L) -// -// Input: Q = flow rate seen by inlet (cfs) -// L = length of inlet opening (ft) -// Output: returns captured flow rate (cfs) -// Purpose: finds the flow captured by an on-sag inlet. -// -{ - double Se = Sx, // equivalent gutter slope - Lt, // length for full capture - Sr, // ratio of gutter slope to cross slope - Eo = 0.0, // ratio of gutter to total flow - E = 1.0; // capture efficiency - -// a, W, Sx, Sw, SL, & n were from getConduitGeometry(). T was from getFlowSpread(). - - // --- for depressed gutter section - if (a > 0.0) - { - Sr = Sw / Sx; - Eo = getEo(Sr, T-W, W); - Se = Sx + Sw * Eo; //HEC-22 Eq(4-24) - } - - // --- opening length for full capture - Lt = 0.6 * pow(Q, 0.42) * pow(SL, 0.3) * - pow(1.0/(n*Se), 0.6); //HEC-22 Eq(4-22a) - - // --- capture efficiency for actual opening length - if (L < Lt) - { - E = 1.0 - (L/Lt); - E = 1 - pow(E, 1.8); //HEC-22 Eq(4-23) - } - E = MIN(E, 1.0); - E = MAX(E, 0.0); - return E * Q; -} - -//============================================================================= - -double getGutterFlowRatio(double w) -// -// Input: w = gutter width (ft) -// Output: returns a flow ratio -// Purpose: computes the ratio of flow over a width of gutter to the total -// flow in a street cross section. -// -{ - if (T <= w) return 1.0; - else if (a > 0.0) - return getEo(Sw / Sx, T - w, w); - else - return 1.0 - pow((1.0 - w / T), 2.67); //HEC-22 Eq(4-16) -} - -//============================================================================= - -double getGutterAreaRatio(double Wg, double A) -// -// Input: Wg = width of grate inlet (ft) -// A = total flow area (ft2) -// Output: returns an area ratio -// Purpose: computes the ratio of the flow area above a grate to the flow -// area above depressed gutter in a street cross section. -// -{ - double As, // flow area beyond gutter width (ft2) - Ag; // flow area over grate width (ft2) - - if (Wg >= W) return 1.0; - if (T <= Wg) return 1.0; - if (T <= W) return Wg / T; - As = 0.5 * SQR((T - W)) * Sx; - Ag = Wg * ( (T * Sx) + a - (Wg * Sw / 2.) ); - return Ag / (A - As); -} - -//============================================================================= - -double getSplashOverVelocity(int grateType, double L) -// -// Input: grateType = grate inlet type code -// L = length of grate inlet (ft) -// Output: returns a splash over velocity -// Purpose: computes the splash over velocity for a standard type of grate -// inlet as a function of its length. -// -{ - return SplashCoeffs[grateType][0] + - SplashCoeffs[grateType][1] * L - - SplashCoeffs[grateType][2] * L * L + - SplashCoeffs[grateType][3] * L * L * L; -} - -//============================================================================= - -double getOnSagCapturedFlow(TInlet* inlet, double q, double d) -// -// Input: inlet = an inlet object placed in a conduit link -// q = flow in link prior to any inlet capture (cfs) -// d = flow depth seen by inlet (ft) -// Output: returns flow captured by the inlet (cfs) -// Purpose: computes flow captured by an inlet placed on-sag. -// -{ - int linkIndex, designIndex, totalInlets; - double qCaptured = 0.0, qMax = BIG; - - if (inlet->numInlets == 0) return 0.0; - totalInlets = Nsides * inlet->numInlets; - linkIndex = inlet->linkIndex; - designIndex = inlet->designIndex; - - // --- store conduit geometry in shared variables - getConduitGeometry(inlet); - - // --- set flow limit per inlet - if (inlet->flowLimit > 0.0) - qMax = inlet->flowLimit; - - // --- find nominal flow captured by inlet - qCaptured = getOnSagInletCapture(designIndex, fabs(d)); - - // --- find actual flow captured by the inlet - qCaptured *= inlet->clogFactor; - qCaptured = MIN(qCaptured, qMax); - qCaptured *= (double)totalInlets; - return qCaptured; -} - -//============================================================================= - -double getOnSagInletCapture(int i, double d) -// -// Input: i = inlet type index -// d = water level seen by inlet (ft) -// Output: returns captured flow rate (cfs) -// Purpose: finds the flow captured by an on-sag inlet. -// -{ - double Lsweep = 0.0, Lcurb = 0.0, Lgrate = 0.0; - double Qsw = 0.0, //Sweeper curb opening weir flow - Qso = 0.0, //Sweeper curb opening orifice flow - Qgw = 0.0, //Grate weir flow - Qgo = 0.0, //Grate orifice flow - Qcw = 0.0, //Curb opening weir flow - Qco = 0.0; //Curb opening orifice flow - - if (InletDesigns[i].slottedInlet.length > 0.0) - return getOnSagSlottedFlow(i, d); - - Lgrate = InletDesigns[i].grateInlet.length; - if (Lgrate > 0.0) findOnSagGrateFlows(i, d, &Qgw, &Qgo); - - Lcurb = InletDesigns[i].curbInlet.length; - if (Lcurb > 0.0) - { - Lsweep = Lcurb - Lgrate; - if (Lsweep > 0.0) findOnSagCurbFlows(i, d, Lsweep, &Qsw, &Qso); - if (Qgo > 0.0) findOnSagCurbFlows(i, d, Lgrate, &Qcw, &Qco); - } - return Qgw + Qgo + Qsw + Qso + Qco; -} - -//============================================================================= - -void findOnSagGrateFlows(int i, double d, double *Qw, double *Qo) -// -// Input: i = inlet type index -// d = water level seen by inlet (ft) -// Output: Qw = flow captured in weir mode (cfs) -// Qo = flow captured in orifice mode (cfs) -// Purpose: finds the flow captured by an on-sag grate inlet. -// -{ - int grateType = InletDesigns[i].grateInlet.type; - double Lg = InletDesigns[i].grateInlet.length; - double Wg = InletDesigns[i].grateInlet.width; - double P, // grate perimeter (ft) - Ao, // grate opening area (ft2) - di; // average flow depth across grate (ft) - - // --- for drop grate inlets - if (InletDesigns[i].type == DROP_GRATE_INLET) - { - di = d; - P = 2.0 * (Lg + Wg); - } - - // --- for gutter grate inlets: - else - { - // --- check for spread within grate width - if (d <= Wg * Sw) - Wg = d / Sw; - - // --- avergage depth over grate - di = d - (Wg / 2.0) * Sw; - - // --- effective grate perimeter - P = Lg + 2.0 * Wg; - } - - if (grateType == GENERIC) - Ao = Lg * Wg * InletDesigns[i].grateInlet.fracOpenArea; - else - Ao = Lg * Wg * GrateOpeningRatios[grateType]; - - // --- weir flow applies (based on depth where result of - // weir eqn. equals result of orifice eqn.) - - if (d <= 1.79 * Ao / P) - { - *Qw = 3.0 * P * pow(di, 1.5); //HEC-22 Eq(4-26) - } - - // --- orifice flow applies - else - { - *Qo = 0.67 * Ao * sqrt(2.0 * 32.16 * di); //HEC-22 Eq(4-27) - } -} - -//============================================================================= - -void findOnSagCurbFlows(int i, double d, double L, double *Qw, double *Qo) -// -// Input: i = inlet type index -// d = water level seen by inlet (ft) -// L = length of curb opening (ft) -// Output: Qw = flow captured in weir mode (cfs) -// Qo = flow captured in orifice mode (cfs) -// Purpose: finds the flow captured by an on-sag curb opening inlet. -// -{ - int throatAngle = InletDesigns[i].curbInlet.throatAngle; - double h = InletDesigns[i].curbInlet.height; - double Qweir, Qorif, P; - double dweir, dorif, r; - - // --- check for orifice flow - if (L <= 0.0) return; - if (InletDesigns[i].type == DROP_CURB_INLET) L = L * 4.0; - dorif = 1.4 * h; - if (d > dorif) - { - *Qo = getCurbOrificeFlow(d, h, L, throatAngle); - return; - } - - // --- for uniform cross slope or very long opening - if (a == 0.0 || L > 12.0) - { - // --- check for weir flow - dweir = h; - if (d < dweir) - { - *Qw = 3.0 * L * pow(d, 1.5); //HEC-22 Eq(4-30) - return; - } - else Qweir = 3.0 * L * pow(dweir, 1.5); - } - - // --- for depressed gutter - else - { - // --- check for weir flow - P = L + 1.8 * W; - dweir = h + a; - if (d < dweir) - { - *Qw = 2.3 * P * pow(d, 1.5); //HEC-22 Eq(4-28) - return; - } - else Qweir = 2.3 * P * pow(dweir, 1.5); - } - - // --- interpolate between Qweir at depth dweir and Qorif at depth dorif - Qorif = getCurbOrificeFlow(dorif, h, L, throatAngle); - r = (d - dweir) / (dorif - dweir); - *Qw = (1.0 -r) * Qweir; - *Qo = r * Qorif; -} - -//============================================================================= - -double getCurbOrificeFlow(double di, double h, double L, int throatAngle) -// -// Input: di = water level at lip of inlet opening (ft) -// h = height of curb opening (ft) -// L = length of curb opening (ft) -// throatAngle = type of throat angle in curb opening -// Output: return flow captured by inlet (cfs) -// Purpose: finds the flow captured by an on-sag curb opening inlet under -// orifice flow conditions. -// -{ - double d = di; - if (throatAngle == HORIZONTAL_THROAT) - d = di - h / 2.0; - else if (throatAngle == INCLINED_THROAT) - d = di + (h / 2.0) * 0.7071; - return 0.67 * h * L * sqrt(2.0 * 32.16 * d); //HEC-22 Eq(4-31a) -} - -//============================================================================= - -double getOnSagSlottedFlow(int i, double d) -// -// Input: i = inlet type index -// d = water level seen by inlet (ft) -// Output: returns captured flow rate (cfs) -// Purpose: finds the flow captured by an on-sag slotted inlet. -// -// Note: weir flow = orifice flow at d = 2.587 * inlet width -{ - double L = InletDesigns[i].slottedInlet.length; - double w = InletDesigns[i].slottedInlet.width; - - if (d <= 2.587 * w) - return 2.48 * L * pow(d, 1.5); //HEC-22 Eq(4-32) - else - return 0.8 * L * w * sqrt(64.32 * d); //HEC-22 Eq(4-33) -} - -//============================================================================= - -void getBackflowRatios() -// -// Input: none -// Output: overflow ratio for each inlet -// Purpose: finds the fraction of the overflow produced by an inlet's capture -// node that becomes backflow into the inlet. -// -// Note: when a capture node receives flow from two or more inlets -// its backflow is divided among the inlets based on: -// i) the fraction of total open area for standard inlets -// ii) the fraction of total number of inlets for custom inlets -{ - TInlet* inlet; - double area; - double f; - int n; - - // --- info for each node receiving flow from an inlet - typedef struct - { - int numInletLinks; // total # inlet links - int numStdInletLinks; // total # standard inlet links - int numCustomInlets; // # custom inlets - double totalInletArea; // open area of standard inlets - } TInletNode; - TInletNode* inletNodes = (TInletNode *) calloc(Nobjects[NODE], sizeof(TInletNode)); - if (inletNodes == NULL) return; - - // --- Finds each inlet's contribution to its capture node - for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) - { - n = inlet->nodeIndex; - inletNodes[n].numInletLinks++; - area = getInletArea(inlet); - if (area > 0.0) - { - inletNodes[n].numStdInletLinks++; - inletNodes[n].totalInletArea += area; - } - else - inletNodes[n].numCustomInlets += inlet->numInlets; - } - - // --- find fraction of capture node's overflow that becomes inlet backflow - for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) - { - // --- f is ratio of links with standard inlets to all inlet links - // connected to receptor node n - n = inlet->nodeIndex; - f = (double) inletNodes[n].numStdInletLinks / - (double) inletNodes[n].numInletLinks; - - // --- backflow ratio depends if inlet is standard or custom (area = 0) - area = getInletArea(inlet); - if (area == 0.0) - inlet->backflowRatio = (double)inlet->numInlets / - (double)inletNodes[n].numCustomInlets * (1. - f); - else - inlet->backflowRatio = area / inletNodes[n].totalInletArea * f; - } - free(inletNodes); -} - -//============================================================================= - -double getInletArea(TInlet* inlet) -// -// Input: inlet = an inlet object placed in a conduit link -// Output: returns the unclogged open area of the inlet (ft2) -// Purpose: finds the total open flow area inlets placed in a conduit. -// -{ - double area = 0.0; - double curbLength; - int i = inlet->designIndex; - int grateType = InletDesigns[i].grateInlet.type; - - if (InletDesigns[i].grateInlet.length > 0.0) - { - area = InletDesigns[i].grateInlet.length * InletDesigns[i].grateInlet.width; - if (grateType == GENERIC) - area *= InletDesigns[i].grateInlet.fracOpenArea; - else - area *= GrateOpeningRatios[grateType]; - } - - curbLength = InletDesigns[i].curbInlet.length - InletDesigns[i].grateInlet.length; - if (curbLength > 0.0) - area += curbLength * InletDesigns[i].curbInlet.height; - - if (InletDesigns[i].slottedInlet.length > 0.0) - area = InletDesigns[i].slottedInlet.length * InletDesigns[i].slottedInlet.width; - return area * inlet->numInlets * inlet->clogFactor; -} - -//============================================================================= - -double getCustomCapturedFlow(TInlet* inlet, double q, double d) -{ - int i = inlet->designIndex; // inlet's position in InletDesigns array - int j; // counter for replicate inlets - int sides = 1; // number of sides for inlet's street (1 or 2) - int c; // an index into the Curve array - double qApproach, // inlet's approach flow (cfs) - qBypassed, // inlet's bypassed flow (cfs) - qCaptured, // inlet's captured flow (cfs) - qIncrement, // increment to captured flow (cfs) - qMax = BIG; // user-supplied flow capture limit (cfs) - - if (inlet->numInlets == 0) return 0.0; - - // --- set limit on max. flow captured per inlet - qMax = BIG; - if (inlet->flowLimit > 0.0) qMax = inlet->flowLimit; - - // --- get number of sides to a street xsection - xsect = &Link[inlet->linkIndex].xsect; - if (xsect->type == STREET_XSECT) - sides = Street[xsect->transect].sides; - - // --- adjust flow for 2-sided street - qApproach = q / sides; - qBypassed = qApproach; - qCaptured = 0.0; - - // --- get index of inlet's capture curve - c = InletDesigns[i].customCurve; - if (c >= 0) - { - // --- curve is captured flow v. approach flow - if (Curve[c].curveType == DIVERSION_CURVE) - { - // --- add up incrmental capture of each replicate inlet - for (j = 1; j <= inlet->numInlets; j++) - { - qIncrement = inlet->clogFactor * - table_lookupEx(&Curve[c], qBypassed * UCF(FLOW)) / UCF(FLOW); - qIncrement = MIN(qIncrement, qMax); - qIncrement = MIN(qIncrement, qBypassed); - qCaptured += qIncrement; - qBypassed -= qIncrement; - if (qBypassed < MIN_RUNOFF_FLOW) break; - } - } - - // --- curve is captured flow v. downstream node depth - else if (Curve[c].curveType == RATING_CURVE) - { - qCaptured = inlet->numInlets * inlet->clogFactor * - table_lookupEx(&Curve[c], d * UCF(LENGTH)) / UCF(FLOW); - } - qCaptured *= sides; - } - return qCaptured; -} diff --git a/src/inlet.h b/src/inlet.h deleted file mode 100644 index ca7830725..000000000 --- a/src/inlet.h +++ /dev/null @@ -1,30 +0,0 @@ -//----------------------------------------------------------------------------- -// inlet.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Street/Channel Inlet Functions -// -//----------------------------------------------------------------------------- -#ifndef INLET_H -#define INLET_H - -typedef struct TInlet TInlet; - -int inlet_create(int nInlets); -void inlet_delete(); -int inlet_readDesignParams(char* tok[], int ntoks); -int inlet_readUsageParams(char* tok[], int ntoks); -void inlet_validate(); - -void inlet_findCapturedFlows(double tStep); -void inlet_adjustQualInflows(); -void inlet_adjustQualOutflows(); - -void inlet_writeStatsReport(); -double inlet_capturedFlow(int link); - -#endif diff --git a/src/input.c b/src/input.c deleted file mode 100644 index c0f7a0d9f..000000000 --- a/src/input.c +++ /dev/null @@ -1,930 +0,0 @@ -//----------------------------------------------------------------------------- -// input.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 06/01/22 (Build 5.2.1) -// Author: L. Rossman -// -// Input data processing functions. -// -// Update History -// ============== -// Build 5.1.007: -// - Support added for climate adjustment input data. -// Build 5.1.011: -// - Support added for reading hydraulic event dates. -// Build 5.1.015: -// - Support added for multiple infiltration methods within a project. -// Build 5.2.0: -// - Support added for Streets and Inlets. -// - Support added for named variables & math expressions in control rules. -// Build 5.2.1: -// - Possible integer underflow avoided in getTokens() function. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include -#include "headers.h" -#include "lid.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -static const int MAXERRS = 100; // Max. input errors reported - -//----------------------------------------------------------------------------- -// Shared variables -//----------------------------------------------------------------------------- -static char *Tok[MAXTOKS]; // String tokens from line of input -static int Ntokens; // Number of tokens in line of input -static int Mobjects[MAX_OBJ_TYPES]; // Working number of objects of each type -static int Mnodes[MAX_NODE_TYPES]; // Working number of node objects -static int Mlinks[MAX_LINK_TYPES]; // Working number of link objects -static int Mevents; // Working number of event periods - -//----------------------------------------------------------------------------- -// External Functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// input_countObjects (called by swmm_open in swmm5.c) -// input_readData (called by swmm_open in swmm5.c) - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static int addObject(int objType, char* id); -static int getTokens(char *s); -static int parseLine(int sect, char* line); -static int readOption(char* line); -static int readTitle(char* line); -static int readControl(char* tok[], int ntoks); -static int readNode(int type); -static int readLink(int type); -static int readEvent(char* tok[], int ntoks); - -//============================================================================= - -int input_countObjects() -// -// Input: none -// Output: returns error code -// Purpose: reads input file to determine number of system objects. -// -{ - char line[MAXLINE+1]; // line from input data file - char wLine[MAXLINE+1]; // working copy of input line - char *tok; // first string token of line - int sect = -1, newsect; // input data sections - int errcode = 0; // error code - int errsum = 0; // number of errors found - int i; - long lineCount = 0; - - // --- initialize number of objects & set default values - if ( ErrorCode ) return ErrorCode; - error_setInpError(0, ""); - for (i = 0; i < MAX_OBJ_TYPES; i++) Nobjects[i] = 0; - for (i = 0; i < MAX_NODE_TYPES; i++) Nnodes[i] = 0; - for (i = 0; i < MAX_LINK_TYPES; i++) Nlinks[i] = 0; - controls_init(); - - // --- make pass through data file counting number of each object - while ( fgets(line, MAXLINE, Finp.file) != NULL ) - { - // --- skip blank lines & those beginning with a comment - lineCount++; - sstrncpy(wLine, line, MAXLINE); // make working copy of line - tok = strtok(wLine, SEPSTR); // get first text token on line - if ( tok == NULL ) continue; - if ( *tok == ';' ) continue; - - // --- check if line begins with a new section heading - if ( *tok == '[' ) - { - // --- look for heading in list of section keywords - newsect = findmatch(tok, SectWords); - if ( newsect >= 0 ) - { - sect = newsect; - continue; - } - else - { - sect = -1; - errcode = ERR_KEYWORD; - } - } - - // --- if in OPTIONS section then read the option setting - // otherwise add object and its ID name (tok) to project - if ( sect == s_OPTION ) errcode = readOption(line); - else if ( sect >= 0 ) errcode = addObject(sect, tok); - - // --- report any error found - if ( errcode ) - { - report_writeInputErrorMsg(errcode, sect, line, lineCount); - errsum++; - if (errsum >= MAXERRS ) break; - } - } - - // --- set global error code if input errors were found - if ( errsum > 0 ) ErrorCode = ERR_INPUT; - return ErrorCode; -} - -//============================================================================= - -int input_readData() -// -// Input: none -// Output: returns error code -// Purpose: reads input file to determine input parameters for each object. -// -{ - char line[MAXLINE+1]; // line from input data file - char wLine[MAXLINE+1]; // working copy of input line - char* comment; // ptr. to start of comment in input line - int sect, newsect; // data sections - int inperr, errsum; // error code & total error count - int lineLength; // number of characters in input line - int i; - long lineCount = 0; - - // --- initialize working item count arrays - // (final counts in Mobjects, Mnodes & Mlinks should - // match those in Nobjects, Nnodes and Nlinks). - if ( ErrorCode ) return ErrorCode; - error_setInpError(0, ""); - for (i = 0; i < MAX_OBJ_TYPES; i++) Mobjects[i] = 0; - for (i = 0; i < MAX_NODE_TYPES; i++) Mnodes[i] = 0; - for (i = 0; i < MAX_LINK_TYPES; i++) Mlinks[i] = 0; - Mevents = 0; - - // --- initialize starting date for all time series - for ( i = 0; i < Nobjects[TSERIES]; i++ ) - { - Tseries[i].lastDate = StartDate + StartTime; - } - - // --- read each line from input file - sect = 0; - errsum = 0; - rewind(Finp.file); - while ( fgets(line, MAXLINE, Finp.file) != NULL ) - { - // --- make copy of line and scan for tokens - lineCount++; - sstrncpy(wLine, line, MAXLINE); - Ntokens = getTokens(wLine); - - // --- skip blank lines and comments - if ( Ntokens == 0 ) continue; - if ( *Tok[0] == ';' ) continue; - - // --- check if max. line length exceeded - lineLength = (int)strlen(line); - if ( lineLength >= MAXLINE ) - { - // --- don't count comment if present - comment = strchr(line, ';'); - if ( comment ) lineLength = (int)(comment - line); // Pointer math here - if ( lineLength >= MAXLINE ) - { - inperr = ERR_LINE_LENGTH; - report_writeInputErrorMsg(inperr, sect, line, lineCount); - errsum++; - } - } - - // --- check if at start of a new input section - if (*Tok[0] == '[') - { - // --- match token against list of section keywords - newsect = findmatch(Tok[0], SectWords); - if (newsect >= 0) - { - // --- SPECIAL CASE FOR TRANSECTS - // finish processing the last set of transect data - if ( sect == s_TRANSECT ) - transect_validate(Nobjects[TRANSECT]-1); - - // --- begin a new input section - sect = newsect; - continue; - } - else - { - inperr = error_setInpError(ERR_KEYWORD, Tok[0]); - report_writeInputErrorMsg(inperr, sect, line, lineCount); - errsum++; - break; - } - } - - // --- otherwise parse tokens from input line - else - { - inperr = parseLine(sect, line); - if ( inperr > 0 ) - { - errsum++; - if ( errsum > MAXERRS ) report_writeLine(FMT19); - else report_writeInputErrorMsg(inperr, sect, line, lineCount); - } - } - - // --- stop if reach end of file or max. error count - if (errsum > MAXERRS) break; - } /* End of while */ - - // --- check for errors - if (errsum > 0) ErrorCode = ERR_INPUT; - return ErrorCode; -} - -//============================================================================= - -int addObject(int objType, char* id) -// -// Input: objType = object type index -// id = object's ID string -// Output: returns an error code -// Purpose: adds a new object to the project. -// -{ - int errcode = 0; - switch( objType ) - { - case s_RAINGAGE: - if ( !project_addObject(GAGE, id, Nobjects[GAGE]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[GAGE]++; - break; - - case s_SUBCATCH: - if ( !project_addObject(SUBCATCH, id, Nobjects[SUBCATCH]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[SUBCATCH]++; - break; - - case s_AQUIFER: - if ( !project_addObject(AQUIFER, id, Nobjects[AQUIFER]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[AQUIFER]++; - break; - - case s_UNITHYD: - // --- the same Unit Hydrograph can span several lines - if ( project_findObject(UNITHYD, id) < 0 ) - { - if ( !project_addObject(UNITHYD, id, Nobjects[UNITHYD]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[UNITHYD]++; - } - break; - - case s_SNOWMELT: - // --- the same Snowmelt object can appear on several lines - if ( project_findObject(SNOWMELT, id) < 0 ) - { - if ( !project_addObject(SNOWMELT, id, Nobjects[SNOWMELT]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[SNOWMELT]++; - } - break; - - case s_JUNCTION: - if ( !project_addObject(NODE, id, Nobjects[NODE]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[NODE]++; - Nnodes[JUNCTION]++; - break; - - case s_OUTFALL: - if ( !project_addObject(NODE, id, Nobjects[NODE]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[NODE]++; - Nnodes[OUTFALL]++; - break; - - case s_STORAGE: - if ( !project_addObject(NODE, id, Nobjects[NODE]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[NODE]++; - Nnodes[STORAGE]++; - break; - - case s_DIVIDER: - if ( !project_addObject(NODE, id, Nobjects[NODE]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[NODE]++; - Nnodes[DIVIDER]++; - break; - - case s_CONDUIT: - if ( !project_addObject(LINK, id, Nobjects[LINK]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[LINK]++; - Nlinks[CONDUIT]++; - break; - - case s_PUMP: - if ( !project_addObject(LINK, id, Nobjects[LINK]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[LINK]++; - Nlinks[PUMP]++; - break; - - case s_ORIFICE: - if ( !project_addObject(LINK, id, Nobjects[LINK]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[LINK]++; - Nlinks[ORIFICE]++; - break; - - case s_WEIR: - if ( !project_addObject(LINK, id, Nobjects[LINK]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[LINK]++; - Nlinks[WEIR]++; - break; - - case s_OUTLET: - if ( !project_addObject(LINK, id, Nobjects[LINK]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[LINK]++; - Nlinks[OUTLET]++; - break; - - case s_POLLUTANT: - if ( !project_addObject(POLLUT, id, Nobjects[POLLUT]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[POLLUT]++; - break; - - case s_LANDUSE: - if ( !project_addObject(LANDUSE, id, Nobjects[LANDUSE]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[LANDUSE]++; - break; - - case s_PATTERN: - // --- a time pattern can span several lines - if ( project_findObject(TIMEPATTERN, id) < 0 ) - { - if ( !project_addObject(TIMEPATTERN, id, Nobjects[TIMEPATTERN]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[TIMEPATTERN]++; - } - break; - - case s_CURVE: - // --- a Curve can span several lines - if ( project_findObject(CURVE, id) < 0 ) - { - if ( !project_addObject(CURVE, id, Nobjects[CURVE]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[CURVE]++; - - // --- check for a conduit shape curve - id = strtok(NULL, SEPSTR); - if ( findmatch(id, CurveTypeWords) == SHAPE_CURVE ) - Nobjects[SHAPE]++; - } - break; - - case s_TIMESERIES: - // --- a Time Series can span several lines - if ( project_findObject(TSERIES, id) < 0 ) - { - if ( !project_addObject(TSERIES, id, Nobjects[TSERIES]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[TSERIES]++; - } - break; - - case s_CONTROL: - if ( match(id, w_RULE) ) Nobjects[CONTROL]++; - else controls_addToCount(id); - break; - - case s_TRANSECT: - // --- for TRANSECTS, ID name appears as second entry on X1 line - if ( match(id, "X1") ) - { - id = strtok(NULL, SEPSTR); - if ( id ) - { - if ( !project_addObject(TRANSECT, id, Nobjects[TRANSECT]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[TRANSECT]++; - } - } - break; - - case s_LID_CONTROL: - // --- an LID object can span several lines - if ( project_findObject(LID, id) < 0 ) - { - if ( !project_addObject(LID, id, Nobjects[LID]) ) - { - errcode = error_setInpError(ERR_DUP_NAME, id); - } - Nobjects[LID]++; - } - break; - - case s_EVENT: NumEvents++; break; - - case s_STREET: - if ( !project_addObject(STREET, id, Nobjects[STREET]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[STREET]++; - break; - - case s_INLET: - // --- an INLET object can span several lines - if (project_findObject(INLET, id) < 0) - { - if ( !project_addObject(INLET, id, Nobjects[INLET]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[INLET]++; - } - break; - } - return errcode; -} - -//============================================================================= - -int parseLine(int sect, char *line) -// -// Input: sect = current section of input file -// *line = line of text read from input file -// Output: returns error code or 0 if no error found -// Purpose: parses contents of a tokenized line of text read from input file. -// -{ - int j, err; - switch (sect) - { - case s_TITLE: - return readTitle(line); - - case s_RAINGAGE: - j = Mobjects[GAGE]; - err = gage_readParams(j, Tok, Ntokens); - Mobjects[GAGE]++; - return err; - - case s_TEMP: - return climate_readParams(Tok, Ntokens); - - case s_EVAP: - return climate_readEvapParams(Tok, Ntokens); - - case s_ADJUST: - return climate_readAdjustments(Tok, Ntokens); - - case s_SUBCATCH: - j = Mobjects[SUBCATCH]; - err = subcatch_readParams(j, Tok, Ntokens); - Mobjects[SUBCATCH]++; - return err; - - case s_SUBAREA: - return subcatch_readSubareaParams(Tok, Ntokens); - - case s_INFIL: - return infil_readParams(InfilModel, Tok, Ntokens); - - case s_AQUIFER: - j = Mobjects[AQUIFER]; - err = gwater_readAquiferParams(j, Tok, Ntokens); - Mobjects[AQUIFER]++; - return err; - - case s_GROUNDWATER: - return gwater_readGroundwaterParams(Tok, Ntokens); - - case s_GWF: - return gwater_readFlowExpression(Tok, Ntokens); - - case s_SNOWMELT: - return snow_readMeltParams(Tok, Ntokens); - - case s_JUNCTION: - return readNode(JUNCTION); - - case s_OUTFALL: - return readNode(OUTFALL); - - case s_STORAGE: - return readNode(STORAGE); - - case s_DIVIDER: - return readNode(DIVIDER); - - case s_CONDUIT: - return readLink(CONDUIT); - - case s_PUMP: - return readLink(PUMP); - - case s_ORIFICE: - return readLink(ORIFICE); - - case s_WEIR: - return readLink(WEIR); - - case s_OUTLET: - return readLink(OUTLET); - - case s_XSECTION: - return link_readXsectParams(Tok, Ntokens); - - case s_TRANSECT: - return transect_readParams(&Mobjects[TRANSECT], Tok, Ntokens); - - case s_LOSSES: - return link_readLossParams(Tok, Ntokens); - - case s_POLLUTANT: - j = Mobjects[POLLUT]; - err = landuse_readPollutParams(j, Tok, Ntokens); - Mobjects[POLLUT]++; - return err; - - case s_LANDUSE: - j = Mobjects[LANDUSE]; - err = landuse_readParams(j, Tok, Ntokens); - Mobjects[LANDUSE]++; - return err; - - case s_BUILDUP: - return landuse_readBuildupParams(Tok, Ntokens); - - case s_WASHOFF: - return landuse_readWashoffParams(Tok, Ntokens); - - case s_COVERAGE: - return subcatch_readLanduseParams(Tok, Ntokens); - - case s_INFLOW: - return inflow_readExtInflow(Tok, Ntokens); - - case s_DWF: - return inflow_readDwfInflow(Tok, Ntokens); - - case s_PATTERN: - return inflow_readDwfPattern(Tok, Ntokens); - - case s_RDII: - return rdii_readRdiiInflow(Tok, Ntokens); - - case s_UNITHYD: - return rdii_readUnitHydParams(Tok, Ntokens); - - case s_LOADING: - return subcatch_readInitBuildup(Tok, Ntokens); - - case s_TREATMENT: - return treatmnt_readExpression(Tok, Ntokens); - - case s_CURVE: - return table_readCurve(Tok, Ntokens); - - case s_TIMESERIES: - return table_readTimeseries(Tok, Ntokens); - - case s_CONTROL: - return readControl(Tok, Ntokens); - - case s_REPORT: - return report_readOptions(Tok, Ntokens); - - case s_FILE: - return iface_readFileParams(Tok, Ntokens); - - case s_LID_CONTROL: - return lid_readProcParams(Tok, Ntokens); - - case s_LID_USAGE: - return lid_readGroupParams(Tok, Ntokens); - - case s_EVENT: - return readEvent(Tok, Ntokens); - - case s_STREET: - return street_readParams(Tok, Ntokens); - - case s_INLET: - return inlet_readDesignParams(Tok, Ntokens); - - case s_INLET_USAGE: - return inlet_readUsageParams(Tok, Ntokens); - - default: return 0; - } -} - -//============================================================================= - -int readControl(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// Purpose: reads a line of input for a control rule. -// -{ - int index; - int keyword; - - // --- check for minimum number of tokens - if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); - - if (match(tok[0], w_VARIABLE)) - return controls_addVariable(tok, ntoks); - if (match(tok[0], w_EXPRESSION)) - return controls_addExpression(tok, ntoks); - - // --- get index of control rule keyword - keyword = findmatch(tok[0], RuleKeyWords); - if ( keyword < 0 ) return error_setInpError(ERR_KEYWORD, tok[0]); - - // --- if line begins a new control rule, add rule ID to the database - if ( keyword == 0 ) - { - if ( !project_addObject(CONTROL, tok[1], Mobjects[CONTROL]) ) - { - return error_setInpError(ERR_DUP_NAME, Tok[1]); - } - Mobjects[CONTROL]++; - } - - // --- get index of last control rule processed - index = Mobjects[CONTROL] - 1; - if ( index < 0 ) return error_setInpError(ERR_RULE, ""); - - // --- add current line as a new clause to the control rule - return controls_addRuleClause(index, keyword, Tok, Ntokens); -} - -//============================================================================= - -int readOption(char* line) -// -// Input: line = line of input data -// Output: returns error code -// Purpose: reads an input line containing a project option. -// -{ - Ntokens = getTokens(line); - if ( Ntokens < 2 ) return 0; - return project_readOption(Tok[0], Tok[1]); -} - -//============================================================================= - -int readTitle(char* line) -// -// Input: line = line from input file -// Output: returns error code -// Purpose: reads project title from line of input. -// -{ - int i, n; - for (i = 0; i < MAXTITLE; i++) - { - // --- find next empty Title entry - if ( strlen(Title[i]) == 0 ) - { - // --- strip line feed character from input line - n = (int)strlen(line); - if (line[n-1] == 10) line[n-1] = ' '; - - // --- copy input line into Title entry - sstrncpy(Title[i], line, MAXMSG); - break; - } - } - return 0; -} - -//============================================================================= - -int readNode(int type) -// -// Input: type = type of node -// Output: returns error code -// Purpose: reads data for a node from a line of input. -// -{ - int j = Mobjects[NODE]; - int k = Mnodes[type]; - int err = node_readParams(j, type, k, Tok, Ntokens); - Mobjects[NODE]++; - Mnodes[type]++; - return err; -} - -//============================================================================= - -int readLink(int type) -// -// Input: type = type of link -// Output: returns error code -// Purpose: reads data for a link from a line of input. -// -{ - int j = Mobjects[LINK]; - int k = Mlinks[type]; - int err = link_readParams(j, type, k, Tok, Ntokens); - Mobjects[LINK]++; - Mlinks[type]++; - return err; -} - -//============================================================================= - -int readEvent(char* tok[], int ntoks) -{ - DateTime x[4]; - - if ( ntoks < 4 ) return error_setInpError(ERR_ITEMS, ""); - if ( !datetime_strToDate(tok[0], &x[0]) ) - return error_setInpError(ERR_DATETIME, tok[0]); - if ( !datetime_strToTime(tok[1], &x[1]) ) - return error_setInpError(ERR_DATETIME, tok[1]); - if ( !datetime_strToDate(tok[2], &x[2]) ) - return error_setInpError(ERR_DATETIME, tok[2]); - if ( !datetime_strToTime(tok[3], &x[3]) ) - return error_setInpError(ERR_DATETIME, tok[3]); - - Event[Mevents].start = x[0] + x[1]; - Event[Mevents].end = x[2] + x[3]; - if ( Event[Mevents].start >= Event[Mevents].end ) - return error_setInpError(ERR_DATETIME, " - start date exceeds end date"); - Mevents++; - return 0; -} - -//============================================================================= - -int findmatch(char *s, char *keyword[]) -// -// Input: s = character string -// keyword = array of keyword strings -// Output: returns index of matching keyword or -1 if no match found -// Purpose: finds match between string and array of keyword strings. -// -{ - int i = 0; - while (keyword[i] != NULL) - { - if (match(s, keyword[i])) return(i); - i++; - } - return(-1); -} - -//============================================================================= - -int match(char *str, char *substr) -// -// Input: str = character string being searched -// substr = sub-string being searched for -// Output: returns 1 if sub-string found, 0 if not -// Purpose: sees if a sub-string of characters appears in a string -// (not case sensitive). -// -{ - int i,j,k; - - // --- fail if substring is empty - if (!substr[0]) return(0); - - // --- skip leading blanks of str - for (k = 0; str[k]; k++) - { - if (str[k] != ' ') break; - } - - // --- check if substr matches remainder of str - for (i = k,j = 0; substr[j]; i++,j++) - { - if (!str[i] || UCHAR(str[i]) != UCHAR(substr[j])) return(0); - } - return(1); -} - -//============================================================================= - -int getInt(char *s, int *y) -// -// Input: s = a character string -// Output: y = converted value of s, -// returns 1 if conversion successful, 0 if not -// Purpose: converts a string to an integer number. -// -{ - double x; - if ( getDouble(s, &x) ) - { - if ( x < 0.0 ) x -= 0.01; - else x += 0.01; - *y = (int)x; - return 1; - } - *y = 0; - return 0; -} - -//============================================================================= - -int getFloat(char *s, float *y) -// -// Input: s = a character string -// Output: y = converted value of s, -// returns 1 if conversion successful, 0 if not -// Purpose: converts a string to a single precision floating point number. -// -{ - char *endptr; - *y = (float) strtod(s, &endptr); - if (*endptr > 0) return(0); - return(1); -} - -//============================================================================= - -int getDouble(char *s, double *y) -// -// Input: s = a character string -// Output: y = converted value of s, -// returns 1 if conversion successful, 0 if not -// Purpose: converts a string to a double precision floating point number. -// -{ - char *endptr; - *y = strtod(s, &endptr); - if (*endptr > 0) return(0); - return(1); -} - -//============================================================================= - -int getTokens(char *s) -// -// Input: s = a character string -// Output: returns number of tokens found in s -// Purpose: scans a string for tokens, saving pointers to them -// in shared variable Tok[]. -// -// Notes: Tokens can be separated by the characters listed in SEPSTR -// (spaces, tabs, newline, carriage return) which is defined -// in CONSTS.H. Text between quotes is treated as a single token. -// -{ - int len, n; - int m; - char *c; - - // --- begin with no tokens - for (n = 0; n < MAXTOKS; n++) Tok[n] = NULL; - n = 0; - - // --- truncate s at start of comment - c = strchr(s,';'); - if (c) *c = '\0'; - len = (int)strlen(s); - - // --- scan s for tokens until nothing left - while (len > 0 && n < MAXTOKS) - { - m = (int)strcspn(s,SEPSTR); // find token length - if (m == 0) s++; // no token found - else - { - if (*s == '"') // token begins with quote - { - s++; // start token after quote - len--; // reduce length of s - m = (int)strcspn(s,"\"\n"); // find end quote or new line - } - s[m] = '\0'; // null-terminate the token - Tok[n] = s; // save pointer to token - n++; // update token count - s += m+1; // begin next token - } - len -= m+1; // update length of s - } - return n; -} - -//============================================================================= diff --git a/src/inputrpt.c b/src/inputrpt.c deleted file mode 100644 index 46a6f952f..000000000 --- a/src/inputrpt.c +++ /dev/null @@ -1,354 +0,0 @@ -//----------------------------------------------------------------------------- -// inputrpt.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Report writing functions for input data summary. -// -// Update History -// ============== -// Build 5.2.0: -// - Support added for reporting Street geometry tables. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include "headers.h" -#include "lid.h" - -#define WRITE(x) (report_writeLine((x))) - -//============================================================================= - -void inputrpt_writeInput() -// -// Input: none -// Output: none -// Purpose: writes summary of input data to report file. -// -{ - int m; - int i, k; - int lidCount = 0; - if ( ErrorCode ) return; - - WRITE(""); - WRITE("*************"); - WRITE("Element Count"); - WRITE("*************"); - fprintf(Frpt.file, "\n Number of rain gages ...... %d", Nobjects[GAGE]); - fprintf(Frpt.file, "\n Number of subcatchments ... %d", Nobjects[SUBCATCH]); - fprintf(Frpt.file, "\n Number of nodes ........... %d", Nobjects[NODE]); - fprintf(Frpt.file, "\n Number of links ........... %d", Nobjects[LINK]); - fprintf(Frpt.file, "\n Number of pollutants ...... %d", Nobjects[POLLUT]); - fprintf(Frpt.file, "\n Number of land uses ....... %d", Nobjects[LANDUSE]); - - if ( Nobjects[POLLUT] > 0 ) - { - WRITE(""); - WRITE(""); - WRITE("*****************"); - WRITE("Pollutant Summary"); - WRITE("*****************"); - fprintf(Frpt.file, - "\n Ppt. GW Kdecay"); - fprintf(Frpt.file, - "\n Name Units Concen. Concen. 1/days CoPollutant"); - fprintf(Frpt.file, - "\n -----------------------------------------------------------------------"); - for (i = 0; i < Nobjects[POLLUT]; i++) - { - fprintf(Frpt.file, "\n %-20s %5s%10.2f%10.2f%10.2f", Pollut[i].ID, - QualUnitsWords[Pollut[i].units], Pollut[i].pptConcen, - Pollut[i].gwConcen, Pollut[i].kDecay*SECperDAY); - if ( Pollut[i].coPollut >= 0 ) - fprintf(Frpt.file, " %-s (%.2f)", - Pollut[Pollut[i].coPollut].ID, Pollut[i].coFraction); - } - } - - if ( Nobjects[LANDUSE] > 0 ) - { - WRITE(""); - WRITE(""); - WRITE("***************"); - WRITE("Landuse Summary"); - WRITE("***************"); - fprintf(Frpt.file, - "\n Sweeping Maximum Last"); - fprintf(Frpt.file, - "\n Name Interval Removal Swept"); - fprintf(Frpt.file, - "\n ---------------------------------------------------"); - for (i=0; i 0 ) - { - WRITE(""); - WRITE(""); - WRITE("****************"); - WRITE("Raingage Summary"); - WRITE("****************"); - fprintf(Frpt.file, -"\n Data Recording"); - fprintf(Frpt.file, -"\n Name Data Source Type Interval "); - fprintf(Frpt.file, -"\n ------------------------------------------------------------------------"); - for (i = 0; i < Nobjects[GAGE]; i++) - { - if ( Gage[i].tSeries >= 0 ) - { - fprintf(Frpt.file, "\n %-20s %-30s ", - Gage[i].ID, Tseries[Gage[i].tSeries].ID); - fprintf(Frpt.file, "%-10s %3d min.", - RainTypeWords[Gage[i].rainType], - (Gage[i].rainInterval)/60); - } - else fprintf(Frpt.file, "\n %-20s %-30s", - Gage[i].ID, Gage[i].fname); - } - } - - if ( Nobjects[SUBCATCH] > 0 ) - { - WRITE(""); - WRITE(""); - WRITE("********************"); - WRITE("Subcatchment Summary"); - WRITE("********************"); - fprintf(Frpt.file, -"\n Name Area Width %%Imperv %%Slope Rain Gage Outlet "); - fprintf(Frpt.file, -"\n -----------------------------------------------------------------------------------------------------------"); - for (i = 0; i < Nobjects[SUBCATCH]; i++) - { - fprintf(Frpt.file,"\n %-20s %10.2f%10.2f%10.2f%10.4f %-20s ", - Subcatch[i].ID, Subcatch[i].area*UCF(LANDAREA), - Subcatch[i].width*UCF(LENGTH), Subcatch[i].fracImperv*100.0, - Subcatch[i].slope*100.0, Gage[Subcatch[i].gage].ID); - if ( Subcatch[i].outNode >= 0 ) - { - fprintf(Frpt.file, "%-20s", Node[Subcatch[i].outNode].ID); - } - else if ( Subcatch[i].outSubcatch >= 0 ) - { - fprintf(Frpt.file, "%-20s", Subcatch[Subcatch[i].outSubcatch].ID); - } - if ( Subcatch[i].lidArea ) lidCount++; - } - } - if ( lidCount > 0 ) lid_writeSummary(); - - if ( Nobjects[NODE] > 0 ) - { - WRITE(""); - WRITE(""); - WRITE("************"); - WRITE("Node Summary"); - WRITE("************"); - fprintf(Frpt.file, -"\n Invert Max. Ponded External"); - fprintf(Frpt.file, -"\n Name Type Elev. Depth Area Inflow "); - fprintf(Frpt.file, -"\n -------------------------------------------------------------------------------"); - for (i = 0; i < Nobjects[NODE]; i++) - { - fprintf(Frpt.file, "\n %-20s %-16s%10.2f%10.2f%10.1f", Node[i].ID, - NodeTypeWords[Node[i].type-JUNCTION], - Node[i].invertElev*UCF(LENGTH), - Node[i].fullDepth*UCF(LENGTH), - Node[i].pondedArea*UCF(LENGTH)*UCF(LENGTH)); - if ( Node[i].extInflow || Node[i].dwfInflow || Node[i].rdiiInflow ) - { - fprintf(Frpt.file, " Yes"); - } - } - } - - if ( Nobjects[LINK] > 0 ) - { - WRITE(""); - WRITE(""); - WRITE("************"); - WRITE("Link Summary"); - WRITE("************"); - fprintf(Frpt.file, -"\n Name From Node To Node Type Length %%Slope Roughness"); - fprintf(Frpt.file, -"\n ---------------------------------------------------------------------------------------------"); - for (i = 0; i < Nobjects[LINK]; i++) - { - // --- list end nodes in their original orientation - if ( Link[i].direction == 1 ) - fprintf(Frpt.file, "\n %-16s %-16s %-16s ", - Link[i].ID, Node[Link[i].node1].ID, Node[Link[i].node2].ID); - else - fprintf(Frpt.file, "\n %-16s %-16s %-16s ", - Link[i].ID, Node[Link[i].node2].ID, Node[Link[i].node1].ID); - - // --- list link type - if ( Link[i].type == PUMP ) - { - k = Link[i].subIndex; - fprintf(Frpt.file, "%-5s PUMP ", - PumpTypeWords[Pump[k].type]); - } - else fprintf(Frpt.file, "%-12s", - LinkTypeWords[Link[i].type-CONDUIT]); - - // --- list length, slope and roughness for conduit links - if (Link[i].type == CONDUIT) - { - k = Link[i].subIndex; - fprintf(Frpt.file, "%10.1f%10.4f%10.4f", - Conduit[k].length*UCF(LENGTH), - Conduit[k].slope*100.0*Link[i].direction, - Conduit[k].roughness); - } - } - - WRITE(""); - WRITE(""); - WRITE("*********************"); - WRITE("Cross Section Summary"); - WRITE("*********************"); - fprintf(Frpt.file, -"\n Full Full Hyd. Max. No. of Full"); - fprintf(Frpt.file, -"\n Conduit Shape Depth Area Rad. Width Barrels Flow"); - fprintf(Frpt.file, -"\n ---------------------------------------------------------------------------------------"); - for (i = 0; i < Nobjects[LINK]; i++) - { - if (Link[i].type == CONDUIT) - { - k = Link[i].subIndex; - fprintf(Frpt.file, "\n %-16s ", Link[i].ID); - if ( Link[i].xsect.type == CUSTOM ) - fprintf(Frpt.file, "%-16s ", Curve[Link[i].xsect.transect].ID); - else if ( Link[i].xsect.type == IRREGULAR ) - fprintf(Frpt.file, "%-16s ", - Transect[Link[i].xsect.transect].ID); - else if ( Link[i].xsect.type == STREET_XSECT ) - fprintf(Frpt.file, "%-16s ", - Street[Link[i].xsect.transect].ID); - else fprintf(Frpt.file, "%-16s ", - XsectTypeWords[Link[i].xsect.type]); - fprintf(Frpt.file, "%8.2f %8.2f %8.2f %8.2f %3d %8.2f", - Link[i].xsect.yFull*UCF(LENGTH), - Link[i].xsect.aFull*UCF(LENGTH)*UCF(LENGTH), - Link[i].xsect.rFull*UCF(LENGTH), - Link[i].xsect.wMax*UCF(LENGTH), - Conduit[k].barrels, - Link[i].qFull*UCF(FLOW)); - } - } - } - - if (Nobjects[SHAPE] > 0) - { - WRITE(""); - WRITE(""); - WRITE("*************"); - WRITE("Shape Summary"); - WRITE("*************"); - for (i = 0; i < Nobjects[SHAPE]; i++) - { - k = Shape[i].curve; - fprintf(Frpt.file, "\n\n Shape %s", Curve[k].ID); - fprintf(Frpt.file, "\n Area: "); - for ( m = 1; m < N_SHAPE_TBL; m++) - { - if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); - fprintf(Frpt.file, "%10.4f ", Shape[i].areaTbl[m]); - } - fprintf(Frpt.file, "\n Hrad: "); - for ( m = 1; m < N_SHAPE_TBL; m++) - { - if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); - fprintf(Frpt.file, "%10.4f ", Shape[i].hradTbl[m]); - } - fprintf(Frpt.file, "\n Width: "); - for ( m = 1; m < N_SHAPE_TBL; m++) - { - if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); - fprintf(Frpt.file, "%10.4f ", Shape[i].widthTbl[m]); - } - } - } - - if (Nobjects[TRANSECT] > 0) - { - WRITE(""); - WRITE(""); - WRITE("****************"); - WRITE("Transect Summary"); - WRITE("****************"); - for (i = 0; i < Nobjects[TRANSECT]; i++) - { - fprintf(Frpt.file, "\n\n Transect %s", Transect[i].ID); - fprintf(Frpt.file, "\n Area: "); - for ( m = 1; m < N_TRANSECT_TBL; m++) - { - if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); - fprintf(Frpt.file, "%10.4f ", Transect[i].areaTbl[m]); - } - fprintf(Frpt.file, "\n Hrad: "); - for ( m = 1; m < N_TRANSECT_TBL; m++) - { - if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); - fprintf(Frpt.file, "%10.4f ", Transect[i].hradTbl[m]); - } - fprintf(Frpt.file, "\n Width: "); - for ( m = 1; m < N_TRANSECT_TBL; m++) - { - if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); - fprintf(Frpt.file, "%10.4f ", Transect[i].widthTbl[m]); - } - } - } - - if (Nobjects[STREET] > 0) - { - WRITE(""); - WRITE(""); - WRITE("**************"); - WRITE("Street Summary"); - WRITE("**************"); - for (i = 0; i < Nobjects[STREET]; i++) - { - fprintf(Frpt.file, "\n\n Street %s", Street[i].ID); - fprintf(Frpt.file, "\n Area: "); - for (m = 1; m < Street[i].transect.nTbl; m++) - { - if (m % 5 == 1) fprintf(Frpt.file, "\n "); - fprintf(Frpt.file, "%10.4f ", Street[i].transect.areaTbl[m]); - } - fprintf(Frpt.file, "\n Hrad: "); - for (m = 1; m < Street[i].transect.nTbl; m++) - { - if (m % 5 == 1) fprintf(Frpt.file, "\n "); - fprintf(Frpt.file, "%10.4f ", Street[i].transect.hradTbl[m]); - } - fprintf(Frpt.file, "\n Width: "); - for (m = 1; m < Street[i].transect.nTbl; m++) - { - if (m % 5 == 1) fprintf(Frpt.file, "\n "); - fprintf(Frpt.file, "%10.4f ", Street[i].transect.widthTbl[m]); - } - } - } - WRITE(""); -} diff --git a/src/keywords.c b/src/keywords.c deleted file mode 100644 index 5f4b4e443..000000000 --- a/src/keywords.c +++ /dev/null @@ -1,172 +0,0 @@ -//----------------------------------------------------------------------------- -// keywords.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 06/01/22 (Build 5.2.1) -// Author: L. Rossman -// -// Exportable keyword dictionary -// -// NOTE: the keywords in each list must appear in same order used -// by its complementary enumerated variable in enums.h and -// must be terminated by NULL. The actual text of each keyword -// is defined in text.h. -// -// Update History -// ============== -// Build 5.1.007: -// - Keywords for Ignore RDII option and groundwater flow equation -// and climate adjustment input sections added. -// Build 5.1.008: -// - Keyword arrays placed in alphabetical order for better readability. -// - Keywords added for Minimum Routing Step and Number of Threads options. -// Build 5.1.010: -// - New Modified Green Ampt keyword added to InfilModelWords. -// - New Roadway weir keyword added to WeirTypeWords. -// Build 5.1.011: -// - New section keyword for [EVENTS] added. -// Build 5.1.013: -// - New option keywords w_SURCHARGE_METHOD, w_RULE_STEP, w_AVERAGES -// and w_WEIR added. -// Build 5.2.0: -// - Support added for Streets and Inlets. -// - Support added for variable speed pumps. -// - Support added for analytical storage shapes. -// - Support added for RptFlags.disabled option. -// Build 5.2.1: -// - Adds NONE to the list of NormalFlowWords. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include // need this to define NULL -#include "text.h" - -char* BuildupTypeWords[] = { w_NONE, w_POW, w_EXP, w_SAT, w_EXT, NULL}; -char* CurveTypeWords[] = { w_STORAGE, w_DIVERSION, w_TIDAL, w_RATING, - w_CONTROLS, w_SHAPE, w_WEIR, - w_PUMP1, w_PUMP2, w_PUMP3, w_PUMP4, - w_PUMP5, NULL}; -char* DividerTypeWords[] = { w_CUTOFF, w_TABULAR, w_WEIR, w_OVERFLOW, NULL}; -char* EvapTypeWords[] = { w_CONSTANT, w_MONTHLY, w_TIMESERIES, - w_TEMPERATURE, w_FILE, w_RECOVERY, - w_DRYONLY, NULL}; -char* FileTypeWords[] = { w_RAINFALL, w_RUNOFF, w_HOTSTART, w_RDII, - w_INFLOWS, w_OUTFLOWS, NULL}; -char* FileModeWords[] = { w_NO, w_SCRATCH, w_USE, w_SAVE, NULL}; -char* FlowUnitWords[] = { w_CFS, w_GPM, w_MGD, w_CMS, w_LPS, w_MLD, NULL}; -char* ForceMainEqnWords[] = { w_H_W, w_D_W, NULL}; -char* GageDataWords[] = { w_TIMESERIES, w_FILE, NULL}; -char* InfilModelWords[] = { w_HORTON, w_MOD_HORTON, w_GREEN_AMPT, - w_MOD_GREEN_AMPT, w_CURVE_NUMEBR, NULL}; -char* InertDampingWords[] = { w_NONE, w_PARTIAL, w_FULL, NULL}; -char* LinkOffsetWords[] = { w_DEPTH, w_ELEVATION, NULL}; -char* LinkTypeWords[] = { w_CONDUIT, w_PUMP, w_ORIFICE, - w_WEIR, w_OUTLET }; -char* LoadUnitsWords[] = { w_LBS, w_KG, w_LOGN }; -char* NodeTypeWords[] = { w_JUNCTION, w_OUTFALL, - w_STORAGE, w_DIVIDER }; -char* NoneAllWords[] = { w_NONE, w_ALL, NULL}; -char* NormalFlowWords[] = { w_SLOPE, w_FROUDE, w_BOTH, w_NONE, NULL}; -char* NormalizerWords[] = { w_PER_AREA, w_PER_CURB, NULL}; -char* NoYesWords[] = { w_NO, w_YES, NULL}; -char* OffOnWords[] = { w_OFF, w_ON, NULL}; -char* OldRouteModelWords[] = { w_NONE, w_NF, w_KW, w_EKW, w_DW, NULL}; -char* OptionWords[] = { w_FLOW_UNITS, w_INFIL_MODEL, - w_ROUTE_MODEL, w_START_DATE, - w_START_TIME, w_END_DATE, - w_END_TIME, w_REPORT_START_DATE, - w_REPORT_START_TIME, w_SWEEP_START, - w_SWEEP_END, w_START_DRY_DAYS, - w_WET_STEP, w_DRY_STEP, - w_ROUTE_STEP, w_RULE_STEP, - w_REPORT_STEP, - w_ALLOW_PONDING, w_INERT_DAMPING, - w_SLOPE_WEIGHTING, w_VARIABLE_STEP, - w_NORMAL_FLOW_LTD, w_LENGTHENING_STEP, - w_MIN_SURFAREA, w_COMPATIBILITY, - w_SKIP_STEADY_STATE, w_TEMPDIR, - w_IGNORE_RAINFALL, w_FORCE_MAIN_EQN, - w_LINK_OFFSETS, w_MIN_SLOPE, - w_IGNORE_SNOWMELT, w_IGNORE_GWATER, - w_IGNORE_ROUTING, w_IGNORE_QUALITY, - 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, - NULL }; -char* OrificeTypeWords[] = { w_SIDE, w_BOTTOM, NULL}; -char* OutfallTypeWords[] = { w_FREE, w_NORMAL, w_FIXED, w_TIDAL, - w_TIMESERIES, NULL}; -char* PatternTypeWords[] = { w_MONTHLY, w_DAILY, w_HOURLY, w_WEEKEND, NULL}; -char* PondingUnitsWords[] = { w_PONDED_FEET, w_PONDED_METERS }; -char* ProcessVarWords[] = { w_HRT, w_DT, w_FLOW, w_DEPTH, w_AREA, NULL}; -char* PumpTypeWords[] = { w_TYPE1, w_TYPE2, w_TYPE3, w_TYPE4, w_TYPE5, w_IDEAL }; -char* QualUnitsWords[] = { w_MGperL, w_UGperL, w_COUNTperL, NULL}; -char* RainTypeWords[] = { w_INTENSITY, w_VOLUME, w_CUMULATIVE, NULL}; -char* RainUnitsWords[] = { w_INCHES, w_MMETER, NULL}; -char* RelationWords[] = { w_TABULAR, w_FUNCTIONAL, - w_CYLINDRICAL, w_CONICAL, w_PARABOLIC, - w_PYRAMIDAL, NULL}; -char* ReportWords[] = { w_DISABLED, w_INPUT, w_SUBCATCH, w_NODE, w_LINK, - w_CONTINUITY, w_FLOWSTATS,w_CONTROLS, - w_AVERAGES, w_NODESTATS, NULL}; -char* RouteModelWords[] = { w_NONE, w_STEADY, w_KINWAVE, w_XKINWAVE, - w_DYNWAVE, NULL}; -char* RuleKeyWords[] = { w_RULE, w_IF, w_AND, w_OR, w_THEN, w_ELSE, - w_PRIORITY, NULL}; -char* SectWords[] = { ws_TITLE, ws_OPTION, - ws_FILE, ws_RAINGAGE, - ws_TEMP, ws_EVAP, - ws_SUBCATCH, ws_SUBAREA, - ws_INFIL, ws_AQUIFER, - ws_GROUNDWATER, ws_SNOWMELT, - ws_JUNCTION, ws_OUTFALL, - ws_STORAGE, ws_DIVIDER, - ws_CONDUIT, ws_PUMP, - ws_ORIFICE, ws_WEIR, - ws_OUTLET, ws_XSECTION, - ws_TRANSECT, ws_LOSS, - ws_CONTROL, ws_POLLUTANT, - ws_LANDUSE, ws_BUILDUP, - ws_WASHOFF, ws_COVERAGE, - ws_INFLOW, ws_DWF, - ws_PATTERN, ws_RDII, - ws_UNITHYD, ws_LOADING, - ws_TREATMENT, ws_CURVE, - ws_TIMESERIES, ws_REPORT, - ws_COORDINATE, ws_VERTICES, - ws_POLYGON, ws_LABEL, - ws_SYMBOL, ws_BACKDROP, - ws_TAG, ws_PROFILE, - ws_MAP, ws_LID_CONTROL, - ws_LID_USAGE, ws_GWF, - ws_ADJUST, ws_EVENT, - ws_STREET, ws_INLET_USAGE, - ws_INLET, NULL}; -char* SnowmeltWords[] = { w_PLOWABLE, w_IMPERV, w_PERV, w_REMOVAL, NULL}; -char* SurchargeWords[] = { w_EXTRAN, w_SLOT, NULL}; -char* TempKeyWords[] = { w_TIMESERIES, w_FILE, w_WINDSPEED, w_SNOWMELT, - w_ADC, NULL}; -char* TransectKeyWords[] = { w_NC, w_X1, w_GR, NULL}; -char* TreatTypeWords[] = { w_REMOVAL, w_CONCEN, NULL}; -char* UHTypeWords[] = { w_SHORT, w_MEDIUM, w_LONG, NULL}; -char* VolUnitsWords[] = { w_MGAL, w_MLTRS }; -char* VolUnitsWords2[] = { w_GAL, w_LTR }; -char* WashoffTypeWords[] = { w_NONE, w_EXP, w_RC, w_EMC, NULL}; -char* WeirTypeWords[] = { w_TRANSVERSE, w_SIDEFLOW, w_VNOTCH, - w_TRAPEZOIDAL, w_ROADWAY, NULL}; -char* XsectTypeWords[] = { w_DUMMY, w_CIRCULAR, - w_FILLED_CIRCULAR, w_RECT_CLOSED, - w_RECT_OPEN, w_TRAPEZOIDAL, - w_TRIANGULAR, w_PARABOLIC, - w_POWERFUNC, w_RECT_TRIANG, - w_RECT_ROUND, w_MOD_BASKET, - w_HORIZELLIPSE, w_VERTELLIPSE, - w_ARCH, w_EGGSHAPED, - w_HORSESHOE, w_GOTHIC, - w_CATENARY, w_SEMIELLIPTICAL, - w_BASKETHANDLE, w_SEMICIRCULAR, - w_IRREGULAR, w_CUSTOM, - w_FORCE_MAIN, w_STREET, - NULL}; diff --git a/src/keywords.h b/src/keywords.h deleted file mode 100644 index e330c59bc..000000000 --- a/src/keywords.h +++ /dev/null @@ -1,73 +0,0 @@ -//----------------------------------------------------------------------------- -// keywords.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Exportable keyword dictionary -// -// Update History -// ============== -// Build 5.1.008: -// - Keyword arrays listed in alphabetical order. -// Build 5.1.013: -// - New keyword array defined for surcharge method. -//----------------------------------------------------------------------------- - -#ifndef KEYWORDS_H -#define KEYWORDS_H - - -extern char* BuildupTypeWords[]; -extern char* CurveTypeWords[]; -extern char* DividerTypeWords[]; -extern char* DynWaveMethodWords[]; -extern char* EvapTypeWords[]; -extern char* FileModeWords[]; -extern char* FileTypeWords[]; -extern char* FlowUnitWords[]; -extern char* ForceMainEqnWords[]; -extern char* GageDataWords[]; -extern char* InertDampingWords[]; -extern char* InfilModelWords[]; -extern char* LinkOffsetWords[]; -extern char* LinkTypeWords[]; -extern char* LoadUnitsWords[]; -extern char* NodeTypeWords[]; -extern char* NoneAllWords[]; -extern char* NormalFlowWords[]; -extern char* NormalizerWords[]; -extern char* NoYesWords[]; -extern char* OldRouteModelWords[]; -extern char* OffOnWords[]; -extern char* OptionWords[]; -extern char* OrificeTypeWords[]; -extern char* OutfallTypeWords[]; -extern char* PatternTypeWords[]; -extern char* PondingUnitsWords[]; -extern char* ProcessVarWords[]; -extern char* PumpTypeWords[]; -extern char* QualUnitsWords[]; -extern char* RainTypeWords[]; -extern char* RainUnitsWords[]; -extern char* ReportWords[]; -extern char* RelationWords[]; -extern char* RouteModelWords[]; -extern char* RuleKeyWords[]; -extern char* SectWords[]; -extern char* SnowmeltWords[]; -extern char* SurchargeWords[]; -extern char* TempKeyWords[]; -extern char* TransectKeyWords[]; -extern char* TreatTypeWords[]; -extern char* UHTypeWords[]; -extern char* VolUnitsWords[]; -extern char* VolUnitsWords2[]; -extern char* WashoffTypeWords[]; -extern char* WeirTypeWords[]; -extern char* XsectTypeWords[]; - - -#endif //KEYWORDS_H diff --git a/src/kinwave.c b/src/kinwave.c deleted file mode 100644 index b137e4ce9..000000000 --- a/src/kinwave.c +++ /dev/null @@ -1,272 +0,0 @@ -//----------------------------------------------------------------------------- -// kinwave.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// M. Tryby (EPA) -// -// Kinematic wave flow routing functions. -// -// Update History -// ============== -// 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 - -#include -#include "headers.h" -#include "findroot.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -static const double WX = 0.6; // distance weighting -static const double WT = 0.6; // time weighting -static const double EPSIL = 0.001; // convergence criterion - -//----------------------------------------------------------------------------- -// Shared variables -//----------------------------------------------------------------------------- -static double Beta1; -static double C1; -static double C2; -static double Afull; -static double Qfull; -static TXsect* pXsect; - -//----------------------------------------------------------------------------- -// External functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// kinwave_execute (called by flowrout_execute) - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static int solveContinuity(double qin, double ain, double* aout); -static void evalContinuity(double a, double* f, double* df, void* p); - -//============================================================================= - -int kinwave_execute(int j, double* qinflow, double* qoutflow, double tStep) -// -// Input: j = link index -// qinflow = inflow at current time (cfs) -// tStep = time step (sec) -// Output: qoutflow = outflow at current time (cfs), -// returns number of iterations used -// Purpose: finds outflow over time step tStep given flow entering a -// conduit using Kinematic Wave flow routing. -// -// -// ^ q3 -// t | -// | qin, ain |-------------------| qout, aout -// | | Flow ---> | -// |----> x q1, a1 |-------------------| q2, a2 -// -// -{ - int k; - int result = 1; - double dxdt, dq; - double ain, aout; - double qin, qout; - double a1, a2, q1, q2, q3; - - // --- no routing for non-conduit link - (*qoutflow) = (*qinflow); - if ( Link[j].type != CONDUIT ) return result; - - // --- no routing for dummy xsection - if ( Link[j].xsect.type == DUMMY ) return result; - - // --- assign module-level variables - pXsect = &Link[j].xsect; - Qfull = Link[j].qFull; - Afull = Link[j].xsect.aFull; - k = Link[j].subIndex; - Beta1 = Conduit[k].beta / Qfull; - - // --- normalize previous flows - q1 = Conduit[k].q1 / Qfull; - q2 = Conduit[k].q2 / Qfull; - - // --- normalize inflow - qin = (*qinflow) / Conduit[k].barrels / Qfull; - - // --- compute evaporation and infiltration loss rate - q3 = link_getLossRate(j, qin*Qfull) / Qfull; - - // --- normalize previous areas - a1 = Conduit[k].a1 / Afull; - a2 = Conduit[k].a2 / Afull; - - // --- use full area when inlet flow >= full flow - if ( qin >= 1.0 ) ain = 1.0; - - // --- get normalized inlet area corresponding to inlet flow - else ain = xsect_getAofS(pXsect, qin/Beta1) / Afull; - - // --- check for no flow - if ( qin <= TINY && q2 <= TINY ) - { - qout = 0.0; - aout = 0.0; - } - - // --- otherwise solve finite difference form of continuity eqn. - else - { - // --- compute constant factors - dxdt = link_getLength(j) / tStep * Afull / Qfull; - dq = q2 - q1; - C1 = dxdt * WT / WX; - C2 = (1.0 - WT) * (ain - a1); - C2 = C2 - WT * a2; - C2 = C2 * dxdt / WX; - C2 = C2 + (1.0 - WX) / WX * dq - qin; - C2 = C2 + q3 / WX; - - // --- starting guess for aout is value from previous time step - aout = a2; - - // --- solve continuity equation for aout - result = solveContinuity(qin, ain, &aout); - - // --- report error if continuity eqn. not solved - if ( result == -1 ) - { - report_writeErrorMsg(ERR_KINWAVE, Link[j].ID); - return 1; - } - if ( result <= 0 ) result = 1; - - // --- compute normalized outlet flow from outlet area - qout = Beta1 * xsect_getSofA(pXsect, aout*Afull); - if ( qin > 1.0 ) qin = 1.0; - } - - // --- save new flows and areas - Conduit[k].q1 = qin * Qfull; - Conduit[k].a1 = ain * Afull; - Conduit[k].q2 = qout * Qfull; - Conduit[k].a2 = aout * Afull; - Conduit[k].fullState = - link_getFullState(Conduit[k].a1, Conduit[k].a2, Afull); - (*qinflow) = Conduit[k].q1 * Conduit[k].barrels; - (*qoutflow) = Conduit[k].q2 * Conduit[k].barrels; - return result; -} - -//============================================================================= - -int solveContinuity(double qin, double ain, double* aout) -// -// Input: qin = upstream normalized flow -// ain = upstream normalized area -// aout = downstream normalized area -// Output: new value for aout; returns an error code -// Purpose: solves continuity equation f(a) = Beta1*S(a) + C1*a + C2 = 0 -// for 'a' using the Newton-Raphson root finder function. -// Return code has the following meanings: -// >= 0 number of function evaluations used -// -1 Newton function failed -// -2 flow always above max. flow -// -3 flow always below zero -// -// Note: pXsect (pointer to conduit's cross-section), and constants Beta1, -// C1, and C2 are module-level shared variables assigned values -// in kinwave_execute(). -// -{ - int n; // # evaluations or error code - double aLo, aHi, aTmp; // lower/upper bounds on a - double fLo, fHi; // lower/upper bounds on f - double tol = EPSIL; // absolute convergence tol. - - // --- first determine bounds on 'a' so that f(a) passes through 0. - - // --- set upper bound to area at full flow - aHi = 1.0; - fHi = 1.0 + C1 + C2; - - // --- try setting lower bound to area where section factor is maximum - aLo = xsect_getAmax(pXsect) / Afull; - if ( aLo < aHi ) - { - fLo = ( Beta1 * pXsect->sMax ) + (C1 * aLo) + C2; - } - else fLo = fHi; - - // --- if fLo and fHi have same sign then set lower bound to 0 - if ( fHi*fLo > 0.0 ) - { - aHi = aLo; - fHi = fLo; - aLo = 0.0; - fLo = C2; - } - - // --- proceed with search for root if fLo and fHi have different signs - if ( fHi*fLo <= 0.0 ) - { - // --- start search at midpoint of lower/upper bounds - // if initial value outside of these bounds - if ( *aout < aLo || *aout > aHi ) *aout = 0.5*(aLo + aHi); - - // --- if fLo > fHi then switch aLo and aHi - if ( fLo > fHi ) - { - aTmp = aLo; - aLo = aHi; - aHi = aTmp; - } - - // --- call the Newton root finder method passing it the - // evalContinuity function to evaluate the function - // and its derivatives - n = findroot_Newton(aLo, aHi, aout, tol, evalContinuity, NULL); - - // --- check if root finder succeeded - if ( n <= 0 ) n = -1; - } - - // --- if lower/upper bound functions both negative then use full flow - else if ( fLo < 0.0 ) - { - if ( qin > 1.0 ) *aout = ain; - else *aout = 1.0; - n = -2; - } - - // --- if lower/upper bound functions both positive then use no flow - else if ( fLo > 0 ) - { - *aout = 0.0; - n = -3; - } - else n = -1; - return n; -} - -//============================================================================= - -void evalContinuity(double a, double* f, double* df, void* p) -// -// Input: a = outlet normalized area -// Output: f = value of continuity eqn. -// df = derivative of continuity eqn. -// Purpose: computes value of continuity equation (f) and its derivative (df) -// w.r.t. normalized area for link with normalized outlet area 'a'. -// -{ - *f = (Beta1 * xsect_getSofA(pXsect, a*Afull)) + (C1 * a) + C2; - *df = (Beta1 * Afull * xsect_getdSdA(pXsect, a*Afull)) + C1; -} - -//============================================================================= diff --git a/src/landuse.c b/src/landuse.c deleted file mode 100644 index 4c8402790..000000000 --- a/src/landuse.c +++ /dev/null @@ -1,723 +0,0 @@ -//----------------------------------------------------------------------------- -// landuse.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Pollutant buildup and washoff functions. -// -// Update History -// ============== -// Build 5.1.008: -// - landuse_getWashoffMass() re-named to landuse_getWashoffQual() and -// modified to return concentration instead of mass load. -// - landuse_getRunoffLoad() re-named to landuse_getWashoffLoad() and -// modified to work with landuse_getWashoffQual(). -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include "headers.h" - -//----------------------------------------------------------------------------- -// External functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// landuse_readParams (called by parseLine in input.c) -// landuse_readPollutParams (called by parseLine in input.c) -// landuse_readBuildupParams (called by parseLine in input.c) -// landuse_readWashoffParams (called by parseLine in input.c) - -// landuse_getInitBuildup (called by subcatch_initState) -// landuse_getBuildup (called by surfqual_getBuildup) -// landuse_getWashoffLoad (called by surfqual_getWashoff) -// landuse_getCoPollutLoad (called by surfqual_getwashoff)); -// landuse_getAvgBMPEffic (called by updatePondedQual in surfqual.c) - -//----------------------------------------------------------------------------- -// Function declarations -//----------------------------------------------------------------------------- -static double landuse_getBuildupDays(int landuse, int pollut, double buildup); -static double landuse_getBuildupMass(int landuse, int pollut, double days); -static double landuse_getWashoffQual(int landuse, int pollut, double buildup, - double runoff, double area); -static double landuse_getExternalBuildup(int i, int p, double buildup, - double tStep); - -//============================================================================= - -int landuse_readParams(int j, char* tok[], int ntoks) -// -// Input: j = land use index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads landuse parameters from a tokenized line of input. -// -// Data format is: -// landuseID (sweepInterval sweepRemoval sweepDays0) -// -{ - char *id; - if ( ntoks < 1 ) return error_setInpError(ERR_ITEMS, ""); - id = project_findID(LANDUSE, tok[0]); - if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); - Landuse[j].ID = id; - if ( ntoks > 1 ) - { - if ( ntoks < 4 ) return error_setInpError(ERR_ITEMS, ""); - if ( ! getDouble(tok[1], &Landuse[j].sweepInterval) ) - return error_setInpError(ERR_NUMBER, tok[1]); - if ( ! getDouble(tok[2], &Landuse[j].sweepRemoval) ) - return error_setInpError(ERR_NUMBER, tok[2]); - if ( ! getDouble(tok[3], &Landuse[j].sweepDays0) ) - return error_setInpError(ERR_NUMBER, tok[3]); - } - else - { - Landuse[j].sweepInterval = 0.0; - Landuse[j].sweepRemoval = 0.0; - Landuse[j].sweepDays0 = 0.0; - } - if ( Landuse[j].sweepRemoval < 0.0 - || Landuse[j].sweepRemoval > 1.0 ) - return error_setInpError(ERR_NUMBER, tok[2]); - return 0; -} - -//============================================================================= - -int landuse_readPollutParams(int j, char* tok[], int ntoks) -// -// Input: j = pollutant index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads pollutant parameters from a tokenized line of input. -// -// Data format is: -// ID Units cRain cGW cRDII kDecay (snowOnly coPollut coFrac cDWF cInit) -// -{ - int i, k, coPollut, snowFlag; - double x[4], coFrac, cDWF, cInit; - char *id; - - // --- extract pollutant name & units - if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); - id = project_findID(POLLUT, tok[0]); - if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); - k = findmatch(tok[1], QualUnitsWords); - if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); - - // --- extract concen. in rain, gwater, & I&I - for ( i = 2; i <= 4; i++ ) - { - if ( ! getDouble(tok[i], &x[i-2]) || x[i-2] < 0.0 ) - { - return error_setInpError(ERR_NUMBER, tok[i]); - } - } - - // --- extract decay coeff. (which can be negative for growth) - if ( ! getDouble(tok[5], &x[3]) ) - { - return error_setInpError(ERR_NUMBER, tok[5]); - } - - // --- set defaults for snow only flag & co-pollut. parameters - snowFlag = 0; - coPollut = -1; - coFrac = 0.0; - cDWF = 0.0; - cInit = 0.0; - - // --- check for snow only flag - if ( ntoks >= 7 ) - { - snowFlag = findmatch(tok[6], NoYesWords); - if ( snowFlag < 0 ) return error_setInpError(ERR_KEYWORD, tok[6]); - } - - // --- check for co-pollutant - if ( ntoks >= 9 ) - { - if ( !strcomp(tok[7], "*") ) - { - coPollut = project_findObject(POLLUT, tok[7]); - if ( coPollut < 0 ) return error_setInpError(ERR_NAME, tok[7]); - if ( ! getDouble(tok[8], &coFrac) || coFrac < 0.0 ) - return error_setInpError(ERR_NUMBER, tok[8]); - } - } - - // --- check for DWF concen. - if ( ntoks >= 10 ) - { - if ( ! getDouble(tok[9], &cDWF) || cDWF < 0.0) - return error_setInpError(ERR_NUMBER, tok[9]); - } - - // --- check for initial concen. - if ( ntoks >= 11 ) - { - if ( ! getDouble(tok[10], &cInit) || cInit < 0.0 ) - return error_setInpError(ERR_NUMBER, tok[9]); - } - - // --- save values for pollutant object - Pollut[j].ID = id; - Pollut[j].units = k; - if ( Pollut[j].units == MG ) Pollut[j].mcf = UCF(MASS); - else if ( Pollut[j].units == UG ) Pollut[j].mcf = UCF(MASS) / 1000.0; - else Pollut[j].mcf = 1.0; - Pollut[j].pptConcen = x[0]; - Pollut[j].gwConcen = x[1]; - Pollut[j].rdiiConcen = x[2]; - Pollut[j].kDecay = x[3]/SECperDAY; - Pollut[j].snowOnly = snowFlag; - Pollut[j].coPollut = coPollut; - Pollut[j].coFraction = coFrac; - Pollut[j].dwfConcen = cDWF; - Pollut[j].initConcen = cInit; - return 0; -} - -//============================================================================= - -int landuse_readBuildupParams(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads pollutant buildup parameters from a tokenized line of input. -// -// Data format is: -// landuseID pollutID buildupType c1 c2 c3 normalizerType -// -{ - int i, j, k, n, p; - double c[3] = {0, 0, 0}, tmax; - - if ( ntoks < 3 ) return 0; - j = project_findObject(LANDUSE, tok[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); - p = project_findObject(POLLUT, tok[1]); - if ( p < 0 ) return error_setInpError(ERR_NAME, tok[1]); - k = findmatch(tok[2], BuildupTypeWords); - if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[2]); - Landuse[j].buildupFunc[p].funcType = k; - if ( k > NO_BUILDUP ) - { - if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); - if ( k != EXTERNAL_BUILDUP ) for (i=0; i<3; i++) - { - if ( ! getDouble(tok[i+3], &c[i]) || c[i] < 0.0 ) - { - return error_setInpError(ERR_NUMBER, tok[i+3]); - } - } - n = findmatch(tok[6], NormalizerWords); - if (n < 0 ) return error_setInpError(ERR_KEYWORD, tok[6]); - Landuse[j].buildupFunc[p].normalizer = n; - } - - // Find time until max. buildup (or time series for external buildup) - switch (Landuse[j].buildupFunc[p].funcType) - { - case POWER_BUILDUP: - // --- check for too small or large an exponent - if ( c[2] > 0.0 && (c[2] < 0.01 || c[2] > 10.0) ) - return error_setInpError(ERR_KEYWORD, tok[5]); - - // --- find time to reach max. buildup - // --- use zero if coeffs. are 0 - if ( c[1]*c[2] == 0.0 ) tmax = 0.0; - - // --- use 10 years if inverse power function tends to blow up - else if ( log10(c[0]) / c[2] > 3.5 ) tmax = 3650.0; - - // --- otherwise use inverse power function - else tmax = pow(c[0]/c[1], 1.0/c[2]); - break; - - case EXPON_BUILDUP: - if ( c[1] == 0.0 ) tmax = 0.0; - else tmax = -log(0.001)/c[1]; - break; - - case SATUR_BUILDUP: - tmax = 1000.0*c[2]; - break; - - case EXTERNAL_BUILDUP: - if ( !getDouble(tok[3], &c[0]) || c[0] < 0.0 ) //max. buildup - return error_setInpError(ERR_NUMBER, tok[3]); - if ( !getDouble(tok[4], &c[1]) || c[1] < 0.0 ) //scaling factor - return error_setInpError(ERR_NUMBER, tok[3]); - n = project_findObject(TSERIES, tok[5]); //time series - if ( n < 0 ) return error_setInpError(ERR_NAME, tok[4]); - Tseries[n].refersTo = EXTERNAL_BUILDUP; - c[2] = n; - tmax = 0.0; - break; - - default: - tmax = 0.0; - } - - // Assign parameters to buildup object - Landuse[j].buildupFunc[p].coeff[0] = c[0]; - Landuse[j].buildupFunc[p].coeff[1] = c[1]; - Landuse[j].buildupFunc[p].coeff[2] = c[2]; - Landuse[j].buildupFunc[p].maxDays = tmax; - return 0; -} - -//============================================================================= - -int landuse_readWashoffParams(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads pollutant washoff parameters from a tokenized line of input. -// -// Data format is: -// landuseID pollutID washoffType c1 c2 sweepEffic bmpRemoval -{ - int i, j, p; - int func; - double x[4]; - - if ( ntoks < 3 ) return 0; - for (i=0; i<4; i++) x[i] = 0.0; - func = NO_WASHOFF; - j = project_findObject(LANDUSE, tok[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); - p = project_findObject(POLLUT, tok[1]); - if ( p < 0 ) return error_setInpError(ERR_NAME, tok[1]); - if ( ntoks > 2 ) - { - func = findmatch(tok[2], WashoffTypeWords); - if ( func < 0 ) return error_setInpError(ERR_KEYWORD, tok[2]); - if ( func != NO_WASHOFF ) - { - if ( ntoks < 5 ) return error_setInpError(ERR_ITEMS, ""); - if ( ! getDouble(tok[3], &x[0]) ) - return error_setInpError(ERR_NUMBER, tok[3]); - if ( ! getDouble(tok[4], &x[1]) ) - return error_setInpError(ERR_NUMBER, tok[4]); - if ( ntoks >= 6 ) - { - if ( ! getDouble(tok[5], &x[2]) ) - return error_setInpError(ERR_NUMBER, tok[5]); - } - if ( ntoks >= 7 ) - { - if ( ! getDouble(tok[6], &x[3]) ) - return error_setInpError(ERR_NUMBER, tok[6]); - } - } - } - - // --- check for valid parameter values - // x[0] = washoff coeff. - // x[1] = washoff expon. - // x[2] = sweep effic. - // x[3] = BMP effic. - if ( x[0] < 0.0 ) return error_setInpError(ERR_NUMBER, tok[3]); - if ( x[1] < -10.0 || x[1] > 10.0 ) - return error_setInpError(ERR_NUMBER, tok[4]);; - if ( x[2] < 0.0 || x[2] > 100.0 ) - return error_setInpError(ERR_NUMBER, tok[5]); - if ( x[3] < 0.0 || x[3] > 100.0 ) - return error_setInpError(ERR_NUMBER, tok[6]); - - // --- convert units of washoff coeff. - if ( func == EXPON_WASHOFF ) x[0] /= 3600.0; - if ( func == RATING_WASHOFF ) x[0] *= pow(UCF(FLOW), x[1]); - if ( func == EMC_WASHOFF ) x[0] *= LperFT3; - - // --- assign washoff parameters to washoff object - Landuse[j].washoffFunc[p].funcType = func; - Landuse[j].washoffFunc[p].coeff = x[0]; - Landuse[j].washoffFunc[p].expon = x[1]; - Landuse[j].washoffFunc[p].sweepEffic = x[2] / 100.0; - Landuse[j].washoffFunc[p].bmpEffic = x[3] / 100.0; - return 0; -} - -//============================================================================= - -void landuse_getInitBuildup(TLandFactor* landFactor, double* initBuildup, - double area, double curb) -// -// Input: landFactor = array of land use factors -// initBuildup = total initial buildup of each pollutant -// area = subcatchment's area (ft2) -// curb = subcatchment's curb length (users units) -// Output: modifies each land use factor's initial pollutant buildup -// Purpose: determines the initial buildup of each pollutant on -// each land use for a given subcatchment. -// -// Notes: Contributions from co-pollutants to initial buildup are not -// included since the co-pollutant mechanism only applies to -// washoff. -// -{ - int i, p; - double startDrySeconds; // antecedent dry period (sec) - double f; // faction of total land area - double fArea; // area of land use (ft2) - double fCurb; // curb length of land use - double buildup; // pollutant mass buildup - - // --- convert antecedent dry days into seconds - startDrySeconds = StartDryDays*SECperDAY; - - // --- examine each land use - for (i = 0; i < Nobjects[LANDUSE]; i++) - { - // --- initialize date when last swept - landFactor[i].lastSwept = StartDateTime - Landuse[i].sweepDays0; - - // --- determine area and curb length covered by land use - f = landFactor[i].fraction; - fArea = f * area * UCF(LANDAREA); - fCurb = f * curb; - - // --- determine buildup of each pollutant - for (p = 0; p < Nobjects[POLLUT]; p++) - { - // --- if an initial loading was supplied, then use it to - // find the starting buildup over the land use - buildup = 0.0; - if ( initBuildup[p] > 0.0 ) buildup = initBuildup[p] * fArea; - - // --- otherwise use the land use's buildup function to - // compute a buildup over the antecedent dry period - else buildup = landuse_getBuildup(i, p, fArea, fCurb, buildup, - startDrySeconds); - landFactor[i].buildup[p] = buildup; - } - } -} - -//============================================================================= - -double landuse_getBuildup(int i, int p, double area, double curb, double buildup, - double tStep) -// -// Input: i = land use index -// p = pollutant index -// area = land use area (ac or ha) -// curb = land use curb length (users units) -// buildup = current pollutant buildup (lbs or kg) -// tStep = time increment for buildup (sec) -// Output: returns new buildup mass (lbs or kg) -// Purpose: computes new pollutant buildup on a landuse after a time increment. -// -{ - int n; // normalizer code - double days; // accumulated days of buildup - double perUnit; // normalizer value (area or curb length) - - // --- return current buildup if no buildup function or time increment - if ( Landuse[i].buildupFunc[p].funcType == NO_BUILDUP || tStep == 0.0 ) - { - return buildup; - } - - // --- see what buildup is normalized to - n = Landuse[i].buildupFunc[p].normalizer; - perUnit = 1.0; - if ( n == PER_AREA ) perUnit = area; - if ( n == PER_CURB ) perUnit = curb; - if ( perUnit == 0.0 ) return 0.0; - - // --- buildup determined by loading time series - if ( Landuse[i].buildupFunc[p].funcType == EXTERNAL_BUILDUP ) - { - return landuse_getExternalBuildup(i, p, buildup/perUnit, tStep) * - perUnit; - } - - // --- determine equivalent days of current buildup - days = landuse_getBuildupDays(i, p, buildup/perUnit); - - // --- compute buildup after adding on time increment - days += tStep / SECperDAY; - return landuse_getBuildupMass(i, p, days) * perUnit; -} - -//============================================================================= - -double landuse_getBuildupDays(int i, int p, double buildup) -// -// Input: i = land use index -// p = pollutant index -// buildup = amount of pollutant buildup -// Output: returns number of days it takes for buildup to reach a given level -// Purpose: finds the number of days corresponding to a pollutant buildup. -// -{ - double c0 = Landuse[i].buildupFunc[p].coeff[0]; - double c1 = Landuse[i].buildupFunc[p].coeff[1]; - double c2 = Landuse[i].buildupFunc[p].coeff[2]; - - if ( buildup == 0.0 ) return 0.0; - if ( buildup >= c0 ) return Landuse[i].buildupFunc[p].maxDays; - switch (Landuse[i].buildupFunc[p].funcType) - { - case POWER_BUILDUP: - if ( c1*c2 == 0.0 ) return 0.0; - else return pow( (buildup/c1), (1.0/c2) ); - - case EXPON_BUILDUP: - if ( c0*c1 == 0.0 ) return 0.0; - else return -log(1. - buildup/c0) / c1; - - case SATUR_BUILDUP: - if ( c0 == 0.0 ) return 0.0; - else return buildup*c2 / (c0 - buildup); - - default: - return 0.0; - } -} - -//============================================================================= - -double landuse_getBuildupMass(int i, int p, double days) -// -// Input: i = land use index -// p = pollutant index -// days = time over which buildup has occurred (days) -// Output: returns mass of pollutant buildup (lbs or kg per area or curblength) -// Purpose: finds amount of buildup of pollutant on a land use. -// -{ - double b; - double c0 = Landuse[i].buildupFunc[p].coeff[0]; - double c1 = Landuse[i].buildupFunc[p].coeff[1]; - double c2 = Landuse[i].buildupFunc[p].coeff[2]; - - if ( days == 0.0 ) return 0.0; - if ( days >= Landuse[i].buildupFunc[p].maxDays ) return c0; - switch (Landuse[i].buildupFunc[p].funcType) - { - case POWER_BUILDUP: - b = c1 * pow(days, c2); - if ( b > c0 ) b = c0; - break; - - case EXPON_BUILDUP: - b = c0*(1.0 - exp(-days*c1)); - break; - - case SATUR_BUILDUP: - b = days*c0/(c2 + days); - break; - - default: b = 0.0; - } - return b; -} - -//============================================================================= - -double landuse_getAvgBmpEffic(int j, int p) -// -// Input: j = subcatchment index -// p = pollutant index -// Output: returns a BMP removal fraction for pollutant p -// Purpose: finds the overall average BMP removal achieved for pollutant p -// treated in subcatchment j. -// -{ - int i; - double r = 0.0; - for (i = 0; i < Nobjects[LANDUSE]; i++) - { - r += Subcatch[j].landFactor[i].fraction * - Landuse[i].washoffFunc[p].bmpEffic; - } - return r; -} - -//============================================================================= - -double landuse_getWashoffLoad(int i, int p, double area, - TLandFactor landFactor[], double runoff, double vOutflow) -// -// Input: i = land use index -// p = pollut. index -// area = sucatchment area (ft2) -// landFactor[] = array of land use data for subcatchment -// runoff = runoff flow generated by subcatchment (ft/sec) -// vOutflow = runoff volume leaving the subcatchment (ft3) -// Output: returns pollutant runoff load (mass) -// Purpose: computes pollutant load generated by a land use over a time step. -// -{ - double landuseArea; // area of current land use (ft2) - double buildup; // current pollutant buildup (lb or kg) - double washoffQual; // pollutant concentration in washoff (mass/ft3) - double washoffLoad; // pollutant washoff load over time step (lb or kg) - double bmpRemoval; // pollutant load removed by BMP treatment (lb or kg) - - // --- compute concen. of pollutant in washoff (mass/ft3) - buildup = landFactor[i].buildup[p]; - landuseArea = landFactor[i].fraction * area; - washoffQual = landuse_getWashoffQual(i, p, buildup, runoff, landuseArea); - - // --- compute washoff load exported (lbs or kg) from landuse - // (Pollut[].mcf converts from mg (or ug) mass units to lbs (or kg) - washoffLoad = washoffQual * vOutflow * landuseArea / area * Pollut[p].mcf; - - // --- if buildup modelled, reduce it by amount of washoff - if ( Landuse[i].buildupFunc[p].funcType != NO_BUILDUP || - buildup > washoffLoad ) - { - washoffLoad = MIN(washoffLoad, buildup); - buildup -= washoffLoad; - landFactor[i].buildup[p] = buildup; - } - - // --- otherwise add washoff to buildup mass balance totals - // so that things will balance - else - { - massbal_updateLoadingTotals(BUILDUP_LOAD, p, washoffLoad); - landFactor[i].buildup[p] = 0.0; - } - - // --- apply any BMP removal to washoff - bmpRemoval = Landuse[i].washoffFunc[p].bmpEffic * washoffLoad; - if ( bmpRemoval > 0.0 ) - { - massbal_updateLoadingTotals(BMP_REMOVAL_LOAD, p, bmpRemoval); - washoffLoad -= bmpRemoval; - } - - // --- return washoff load converted back to mass (mg or ug) - return washoffLoad / Pollut[p].mcf; -} - -//============================================================================= - -double landuse_getWashoffQual(int i, int p, double buildup, double runoff, - double area) -// -// Input: i = land use index -// p = pollutant index -// buildup = current buildup over land use (lbs or kg) -// runoff = current runoff on subcatchment (ft/sec) -// area = area devoted to land use (ft2) -// Output: returns pollutant concentration in washoff (mass/ft3) -// Purpose: finds concentration of pollutant washed off a land use. -// -// Notes: "coeff" for each washoff function was previously adjusted to -// result in units of mass/sec -// -{ - double cWashoff = 0.0; - double coeff = Landuse[i].washoffFunc[p].coeff; - double expon = Landuse[i].washoffFunc[p].expon; - int func = Landuse[i].washoffFunc[p].funcType; - - // --- if no washoff function or no runoff, return 0 - if ( func == NO_WASHOFF || runoff == 0.0 ) return 0.0; - - // --- if buildup function exists but no current buildup, return 0 - if ( Landuse[i].buildupFunc[p].funcType != NO_BUILDUP && buildup == 0.0 ) - return 0.0; - - // --- Exponential Washoff function - if ( func == EXPON_WASHOFF ) - { - // --- evaluate washoff eqn. with runoff in in/hr (or mm/hr) - // and buildup converted from lbs (or kg) to concen. mass units - cWashoff = coeff * pow(runoff * UCF(RAINFALL), expon) * - buildup / Pollut[p].mcf; - cWashoff /= runoff * area; - } - - // --- Rating Curve Washoff function - else if ( func == RATING_WASHOFF ) - { - cWashoff = coeff * pow(runoff * area, expon-1.0); - } - - // --- Event Mean Concentration Washoff - else if ( func == EMC_WASHOFF ) - { - cWashoff = coeff; // coeff includes LperFT3 factor - } - return cWashoff; -} - -//============================================================================= - -double landuse_getCoPollutLoad(int p, double washoff[]) -// -// Input: p = pollutant index -// washoff = pollut. washoff rate (mass/sec) -// Output: returns washoff mass added by co-pollutant relation (mass) -// Purpose: finds washoff mass added by a co-pollutant of a given pollutant. -// -{ - int k; - double w; - - // --- check if pollutant p has a co-pollutant k - k = Pollut[p].coPollut; - if ( k >= 0 ) - { - // --- compute addition to washoff from co-pollutant - w = Pollut[p].coFraction * washoff[k]; - - // --- add washoff to buildup mass balance totals - // so that things will balance - massbal_updateLoadingTotals(BUILDUP_LOAD, p, w * Pollut[p].mcf); - return w; - } - return 0.0; -} - -//============================================================================= - -double landuse_getExternalBuildup(int i, int p, double buildup, double tStep) -// -// Input: i = landuse index -// p = pollutant index -// buildup = buildup at start of time step (mass/unit) -// tStep = time step (sec) -// Output: returns pollutant buildup at end of time interval (mass/unit) -// Purpose: finds pollutant buildup contributed by external loading over a -// given time step. -// -{ - double maxBuildup = Landuse[i].buildupFunc[p].coeff[0]; - double sf = Landuse[i].buildupFunc[p].coeff[1]; // scaling factor - int ts = (int)floor(Landuse[i].buildupFunc[p].coeff[2]); // time series index - double rate = 0.0; - - // --- no buildup increment at start of simulation - if (NewRunoffTime == 0.0) return 0.0; - - // --- get buildup rate (mass/unit/day) over the interval - if ( ts >= 0 ) - { - rate = sf * table_tseriesLookup(&Tseries[ts], - getDateTime(NewRunoffTime), FALSE); - } - - // --- compute buildup at end of time interval - buildup = buildup + rate * tStep / SECperDAY; - buildup = MIN(buildup, maxBuildup); - return buildup; -} diff --git a/src/lid.c b/src/lid.c deleted file mode 100644 index 7bb69dcef..000000000 --- a/src/lid.c +++ /dev/null @@ -1,2031 +0,0 @@ -//----------------------------------------------------------------------------- -// lid.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// This module handles all data processing involving LID (Low Impact -// Development) practices used to treat runoff for individual subcatchments -// within a project. The actual computation of LID performance is made by -// functions within the lidproc.c module. See LidTypes below for the types -// of LIDs that can be modeled. -// -// An LID process is described by the TLidProc data structure and consists of -// size-independent design data for the different vertical layers that make -// up a specific type of LID. The collection of these LID process designs is -// stored in the LidProcs array. -// -// When a member of LidProcs is to be deployed in a particular subcatchment, -// its sizing and treatment data are stored in a TLidUnit data structure. -// The collection of all TLidUnits deployed in a subcatchment is held in a -// TLidGroup list data structure. The LidGroups array contains a TLidGroup -// list for each subcatchment in the project. -// -// During a runoff time step, each subcatchment calls the lid_getRunoff() -// function to compute flux rates and a water balance through each layer -// of each LID unit in the subcatchment. The resulting outflows (runoff, -// drain flow, evaporation and infiltration) are added to those computed -// for the non-LID portion of the subcatchment. -// -// An option exists for the detailed time series of flux rates and storage -// levels for a specific LID unit to be written to a text file named by the -// user for viewing outside of the SWMM program. -// -// Update History -// ============== -// Build 5.1.008: -// - More input error reporting added. -// - Rooftop Disconnection added to the types of LIDs. -// - LID drain flows are now tracked separately. -// - LID drain flows can now be routed to separate outlets. -// - Check added to insure LID flows not returned to nonexistent pervious area. -// Build 5.1.009: -// - Fixed bug where LID's could return outflow to non-LID area when LIDs -// make up entire subcatchment. -// Build 5.1.010: -// - Support for new Modified Green Ampt infiltration model added. -// - Imported variable HasWetLids now properly initialized. -// - Initial state of reporting (lidUnit->rptFile->wasDry) changed to -// prevent duplicate printing of first line of detailed report file. -// Build 5.1.011: -// - The top of the storage layer is no longer used as a limit for an -// underdrain offset thus allowing upturned drains to be modeled. -// - Column headings for the detailed LID report file were modified. -// Build 5.1.012: -// - Redefined initialization of wasDry for LID reporting. -// Build 5.1.013: -// - Support added for LID units treating pervious area runoff. -// - Support added for open/closed head levels and multiplier v. head -// 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. -// Build 5.1.015: -// - Support added for mutiple infiltration methods within a project. -// Build 5.2.0: -// - Covered property added to RAIN_BARREL parameters -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include "headers.h" -#include "lid.h" - -#define ERR_PAVE_LAYER " - check pavement layer parameters" -#define ERR_SOIL_LAYER " - check soil layer parameters" -#define ERR_STOR_LAYER " - check storage layer parameters" -#define ERR_SWALE_SURF " - check swale surface parameters" -#define ERR_GREEN_AMPT " - check subcatchment Green-Ampt parameters" -#define ERR_DRAIN_OFFSET " - drain offset exceeds storage height" -#define ERR_DRAIN_HEADS " - invalid drain open/closed heads" -#define ERR_SWALE_WIDTH " - invalid swale width" - -//----------------------------------------------------------------------------- -// Enumerations -//----------------------------------------------------------------------------- -enum LidLayerTypes { - SURF, // surface layer - SOIL, // soil layer - STOR, // storage layer - PAVE, // pavement layer - DRAINMAT, // drainage mat layer - DRAIN, // underdrain system - REMOVALS}; // pollutant removals - -//// Note: DRAINMAT must be placed before DRAIN so the two keywords can -/// be distinguished from one another when parsing a line of input. - -char* LidLayerWords[] = - {"SURFACE", "SOIL", "STORAGE", "PAVEMENT", "DRAINMAT", "DRAIN", - "REMOVALS", NULL}; - -char* LidTypeWords[] = - {"BC", //bio-retention cell - "RG", //rain garden - "GR", //green roof - "IT", //infiltration trench - "PP", //porous pavement - "RB", //rain barrel - "VS", //vegetative swale - "RD", //rooftop disconnection - NULL}; - -//----------------------------------------------------------------------------- -// Data Structures -//----------------------------------------------------------------------------- - -// LID List - list of LID units contained in an LID group -struct LidList -{ - TLidUnit* lidUnit; // ptr. to a LID unit - struct LidList* nextLidUnit; -}; -typedef struct LidList TLidList; - -// LID Group - collection of LID units applied to a specific subcatchment -struct LidGroup -{ - double pervArea; // amount of pervious area in group (ft2) - double flowToPerv; // total flow sent to pervious area (cfs) - double oldDrainFlow; // total drain flow in previous period (cfs) - double newDrainFlow; // total drain flow in current period (cfs) - TLidList* lidList; // list of LID units in the group -}; -typedef struct LidGroup* TLidGroup; - - -//----------------------------------------------------------------------------- -// Shared Variables -//----------------------------------------------------------------------------- -static TLidProc* LidProcs; // array of LID processes -static int LidCount; // number of LID processes -static TLidGroup* LidGroups; // array of LID process groups -static int GroupCount; // number of LID groups (subcatchments) - -static double EvapRate; // evaporation rate (ft/s) -static double NativeInfil; // native soil infil. rate (ft/s) -static double MaxNativeInfil; // native soil infil. rate limit (ft/s) - -//----------------------------------------------------------------------------- -// Imported Variables (from SUBCATCH.C) -//----------------------------------------------------------------------------- -// Volumes (ft3) for a subcatchment over a time step -extern double Vevap; // evaporation -extern double Vpevap; // pervious area evaporation -extern double Vinfil; // non-LID infiltration -extern double VlidInfil; // infiltration from LID units -extern double VlidIn; // impervious area flow to LID units -extern double VlidOut; // surface outflow from LID units -extern double VlidDrain; // drain outflow from LID units -extern double VlidReturn; // LID outflow returned to pervious area -extern char HasWetLids; // TRUE if any LIDs are wet - // (from RUNOFF.C) - -//----------------------------------------------------------------------------- -// External Functions (prototyped in lid.h) -//----------------------------------------------------------------------------- -// lid_create called by createObjects in project.c -// lid_delete called by deleteObjects in project.c -// lid_validate called by project_validate -// lid_initState called by project_init - -// lid_readProcParams called by parseLine in input.c -// lid_readGroupParams called by parseLine in input.c - -// lid_setOldGroupState called by subcatch_setOldState -// lid_setReturnQual called by findLidLoads in surfqual.c -// lid_getReturnQual called by subcatch_getRunon - -// lid_getPervArea called by subcatch_getFracPerv -// lid_getFlowToPerv called by subcatch_getRunon -// lid_getSurfaceDepth called by subcatch_getDepth -// lid_getDepthOnPavement called by sweptSurfacesDry in subcatch.c -// lid_getStoredVolume called by subcatch_getStorage -// lid_getRunon called by subcatch_getRunon -// lid_getRunoff called by subcatch_getRunoff - -// lid_addDrainRunon called by subcatch_getRunon -// lid_addDrainLoads called by surfqual_getWashoff -// lid_addDrainInflow called by addLidDrainInflows in routing.c - -// lid_writeSummary called by inputrpt_writeInput -// lid_writeWaterBalance called by statsrpt_writeReport - - -//----------------------------------------------------------------------------- -// Local Functions -//----------------------------------------------------------------------------- -static void freeLidGroup(int j); -static int readSurfaceData(int j, char* tok[], int ntoks); -static int readPavementData(int j, char* tok[], int ntoks); -static int readSoilData(int j, char* tok[], int ntoks); -static int readStorageData(int j, char* tok[], int ntoks); -static int readDrainData(int j, char* tok[], int ntoks); -static int readDrainMatData(int j, char* toks[], int ntoks); -static int readRemovalsData(int j, char* toks[], int ntoks); - -static int addLidUnit(int j, int k, int n, double x[], char* fname, - int drainSubcatch, int drainNode); -static int createLidRptFile(TLidUnit* lidUnit, char* fname); -static void initLidRptFile(char* title, char* lidID, char* subcatchID, - TLidUnit* lidUnit); -static void validateLidProc(int j); -static void validateLidGroup(int j); - -static int isLidPervious(int k); -static double getImpervAreaRunoff(int j); -static double getPervAreaRunoff(int j); -static double getSurfaceDepth(int subcatch); -static double getRainInflow(int j, TLidUnit* lidUnit); -static void findNativeInfil(int j, double tStep); - - -static void evalLidUnit(int j, TLidUnit* lidUnit, double lidArea, - double lidInflow, double tStep, double *qRunoff, - double *qDrain, double *qReturn); - -//============================================================================= - -void lid_create(int lidCount, int subcatchCount) -// -// Purpose: creates an array of LID objects. -// Input: n = number of LID processes -// Output: none -// -{ - int j; - - //... assign NULL values to LID arrays - LidProcs = NULL; - LidGroups = NULL; - LidCount = lidCount; - - //... create LID groups - GroupCount = subcatchCount; - if ( GroupCount > 0 ) - { - 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)); - if ( LidProcs == NULL ) - { - ErrorCode = ERR_MEMORY; - return; - } - - //... initialize LID objects - for (j = 0; j < LidCount; j++) - { - LidProcs[j].lidType = -1; - LidProcs[j].surface.thickness = 0.0; - LidProcs[j].surface.voidFrac = 1.0; - LidProcs[j].surface.roughness = 0.0; - LidProcs[j].surface.surfSlope = 0.0; - LidProcs[j].pavement.thickness = 0.0; - LidProcs[j].soil.thickness = 0.0; - LidProcs[j].storage.thickness = 0.0; - LidProcs[j].storage.kSat = 0.0; - LidProcs[j].drain.coeff = 0.0; - LidProcs[j].drain.offset = 0.0; - LidProcs[j].drainMat.thickness = 0.0; - LidProcs[j].drainMat.roughness = 0.0; - LidProcs[j].drainRmvl = NULL; - LidProcs[j].drainRmvl = (double *) - calloc(Nobjects[POLLUT], sizeof(double)); - if (LidProcs[j].drainRmvl == NULL) - { - ErrorCode = ERR_MEMORY; - return; - } - } -} - -//============================================================================= - -void lid_delete() -// -// Purpose: deletes all LID objects -// Input: none -// Output: none -// -{ - int j; - for (j = 0; j < GroupCount; j++) freeLidGroup(j); - FREE(LidGroups); - for (j = 0; j < LidCount; j++) FREE(LidProcs[j].drainRmvl); - FREE(LidProcs); - GroupCount = 0; - LidCount = 0; -} - -//============================================================================= - -void freeLidGroup(int j) -// -// Purpose: frees all LID units associated with a subcatchment. -// Input: j = group (or subcatchment) index -// Output: none -// -{ - TLidGroup lidGroup = LidGroups[j]; - TLidList* lidList; - TLidUnit* lidUnit; - TLidList* nextLidUnit; - - if ( lidGroup == NULL ) return; - lidList = lidGroup->lidList; - while (lidList) - { - lidUnit = lidList->lidUnit; - if ( lidUnit->rptFile ) - { - if ( lidUnit->rptFile->file ) fclose(lidUnit->rptFile->file); - free(lidUnit->rptFile); - } - nextLidUnit = lidList->nextLidUnit; - free(lidUnit); - free(lidList); - lidList = nextLidUnit; - } - free(lidGroup); - LidGroups[j] = NULL; -} - -//============================================================================= - -int lid_readProcParams(char* toks[], int ntoks) -// -// Purpose: reads LID process information from line of input data file -// Input: toks = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// -// Format for first line that defines a LID process is: -// LID_ID LID_Type -// -// Followed by some combination of lines below depending on LID_Type: -// LID_ID SURFACE -// LID_ID PAVEMENT -// LID_ID SOIL -// LID_ID STORAGE -// LID_ID DRAIN -// LID_ID DRAINMAT -// LID_ID REMOVALS -// -{ - int j, m; - - // --- check for minimum number of tokens - if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); - - // --- check that LID exists in database - j = project_findObject(LID, toks[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, toks[0]); - - // --- assign ID if not done yet - if ( LidProcs[j].ID == NULL ) - LidProcs[j].ID = project_findID(LID, toks[0]); - - // --- check if second token is the type of LID - m = findmatch(toks[1], LidTypeWords); - if ( m >= 0 ) - { - LidProcs[j].lidType = m; - return 0; - } - - // --- check if second token is name of LID layer - else m = findmatch(toks[1], LidLayerWords); - - // --- read input parameters for the identified layer - switch (m) - { - case SURF: return readSurfaceData(j, toks, ntoks); - case SOIL: return readSoilData(j, toks, ntoks); - case STOR: return readStorageData(j, toks, ntoks); - case PAVE: return readPavementData(j, toks, ntoks); - case DRAIN: return readDrainData(j, toks, ntoks); - case DRAINMAT: return readDrainMatData(j, toks, ntoks); - case REMOVALS: return readRemovalsData(j, toks, ntoks); - } - return error_setInpError(ERR_KEYWORD, toks[1]); -} - -//============================================================================= - -int lid_readGroupParams(char* toks[], int ntoks) -// -// Purpose: reads input data for a LID unit placed in a subcatchment. -// Input: toks = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// -// Format of input data line is: -// Subcatch_ID LID_ID Number Area Width InitSat FromImp ToPerv -// (RptFile DrainTo FromPerv) -// where: -// Subcatch_ID = name of subcatchment -// LID_ID = name of LID process -// Number (n) = number of replicate units -// Area (x[0]) = area of each unit -// Width (x[1]) = outflow width of each unit -// InitSat (x[2]) = % that LID is initially saturated -// FromImp (x[3]) = % of impervious runoff sent to LID -// ToPerv (x[4]) = 1 if outflow goes to pervious sub-area; 0 if not -// RptFile = name of detailed results file (optional) -// DrainTo = name of subcatch/node for drain flow (optional) -// FromPerv (x[5]) = % of pervious runoff sent to LID -// -{ - int i, j, k, n; - double x[6]; - char* fname = NULL; - int drainSubcatch = -1, drainNode = -1; - - //... check for valid number of input tokens - if ( ntoks < 8 ) return error_setInpError(ERR_ITEMS, ""); - - //... find subcatchment - j = project_findObject(SUBCATCH, toks[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, toks[0]); - - //... find LID process in list of LID processes - k = project_findObject(LID, toks[1]); - if ( k < 0 ) return error_setInpError(ERR_NAME, toks[1]); - - //... get number of replicates - n = atoi(toks[2]); - if ( n < 0 ) return error_setInpError(ERR_NUMBER, toks[2]); - if ( n == 0 ) return 0; - - //... convert next 4 tokens to doubles - for (i = 3; i <= 7; i++) - { - if ( ! getDouble(toks[i], &x[i-3]) || x[i-3] < 0.0 ) - return error_setInpError(ERR_NUMBER, toks[i]); - } - - //... check for valid percentages on tokens 5 & 6 (x[2] & x[3]) - for (i = 2; i <= 3; i++) if ( x[i] > 100.0 ) - return error_setInpError(ERR_NUMBER, toks[i+3]); - - //... read optional report file name - if ( ntoks >= 9 && strcmp(toks[8], "*") != 0 ) fname = toks[8]; - - //... read optional underdrain outlet - if ( ntoks >= 10 && strcmp(toks[9], "*") != 0 ) - { - drainSubcatch = project_findObject(SUBCATCH, toks[9]); - if ( drainSubcatch < 0 ) - { - drainNode = project_findObject(NODE, toks[9]); - if ( drainNode < 0 ) return error_setInpError(ERR_NAME, toks[9]); - } - } - - //... read percent of pervious area treated by LID unit - x[5] = 0.0; - if (ntoks >= 11) - { - if (!getDouble(toks[10], &x[5]) || x[5] < 0.0 || x[5] > 100.0) - return error_setInpError(ERR_NUMBER, toks[10]); - } - - //... create a new LID unit and add it to the subcatchment's LID group - return addLidUnit(j, k, n, x, fname, drainSubcatch, drainNode); -} - -//============================================================================= - -int addLidUnit(int j, int k, int n, double x[], char* fname, - int drainSubcatch, int drainNode) -// -// Purpose: adds an LID unit to a subcatchment's LID group. -// Input: j = subcatchment index -// k = LID control index -// n = number of replicate units -// x = LID unit's parameters -// fname = name of detailed performance report file -// drainSubcatch = index of subcatchment receiving underdrain flow -// drainNode = index of node receiving underdrain flow -// Output: returns an error code -// -{ - TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; - - //... create a LID group (pointer to an LidGroup struct) - // if one doesn't already exist - lidGroup = LidGroups[j]; - if ( !lidGroup ) - { - lidGroup = (struct LidGroup *) malloc(sizeof(struct LidGroup)); - if ( !lidGroup ) return error_setInpError(ERR_MEMORY, ""); - lidGroup->lidList = NULL; - LidGroups[j] = lidGroup; - } - - //... create a new LID unit to add to the group - lidUnit = (TLidUnit *) malloc(sizeof(TLidUnit)); - if ( !lidUnit ) return error_setInpError(ERR_MEMORY, ""); - lidUnit->rptFile = NULL; - - //... add the LID unit to the group - lidList = (TLidList *) malloc(sizeof(TLidList)); - if ( !lidList ) - { - free(lidUnit); - return error_setInpError(ERR_MEMORY, ""); - } - lidList->lidUnit = lidUnit; - lidList->nextLidUnit = lidGroup->lidList; - lidGroup->lidList = lidList; - - //... assign parameter values to LID unit - lidUnit->lidIndex = k; - lidUnit->number = n; - lidUnit->area = x[0] / SQR(UCF(LENGTH)); - lidUnit->fullWidth = x[1] / UCF(LENGTH); - lidUnit->initSat = x[2] / 100.0; - lidUnit->fromImperv = x[3] / 100.0; - lidUnit->toPerv = (x[4] > 0.0); - lidUnit->fromPerv = x[5] / 100.0; - lidUnit->drainSubcatch = drainSubcatch; - lidUnit->drainNode = drainNode; - - //... open report file if it was supplied - if ( fname != NULL ) - { - if ( !createLidRptFile(lidUnit, fname) ) - return error_setInpError(ERR_RPT_FILE, fname); - } - return 0; -} - -//============================================================================= - -int createLidRptFile(TLidUnit* lidUnit, char* fname) -{ - TLidRptFile* rptFile; - - rptFile = (TLidRptFile *) malloc(sizeof(TLidRptFile)); - if ( rptFile == NULL ) return 0; - lidUnit->rptFile = rptFile; - rptFile->file = fopen(fname, "wt"); - if ( rptFile->file == NULL ) return 0; - return 1; -} - -//============================================================================= - -int readSurfaceData(int j, char* toks[], int ntoks) -// -// Purpose: reads surface layer data for a LID process from line of input -// data file -// Input: j = LID process index -// toks = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// -// Format of data is: -// LID_ID SURFACE StorageHt VegVolFrac Roughness SurfSlope SideSlope -// -{ - int i; - double x[5]; - - if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); - for (i = 2; i < 7; i++) - { - if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) - return error_setInpError(ERR_NUMBER, toks[i]); - } - if ( x[1] >= 1.0 ) return error_setInpError(ERR_NUMBER, toks[3]); - if ( x[0] == 0.0 ) x[1] = 0.0; - - LidProcs[j].surface.thickness = x[0] / UCF(RAINDEPTH); - LidProcs[j].surface.voidFrac = 1.0 - x[1]; - LidProcs[j].surface.roughness = x[2]; - LidProcs[j].surface.surfSlope = x[3] / 100.0; - LidProcs[j].surface.sideSlope = x[4]; - return 0; -} - -//============================================================================= - -int readPavementData(int j, char* toks[], int ntoks) -// -// Purpose: reads pavement layer data for a LID process from line of input -// data file -// Input: j = LID process index -// toks = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// -// Format of data is: -// LID_ID PAVEMENT Thickness VoidRatio FracImperv Permeability ClogFactor -// (RegenDays RegenDegree) -// -{ - int i; - double x[7]; - - if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); - for (i = 2; i < 7; i++) - { - if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) - return error_setInpError(ERR_NUMBER, toks[i]); - } - - // ... read optional clogging regeneration properties - x[5] = 0.0; - if (ntoks > 7) - { - if (!getDouble(toks[7], &x[5]) || x[5] < 0.0) - return error_setInpError(ERR_NUMBER, toks[7]); - } - x[6] = 0.0; - if (ntoks > 8) - { - if (!getDouble(toks[8], &x[6]) || x[6] < 0.0 || x[6] > 1.0) - return error_setInpError(ERR_NUMBER, toks[8]); - } - - //... convert void ratio to void fraction - x[1] = x[1]/(x[1] + 1.0); - - LidProcs[j].pavement.thickness = x[0] / UCF(RAINDEPTH); - LidProcs[j].pavement.voidFrac = x[1]; - LidProcs[j].pavement.impervFrac = x[2]; - LidProcs[j].pavement.kSat = x[3] / UCF(RAINFALL); - LidProcs[j].pavement.clogFactor = x[4]; - LidProcs[j].pavement.regenDays = x[5]; - LidProcs[j].pavement.regenDegree = x[6]; - return 0; -} - -//============================================================================= - -int readSoilData(int j, char* toks[], int ntoks) -// -// Purpose: reads soil layer data for a LID process from line of input -// data file -// Input: j = LID process index -// toks = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// -// Format of data is: -// LID_ID SOIL Thickness Porosity FieldCap WiltPt Ksat Kslope Suction -// -{ - int i; - double x[7]; - - if ( ntoks < 9 ) return error_setInpError(ERR_ITEMS, ""); - for (i = 2; i < 9; i++) - { - if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) - return error_setInpError(ERR_NUMBER, toks[i]); - } - LidProcs[j].soil.thickness = x[0] / UCF(RAINDEPTH); - LidProcs[j].soil.porosity = x[1]; - LidProcs[j].soil.fieldCap = x[2]; - LidProcs[j].soil.wiltPoint = x[3]; - LidProcs[j].soil.kSat = x[4] / UCF(RAINFALL); - LidProcs[j].soil.kSlope = x[5]; - LidProcs[j].soil.suction = x[6] / UCF(RAINDEPTH); - return 0; -} - -//============================================================================= - -int readStorageData(int j, char* toks[], int ntoks) -// -// Purpose: reads drainage layer data for a LID process from line of input -// data file -// Input: j = LID process index -// toks = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// -// Format of data is: -// LID_ID STORAGE Thickness VoidRatio Ksat ClogFactor (YES/NO) -// -{ - int i; - int covered = FALSE; - double x[6]; - - //... read numerical parameters - if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); - for (i = 2; i < 6; i++) - { - if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) - return error_setInpError(ERR_NUMBER, toks[i]); - } - - //... check if rain barrel is covered - if (ntoks > 6) - { - if (match(toks[6], w_YES)) - covered = TRUE; - } - - //... convert void ratio to void fraction - x[1] = x[1]/(x[1] + 1.0); - - //... save parameters to LID storage layer structure - LidProcs[j].storage.thickness = x[0] / UCF(RAINDEPTH); - LidProcs[j].storage.voidFrac = x[1]; - LidProcs[j].storage.kSat = x[2] / UCF(RAINFALL); - LidProcs[j].storage.clogFactor = x[3]; - LidProcs[j].storage.covered = covered; - return 0; -} - -//============================================================================= - -int readDrainData(int j, char* toks[], int ntoks) -// -// Purpose: reads underdrain data for a LID process from line of input -// data file -// Input: j = LID process index -// toks = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// -// Format of data is: -// LID_ID DRAIN coeff expon offset delay hOpen hClose curve -// -{ - int i; - double x[6]; - - //... read numerical parameters - if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); - for (i = 0; i < 6; i++) x[i] = 0.0; - for (i = 2; i < 8; i++) - { - if ( (ntoks > i) && (! getDouble(toks[i], &x[i-2]) || x[i-2]) < 0.0 ) - return error_setInpError(ERR_NUMBER, toks[i]); - } - - i = -1; - if ( ntoks >= 9 ) - { - i = project_findObject(CURVE, toks[8]); - if (i < 0) return error_setInpError(ERR_NAME, toks[8]); - } - - //... save parameters to LID drain layer structure - LidProcs[j].drain.coeff = x[0]; - LidProcs[j].drain.expon = x[1]; - LidProcs[j].drain.offset = x[2] / UCF(RAINDEPTH); - LidProcs[j].drain.delay = x[3] * 3600.0; - LidProcs[j].drain.hOpen = x[4] / UCF(RAINDEPTH); - LidProcs[j].drain.hClose = x[5] / UCF(RAINDEPTH); - LidProcs[j].drain.qCurve = i; - return 0; -} - -//============================================================================= - -int readDrainMatData(int j, char* toks[], int ntoks) -// -// Purpose: reads drainage mat data for a LID process from line of input -// data file -// Input: j = LID process index -// toks = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// -// Format of data is: -// LID_ID DRAINMAT thickness voidRatio roughness -// -{ - int i; - double x[3]; - - //... read numerical parameters - if ( ntoks < 5 ) return error_setInpError(ERR_ITEMS, ""); - if ( LidProcs[j].lidType != GREEN_ROOF ) return 0; - for (i = 2; i < 5; i++) - { - if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) - return error_setInpError(ERR_NUMBER, toks[i]); - } - - //... save parameters to LID drain layer structure - LidProcs[j].drainMat.thickness = x[0] / UCF(RAINDEPTH);; - LidProcs[j].drainMat.voidFrac = x[1]; - LidProcs[j].drainMat.roughness = x[2]; - return 0; -} - -//============================================================================= - -int readRemovalsData(int j, char* toks[], int ntoks) -// -// Purpose: reads pollutant removal data for a LID process from line of input -// data file -// Input: j = LID process index -// toks = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// -// Format of data is: -// LID_ID REMOVALS pollut1 %removal1 pollut2 %removal2 ... -// -{ - int i = 2; - int p; - double rmvl; - - //... start with 3rd token - if (ntoks < 4) return error_setInpError(ERR_ITEMS, ""); - while (ntoks > i) - { - //... find pollutant index from its name - p = project_findObject(POLLUT, toks[i]); - if (p < 0) return error_setInpError(ERR_NAME, toks[i]); - - //... check that a next token exists - i++; - if (ntoks == i) return error_setInpError(ERR_ITEMS, ""); - - //... get the % removal value from the next token - if (!getDouble(toks[i], &rmvl) || rmvl < 0.0 || rmvl > 100.0) - return error_setInpError(ERR_NUMBER, toks[i]); - - //... save the pollutant removal for the LID process as a fraction - LidProcs[j].drainRmvl[p] = rmvl / 100.0; - i++; - } - return 0; -} -//============================================================================= - -void lid_writeSummary() -// -// Purpose: writes summary of LID processes used to report file. -// Input: none -// Output: none -// -{ - int j, k; - double pctArea; - TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; - - fprintf(Frpt.file, "\n"); - fprintf(Frpt.file, "\n"); - fprintf(Frpt.file, "\n *******************"); - fprintf(Frpt.file, "\n LID Control Summary"); - fprintf(Frpt.file, "\n *******************"); - - - fprintf(Frpt.file, -"\n No. of Unit Unit %% Area %% Imperv %% Perv"); //(5.1.013) - fprintf(Frpt.file, // -"\n Subcatchment LID Control Units Area Width Covered Treated Treated"); // - fprintf(Frpt.file, // -"\n ---------------------------------------------------------------------------------------------------"); // - - for (j = 0; j < GroupCount; j++) - { - lidGroup = LidGroups[j]; - if ( lidGroup == NULL ) continue; - lidList = lidGroup->lidList; - while ( lidList ) - { - lidUnit = lidList->lidUnit; - k = lidUnit->lidIndex; - pctArea = lidUnit->area * lidUnit->number / Subcatch[j].area * 100.0; - fprintf(Frpt.file, "\n %-16s %-16s", Subcatch[j].ID, LidProcs[k].ID); - fprintf(Frpt.file, "%6d %10.2f %10.2f %10.2f %10.2f %10.2f", - lidUnit->number, lidUnit->area * SQR(UCF(LENGTH)), - lidUnit->fullWidth * UCF(LENGTH), pctArea, - lidUnit->fromImperv*100.0, lidUnit->fromPerv*100.0); - lidList = lidList->nextLidUnit; - } - } -} - -//============================================================================= - -void lid_validate() -// -// Purpose: validates LID process and group parameters. -// Input: none -// Output: none -// -{ - int j; - for (j = 0; j < LidCount; j++) validateLidProc(j); - for (j = 0; j < GroupCount; j++) validateLidGroup(j); -} - -//============================================================================= - -void validateLidProc(int j) -// -// Purpose: validates LID process parameters. -// Input: j = LID process index -// Output: none -// -{ - int layerMissing = FALSE; - - //... check that LID type was supplied - if ( LidProcs[j].lidType < 0 ) - { - report_writeErrorMsg(ERR_LID_TYPE, LidProcs[j].ID); - return; - } - - //... check that required layers were defined - switch (LidProcs[j].lidType) - { - case BIO_CELL: - case RAIN_GARDEN: - if ( LidProcs[j].soil.thickness <= 0.0 ) layerMissing = TRUE; - break; - case GREEN_ROOF: - if ( LidProcs[j].soil.thickness <= 0.0 ) layerMissing = TRUE; - if ( LidProcs[j].drainMat.thickness <= 0.0) layerMissing = TRUE; - break; - case POROUS_PAVEMENT: - if ( LidProcs[j].pavement.thickness <= 0.0 ) layerMissing = TRUE; - break; - case INFIL_TRENCH: - if ( LidProcs[j].storage.thickness <= 0.0 ) layerMissing = TRUE; - break; - } - if ( layerMissing ) - { - report_writeErrorMsg(ERR_LID_LAYER, LidProcs[j].ID); - return; - } - - //... check pavement layer parameters - if ( LidProcs[j].lidType == POROUS_PAVEMENT ) - { - if ( LidProcs[j].pavement.thickness <= 0.0 - || LidProcs[j].pavement.kSat <= 0.0 - || LidProcs[j].pavement.voidFrac <= 0.0 - || LidProcs[j].pavement.voidFrac > 1.0 - || LidProcs[j].pavement.impervFrac > 1.0 ) - - { - sstrncpy(Msg, LidProcs[j].ID, MAXMSG); - sstrcat(Msg, ERR_PAVE_LAYER, MAXMSG); - report_writeErrorMsg(ERR_LID_PARAMS, Msg); - } - } - - //... check soil layer parameters - if ( LidProcs[j].soil.thickness > 0.0 ) - { - if ( LidProcs[j].soil.porosity <= 0.0 - || LidProcs[j].soil.fieldCap >= LidProcs[j].soil.porosity - || LidProcs[j].soil.wiltPoint >= LidProcs[j].soil.fieldCap - || LidProcs[j].soil.kSat <= 0.0 - || LidProcs[j].soil.kSlope < 0.0 ) - { - sstrncpy(Msg, LidProcs[j].ID, MAXMSG); - sstrcat(Msg, ERR_SOIL_LAYER, MAXMSG); - report_writeErrorMsg(ERR_LID_PARAMS, Msg); - } - } - - //... check storage layer parameters - if ( LidProcs[j].storage.thickness > 0.0 ) - { - if ( LidProcs[j].storage.voidFrac <= 0.0 || - LidProcs[j].storage.voidFrac > 1.0 ) - { - sstrncpy(Msg, LidProcs[j].ID, MAXMSG); - sstrcat(Msg, ERR_STOR_LAYER, MAXMSG); - report_writeErrorMsg(ERR_LID_PARAMS, Msg); - } - } - - //... if no storage layer adjust void fraction and drain offset - else - { - LidProcs[j].storage.voidFrac = 1.0; - LidProcs[j].drain.offset = 0.0; - } - - //... check for invalid drain open/closed heads - if (LidProcs[j].drain.hOpen > 0.0 && - LidProcs[j].drain.hOpen <= LidProcs[j].drain.hClose) - { - sstrncpy(Msg, LidProcs[j].ID, MAXMSG); - sstrcat(Msg, ERR_DRAIN_HEADS, MAXMSG); - report_writeErrorMsg(ERR_LID_PARAMS, Msg); - } - - //... compute the surface layer's overland flow constant (alpha) - if ( LidProcs[j].lidType == VEG_SWALE ) - { - if ( LidProcs[j].surface.roughness * - LidProcs[j].surface.surfSlope <= 0.0 || - LidProcs[j].surface.thickness == 0.0 - ) - { - sstrncpy(Msg, LidProcs[j].ID, MAXMSG); - sstrcat(Msg, ERR_SWALE_SURF, MAXMSG); - report_writeErrorMsg(ERR_LID_PARAMS, Msg); - } - else LidProcs[j].surface.alpha = - 1.49 * sqrt(LidProcs[j].surface.surfSlope) / - LidProcs[j].surface.roughness; - } - else - { - //... compute surface overland flow coeff. - if ( LidProcs[j].surface.roughness > 0.0 ) - LidProcs[j].surface.alpha = 1.49 / LidProcs[j].surface.roughness * - sqrt(LidProcs[j].surface.surfSlope); - else LidProcs[j].surface.alpha = 0.0; - } - - //... compute drainage mat layer's flow coeff. - if ( LidProcs[j].drainMat.roughness > 0.0 ) - { - LidProcs[j].drainMat.alpha = 1.49 / LidProcs[j].drainMat.roughness * - sqrt(LidProcs[j].surface.surfSlope); - } - else LidProcs[j].drainMat.alpha = 0.0; - - - //... convert clogging factors to void volume basis - if ( LidProcs[j].pavement.thickness > 0.0 ) - { - LidProcs[j].pavement.clogFactor *= - LidProcs[j].pavement.thickness * LidProcs[j].pavement.voidFrac * - (1.0 - LidProcs[j].pavement.impervFrac); - } - if ( LidProcs[j].storage.thickness > 0.0 ) - { - LidProcs[j].storage.clogFactor *= - LidProcs[j].storage.thickness * LidProcs[j].storage.voidFrac; - } - else LidProcs[j].storage.clogFactor = 0.0; - - //... for certain LID types, immediate overflow of excess surface water - // occurs if either the surface roughness or slope is zero - LidProcs[j].surface.canOverflow = TRUE; - switch (LidProcs[j].lidType) - { - case ROOF_DISCON: LidProcs[j].surface.canOverflow = FALSE; break; - case INFIL_TRENCH: - case POROUS_PAVEMENT: - case BIO_CELL: - case RAIN_GARDEN: - case GREEN_ROOF: - if ( LidProcs[j].surface.alpha > 0.0 ) - LidProcs[j].surface.canOverflow = FALSE; - } - - //... rain barrels have 100% void space and impermeable bottom - if ( LidProcs[j].lidType == RAIN_BARREL ) - { - LidProcs[j].storage.voidFrac = 1.0; - LidProcs[j].storage.kSat = 0.0; - } - - //... set storage layer parameters of a green roof - if ( LidProcs[j].lidType == GREEN_ROOF ) - { - LidProcs[j].storage.thickness = LidProcs[j].drainMat.thickness; - LidProcs[j].storage.voidFrac = LidProcs[j].drainMat.voidFrac; - LidProcs[j].storage.clogFactor = 0.0; - LidProcs[j].storage.kSat = 0.0; - } -} - -//============================================================================= - -void validateLidGroup(int j) -// -// Purpose: validates properties of LID units grouped in a subcatchment. -// Input: j = subcatchment index -// Output: returns 1 if data are valid, 0 if not -// -{ - int k; - double p[3]; - double totalArea = Subcatch[j].area; - double totalLidArea = 0.0; - double fromImperv = 0.0; - double fromPerv = 0.0; - TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; - - lidGroup = LidGroups[j]; - if ( lidGroup == NULL ) return; - lidList = lidGroup->lidList; - while ( lidList ) - { - lidUnit = lidList->lidUnit; - k = lidUnit->lidIndex; - - //... update contributing fractions - totalLidArea += (lidUnit->area * lidUnit->number); - fromImperv += lidUnit->fromImperv; - fromPerv += lidUnit->fromPerv; - - //... assign biocell soil layer infiltration parameters - lidUnit->soilInfil.Ks = 0.0; - if ( LidProcs[k].soil.thickness > 0.0 ) - { - p[0] = LidProcs[k].soil.suction * UCF(RAINDEPTH); - p[1] = LidProcs[k].soil.kSat * UCF(RAINFALL); - p[2] = (LidProcs[k].soil.porosity - LidProcs[k].soil.wiltPoint) * - (1.0 - lidUnit->initSat); - if ( grnampt_setParams(&(lidUnit->soilInfil), p) == FALSE ) - { - sstrncpy(Msg, LidProcs[k].ID, MAXMSG); - sstrcat(Msg, ERR_SOIL_LAYER, MAXMSG); - report_writeErrorMsg(ERR_LID_PARAMS, Msg); - } - } - - //... assign vegetative swale infiltration parameters - if ( LidProcs[k].lidType == VEG_SWALE ) - { - if ( Subcatch[j].infilModel == GREEN_AMPT || - Subcatch[j].infilModel == MOD_GREEN_AMPT ) - { - grnampt_getParams(j, p); - if ( grnampt_setParams(&(lidUnit->soilInfil), p) == FALSE ) - { - sstrncpy(Msg, LidProcs[k].ID, MAXMSG); - sstrcat(Msg, ERR_GREEN_AMPT, MAXMSG); - report_writeErrorMsg(ERR_LID_PARAMS, Msg); - } - } - if ( lidUnit->fullWidth <= 0.0 ) - { - sstrncpy(Msg, LidProcs[k].ID, MAXMSG); - sstrcat(Msg, ERR_SWALE_WIDTH, MAXMSG); - report_writeErrorMsg(ERR_LID_PARAMS, Msg); - } - } - - //... LID unit cannot send outflow back to subcatchment's - // pervious area if none exists - if ( Subcatch[j].fracImperv >= 0.999 ) lidUnit->toPerv = 0; - - //... assign drain outlet if not set by user - if ( lidUnit->drainNode == -1 && lidUnit->drainSubcatch == -1 ) - { - lidUnit->drainNode = Subcatch[j].outNode; - lidUnit->drainSubcatch = Subcatch[j].outSubcatch; - } - lidList = lidList->nextLidUnit; - } - - //... check contributing area fractions - if ( totalLidArea > 1.001 * totalArea ) - { - report_writeErrorMsg(ERR_LID_AREAS, Subcatch[j].ID); - } - if ( fromImperv > 1.001 || fromPerv > 1.001 ) - { - report_writeErrorMsg(ERR_LID_CAPTURE_AREA, Subcatch[j].ID); - } - - //... Make subcatchment LID area equal total area if the two are close - if ( totalLidArea > 0.999 * totalArea ) totalLidArea = totalArea; - Subcatch[j].lidArea = totalLidArea; -} - -//============================================================================= - -void lid_initState() -// -// Purpose: initializes the internal state of each LID in a subcatchment. -// Input: none -// Output: none -// -{ - int i, j, k; - TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; - double initVol; - double initDryTime = StartDryDays * SECperDAY; - - HasWetLids = FALSE; - for (j = 0; j < GroupCount; j++) - { - //... check if group exists - lidGroup = LidGroups[j]; - if ( lidGroup == NULL ) continue; - - //... initialize group variables - lidGroup->pervArea = 0.0; - lidGroup->flowToPerv = 0.0; - lidGroup->oldDrainFlow = 0.0; - lidGroup->newDrainFlow = 0.0; - - //... examine each LID in the group - lidList = lidGroup->lidList; - while ( lidList ) - { - //... initialize depth & moisture content - lidUnit = lidList->lidUnit; - k = lidUnit->lidIndex; - lidUnit->surfaceDepth = 0.0; - lidUnit->storageDepth = 0.0; - lidUnit->soilMoisture = 0.0; - lidUnit->paveDepth = 0.0; - lidUnit->dryTime = initDryTime; - lidUnit->volTreated = 0.0; - lidUnit->nextRegenDay = LidProcs[k].pavement.regenDays; - initVol = 0.0; - if ( LidProcs[k].soil.thickness > 0.0 ) - { - lidUnit->soilMoisture = LidProcs[k].soil.wiltPoint + - lidUnit->initSat * (LidProcs[k].soil.porosity - - LidProcs[k].soil.wiltPoint); - initVol += lidUnit->soilMoisture * LidProcs[k].soil.thickness; - } - if ( LidProcs[k].storage.thickness > 0.0 ) - { - lidUnit->storageDepth = lidUnit->initSat * - LidProcs[k].storage.thickness; - initVol += lidUnit->storageDepth * LidProcs[k].storage.voidFrac; - } - if ( LidProcs[k].drainMat.thickness > 0.0 ) - { - lidUnit->storageDepth = lidUnit->initSat * - LidProcs[k].drainMat.thickness; - initVol += lidUnit->storageDepth * LidProcs[k].drainMat.voidFrac; - } - if ( lidUnit->initSat > 0.0 ) HasWetLids = TRUE; - - //... initialize water balance totals - lidproc_initWaterBalance(lidUnit, initVol); - lidUnit->volTreated = 0.0; - - //... initialize report file for the LID - if ( lidUnit->rptFile ) - { - initLidRptFile(Title[0], LidProcs[k].ID, Subcatch[j].ID, lidUnit); - } - - //... initialize drain flows - lidUnit->oldDrainFlow = 0.0; - lidUnit->newDrainFlow = 0.0; - - //... set previous flux rates to 0 - for (i = 0; i < MAX_LAYERS; i++) - { - lidUnit->oldFluxRates[i] = 0.0; - } - - //... initialize infiltration state variables - if ( lidUnit->soilInfil.Ks > 0.0 ) - grnampt_initState(&(lidUnit->soilInfil)); - - //... add contribution to pervious LID area - if ( isLidPervious(lidUnit->lidIndex) ) - lidGroup->pervArea += (lidUnit->area * lidUnit->number); - lidList = lidList->nextLidUnit; - } - } -} - -//============================================================================= - -void lid_setOldGroupState(int j) -// -// Purpose: saves the current drain flow rate for the LIDs in a subcatchment. -// Input: j = subcatchment index -// Output: none -// -{ - TLidList* lidList; - if ( LidGroups[j] != NULL ) - { - LidGroups[j]->oldDrainFlow = LidGroups[j]->newDrainFlow; - LidGroups[j]->newDrainFlow = 0.0; - lidList = LidGroups[j]->lidList; - while (lidList) - { - lidList->lidUnit->oldDrainFlow = lidList->lidUnit->newDrainFlow; - lidList->lidUnit->newDrainFlow = 0.0; - lidList = lidList->nextLidUnit; - } - } -} - -//============================================================================= - -int isLidPervious(int k) -// -// Purpose: determines if a LID process allows infiltration or not. -// Input: k = LID process index -// Output: returns 1 if process is pervious or 0 if not -// -{ - return ( LidProcs[k].storage.thickness == 0.0 || - LidProcs[k].storage.kSat > 0.0 ); -} - -//============================================================================= - -double getSurfaceDepth(int j) -// -// Purpose: computes the depth (volume per unit area) of ponded water on the -// surface of all LIDs within a subcatchment. -// Input: j = subcatchment index -// Output: returns volumetric depth of ponded water (ft) -// -{ - int k; - double depth = 0.0; - TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; - - lidGroup = LidGroups[j]; - if ( lidGroup == NULL ) return 0.0; - if ( Subcatch[j].lidArea == 0.0 ) return 0.0; - lidList = lidGroup->lidList; - while ( lidList ) - { - lidUnit = lidList->lidUnit; - k = lidUnit->lidIndex; - depth += lidUnit->surfaceDepth * LidProcs[k].surface.voidFrac * - lidUnit->area * lidUnit->number; - lidList = lidList->nextLidUnit; - } - return depth / Subcatch[j].lidArea; -} - -//============================================================================= - -double lid_getPervArea(int j) -// -// Purpose: retrieves amount of pervious LID area in a subcatchment. -// Input: j = subcatchment index -// Output: returns amount of pervious LID area (ft2) -// -{ - if ( LidGroups[j] ) return LidGroups[j]->pervArea; - else return 0.0; -} - -//============================================================================= - -double lid_getFlowToPerv(int j) -// -// Purpose: retrieves flow returned from LID treatment to pervious area of -// a subcatchment. -// Input: j = subcatchment index -// Output: returns flow returned to pervious area (cfs) -// -{ - if ( LidGroups[j] != NULL ) return LidGroups[j]->flowToPerv; - return 0.0; -} - -//============================================================================= - -double lid_getStoredVolume(int j) -// -// Purpose: computes stored volume of water for all LIDs -// grouped within a subcatchment. -// Input: j = subcatchment index -// Output: returns stored volume of water (ft3) -// -{ - double total = 0.0; - TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; - - lidGroup = LidGroups[j]; - if ( lidGroup == NULL || Subcatch[j].lidArea == 0.0 ) return 0.0; - lidList = lidGroup->lidList; - while ( lidList ) - { - lidUnit = lidList->lidUnit; - total += lidUnit->waterBalance.finalVol * lidUnit->area * lidUnit->number; - lidList = lidList->nextLidUnit; - } - return total; -} - -//============================================================================= - -double lid_getDrainFlow(int j, int timePeriod) -// -// Purpose: returns flow from all of a subcatchment's LID drains for -// a designated time period -// Input: j = subcatchment index -// timePeriod = either PREVIOUS or CURRENT -// Output: total drain flow (cfs) from the subcatchment. -{ - if ( LidGroups[j] != NULL ) - { - if ( timePeriod == PREVIOUS ) return LidGroups[j]->oldDrainFlow; - else return LidGroups[j]->newDrainFlow; - } - return 0.0; -} - -//============================================================================= - -void lid_addDrainLoads(int j, double c[], double tStep) -// -// Purpose: adds pollutant loads routed from drains to system -// mass balance totals. -// Input: j = subcatchment index -// c = array of pollutant washoff concentrations (mass/L) -// tStep = time step (sec) -// Output: none. -// -{ - int isRunoffLoad; // true if drain becomes external runoff load - int p; // pollutant index - double r; // pollutant fractional removal - double w; // pollutant mass load (lb or kg) - TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; - - //... check if LID group exists - lidGroup = LidGroups[j]; - if ( lidGroup != NULL ) - { - //... examine each LID unit in the group - lidList = lidGroup->lidList; - while ( lidList ) - { - lidUnit = lidList->lidUnit; - - //... see if unit's drain flow becomes external runoff - isRunoffLoad = (lidUnit->drainNode >= 0 || - lidUnit->drainSubcatch == j); - - //... 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; - - //... get fractional removal for this load - r = LidProcs[lidUnit->lidIndex].drainRmvl[p]; - - //... update system mass balance totals - massbal_updateLoadingTotals(BMP_REMOVAL_LOAD, p, r*w); - if (isRunoffLoad) - massbal_updateLoadingTotals(RUNOFF_LOAD, p, w*(1.0 - r)); - } - - // process next LID unit in the group - lidList = lidList->nextLidUnit; - } - } -} - -//============================================================================= - -void lid_addDrainRunon(int j) -// -// Purpose: adds drain flows from LIDs in a given subcatchment to the -// subcatchments that were designated to receive them -// Input: j = index of subcatchment contributing underdrain flows -// Output: none. -// -{ - int i; // index of an LID unit's LID process - int k; // index of subcatchment receiving LID drain flow - int p; // pollutant index - double q; // drain flow rate (cfs) - double w; // mass of polllutant from drain flow - TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; - - //... check if LID group exists - lidGroup = LidGroups[j]; - if ( lidGroup != NULL ) - { - //... examine each LID in the group - lidList = lidGroup->lidList; - while ( lidList ) - { - //... see if LID's drain discharges to another subcatchment - lidUnit = lidList->lidUnit; - i = lidUnit->lidIndex; - k = lidUnit->drainSubcatch; - if ( k >= 0 && k != j ) - { - //... distribute drain flow across subcatchment's areas - q = lidUnit->oldDrainFlow; - subcatch_addRunonFlow(k, q); - - //... add pollutant loads from drain to subcatchment - // (newQual[] contains loading rate (mass/sec) at this - // point which is converted later on to a concentration) - for (p = 0; p < Nobjects[POLLUT]; p++) - { - w = q * Subcatch[j].oldQual[p] * LperFT3; - w = w * (1.0 - LidProcs[i].drainRmvl[p]); - Subcatch[k].newQual[p] += w; - } - } - lidList = lidList->nextLidUnit; - } - } -} - -//============================================================================= - -void lid_addDrainInflow(int j, double f) -// -// Purpose: adds LID drain flow to conveyance system nodes -// Input: j = subcatchment index -// f = time interval weighting factor -// Output: none. -// -// Note: this function updates the total lateral flow (Node[].newLatFlow) -// and pollutant mass (Node[].newQual[]) inflow seen by nodes that -// receive drain flow from the LID units in subcatchment j. -{ - int i, // LID process index - k, // node index - p; // pollutant index - double q, // drain flow (cfs) - w, w1, w2; // pollutant mass loads (mass/sec) - TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; - - //... check if LID group exists - lidGroup = LidGroups[j]; - if ( lidGroup != NULL ) - { - //... examine each LID in the group - lidList = lidGroup->lidList; - while ( lidList ) - { - //... see if LID's drain discharges to conveyance system node - lidUnit = lidList->lidUnit; - i = lidUnit->lidIndex; - k = lidUnit->drainNode; - if ( k >= 0 ) - { - //... add drain flow to node's wet weather inflow - q = (1.0 - f) * lidUnit->oldDrainFlow + f * lidUnit->newDrainFlow; - Node[k].newLatFlow += q; - massbal_addInflowFlow(WET_WEATHER_INFLOW, q); - - //... add pollutant load, based on parent subcatchment quality - for (p = 0; p < Nobjects[POLLUT]; p++) - { - //... get previous & current drain loads - w1 = lidUnit->oldDrainFlow * Subcatch[j].oldQual[p]; - w2 = lidUnit->newDrainFlow * Subcatch[j].newQual[p]; - - //... add interpolated load to node's wet weather loading - w = (1.0 - f) * w1 + f * w2; - w = w * (1.0 - LidProcs[i].drainRmvl[p]); - Node[k].newQual[p] += w; - massbal_addInflowQual(WET_WEATHER_INFLOW, p, w); - } - } - lidList = lidList->nextLidUnit; - } - } -} - -//============================================================================= - -void lid_getRunoff(int j, double tStep) -// -// Purpose: computes runoff and drain flows from the LIDs in a subcatchment. -// Input: j = subcatchment index -// tStep = time step (sec) -// Output: updates following global quantities after LID treatment applied: -// Vevap, Vpevap, VlidInfil, VlidIn, VlidOut, VlidDrain. -// -{ - TLidGroup theLidGroup; // group of LIDs placed in the subcatchment - TLidList* lidList; // list of LID units in the group - TLidUnit* lidUnit; // a member of the list of LID units - double lidArea; // area of an LID unit - double qImperv = 0.0; // runoff from impervious areas (cfs) - double qPerv = 0.0; // runoff from pervious areas (cfs) - double lidInflow = 0.0; // inflow to an LID unit (ft/s) - double qRunoff = 0.0; // surface runoff from all LID units (cfs) - double qDrain = 0.0; // drain flow from all LID units (cfs) - double qReturn = 0.0; // LID outflow returned to pervious area (cfs) - - //... return if there are no LID's - theLidGroup = LidGroups[j]; - if ( !theLidGroup ) return; - lidList = theLidGroup->lidList; - if ( !lidList ) return; - - //... determine if evaporation can occur - EvapRate = Evap.rate; - if ( Evap.dryOnly && Subcatch[j].rainfall > 0.0 ) EvapRate = 0.0; - - //... find subcatchment's infiltration rate into native soil - findNativeInfil(j, tStep); - - //... get impervious and pervious area runoff from non-LID - // portion of subcatchment (cfs) - if ( Subcatch[j].area > Subcatch[j].lidArea ) - { - qImperv = getImpervAreaRunoff(j); - qPerv = getPervAreaRunoff(j); - } - - //... evaluate performance of each LID unit placed in the subcatchment - while ( lidList ) - { - //... find area of the LID unit - lidUnit = lidList->lidUnit; - lidArea = lidUnit->area * lidUnit->number; - - //... if LID unit has area, evaluate its performance - if ( lidArea > 0.0 ) - { - //... find runoff from non-LID area treated by LID area (ft/sec) - lidInflow = (qImperv * lidUnit->fromImperv + - qPerv * lidUnit->fromPerv) / lidArea; - - //... update total runoff volume treated - VlidIn += lidInflow * lidArea * tStep; - - //... add rainfall onto LID inflow (ft/s) - lidInflow = lidInflow + getRainInflow(j, lidUnit); - - // ... add upstream runon only if LID occupies full subcatchment - if ( Subcatch[j].area == Subcatch[j].lidArea ) - { - lidInflow += Subcatch[j].runon; - } - - //... evaluate the LID unit's performance, updating the LID group's - // total surface runoff, drain flow, and flow returned to - // pervious area - evalLidUnit(j, lidUnit, lidArea, lidInflow, tStep, - &qRunoff, &qDrain, &qReturn); - } - lidList = lidList->nextLidUnit; - } - - //... save the LID group's total drain & return flows - theLidGroup->newDrainFlow = qDrain; - theLidGroup->flowToPerv = qReturn; - - //... save the LID group's total surface, drain and return flow volumes - VlidOut = qRunoff * tStep; - VlidDrain = qDrain * tStep; - VlidReturn = qReturn * tStep; -} - -//============================================================================= - -void findNativeInfil(int j, double tStep) -// -// Purpose: determines a subcatchment's current infiltration rate into -// its native soil. -// Input: j = subcatchment index -// tStep = time step (sec) -// Output: sets values for module-level variables NativeInfil -// -{ - double nonLidArea; - - //... subcatchment has non-LID pervious area - nonLidArea = Subcatch[j].area - Subcatch[j].lidArea; - if ( nonLidArea > 0.0 && Subcatch[j].fracImperv < 1.0 ) - { - NativeInfil = Vinfil / nonLidArea / tStep; - } - - //... otherwise find infil. rate for the subcatchment's rainfall + runon - else - { - NativeInfil = infil_getInfil(j, tStep, - Subcatch[j].rainfall, - Subcatch[j].runon, - getSurfaceDepth(j)); - } - - //... see if there is any groundwater-imposed limit on infil. - if ( !IgnoreGwater && Subcatch[j].groundwater ) - { - MaxNativeInfil = Subcatch[j].groundwater->maxInfilVol / tStep; - } - else MaxNativeInfil = BIG; -} - -//============================================================================= - -double getRainInflow(int j, TLidUnit* lidUnit) -// -// Purpose: gets rainfall inflow to an LID unit. -// Input: j = subcatchment index -// lidUnit = ptr. to an LID unit -// Output: returns rainfall rate over the LID unit (ft/sec) -// -{ - TLidProc* lidProc = &LidProcs[lidUnit->lidIndex]; - - if (lidProc->lidType == RAIN_BARREL && - lidProc->storage.covered == TRUE) return 0.0; - return Subcatch[j].rainfall; -} - -//============================================================================= - -double getImpervAreaRunoff(int j) -// -// Purpose: computes runoff from impervious area of a subcatchment that -// is available for LID treatment. -// Input: j = subcatchment index -// Output: returns runoff flow rate (cfs) -// -{ - int i; - double q = 0.0, // runoff rate (ft/sec) - nonLidArea; // non-LID area (ft2) - - // --- runoff from impervious area w/ & w/o depression storage - for (i = IMPERV0; i <= IMPERV1; i++) - { - q += Subcatch[j].subArea[i].runoff * Subcatch[j].subArea[i].fArea; - } - - // --- adjust for any fraction of runoff sent to pervious area - if ( Subcatch[j].subArea[IMPERV0].routeTo == TO_PERV && - Subcatch[j].fracImperv < 1.0 ) - { - q *= Subcatch[j].subArea[IMPERV0].fOutlet; - } - nonLidArea = Subcatch[j].area - Subcatch[j].lidArea; - return q * nonLidArea; -} - -//============================================================================= - -double getPervAreaRunoff(int j) -// -// Purpose: computes runoff from pervious area of a subcatchment that -// is available for LID treatment. -// Input: j = subcatchment index -// Output: returns runoff flow rate (cfs) -// -{ - double q = 0.0, // runoff rate (ft/sec) - nonLidArea; // non-LID area (ft2) - - // --- runoff from pervious area - q = Subcatch[j].subArea[PERV].runoff * Subcatch[j].subArea[PERV].fArea; - - // --- adjust for any fraction of runoff sent to impervious area - if (Subcatch[j].subArea[PERV].routeTo == TO_IMPERV && - Subcatch[j].fracImperv > 0.0) - { - q *= Subcatch[j].subArea[PERV].fOutlet; - } - nonLidArea = Subcatch[j].area - Subcatch[j].lidArea; - return q * nonLidArea; -} - -//============================================================================= - -void evalLidUnit(int j, TLidUnit* lidUnit, double lidArea, double lidInflow, - double tStep, double *qRunoff, double *qDrain, double *qReturn) -// -// Purpose: evaluates performance of a specific LID unit over current time step. -// Input: j = subcatchment index -// lidUnit = ptr. to LID unit being evaluated -// lidArea = area of LID unit -// lidInflow = inflow to LID unit (ft/s) -// tStep = time step (sec) -// Output: qRunoff = sum of surface runoff from all LIDs (cfs) -// qDrain = sum of drain flows from all LIDs (cfs) -// qReturn = sum of LID flows returned to pervious area (cfs) -// -{ - TLidProc* lidProc; // LID process associated with lidUnit - double lidRunoff, // surface runoff from LID unit (cfs) - lidEvap, // evaporation rate from LID unit (ft/s) - lidInfil, // infiltration rate from LID unit (ft/s) - lidDrain; // drain flow rate from LID unit (ft/s & cfs) - - //... identify the LID process of the LID unit being analyzed - lidProc = &LidProcs[lidUnit->lidIndex]; - - //... initialize evap and infil losses - lidEvap = 0.0; - lidInfil = 0.0; - - //... find surface runoff from the LID unit (in cfs) - lidRunoff = lidproc_getOutflow(lidUnit, lidProc, lidInflow, EvapRate, - NativeInfil, MaxNativeInfil, tStep, - &lidEvap, &lidInfil, &lidDrain) * lidArea; - - //... convert drain flow to CFS - lidDrain *= lidArea; - - //... revise flows if LID outflow returned to pervious area - if ( lidUnit->toPerv && Subcatch[j].area > Subcatch[j].lidArea ) - { - //... surface runoff is always returned - *qReturn += lidRunoff; - lidRunoff = 0.0; - - //... drain flow returned if it has same outlet as subcatchment - if ( lidUnit->drainNode == Subcatch[j].outNode && - lidUnit->drainSubcatch == Subcatch[j].outSubcatch ) - { - *qReturn += lidDrain; - lidDrain = 0.0; - } - } - - //... update system flow balance if drain flow goes to a - // conveyance system node - if ( lidUnit->drainNode >= 0 ) - { - massbal_updateRunoffTotals(RUNOFF_DRAINS, lidDrain * tStep); - } - - //... save new drain outflow - lidUnit->newDrainFlow = lidDrain; - - //... update moisture losses (ft3) - Vevap += lidEvap * tStep * lidArea; - VlidInfil += lidInfil * tStep * lidArea; - if ( isLidPervious(lidUnit->lidIndex) ) - { - Vpevap += lidEvap * tStep * lidArea; - } - - //... update time since last rainfall (for Rain Barrel emptying) - if ( Subcatch[j].rainfall > MIN_RUNOFF ) lidUnit->dryTime = 0.0; - else lidUnit->dryTime += tStep; - - //... update LID water balance and save results - lidproc_saveResults(lidUnit, UCF(RAINFALL), UCF(RAINDEPTH)); - - //... update LID group totals - *qRunoff += lidRunoff; - *qDrain += lidDrain; -} - -//============================================================================= - -void lid_writeWaterBalance() -// -// Purpose: writes a LID performance summary table to the project's report file. -// Input: none -// Output: none -// -{ - int j; - int k = 0; - double ucf = UCF(RAINDEPTH); - double inflow; - double outflow; - double err; - TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; - - //... check that project has LIDs - for ( j = 0; j < GroupCount; j++ ) - { - if ( LidGroups[j] ) k++; - } - if ( k == 0 ) return; - - //... write table header - fprintf(Frpt.file, - "\n" - "\n ***********************" - "\n LID Performance Summary" - "\n ***********************\n"); - - fprintf(Frpt.file, -"\n --------------------------------------------------------------------------------------------------------------------" -"\n Total Evap Infil Surface Drain Initial Final Continuity" -"\n Inflow Loss Loss Outflow Outflow Storage Storage Error"); - if ( UnitSystem == US ) fprintf(Frpt.file, -"\n Subcatchment LID Control in in in in in in in %%"); - else fprintf(Frpt.file, -"\n Subcatchment LID Control mm mm mm mm mm mm mm %%"); - fprintf(Frpt.file, -"\n --------------------------------------------------------------------------------------------------------------------"); - - //... examine each LID unit in each subcatchment - for ( j = 0; j < GroupCount; j++ ) - { - lidGroup = LidGroups[j]; - if ( !lidGroup || Subcatch[j].lidArea == 0.0 ) continue; - lidList = lidGroup->lidList; - while ( lidList ) - { - //... write water balance components to report file - lidUnit = lidList->lidUnit; - k = lidUnit->lidIndex; - fprintf(Frpt.file, "\n %-16s %-16s", Subcatch[j].ID, - LidProcs[k].ID); - fprintf(Frpt.file, "%10.2f%10.2f%10.2f%10.2f%10.2f%10.2f%10.2f", - lidUnit->waterBalance.inflow*ucf, - lidUnit->waterBalance.evap*ucf, - lidUnit->waterBalance.infil*ucf, - lidUnit->waterBalance.surfFlow*ucf, - lidUnit->waterBalance.drainFlow*ucf, - lidUnit->waterBalance.initVol*ucf, - lidUnit->waterBalance.finalVol*ucf); - - //... compute flow balance error - inflow = lidUnit->waterBalance.initVol + - lidUnit->waterBalance.inflow; - outflow = lidUnit->waterBalance.finalVol + - lidUnit->waterBalance.evap + - lidUnit->waterBalance.infil + - lidUnit->waterBalance.surfFlow + - lidUnit->waterBalance.drainFlow; - if ( inflow > 0.0 ) err = (inflow - outflow) / inflow; - else err = 1.0; - fprintf(Frpt.file, " %10.2f", err*100.0); - lidList = lidList->nextLidUnit; - } - } -} - -//============================================================================= - -void initLidRptFile(char* title, char* lidID, char* subcatchID, TLidUnit* lidUnit) -// -// Purpose: initializes the report file used for a specific LID unit -// Input: title = project's title -// lidID = LID process name -// subcatchID = subcatchment ID name -// lidUnit = ptr. to LID unit -// Output: none -// -{ - static int colCount = 14; - static char* head1[] = { - "\n \t", " Elapsed\t", - " Total\t", " Total\t", " Surface\t", " Pavement\t", " Soil\t", - " Storage\t", " Surface\t", " Drain\t", " Surface\t", " Pavement\t", - " Soil\t", " Storage"}; - static char* head2[] = { - "\n \t", " Time\t", - " Inflow\t", " Evap\t", " Infil\t", " Perc\t", " Perc\t", - " Exfil\t", " Runoff\t", " OutFlow\t", " Level\t", " Level\t", - " Moisture\t", " Level"}; - static char* units1[] = { - "\nDate Time \t", " Hours\t", - " in/hr\t", " in/hr\t", " in/hr\t", " in/hr\t", " in/hr\t", - " in/hr\t", " in/hr\t", " in/hr\t", " inches\t", " inches\t", - " Content\t", " inches"}; - static char* units2[] = { - "\nDate Time \t", " Hours\t", - " mm/hr\t", " mm/hr\t", " mm/hr\t", " mm/hr\t", " mm/hr\t", - " mm/hr\t", " mm/hr\t", " mm/hr\t", " mm\t", " mm\t", - " Content\t", " mm"}; - static char line9[] = " ---------"; - int i; - FILE* f = lidUnit->rptFile->file; - - //... check that file was opened - if ( f == NULL ) return; - - //... write title lines - fprintf(f, "SWMM5 LID Report File\n"); - fprintf(f, "\nProject: %s", title); - fprintf(f, "\nLID Unit: %s in Subcatchment %s\n", lidID, subcatchID); - - //... write column headings - for ( i = 0; i < colCount; i++) fprintf(f, "%s", head1[i]); - for ( i = 0; i < colCount; i++) fprintf(f, "%s", head2[i]); - if ( UnitSystem == US ) - { - for ( i = 0; i < colCount; i++) fprintf(f, "%s", units1[i]); - } - else for ( i = 0; i < colCount; i++) fprintf(f, "%s", units2[i]); - fprintf(f, "\n----------- --------"); - for ( i = 1; i < colCount; i++) fprintf(f, "\t%s", line9); - - //... initialize LID dryness state - lidUnit->rptFile->wasDry = 1; - sstrncpy(lidUnit->rptFile->results, "", 0); -} diff --git a/src/lid.h b/src/lid.h deleted file mode 100644 index b250fcf3a..000000000 --- a/src/lid.h +++ /dev/null @@ -1,236 +0,0 @@ -//----------------------------------------------------------------------------- -// lid.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Public interface for LID functions. -// -// Update History -// ============== -// Build 5.1.008: -// - Support added for Roof Disconnection LID. -// - Support added for separate routing of LID drain flows. -// - Detailed LID reporting modified. -// Build 5.1.011: -// - Water depth replaces moisture content for LID's pavement layer. -// - Arguments for lidproc_saveResults() modified. -// Build 5.1.012: -// - Redefined meaning of wasDry in TLidRptFile structure. -// Build 5.1.013: -// - New member fromPerv added to TLidUnit structure to allow LID -// units to also treat pervious area runoff. -// - New members hOpen and hClose addded to TDrainLayer to open/close -// drain when certain heads are reached. -// - New member qCurve added to TDrainLayer to allow underdrain flow to -// be adjusted by a curve of multiplier v. head. -// - New array drainRmvl added to TLidProc to allow for underdrain -// pollutant removal values. -// - New members added to TPavementLayer and TLidUnit to support -// unclogging permeable pavement at fixed intervals. -// Build 5.2.0: -// - Covered property added to RAIN_BARREL parameters -//----------------------------------------------------------------------------- - -#ifndef LID_H -#define LID_H - -#include -#include -#include -#include "infil.h" - -//----------------------------------------------------------------------------- -// Enumerations -//----------------------------------------------------------------------------- -enum LidTypes { - BIO_CELL, // bio-retention cell - RAIN_GARDEN, // rain garden - GREEN_ROOF, // green roof - INFIL_TRENCH, // infiltration trench - POROUS_PAVEMENT, // porous pavement - RAIN_BARREL, // rain barrel - VEG_SWALE, // vegetative swale - ROOF_DISCON}; // roof disconnection - -enum TimePeriod { - PREVIOUS, // previous time period - CURRENT}; // current time period - -//----------------------------------------------------------------------------- -// Data Structures -//----------------------------------------------------------------------------- -#define MAX_LAYERS 4 - -// LID Surface Layer -typedef struct -{ - double thickness; // depression storage or berm ht. (ft) - double voidFrac; // available fraction of storage volume - double roughness; // surface Mannings n - double surfSlope; // land surface slope (fraction) - double sideSlope; // swale side slope (run/rise) - double alpha; // slope/roughness term in Manning eqn. - char canOverflow; // 1 if immediate outflow of excess water -} TSurfaceLayer; - -// LID Pavement Layer -typedef struct -{ - double thickness; // layer thickness (ft) - double voidFrac; // void volume / total volume - double impervFrac; // impervious area fraction - double kSat; // permeability (ft/sec) - double clogFactor; // clogging factor - double regenDays; // clogging regeneration interval (days) - double regenDegree; // degree of clogging regeneration -} TPavementLayer; - -// LID Soil Layer -typedef struct -{ - double thickness; // layer thickness (ft) - double porosity; // void volume / total volume - double fieldCap; // field capacity - double wiltPoint; // wilting point - double suction; // suction head at wetting front (ft) - double kSat; // saturated hydraulic conductivity (ft/sec) - double kSlope; // slope of log(K) v. moisture content curve -} TSoilLayer; - -// LID Storage Layer -typedef struct -{ - double thickness; // layer thickness (ft) - double voidFrac; // void volume / total volume - double kSat; // saturated hydraulic conductivity (ft/sec) - double clogFactor; // clogging factor - int covered; // TRUE if rain barrel is covered -} TStorageLayer; - -// Underdrain System (part of Storage Layer) -typedef struct -{ - double coeff; // underdrain flow coeff. (in/hr or mm/hr) - double expon; // underdrain head exponent (for in or mm) - double offset; // offset height of underdrain (ft) - double delay; // rain barrel drain delay time (sec) - double hOpen; // head when drain opens (ft) - double hClose; // head when drain closes (ft) - int qCurve; // curve controlling flow rate (optional) -} TDrainLayer; - -// Drainage Mat Layer (for green roofs) -typedef struct -{ - double thickness; // layer thickness (ft) - double voidFrac; // void volume / total volume - double roughness; // Mannings n for green roof drainage mats - double alpha; // slope/roughness term in Manning equation -} TDrainMatLayer; - -// LID Process - generic LID design per unit of area -typedef struct -{ - char* ID; // identifying name - int lidType; // type of LID - TSurfaceLayer surface; // surface layer parameters - TPavementLayer pavement; // pavement layer parameters - TSoilLayer soil; // soil layer parameters - TStorageLayer storage; // storage layer parameters - TDrainLayer drain; // underdrain system parameters - TDrainMatLayer drainMat; // drainage mat layer - double* drainRmvl; // underdrain pollutant removals -} TLidProc; - -// Water Balance Statistics -typedef struct -{ - double inflow; // total inflow (ft) - double evap; // total evaporation (ft) - double infil; // total infiltration (ft) - double surfFlow; // total surface runoff (ft) - double drainFlow; // total underdrain flow (ft) - double initVol; // initial stored volume (ft) - double finalVol; // final stored volume (ft) -} TWaterBalance; - -// LID Report File -typedef struct -{ - FILE* file; // file pointer - int wasDry; // number of successive dry periods - char results[256]; // results for current time period -} TLidRptFile; - -// LID Unit - specific LID process applied over a given area -typedef struct -{ - int lidIndex; // index of LID process - int number; // number of replicate units - double area; // area of single replicate unit (ft2) - double fullWidth; // full top width of single unit (ft) - double botWidth; // bottom width of single unit (ft) - double initSat; // initial saturation of soil & storage layers - double fromImperv; // fraction of impervious area runoff treated - double fromPerv; // fraction of pervious area runoff treated - int toPerv; // 1 if outflow sent to pervious area; 0 if not - int drainSubcatch; // subcatchment receiving drain flow - int drainNode; // node receiving drain flow - TLidRptFile* rptFile; // pointer to detailed report file - - TGrnAmpt soilInfil; // infil. object for biocell soil layer - double surfaceDepth; // depth of ponded water on surface layer (ft) - double paveDepth; // depth of water in porous pavement layer - double soilMoisture; // moisture content of biocell soil layer - double storageDepth; // depth of water in storage layer (ft) - - // net inflow - outflow from previous time step for each LID layer (ft/s) - double oldFluxRates[MAX_LAYERS]; - - double dryTime; // time since last rainfall (sec) - double oldDrainFlow; // previous drain flow (cfs) - double newDrainFlow; // current drain flow (cfs) - double volTreated; // total volume treated (ft) - double nextRegenDay; // next day when unit regenerated - TWaterBalance waterBalance; // water balance quantites -} TLidUnit; - -//----------------------------------------------------------------------------- -// LID Methods -//----------------------------------------------------------------------------- -void lid_create(int lidCount, int subcatchCount); -void lid_delete(void); - -int lid_readProcParams(char* tok[], int ntoks); -int lid_readGroupParams(char* tok[], int ntoks); - -void lid_validate(void); -void lid_initState(void); -void lid_setOldGroupState(int subcatch); - -double lid_getPervArea(int subcatch); -double lid_getFlowToPerv(int subcatch); -double lid_getDrainFlow(int subcatch, int timePeriod); -double lid_getStoredVolume(int subcatch); -void lid_addDrainLoads(int subcatch, double c[], double tStep); -void lid_addDrainRunon(int subcatch); -void lid_addDrainInflow(int subcatch, double f); -void lid_getRunoff(int subcatch, double tStep); -void lid_writeSummary(void); -void lid_writeWaterBalance(void); - -//----------------------------------------------------------------------------- - -void lidproc_initWaterBalance(TLidUnit *lidUnit, double initVol); - -double lidproc_getOutflow(TLidUnit* lidUnit, TLidProc* lidProc, - double inflow, double evap, double infil, double maxInfil, - double tStep, double* lidEvap, double* lidInfil, double* lidDrain); - -void lidproc_saveResults(TLidUnit* lidUnit, double ucfRainfall, - double ucfRainDepth); - -#endif diff --git a/src/lidproc.c b/src/lidproc.c deleted file mode 100644 index aae0ac9bd..000000000 --- a/src/lidproc.c +++ /dev/null @@ -1,1592 +0,0 @@ -//----------------------------------------------------------------------------- -// lidproc.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// This module computes the hydrologic performance of an LID (Low Impact -// Development) unit at a given point in time. -// -// Update History -// ============== -// Build 5.1.007: -// - Euler integration now applied to all LID types except Vegetative -// Swale which continues to use successive approximation. -// - LID layer flux routines were re-written to more accurately model -// flooded conditions. -// Build 5.1.008: -// - MAX_STATE_VARS replaced with MAX_LAYERS. -// - Optional soil layer added to Porous Pavement LID. -// - Rooftop Disconnection added to types of LIDs. -// - Separate accounting of drain flows added. -// - Indicator for currently wet LIDs added. -// - Detailed reporting procedure fixed. -// - Possibile negative head on Bioretention Cell drain avoided. -// - Bug in computing flow through Green Roof drainage mat fixed. -// Build 5.1.009: -// - Fixed typo in net flux rate for vegetative swale LID. -// Build 5.1.010: -// - New modified version of Green-Ampt used for surface layer infiltration. -// Build 5.1.011: -// - Re-named STOR_INFIL to STOR_EXFIL and StorageInfil to StorageExfil to -// better reflect their meaning. -// - Evaporation rates from sub-surface layers reduced by fraction of -// surface that is pervious (applies to block paver systems) -// - Flux rate routines for LIDs with underdrains modified to produce more -// physically meaningful results. -// - Reporting of detailed results re-written. -// Build 5.1.012: -// - Modified upper limit for soil layer percolation. -// - Modified upper limit on surface infiltration into rain gardens. -// - Modified upper limit on drain flow for LIDs with storage layers. -// - Used re-defined wasDry variable for LID reports to fix duplicate lines. -// Build 5.1.013: -// - Support added for open/closed head levels and multiplier v. head curve -// 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. -// Build 5.2.0: -// - Fixed failure to account for effect of Impervious Surface Fraction on -// pavement permeability for Permeable Pavement LID -// - Fixed units conversion for pavement depth in detailed report file. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include -#include "lid.h" -#include "headers.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -#define STOPTOL 0.00328 // integration error tolerance in ft (= 1 mm) -#define MINFLOW 2.3e-8 // flow cutoff for dry conditions (= 0.001 in/hr) - -//----------------------------------------------------------------------------- -// Enumerations -//----------------------------------------------------------------------------- -enum LidLayerTypes { - SURF, // surface layer - SOIL, // soil layer - STOR, // storage layer - PAVE, // pavement layer - DRAIN}; // underdrain system - -enum LidRptVars { - SURF_INFLOW, // inflow to surface layer - TOTAL_EVAP, // evaporation rate from all layers - SURF_INFIL, // infiltration into surface layer - PAVE_PERC, // percolation through pavement layer - SOIL_PERC, // percolation through soil layer - STOR_EXFIL, // exfiltration out of storage layer - SURF_OUTFLOW, // outflow from surface layer - STOR_DRAIN, // outflow from storage layer - SURF_DEPTH, // ponded depth on surface layer - PAVE_DEPTH, // water level in pavement layer - SOIL_MOIST, // moisture content of soil layer - STOR_DEPTH, // water level in storage layer - MAX_RPT_VARS}; - -//----------------------------------------------------------------------------- -// Imported variables -//----------------------------------------------------------------------------- -extern char HasWetLids; // TRUE if any LIDs are wet (declared in runoff.c) - -//----------------------------------------------------------------------------- -// Local Variables -//----------------------------------------------------------------------------- -static TLidUnit* theLidUnit; // ptr. to a subcatchment's LID unit -static TLidProc* theLidProc; // ptr. to a LID process - -static double Tstep; // current time step (sec) -static double EvapRate; // evaporation rate (ft/s) -static double MaxNativeInfil; // native soil infil. rate limit (ft/s) - -static double SurfaceInflow; // precip. + runon to LID unit (ft/s) -static double SurfaceInfil; // infil. rate from surface layer (ft/s) -static double SurfaceEvap; // evap. rate from surface layer (ft/s) -static double SurfaceOutflow; // outflow from surface layer (ft/s) -static double SurfaceVolume; // volume in surface storage (ft) - -static double PaveEvap; // evap. from pavement layer (ft/s) -static double PavePerc; // percolation from pavement layer (ft/s) -static double PaveVolume; // volume stored in pavement layer (ft) - -static double SoilEvap; // evap. from soil layer (ft/s) -static double SoilPerc; // percolation from soil layer (ft/s) -static double SoilVolume; // volume in soil/pavement storage (ft) - -static double StorageInflow; // inflow rate to storage layer (ft/s) -static double StorageExfil; // exfil. rate from storage layer (ft/s) -static double StorageEvap; // evap.rate from storage layer (ft/s) -static double StorageDrain; // underdrain flow rate layer (ft/s) -static double StorageVolume; // volume in storage layer (ft) - -static double Xold[MAX_LAYERS]; // previous moisture level in LID layers - -//----------------------------------------------------------------------------- -// External Functions (declared in lid.h) -//----------------------------------------------------------------------------- -// lidproc_initWaterBalance (called by lid_initState) -// lidproc_getOutflow (called by evalLidUnit in lid.c) -// lidproc_saveResults (called by evalLidUnit in lid.c) - -//----------------------------------------------------------------------------- -// Local Functions -//----------------------------------------------------------------------------- -static void barrelFluxRates(double x[], double f[]); -static void biocellFluxRates(double x[], double f[]); -static void greenRoofFluxRates(double x[], double f[]); -static void pavementFluxRates(double x[], double f[]); -static void trenchFluxRates(double x[], double f[]); -static void swaleFluxRates(double x[], double f[]); -static void roofFluxRates(double x[], double f[]); - -static double getSurfaceOutflowRate(double depth); -static double getSurfaceOverflowRate(double* surfaceDepth); -static double getPavementPermRate(void); -static double getSoilPercRate(double theta); -static double getStorageExfilRate(void); -static double getStorageDrainRate(double storageDepth, double soilTheta, - double paveDepth, double surfaceDepth); -static double getDrainMatOutflow(double depth); -static void getEvapRates(double surfaceVol, double paveVol, - double soilVol, double storageVol, double pervFrac); - -static void updateWaterBalance(TLidUnit *lidUnit, double inflow, - double evap, double infil, double surfFlow, - double drainFlow, double storage); - -static int modpuls_solve(int n, double* x, double* xOld, double* xPrev, - double* xMin, double* xMax, double* xTol, - double* qOld, double* q, double dt, double omega, - void (*derivs)(double*, double*)); - -//============================================================================= - -void lidproc_initWaterBalance(TLidUnit *lidUnit, double initVol) -// -// Purpose: initializes the water balance components of a LID unit. -// Input: lidUnit = a particular LID unit -// initVol = initial water volume stored in the unit (ft) -// Output: none -// -{ - lidUnit->waterBalance.inflow = 0.0; - lidUnit->waterBalance.evap = 0.0; - lidUnit->waterBalance.infil = 0.0; - lidUnit->waterBalance.surfFlow = 0.0; - lidUnit->waterBalance.drainFlow = 0.0; - lidUnit->waterBalance.initVol = initVol; - lidUnit->waterBalance.finalVol = initVol; -} - -//============================================================================= - -double lidproc_getOutflow(TLidUnit* lidUnit, TLidProc* lidProc, double inflow, - double evap, double infil, double maxInfil, - double tStep, double* lidEvap, - double* lidInfil, double* lidDrain) -// -// Purpose: computes runoff outflow from a single LID unit. -// Input: lidUnit = ptr. to specific LID unit being analyzed -// lidProc = ptr. to generic LID process of the LID unit -// inflow = runoff rate captured by LID unit (ft/s) -// evap = potential evaporation rate (ft/s) -// infil = infiltration rate to native soil (ft/s) -// maxInfil = max. infiltration rate to native soil (ft/s) -// tStep = time step (sec) -// Output: lidEvap = evaporation rate for LID unit (ft/s) -// lidInfil = infiltration rate for LID unit (ft/s) -// lidDrain = drain flow for LID unit (ft/s) -// returns surface runoff rate from the LID unit (ft/s) -// -{ - int i; - double x[MAX_LAYERS]; // layer moisture levels - double xOld[MAX_LAYERS]; // work vector - double xPrev[MAX_LAYERS]; // work vector - double xMin[MAX_LAYERS]; // lower limit on moisture levels - double xMax[MAX_LAYERS]; // upper limit on moisture levels - double fOld[MAX_LAYERS]; // previously computed flux rates - double f[MAX_LAYERS]; // newly computed flux rates - - // convergence tolerance on moisture levels (ft, moisture fraction , ft) - double xTol[MAX_LAYERS] = {STOPTOL, STOPTOL, STOPTOL, STOPTOL}; - - double omega = 0.0; // integration time weighting - - //... define a pointer to function that computes flux rates through the LID - void (*fluxRates) (double *, double *) = NULL; - - //... save references to the LID process and LID unit - theLidProc = lidProc; - theLidUnit = lidUnit; - - //... save evap, max. infil. & time step to shared variables - EvapRate = evap; - MaxNativeInfil = maxInfil; - Tstep = tStep; - - //... store current moisture levels in vector x - x[SURF] = theLidUnit->surfaceDepth; - x[SOIL] = theLidUnit->soilMoisture; - x[STOR] = theLidUnit->storageDepth; - x[PAVE] = theLidUnit->paveDepth; - - //... 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; - SurfaceOutflow = 0.0; - PaveEvap = 0.0; - PavePerc = 0.0; - SoilEvap = 0.0; - SoilPerc = 0.0; - StorageInflow = 0.0; - StorageExfil = 0.0; - StorageEvap = 0.0; - StorageDrain = 0.0; - for (i = 0; i < MAX_LAYERS; i++) - { - f[i] = 0.0; - fOld[i] = theLidUnit->oldFluxRates[i]; - xMin[i] = 0.0; - xMax[i] = BIG; - Xold[i] = x[i]; - } - - //... find Green-Ampt infiltration from surface layer - if ( theLidProc->lidType == POROUS_PAVEMENT ) SurfaceInfil = 0.0; - else if ( theLidUnit->soilInfil.Ks > 0.0 ) - { - SurfaceInfil = - grnampt_getInfil(&theLidUnit->soilInfil, Tstep, - SurfaceInflow, theLidUnit->surfaceDepth, - MOD_GREEN_AMPT); - } - else SurfaceInfil = infil; - - //... set moisture limits for soil & storage layers - if ( theLidProc->soil.thickness > 0.0 ) - { - xMin[SOIL] = theLidProc->soil.wiltPoint; - xMax[SOIL] = theLidProc->soil.porosity; - } - if ( theLidProc->pavement.thickness > 0.0 ) - { - xMax[PAVE] = theLidProc->pavement.thickness; - } - if ( theLidProc->storage.thickness > 0.0 ) - { - xMax[STOR] = theLidProc->storage.thickness; - } - if ( theLidProc->lidType == GREEN_ROOF ) - { - xMax[STOR] = theLidProc->drainMat.thickness; - } - - //... determine which flux rate function to use - switch (theLidProc->lidType) - { - case BIO_CELL: - case RAIN_GARDEN: fluxRates = &biocellFluxRates; break; - case GREEN_ROOF: fluxRates = &greenRoofFluxRates; break; - case INFIL_TRENCH: fluxRates = &trenchFluxRates; break; - case POROUS_PAVEMENT: fluxRates = &pavementFluxRates; break; - case RAIN_BARREL: fluxRates = &barrelFluxRates; break; - case ROOF_DISCON: fluxRates = &roofFluxRates; break; - case VEG_SWALE: fluxRates = &swaleFluxRates; - omega = 0.5; - break; - default: return 0.0; - } - - //... update moisture levels and flux rates over the time step - i = modpuls_solve(MAX_LAYERS, x, xOld, xPrev, xMin, xMax, xTol, - fOld, f, tStep, omega, fluxRates); - -/** For debugging only ******************************************** - if (i == 0) - { - fprintf(Frpt.file, - "\n WARNING 09: integration failed to converge at %s %s", - theDate, theTime); - fprintf(Frpt.file, - "\n for LID %s placed in subcatchment %s.", - theLidProc->ID, theSubcatch->ID); - } -*******************************************************************/ - - //... add any surface overflow to surface outflow - if ( theLidProc->surface.canOverflow || theLidUnit->fullWidth == 0.0 ) - { - SurfaceOutflow += getSurfaceOverflowRate(&x[SURF]); - } - - //... save updated results - theLidUnit->surfaceDepth = x[SURF]; - theLidUnit->paveDepth = x[PAVE]; - theLidUnit->soilMoisture = x[SOIL]; - theLidUnit->storageDepth = x[STOR]; - for (i = 0; i < MAX_LAYERS; i++) theLidUnit->oldFluxRates[i] = f[i]; - - //... assign values to LID unit evaporation, infiltration & drain flow - *lidEvap = SurfaceEvap + PaveEvap + SoilEvap + StorageEvap; - *lidInfil = StorageExfil; - *lidDrain = StorageDrain; - - //... return surface outflow (per unit area) from unit - return SurfaceOutflow; -} - -//============================================================================= - -void lidproc_saveResults(TLidUnit* lidUnit, double ucfRainfall, double ucfRainDepth) -// -// Purpose: updates the mass balance for an LID unit and saves -// current flux rates to the LID report file. -// Input: lidUnit = ptr. to LID unit -// ucfRainfall = units conversion factor for rainfall rate -// ucfDepth = units conversion factor for rainfall depth -// Output: none -// -{ - double ucf; // units conversion factor - double totalEvap; // total evaporation rate (ft/s) - double totalVolume; // total volume stored in LID (ft) - double rptVars[MAX_RPT_VARS]; // array of reporting variables - int isDry = FALSE; // true if current state of LID is dry - char timeStamp[TIME_STAMP_SIZE + 1]; // date/time stamp - double elapsedHrs; // elapsed hours - - //... find total evap. rate and stored volume - totalEvap = SurfaceEvap + PaveEvap + SoilEvap + StorageEvap; - totalVolume = SurfaceVolume + PaveVolume + SoilVolume + StorageVolume; - - //... update mass balance totals - updateWaterBalance(theLidUnit, SurfaceInflow, totalEvap, StorageExfil, - SurfaceOutflow, StorageDrain, totalVolume); - - //... check if dry-weather conditions hold - if ( SurfaceInflow < MINFLOW && - SurfaceOutflow < MINFLOW && - StorageDrain < MINFLOW && - StorageExfil < MINFLOW && - totalEvap < MINFLOW - ) isDry = TRUE; - - //... update status of HasWetLids - if ( !isDry ) HasWetLids = TRUE; - - //... write results to LID report file - if ( lidUnit->rptFile ) - { - //... convert rate results to original units (in/hr or mm/hr) - ucf = ucfRainfall; - rptVars[SURF_INFLOW] = SurfaceInflow*ucf; - rptVars[TOTAL_EVAP] = totalEvap*ucf; - rptVars[SURF_INFIL] = SurfaceInfil*ucf; - rptVars[PAVE_PERC] = PavePerc*ucf; - rptVars[SOIL_PERC] = SoilPerc*ucf; - rptVars[STOR_EXFIL] = StorageExfil*ucf; - rptVars[SURF_OUTFLOW] = SurfaceOutflow*ucf; - rptVars[STOR_DRAIN] = StorageDrain*ucf; - - //... convert storage results to original units (in or mm) - ucf = ucfRainDepth; - rptVars[SURF_DEPTH] = theLidUnit->surfaceDepth*ucf; - rptVars[PAVE_DEPTH] = theLidUnit->paveDepth*ucf; - rptVars[SOIL_MOIST] = theLidUnit->soilMoisture; - rptVars[STOR_DEPTH] = theLidUnit->storageDepth*ucf; - - //... if the current LID state is wet but the previous state was dry - // for more than one period then write the saved previous results - // to the report file thus marking the end of a dry period - if ( !isDry && theLidUnit->rptFile->wasDry > 1) - { - fprintf(theLidUnit->rptFile->file, "%s", - theLidUnit->rptFile->results); - } - - //... write the current results to a string which is saved between - // reporting periods - elapsedHrs = NewRunoffTime / 1000.0 / 3600.0; - datetime_getTimeStamp( - M_D_Y, getDateTime(NewRunoffTime), TIME_STAMP_SIZE, timeStamp); - snprintf(theLidUnit->rptFile->results, sizeof(theLidUnit->rptFile->results), - "\n%20s\t %8.3f\t %8.3f\t %8.4f\t %8.3f\t %8.3f\t %8.3f\t %8.3f\t" - "%8.3f\t %8.3f\t %8.3f\t %8.3f\t %8.3f\t %8.3f", - timeStamp, elapsedHrs, rptVars[0], rptVars[1], rptVars[2], - rptVars[3], rptVars[4], rptVars[5], rptVars[6], rptVars[7], - rptVars[8], rptVars[9], rptVars[10], rptVars[11]); - - //... if the current LID state is dry - if ( isDry ) - { - //... if the previous state was wet then write the current - // results to file marking the start of a dry period - if ( theLidUnit->rptFile->wasDry == 0 ) - { - fprintf(theLidUnit->rptFile->file, "%s", - theLidUnit->rptFile->results); - } - - //... increment the number of successive dry periods - theLidUnit->rptFile->wasDry++; - } - - //... if the current LID state is wet - else - { - //... write the current results to the report file - fprintf(theLidUnit->rptFile->file, "%s", - theLidUnit->rptFile->results); - - //... re-set the number of successive dry periods to 0 - theLidUnit->rptFile->wasDry = 0; - } - } -} - -//============================================================================= - -void roofFluxRates(double x[], double f[]) -// -// Purpose: computes flux rates for roof disconnection. -// Input: x = vector of storage levels -// Output: f = vector of flux rates -// -{ - double surfaceDepth = x[SURF]; - - getEvapRates(surfaceDepth, 0.0, 0.0, 0.0, 1.0); - SurfaceVolume = surfaceDepth; - SurfaceInfil = 0.0; - if ( theLidProc->surface.alpha > 0.0 ) - SurfaceOutflow = getSurfaceOutflowRate(surfaceDepth); - else getSurfaceOverflowRate(&surfaceDepth); - StorageDrain = MIN(theLidProc->drain.coeff/UCF(RAINFALL), SurfaceOutflow); - SurfaceOutflow -= StorageDrain; - f[SURF] = (SurfaceInflow - SurfaceEvap - StorageDrain - SurfaceOutflow); -} - -//============================================================================= - -void greenRoofFluxRates(double x[], double f[]) -// -// Purpose: computes flux rates from the layers of a green roof. -// Input: x = vector of storage levels -// Output: f = vector of flux rates -// -{ - // Moisture level variables - double surfaceDepth; - double soilTheta; - double storageDepth; - - // Intermediate variables - double availVolume; - double maxRate; - - // Green roof properties - double soilThickness = theLidProc->soil.thickness; - double storageThickness = theLidProc->storage.thickness; - double soilPorosity = theLidProc->soil.porosity; - double storageVoidFrac = theLidProc->storage.voidFrac; - double soilFieldCap = theLidProc->soil.fieldCap; - double soilWiltPoint = theLidProc->soil.wiltPoint; - - //... retrieve moisture levels from input vector - surfaceDepth = x[SURF]; - soilTheta = x[SOIL]; - storageDepth = x[STOR]; - - //... convert moisture levels to volumes - SurfaceVolume = surfaceDepth * theLidProc->surface.voidFrac; - SoilVolume = soilTheta * soilThickness; - StorageVolume = storageDepth * storageVoidFrac; - - //... get ET rates - availVolume = SoilVolume - soilWiltPoint * soilThickness; - getEvapRates(SurfaceVolume, 0.0, availVolume, StorageVolume, 1.0); - if ( soilTheta >= soilPorosity ) StorageEvap = 0.0; - - //... soil layer perc rate - SoilPerc = getSoilPercRate(soilTheta); - - //... limit perc rate by available water - availVolume = (soilTheta - soilFieldCap) * soilThickness; - maxRate = MAX(availVolume, 0.0) / Tstep - SoilEvap; - SoilPerc = MIN(SoilPerc, maxRate); - SoilPerc = MAX(SoilPerc, 0.0); - - //... storage (drain mat) outflow rate - StorageExfil = 0.0; - StorageDrain = getDrainMatOutflow(storageDepth); - - //... unit is full - if ( soilTheta >= soilPorosity && storageDepth >= storageThickness ) - { - //... outflow from both layers equals limiting rate - maxRate = MIN(SoilPerc, StorageDrain); - SoilPerc = maxRate; - StorageDrain = maxRate; - - //... adjust inflow rate to soil layer - SurfaceInfil = MIN(SurfaceInfil, maxRate); - } - - //... unit not full - else - { - //... limit drainmat outflow by available storage volume - maxRate = storageDepth * storageVoidFrac / Tstep - StorageEvap; - if ( storageDepth >= storageThickness ) maxRate += SoilPerc; - maxRate = MAX(maxRate, 0.0); - StorageDrain = MIN(StorageDrain, maxRate); - - //... limit soil perc inflow by unused storage volume - maxRate = (storageThickness - storageDepth) * storageVoidFrac / Tstep + - StorageDrain + StorageEvap; - SoilPerc = MIN(SoilPerc, maxRate); - - //... adjust surface infil. so soil porosity not exceeded - maxRate = (soilPorosity - soilTheta) * soilThickness / Tstep + - SoilPerc + SoilEvap; - SurfaceInfil = MIN(SurfaceInfil, maxRate); - } - - // ... find surface outflow rate - SurfaceOutflow = getSurfaceOutflowRate(surfaceDepth); - - // ... compute overall layer flux rates - f[SURF] = (SurfaceInflow - SurfaceEvap - SurfaceInfil - SurfaceOutflow) / - theLidProc->surface.voidFrac; - f[SOIL] = (SurfaceInfil - SoilEvap - SoilPerc) / - theLidProc->soil.thickness; - f[STOR] = (SoilPerc - StorageEvap - StorageDrain) / - theLidProc->storage.voidFrac; -} - -//============================================================================= - -void biocellFluxRates(double x[], double f[]) -// -// Purpose: computes flux rates from the layers of a bio-retention cell LID. -// Input: x = vector of storage levels -// Output: f = vector of flux rates -// -{ - // Moisture level variables - double surfaceDepth; - double soilTheta; - double storageDepth; - - // Intermediate variables - double availVolume; - double maxRate; - - // LID layer properties - double soilThickness = theLidProc->soil.thickness; - double soilPorosity = theLidProc->soil.porosity; - double soilFieldCap = theLidProc->soil.fieldCap; - double soilWiltPoint = theLidProc->soil.wiltPoint; - double storageThickness = theLidProc->storage.thickness; - double storageVoidFrac = theLidProc->storage.voidFrac; - - //... retrieve moisture levels from input vector - surfaceDepth = x[SURF]; - soilTheta = x[SOIL]; - storageDepth = x[STOR]; - - //... convert moisture levels to volumes - SurfaceVolume = surfaceDepth * theLidProc->surface.voidFrac; - SoilVolume = soilTheta * soilThickness; - StorageVolume = storageDepth * storageVoidFrac; - - //... get ET rates - availVolume = SoilVolume - soilWiltPoint * soilThickness; - getEvapRates(SurfaceVolume, 0.0, availVolume, StorageVolume, 1.0); - if ( soilTheta >= soilPorosity ) StorageEvap = 0.0; - - //... soil layer perc rate - SoilPerc = getSoilPercRate(soilTheta); - - //... limit perc rate by available water - availVolume = (soilTheta - soilFieldCap) * soilThickness; - maxRate = MAX(availVolume, 0.0) / Tstep - SoilEvap; - SoilPerc = MIN(SoilPerc, maxRate); - SoilPerc = MAX(SoilPerc, 0.0); - - //... exfiltration rate out of storage layer - StorageExfil = getStorageExfilRate(); - - //... underdrain flow rate - StorageDrain = 0.0; - if ( theLidProc->drain.coeff > 0.0 ) - { - StorageDrain = getStorageDrainRate(storageDepth, soilTheta, 0.0, - surfaceDepth); - } - - //... special case of no storage layer present - if ( storageThickness == 0.0 ) - { - StorageEvap = 0.0; - maxRate = MIN(SoilPerc, StorageExfil); - SoilPerc = maxRate; - StorageExfil = maxRate; - - //... limit surface infil. by unused soil volume - maxRate = (soilPorosity - soilTheta) * soilThickness / Tstep + - SoilPerc + SoilEvap; - SurfaceInfil = MIN(SurfaceInfil, maxRate); - } - - //... storage & soil layers are full - else if ( soilTheta >= soilPorosity && storageDepth >= storageThickness ) - { - //... limiting rate is smaller of soil perc and storage outflow - maxRate = StorageExfil + StorageDrain; - if ( SoilPerc < maxRate ) - { - maxRate = SoilPerc; - if ( maxRate > StorageExfil ) StorageDrain = maxRate - StorageExfil; - else - { - StorageExfil = maxRate; - StorageDrain = 0.0; - } - } - else SoilPerc = maxRate; - - //... apply limiting rate to surface infil. - SurfaceInfil = MIN(SurfaceInfil, maxRate); - } - - //... either layer not full - else if ( storageThickness > 0.0 ) - { - //... limit storage exfiltration by available storage volume - maxRate = SoilPerc - StorageEvap + storageDepth*storageVoidFrac/Tstep; - StorageExfil = MIN(StorageExfil, maxRate); - StorageExfil = MAX(StorageExfil, 0.0); - - //... limit underdrain flow by volume above drain offset - if ( StorageDrain > 0.0 ) - { - maxRate = -StorageExfil - StorageEvap; - if ( storageDepth >= storageThickness) maxRate += SoilPerc; - if ( theLidProc->drain.offset <= storageDepth ) - { - maxRate += (storageDepth - theLidProc->drain.offset) * - storageVoidFrac/Tstep; - } - maxRate = MAX(maxRate, 0.0); - StorageDrain = MIN(StorageDrain, maxRate); - } - - //... limit soil perc by unused storage volume - maxRate = StorageExfil + StorageDrain + StorageEvap + - (storageThickness - storageDepth) * - storageVoidFrac/Tstep; - SoilPerc = MIN(SoilPerc, maxRate); - - //... limit surface infil. by unused soil volume - maxRate = (soilPorosity - soilTheta) * soilThickness / Tstep + - SoilPerc + SoilEvap; - SurfaceInfil = MIN(SurfaceInfil, maxRate); - } - - //... find surface layer outflow rate - SurfaceOutflow = getSurfaceOutflowRate(surfaceDepth); - - //... compute overall layer flux rates - f[SURF] = (SurfaceInflow - SurfaceEvap - SurfaceInfil - SurfaceOutflow) / - theLidProc->surface.voidFrac; - f[SOIL] = (SurfaceInfil - SoilEvap - SoilPerc) / - theLidProc->soil.thickness; - if ( storageThickness == 0.0 ) f[STOR] = 0.0; - else f[STOR] = (SoilPerc - StorageEvap - StorageExfil - StorageDrain) / - theLidProc->storage.voidFrac; -} - -//============================================================================= - -void trenchFluxRates(double x[], double f[]) -// -// Purpose: computes flux rates from the layers of an infiltration trench LID. -// Input: x = vector of storage levels -// Output: f = vector of flux rates -// -{ - // Moisture level variables - double surfaceDepth; - double storageDepth; - - // Intermediate variables - double availVolume; - double maxRate; - - // Storage layer properties - double storageThickness = theLidProc->storage.thickness; - double storageVoidFrac = theLidProc->storage.voidFrac; - - //... retrieve moisture levels from input vector - surfaceDepth = x[SURF]; - storageDepth = x[STOR]; - - //... convert moisture levels to volumes - SurfaceVolume = surfaceDepth * theLidProc->surface.voidFrac; - SoilVolume = 0.0; - StorageVolume = storageDepth * storageVoidFrac; - - //... get ET rates - availVolume = (storageThickness - storageDepth) * storageVoidFrac; - getEvapRates(SurfaceVolume, 0.0, 0.0, StorageVolume, 1.0); - - //... no storage evap if surface ponded - if ( surfaceDepth > 0.0 ) StorageEvap = 0.0; - - //... nominal storage inflow - StorageInflow = SurfaceInflow + SurfaceVolume / Tstep; - - //... exfiltration rate out of storage layer - StorageExfil = getStorageExfilRate(); - - //... underdrain flow rate - StorageDrain = 0.0; - if ( theLidProc->drain.coeff > 0.0 ) - { - StorageDrain = getStorageDrainRate(storageDepth, 0.0, 0.0, surfaceDepth); - } - - //... limit storage exfiltration by available storage volume - maxRate = StorageInflow - StorageEvap + storageDepth*storageVoidFrac/Tstep; - StorageExfil = MIN(StorageExfil, maxRate); - StorageExfil = MAX(StorageExfil, 0.0); - - //... limit underdrain flow by volume above drain offset - if ( StorageDrain > 0.0 ) - { - maxRate = -StorageExfil - StorageEvap; - if (storageDepth >= storageThickness ) maxRate += StorageInflow; - if ( theLidProc->drain.offset <= storageDepth ) - { - maxRate += (storageDepth - theLidProc->drain.offset) * - storageVoidFrac/Tstep; - } - maxRate = MAX(maxRate, 0.0); - StorageDrain = MIN(StorageDrain, maxRate); - } - - //... limit storage inflow to not exceed storage layer capacity - maxRate = (storageThickness - storageDepth)*storageVoidFrac/Tstep + - StorageExfil + StorageEvap + StorageDrain; - StorageInflow = MIN(StorageInflow, maxRate); - - //... equate surface infil to storage inflow - SurfaceInfil = StorageInflow; - - //... find surface outflow rate - SurfaceOutflow = getSurfaceOutflowRate(surfaceDepth); - - // ... find net fluxes for each layer - f[SURF] = SurfaceInflow - SurfaceEvap - StorageInflow - SurfaceOutflow / - theLidProc->surface.voidFrac;; - f[STOR] = (StorageInflow - StorageEvap - StorageExfil - StorageDrain) / - theLidProc->storage.voidFrac; - f[SOIL] = 0.0; -} - -//============================================================================= - -void pavementFluxRates(double x[], double f[]) -// -// Purpose: computes flux rates for the layers of a porous pavement LID. -// Input: x = vector of storage levels -// Output: f = vector of flux rates -// -{ - //... Moisture level variables - double surfaceDepth; - double paveDepth; - double soilTheta; - double storageDepth; - - //... Intermediate variables - double pervFrac = (1.0 - theLidProc->pavement.impervFrac); - double storageInflow; // inflow rate to storage layer (ft/s) - double availVolume; - double maxRate; - - //... LID layer properties - double paveVoidFrac = theLidProc->pavement.voidFrac * pervFrac; - double paveThickness = theLidProc->pavement.thickness; - double soilThickness = theLidProc->soil.thickness; - double soilPorosity = theLidProc->soil.porosity; - double soilFieldCap = theLidProc->soil.fieldCap; - double soilWiltPoint = theLidProc->soil.wiltPoint; - double storageThickness = theLidProc->storage.thickness; - double storageVoidFrac = theLidProc->storage.voidFrac; - - //... retrieve moisture levels from input vector - surfaceDepth = x[SURF]; - paveDepth = x[PAVE]; - soilTheta = x[SOIL]; - storageDepth = x[STOR]; - - //... convert moisture levels to volumes - SurfaceVolume = surfaceDepth * theLidProc->surface.voidFrac; - PaveVolume = paveDepth * paveVoidFrac; - SoilVolume = soilTheta * soilThickness; - StorageVolume = storageDepth * storageVoidFrac; - - //... get ET rates - availVolume = SoilVolume - soilWiltPoint * soilThickness; - getEvapRates(SurfaceVolume, PaveVolume, availVolume, StorageVolume, - pervFrac); - - //... no storage evap if soil or pavement layer saturated - if ( paveDepth >= paveThickness || - ( soilThickness > 0.0 && soilTheta >= soilPorosity ) - ) StorageEvap = 0.0; - - //... find nominal rate of surface infiltration into pavement layer - SurfaceInfil = SurfaceInflow + (SurfaceVolume / Tstep); - - //... find perc rate out of pavement layer - PavePerc = getPavementPermRate() * pervFrac; - - //... surface infiltration can't exceed pavement permeability - SurfaceInfil = MIN(SurfaceInfil, PavePerc); - - //... limit pavement perc by available water - maxRate = PaveVolume/Tstep + SurfaceInfil - PaveEvap; - maxRate = MAX(maxRate, 0.0); - PavePerc = MIN(PavePerc, maxRate); - - //... find soil layer perc rate - if ( soilThickness > 0.0 ) - { - SoilPerc = getSoilPercRate(soilTheta); - availVolume = (soilTheta - soilFieldCap) * soilThickness; - maxRate = MAX(availVolume, 0.0) / Tstep - SoilEvap; - SoilPerc = MIN(SoilPerc, maxRate); - SoilPerc = MAX(SoilPerc, 0.0); - } - else SoilPerc = PavePerc; - - //... exfiltration rate out of storage layer - StorageExfil = getStorageExfilRate(); - - //... underdrain flow rate - StorageDrain = 0.0; - if ( theLidProc->drain.coeff > 0.0 ) - { - StorageDrain = getStorageDrainRate(storageDepth, soilTheta, paveDepth, - surfaceDepth); - } - - //... check for adjacent saturated layers - - //... no soil layer, pavement & storage layers are full - if ( soilThickness == 0.0 && - storageDepth >= storageThickness && - paveDepth >= paveThickness ) - { - //... pavement outflow can't exceed storage outflow - maxRate = StorageEvap + StorageDrain + StorageExfil; - if ( PavePerc > maxRate ) PavePerc = maxRate; - - //... storage outflow can't exceed pavement outflow - else - { - //... use up available exfiltration capacity first - StorageExfil = MIN(StorageExfil, PavePerc); - StorageDrain = PavePerc - StorageExfil; - } - - //... set soil perc to pavement perc - SoilPerc = PavePerc; - - //... limit surface infil. by pavement perc - SurfaceInfil = MIN(SurfaceInfil, PavePerc); - } - - //... pavement, soil & storage layers are full - else if ( soilThickness > 0 && - storageDepth >= storageThickness && - soilTheta >= soilPorosity && - paveDepth >= paveThickness ) - { - //... find which layer has limiting flux rate - maxRate = StorageExfil + StorageDrain; - if ( SoilPerc < maxRate) maxRate = SoilPerc; - else maxRate = MIN(maxRate, PavePerc); - - //... use up available storage exfiltration capacity first - if ( maxRate > StorageExfil ) StorageDrain = maxRate - StorageExfil; - else - { - StorageExfil = maxRate; - StorageDrain = 0.0; - } - SoilPerc = maxRate; - PavePerc = maxRate; - - //... limit surface infil. by pavement perc - SurfaceInfil = MIN(SurfaceInfil, PavePerc); - } - - //... storage & soil layers are full - else if ( soilThickness > 0.0 && - storageDepth >= storageThickness && - soilTheta >= soilPorosity ) - { - //... soil perc can't exceed storage outflow - maxRate = StorageDrain + StorageExfil; - if ( SoilPerc > maxRate ) SoilPerc = maxRate; - - //... storage outflow can't exceed soil perc - else - { - //... use up available exfiltration capacity first - StorageExfil = MIN(StorageExfil, SoilPerc); - StorageDrain = SoilPerc - StorageExfil; - } - - //... limit surface infil. by available pavement volume - availVolume = (paveThickness - paveDepth) * paveVoidFrac; - maxRate = availVolume / Tstep + PavePerc + PaveEvap; - SurfaceInfil = MIN(SurfaceInfil, maxRate); - } - - //... soil and pavement layers are full - else if ( soilThickness > 0.0 && - paveDepth >= paveThickness && - soilTheta >= soilPorosity ) - { - PavePerc = MIN(PavePerc, SoilPerc); - SoilPerc = PavePerc; - SurfaceInfil = MIN(SurfaceInfil,PavePerc); - } - - //... no adjoining layers are full - else - { - //... limit storage exfiltration by available storage volume - // (if no soil layer, SoilPerc is same as PavePerc) - maxRate = SoilPerc - StorageEvap + StorageVolume / Tstep; - maxRate = MAX(0.0, maxRate); - StorageExfil = MIN(StorageExfil, maxRate); - - //... limit underdrain flow by volume above drain offset - if ( StorageDrain > 0.0 ) - { - maxRate = -StorageExfil - StorageEvap; - if (storageDepth >= storageThickness ) maxRate += SoilPerc; - if ( theLidProc->drain.offset <= storageDepth ) - { - maxRate += (storageDepth - theLidProc->drain.offset) * - storageVoidFrac/Tstep; - } - maxRate = MAX(maxRate, 0.0); - StorageDrain = MIN(StorageDrain, maxRate); - } - - //... limit soil & pavement outflow by unused storage volume - availVolume = (storageThickness - storageDepth) * storageVoidFrac; - maxRate = availVolume/Tstep + StorageEvap + StorageDrain + StorageExfil; - maxRate = MAX(maxRate, 0.0); - if ( soilThickness > 0.0 ) - { - SoilPerc = MIN(SoilPerc, maxRate); - maxRate = (soilPorosity - soilTheta) * soilThickness / Tstep + - SoilPerc; - } - PavePerc = MIN(PavePerc, maxRate); - - //... limit surface infil. by available pavement volume - availVolume = (paveThickness - paveDepth) * paveVoidFrac; - maxRate = availVolume / Tstep + PavePerc + PaveEvap; - SurfaceInfil = MIN(SurfaceInfil, maxRate); - } - - //... surface outflow - SurfaceOutflow = getSurfaceOutflowRate(surfaceDepth); - - //... compute overall layer flux rates - f[SURF] = SurfaceInflow - SurfaceEvap - SurfaceInfil - SurfaceOutflow; - f[PAVE] = (SurfaceInfil - PaveEvap - PavePerc) / paveVoidFrac; - if ( theLidProc->soil.thickness > 0.0) - { - f[SOIL] = (PavePerc - SoilEvap - SoilPerc) / soilThickness; - storageInflow = SoilPerc; - } - else - { - f[SOIL] = 0.0; - storageInflow = PavePerc; - SoilPerc = 0.0; - } - f[STOR] = (storageInflow - StorageEvap - StorageExfil - StorageDrain) / - storageVoidFrac; -} - -//============================================================================= - -void swaleFluxRates(double x[], double f[]) -// -// Purpose: computes flux rates from a vegetative swale LID. -// Input: x = vector of storage levels -// Output: f = vector of flux rates -// -{ - double depth; // depth of surface water in swale (ft) - double topWidth; // top width of full swale (ft) - double botWidth; // bottom width of swale (ft) - double length; // length of swale (ft) - double surfInflow; // inflow rate to swale (cfs) - double surfWidth; // top width at current water depth (ft) - double surfArea; // surface area of current water depth (ft2) - double flowArea; // x-section flow area (ft2) - double lidArea; // surface area of full swale (ft2) - double hydRadius; // hydraulic radius for current depth (ft) - double slope; // slope of swale side wall (run/rise) - double volume; // swale volume at current water depth (ft3) - double dVdT; // change in volume w.r.t. time (cfs) - double dStore; // depression storage depth (ft) - double xDepth; // depth above depression storage (ft) - - //... retrieve state variable from work vector - depth = x[SURF]; - depth = MIN(depth, theLidProc->surface.thickness); - - //... depression storage depth - dStore = 0.0; - - //... get swale's bottom width - // (0.5 ft minimum to avoid numerical problems) - slope = theLidProc->surface.sideSlope; - topWidth = theLidUnit->fullWidth; - topWidth = MAX(topWidth, 0.5); - botWidth = topWidth - 2.0 * slope * theLidProc->surface.thickness; - if ( botWidth < 0.5 ) - { - botWidth = 0.5; - slope = 0.5 * (topWidth - 0.5) / theLidProc->surface.thickness; - } - - //... swale's length - lidArea = theLidUnit->area; - length = lidArea / topWidth; - - //... top width, surface area and flow area of current ponded depth - surfWidth = botWidth + 2.0 * slope * depth; - surfArea = length * surfWidth; - flowArea = (depth * (botWidth + slope * depth)) * - theLidProc->surface.voidFrac; - - //... wet volume and effective depth - volume = length * flowArea; - - //... surface inflow into swale (cfs) - surfInflow = SurfaceInflow * lidArea; - - //... ET rate in cfs - SurfaceEvap = EvapRate * surfArea; - SurfaceEvap = MIN(SurfaceEvap, volume/Tstep); - - //... infiltration rate to native soil in cfs - StorageExfil = SurfaceInfil * surfArea; - - //... no surface outflow if depth below depression storage - xDepth = depth - dStore; - if ( xDepth <= ZERO ) SurfaceOutflow = 0.0; - - //... otherwise compute a surface outflow - else - { - //... modify flow area to remove depression storage, - flowArea -= (dStore * (botWidth + slope * dStore)) * - theLidProc->surface.voidFrac; - if ( flowArea < ZERO ) SurfaceOutflow = 0.0; - else - { - //... compute hydraulic radius - botWidth = botWidth + 2.0 * dStore * slope; - hydRadius = botWidth + 2.0 * xDepth * sqrt(1.0 + slope*slope); - hydRadius = flowArea / hydRadius; - - //... use Manning Eqn. to find outflow rate in cfs - SurfaceOutflow = theLidProc->surface.alpha * flowArea * - pow(hydRadius, 2./3.); - } - } - - //... net flux rate (dV/dt) in cfs - dVdT = surfInflow - SurfaceEvap - StorageExfil - SurfaceOutflow; - - //... when full, any net positive inflow becomes spillage - if ( depth == theLidProc->surface.thickness && dVdT > 0.0 ) - { - SurfaceOutflow += dVdT; - dVdT = 0.0; - } - - //... convert flux rates to ft/s - SurfaceEvap /= lidArea; - StorageExfil /= lidArea; - SurfaceOutflow /= lidArea; - f[SURF] = dVdT / surfArea; - f[SOIL] = 0.0; - f[STOR] = 0.0; - - //... assign values to layer volumes - SurfaceVolume = volume / lidArea; - SoilVolume = 0.0; - StorageVolume = 0.0; -} - -//============================================================================= - -void barrelFluxRates(double x[], double f[]) -// -// Purpose: computes flux rates for a rain barrel LID. -// Input: x = vector of storage levels -// Output: f = vector of flux rates -// -{ - double storageDepth = x[STOR]; - double head; - double maxValue; - - //... assign values to layer volumes - SurfaceVolume = 0.0; - SoilVolume = 0.0; - StorageVolume = storageDepth; - - //... initialize flows - SurfaceInfil = 0.0; - SurfaceOutflow = 0.0; - StorageDrain = 0.0; - - //... compute outflow if time since last rain exceeds drain delay - // (dryTime is updated in lid.evalLidUnit at each time step) - if ( theLidProc->drain.delay == 0.0 || - theLidUnit->dryTime >= theLidProc->drain.delay ) - { - head = storageDepth - theLidProc->drain.offset; - if ( head > 0.0 ) - { - StorageDrain = getStorageDrainRate(storageDepth, 0.0, 0.0, 0.0); - maxValue = (head/Tstep); - StorageDrain = MIN(StorageDrain, maxValue); - } - } - - //... limit inflow to available storage - StorageInflow = SurfaceInflow; - maxValue = (theLidProc->storage.thickness - storageDepth) / Tstep + - StorageDrain; - StorageInflow = MIN(StorageInflow, maxValue); - SurfaceInfil = StorageInflow; - - //... assign values to layer flux rates - f[SURF] = SurfaceInflow - StorageInflow; - f[STOR] = StorageInflow - StorageDrain; - f[SOIL] = 0.0; -} - -//============================================================================= - -double getSurfaceOutflowRate(double depth) -// -// Purpose: computes outflow rate from a LID's surface layer. -// Input: depth = depth of ponded water on surface layer (ft) -// Output: returns outflow from surface layer (ft/s) -// -// Note: this function should not be applied to swales or rain barrels. -// -{ - double delta; - double outflow; - - //... no outflow if ponded depth below storage depth - delta = depth - theLidProc->surface.thickness; - if ( delta < 0.0 ) return 0.0; - - //... compute outflow from overland flow Manning equation - outflow = theLidProc->surface.alpha * pow(delta, 5.0/3.0) * - theLidUnit->fullWidth / theLidUnit->area; - outflow = MIN(outflow, delta / Tstep); - return outflow; -} - -//============================================================================= - -double getPavementPermRate() -// -// Purpose: computes reduced permeability of a pavement layer due to -// clogging. -// Input: none -// Output: returns the reduced permeability of the pavement layer (ft/s). -// -{ - double permReduction = 0.0; - double clogFactor= theLidProc->pavement.clogFactor; - double regenDays = theLidProc->pavement.regenDays; - - // ... find permeability reduction due to clogging - if ( clogFactor > 0.0 ) - { - // ... see if permeability regeneration has occurred - // (regeneration is assumed to reduce the total - // volumetric loading that the pavement has received) - if ( regenDays > 0.0 ) - { - if ( OldRunoffTime / 1000.0 / SECperDAY >= theLidUnit->nextRegenDay ) - { - // ... reduce total volume treated by degree of regeneration - theLidUnit->volTreated *= - (1.0 - theLidProc->pavement.regenDegree); - - // ... update next day that regenration occurs - theLidUnit->nextRegenDay += regenDays; - } - } - - // ... find permeabiity reduction factor - permReduction = theLidUnit->volTreated / clogFactor; - permReduction = MIN(permReduction, 1.0); - } - - // ... return the effective pavement permeability - return theLidProc->pavement.kSat * (1.0 - permReduction); -} - -//============================================================================= - -double getSoilPercRate(double theta) -// -// Purpose: computes percolation rate of water through a LID's soil layer. -// Input: theta = moisture content (fraction) -// Output: returns percolation rate within soil layer (ft/s) -// -{ - double delta; // moisture deficit - - // ... no percolation if soil moisture <= field capacity - if ( theta <= theLidProc->soil.fieldCap ) return 0.0; - - // ... perc rate = unsaturated hydraulic conductivity - delta = theLidProc->soil.porosity - theta; - return theLidProc->soil.kSat * exp(-delta * theLidProc->soil.kSlope); - -} - -//============================================================================= - -double getStorageExfilRate() -// -// Purpose: computes exfiltration rate from storage zone into -// native soil beneath a LID. -// Input: depth = depth of water storage zone (ft) -// Output: returns infiltration rate (ft/s) -// -{ - double infil = 0.0; - double clogFactor = 0.0; - - if ( theLidProc->storage.kSat == 0.0 ) return 0.0; - if ( MaxNativeInfil == 0.0 ) return 0.0; - - //... reduction due to clogging - clogFactor = theLidProc->storage.clogFactor; - if ( clogFactor > 0.0 ) - { - clogFactor = theLidUnit->waterBalance.inflow / clogFactor; - clogFactor = MIN(clogFactor, 1.0); - } - - //... infiltration rate = storage Ksat reduced by any clogging - infil = theLidProc->storage.kSat * (1.0 - clogFactor); - - //... limit infiltration rate by any groundwater-imposed limit - return MIN(infil, MaxNativeInfil); -} - -//============================================================================= - -double getStorageDrainRate(double storageDepth, double soilTheta, - double paveDepth, double surfaceDepth) -// -// Purpose: computes underdrain flow rate in a LID's storage layer. -// Input: storageDepth = depth of water in storage layer (ft) -// soilTheta = moisture content of soil layer -// paveDepth = effective depth of water in pavement layer (ft) -// surfaceDepth = depth of ponded water on surface layer (ft) -// Output: returns flow in underdrain (ft/s) -// -// Note: drain eqn. is evaluated in user's units. -// Note: head on drain is water depth in storage layer plus the -// layers above it (soil, pavement, and surface in that order) -// minus the drain outlet offset. -{ - int curve = theLidProc->drain.qCurve; - double head = storageDepth; - double outflow = 0.0; - double paveThickness = theLidProc->pavement.thickness; - double soilThickness = theLidProc->soil.thickness; - double soilPorosity = theLidProc->soil.porosity; - double soilFieldCap = theLidProc->soil.fieldCap; - double storageThickness = theLidProc->storage.thickness; - - // --- storage layer is full - if ( storageDepth >= storageThickness ) - { - // --- a soil layer exists - if ( soilThickness > 0.0 ) - { - // --- increase head by fraction of soil layer saturated - if ( soilTheta > soilFieldCap ) - { - head += (soilTheta - soilFieldCap) / - (soilPorosity - soilFieldCap) * soilThickness; - - // --- soil layer is saturated, increase head by water - // depth in layer above it - if ( soilTheta >= soilPorosity ) - { - if ( paveThickness > 0.0 ) head += paveDepth; - else head += surfaceDepth; - } - } - } - - // --- no soil layer so increase head by water level in pavement - // layer and possibly surface layer - if ( paveThickness > 0.0 ) - { - head += paveDepth; - if ( paveDepth >= paveThickness ) head += surfaceDepth; - } - } - - // --- no outflow if: - // a) no prior outflow and head below open threshold - // b) prior outflow and head below closed threshold - if ( theLidUnit->oldDrainFlow == 0.0 && - head <= theLidProc->drain.hOpen ) return 0.0; - if ( theLidUnit->oldDrainFlow > 0.0 && - head <= theLidProc->drain.hClose ) return 0.0; - - // --- make head relative to drain offset - head -= theLidProc->drain.offset; - - // --- compute drain outflow from underdrain flow equation in user units - // (head in inches or mm, flow rate in in/hr or mm/hr) - if ( head > ZERO ) - { - // --- convert head to user units - head *= UCF(RAINDEPTH); - - // --- compute drain outflow in user units - outflow = theLidProc->drain.coeff * - pow(head, theLidProc->drain.expon); - - // --- apply user-supplied control curve to outflow - if (curve >= 0) outflow *= table_lookup(&Curve[curve], head); - - // --- convert outflow to ft/s - outflow /= UCF(RAINFALL); - } - return outflow; -} - -//============================================================================= - -double getDrainMatOutflow(double depth) -// -// Purpose: computes flow rate through a green roof's drainage mat. -// Input: depth = depth of water in drainage mat (ft) -// Output: returns flow in drainage mat (ft/s) -// -{ - //... default is to pass all inflow - double result = SoilPerc; - - //... otherwise use Manning eqn. if its parameters were supplied - if ( theLidProc->drainMat.alpha > 0.0 ) - { - result = theLidProc->drainMat.alpha * pow(depth, 5.0/3.0) * - theLidUnit->fullWidth / theLidUnit->area * - theLidProc->drainMat.voidFrac; - } - return result; -} - -//============================================================================= - -void getEvapRates(double surfaceVol, double paveVol, double soilVol, - double storageVol, double pervFrac) -// -// Purpose: computes surface, pavement, soil, and storage evaporation rates. -// Input: surfaceVol = volume/area of ponded water on surface layer (ft) -// paveVol = volume/area of water in pavement pores (ft) -// soilVol = volume/area of water in soil (or pavement) pores (ft) -// storageVol = volume/area of water in storage layer (ft) -// pervFrac = fraction of surface layer that is pervious -// Output: none -// -{ - double availEvap; - - //... surface evaporation flux - availEvap = EvapRate; - SurfaceEvap = MIN(availEvap, surfaceVol/Tstep); - SurfaceEvap = MAX(0.0, SurfaceEvap); - availEvap = MAX(0.0, (availEvap - SurfaceEvap)); - availEvap *= pervFrac; - - //... no subsurface evap if water is infiltrating - if ( SurfaceInfil > 0.0 ) - { - PaveEvap = 0.0; - SoilEvap = 0.0; - StorageEvap = 0.0; - } - else - { - //... pavement evaporation flux - PaveEvap = MIN(availEvap, paveVol / Tstep); - availEvap = MAX(0.0, (availEvap - PaveEvap)); - - //... soil evaporation flux - SoilEvap = MIN(availEvap, soilVol / Tstep); - availEvap = MAX(0.0, (availEvap - SoilEvap)); - - //... storage evaporation flux - StorageEvap = MIN(availEvap, storageVol / Tstep); - } -} - -//============================================================================= - -double getSurfaceOverflowRate(double* surfaceDepth) -// -// Purpose: finds surface overflow rate from a LID unit. -// Input: surfaceDepth = depth of water stored in surface layer (ft) -// Output: returns the overflow rate (ft/s) -// -{ - double delta = *surfaceDepth - theLidProc->surface.thickness; - if ( delta <= 0.0 ) return 0.0; - *surfaceDepth = theLidProc->surface.thickness; - return delta * theLidProc->surface.voidFrac / Tstep; -} - -//============================================================================= - -void updateWaterBalance(TLidUnit *lidUnit, double inflow, double evap, - double infil, double surfFlow, double drainFlow, double storage) -// -// Purpose: updates components of the water mass balance for a LID unit -// over the current time step. -// Input: lidUnit = a particular LID unit -// inflow = runon + rainfall to the LID unit (ft/s) -// evap = evaporation rate from the unit (ft/s) -// infil = infiltration out the bottom of the unit (ft/s) -// surfFlow = surface runoff from the unit (ft/s) -// drainFlow = underdrain flow from the unit -// storage = volume of water stored in the unit (ft) -// Output: none -// -{ - lidUnit->volTreated += inflow * Tstep; - lidUnit->waterBalance.inflow += inflow * Tstep; - lidUnit->waterBalance.evap += evap * Tstep; - lidUnit->waterBalance.infil += infil * Tstep; - lidUnit->waterBalance.surfFlow += surfFlow * Tstep; - lidUnit->waterBalance.drainFlow += drainFlow * Tstep; - lidUnit->waterBalance.finalVol = storage; -} - -//============================================================================= - -int modpuls_solve(int n, double* x, double* xOld, double* xPrev, - double* xMin, double* xMax, double* xTol, - double* qOld, double* q, double dt, double omega, - void (*derivs)(double*, double*)) -// -// Purpose: solves system of equations dx/dt = q(x) for x at end of time step -// dt using a modified Puls method. -// Input: n = number of state variables -// x = vector of state variables -// xOld = state variable values at start of time step -// xPrev = state variable values from previous iteration -// xMin = lower limits on state variables -// xMax = upper limits on state variables -// xTol = convergence tolerances on state variables -// qOld = flux rates at start of time step -// q = flux rates at end of time step -// dt = time step (sec) -// omega = time weighting parameter (use 0 for Euler method -// or 0.5 for modified Puls method) -// derivs = pointer to function that computes flux rates q as a -// function of state variables x -// Output: returns number of steps required for convergence (or 0 if -// process doesn't converge) -// -{ - int i; - int canStop; - int steps = 1; - int maxSteps = 20; - - //... initialize state variable values - for (i=0; i 0.0 && - fabs(x[i] - xPrev[i]) > xTol[i] ) canStop = 0; - xPrev[i] = x[i]; - } - - //... return if process converges - if (canStop) return steps; - steps++; - } - - //... no convergence so return 0 - return 0; -} diff --git a/src/link.c b/src/link.c deleted file mode 100644 index 052a030b9..000000000 --- a/src/link.c +++ /dev/null @@ -1,2679 +0,0 @@ -//----------------------------------------------------------------------------- -// link.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 10/29/22 (Build 5.2.2) -// Author: L. Rossman -// M. Tryby (EPA) -// -// Conveyance system link functions -// -// Update History -// ============== -// Build 5.1.007: -// - Optional surcharging of weirs introduced. -// Build 5.1.008: -// - Bug in finding flow through surcharged weir fixed. -// - Bug in finding if conduit is upstrm/dnstrm full fixed. -// - Monthly conductivity adjustment applied to conduit seepage. -// - Conduit seepage limited by conduit's flow rate. -// Build 5.1.010: -// - Support added for new ROADWAY_WEIR object. -// - Time of last setting change initialized for links. -// Build 5.1.011: -// - Crest elevation of regulator links raised to downstream invert. -// - Fixed converting roadWidth weir parameter to internal units. -// - Weir shape parameter deprecated. -// - Extra geometric parameters ignored for non-conduit open rectangular -// cross sections. -// Build 5.1.012: -// - Conduit seepage rate now based on flow width, not wetted perimeter. -// - Formula for side flow weir corrected. -// - Crest length contraction adjustments corrected. -// Build 5.1.013: -// - Maximum depth adjustments made for storage units that can surcharge. -// - 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(). -// Build 5.2.0: -// - Support added for Streets and Inlets. -// - Support added for variable speed pumps. -// Build 5.2.1 -// - Warning no longer issued when conduit elevation drop < MIN_DELTA_Z. -// Build 5.2.2: -// - Warning for conduit elevation drop < MIN_DELTA_Z restored. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include -#include "headers.h" -#include "inlet.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -static const double MIN_DELTA_Z = 0.001; // minimum elevation change for conduit - // slopes (ft) - -//----------------------------------------------------------------------------- -// External functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// link_readParams (called by parseLine in input.c) -// link_readXsectParams (called by parseLine in input.c) -// link_readLossParams (called by parseLine in input.c) -// link_validate (called by project_validate in project.c) -// link_initState (called by initObjects in swmm5.c) -// link_setOldHydState (called by routing_execute in routing.c) -// link_setOldQualState (called by routing_execute in routing.c) -// link_setTargetSetting (called by routing_execute in routing.c) -// link_setSetting (called by routing_execute in routing.c) -// link_getResults (called by output_saveLinkResults) -// link_getLength (called in dwflow.c, kinwave.c & flowrout.c) -// link_getFroude (called in dwflow.c) -// link_getInflow (called in flowrout.c & dynwave.c) -// link_setOutfallDepth (called in flowrout.c & dynwave.c) -// link_getYcrit (called by link_setOutfallDepth & in dwflow.c) -// link_getYnorm (called by conduit_initState, link_setOutfallDepth & in dwflow.c) -// link_getVelocity (called by link_getResults & stats_updateLinkStats) -// link_getPower (called by stats_updateLinkStats in stats.c) -// link_getLossRate (called in dwflow.c, kinwave.c & flowrout.c) - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static void link_setParams(int j, int type, int n1, int n2, int k, double x[]); -static void link_convertOffsets(int j); -static double link_getOffsetHeight(int j, double offset, double elev); - -static int conduit_readParams(int j, int k, char* tok[], int ntoks); -static void conduit_validate(int j, int k); -static void conduit_initState(int j, int k); -static void conduit_reverse(int j, int k); -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); - -static int pump_readParams(int j, int k, char* tok[], int ntoks); -static void pump_validate(int j, int k); -static void pump_initState(int j, int k); -static double pump_getInflow(int j); - -static int orifice_readParams(int j, int k, char* tok[], int ntoks); -static void orifice_validate(int j, int k); -static void orifice_setSetting(int j, double tstep); -static double orifice_getWeirCoeff(int j, int k, double h); -static double orifice_getInflow(int j); -static double orifice_getFlow(int j, int k, double head, double f, - int hasFlapGate); - -static int weir_readParams(int j, int k, char* tok[], int ntoks); -static void weir_validate(int j, int k); -static void weir_setSetting(int j); -static double weir_getInflow(int j); -static double weir_getOpenArea(int j, double y); -static void weir_getFlow(int j, int k, double head, double dir, - int hasFlapGate, double* q1, double* q2); -static double weir_getOrificeFlow(int j, double head, double y, double cOrif); -static double weir_getdqdh(int k, double dir, double h, double q1, double q2); - -static int outlet_readParams(int j, int k, char* tok[], int ntoks); -static double outlet_getFlow(int k, double head); -static double outlet_getInflow(int j); - - -//============================================================================= - -int link_readParams(int j, int type, int k, char* tok[], int ntoks) -// -// Input: j = link index -// type = link type code -// k = link type index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads parameters for a specific type of link from a -// tokenized line of input data. -// -{ - switch ( type ) - { - case CONDUIT: return conduit_readParams(j, k, tok, ntoks); - case PUMP: return pump_readParams(j, k, tok, ntoks); - case ORIFICE: return orifice_readParams(j, k, tok, ntoks); - case WEIR: return weir_readParams(j, k, tok, ntoks); - case OUTLET: return outlet_readParams(j, k, tok, ntoks); - default: return 0; - } -} - -//============================================================================= - -int link_readXsectParams(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads a link's cross section parameters from a tokenized -// line of input data. -// Formats: -// Link Shape Geom1 Geom2 Geom3 Geom4 (Barrels Culvert) -// Link IRREGULAR TransectID -// Link STREET StreetID -// -{ - int i, j, k; - double x[4]; - - // --- check for minimum number of tokens - if (ntoks < 3) return error_setInpError(ERR_ITEMS, ""); - - // --- get index of link - j = project_findObject(LINK, tok[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); - - // --- get code of xsection shape - k = findmatch(tok[1], XsectTypeWords); - if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); - - // --- assign default number of barrels to conduit - if ( Link[j].type == CONDUIT ) Conduit[Link[j].subIndex].barrels = 1; - - // --- assume link is not a culvert - Link[j].xsect.culvertCode = 0; - - // --- for irregular shape, find index of transect object - if ( k == IRREGULAR ) - { - i = project_findObject(TRANSECT, tok[2]); - if ( i < 0 ) return error_setInpError(ERR_NAME, tok[2]); - Link[j].xsect.type = k; - Link[j].xsect.transect = i; - return 0; - } - - // --- for street cross section, find index of Street object - else if (k == STREET_XSECT) - { - i = project_findObject(STREET, tok[2]); - if (i < 0) return error_setInpError(ERR_NAME, tok[2]); - Link[j].xsect.type = k; - Link[j].xsect.transect = i; - return 0; - } - - else - { - // --- check that geometric parameters are present - if (ntoks < 6) return error_setInpError(ERR_ITEMS, ""); - - // --- parse max. depth & shape curve for a custom shape - if ( k == CUSTOM ) - { - if ( !getDouble(tok[2], &x[0]) || x[0] <= 0.0 ) - return error_setInpError(ERR_NUMBER, tok[2]); - i = project_findObject(CURVE, tok[3]); - if ( i < 0 ) return error_setInpError(ERR_NAME, tok[3]); - Link[j].xsect.type = k; - Link[j].xsect.transect = i; - Link[j].xsect.yFull = x[0] / UCF(LENGTH); - } - - // --- parse and save geometric parameters - else for (i = 2; i <= 5; i++) - { - if ( !getDouble(tok[i], &x[i-2]) ) - return error_setInpError(ERR_NUMBER, tok[i]); - } - - // --- ignore extra parameters for non-conduit open rectangular shapes - if ( Link[j].type != CONDUIT && k == RECT_OPEN ) - { - x[2] = 0.0; - x[3] = 0.0; - } - if ( !xsect_setParams(&Link[j].xsect, k, x, UCF(LENGTH)) ) - { - return error_setInpError(ERR_NUMBER, ""); - } - - // --- parse number of barrels if present - if ( Link[j].type == CONDUIT && ntoks >= 7 ) - { - i = atoi(tok[6]); - if ( i <= 0 ) return error_setInpError(ERR_NUMBER, tok[6]); - else Conduit[Link[j].subIndex].barrels = (char)i; - } - - // --- parse culvert code if present - if ( Link[j].type == CONDUIT && ntoks >= 8 ) - { - i = atoi(tok[7]); - if ( i < 0 ) return error_setInpError(ERR_NUMBER, tok[7]); - else Link[j].xsect.culvertCode = i; - } - } - return 0; -} - -//============================================================================= - -int link_readLossParams(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads local loss parameters for a link from a tokenized -// line of input data. -// -// Format: LinkID cInlet cOutlet cAvg FlapGate(YES/NO) SeepRate -// -{ - int i, j, k; - double x[3]; - double seepRate = 0.0; - - if ( ntoks < 4 ) return error_setInpError(ERR_ITEMS, ""); - j = project_findObject(LINK, tok[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); - for (i=1; i<=3; i++) - { - if ( ! getDouble(tok[i], &x[i-1]) || x[i-1] < 0.0 ) - return error_setInpError(ERR_NUMBER, tok[i]); - } - k = 0; - if ( ntoks >= 5 ) - { - k = findmatch(tok[4], NoYesWords); - if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[4]); - } - if ( ntoks >= 6 ) - { - if ( ! getDouble(tok[5], &seepRate) ) - return error_setInpError(ERR_NUMBER, tok[5]); - } - Link[j].cLossInlet = x[0]; - Link[j].cLossOutlet = x[1]; - Link[j].cLossAvg = x[2]; - Link[j].hasFlapGate = k; - Link[j].seepRate = seepRate / UCF(RAINFALL); - return 0; -} - -//============================================================================= - -void link_setParams(int j, int type, int n1, int n2, int k, double x[]) -// -// Input: j = link index -// type = link type code -// n1 = index of upstream node -// n2 = index of downstream node -// k = index of link's sub-type -// x = array of parameter values -// Output: none -// Purpose: sets parameters for a link. -// -{ - Link[j].node1 = n1; - Link[j].node2 = n2; - Link[j].type = type; - Link[j].subIndex = k; - Link[j].offset1 = 0.0; - Link[j].offset2 = 0.0; - Link[j].q0 = 0.0; - Link[j].qFull = 0.0; - Link[j].setting = 1.0; - Link[j].targetSetting = 1.0; - Link[j].hasFlapGate = 0; - Link[j].qLimit = 0.0; // 0 means that no limit is defined - Link[j].direction = 1; - - switch (type) - { - case CONDUIT: - Conduit[k].length = x[0] / UCF(LENGTH); - Conduit[k].modLength = Conduit[k].length; - Conduit[k].roughness = x[1]; - Link[j].offset1 = x[2] / UCF(LENGTH); - Link[j].offset2 = x[3] / UCF(LENGTH); - Link[j].q0 = x[4] / UCF(FLOW); - Link[j].qLimit = x[5] / UCF(FLOW); - break; - - case PUMP: - Pump[k].pumpCurve = (int)x[0]; - Link[j].hasFlapGate = FALSE; - Pump[k].initSetting = x[1]; - Pump[k].yOn = x[2] / UCF(LENGTH); - Pump[k].yOff = x[3] / UCF(LENGTH); - Pump[k].xMin = 0.0; - Pump[k].xMax = 0.0; - break; - - case ORIFICE: - Orifice[k].type = (int)x[0]; - Link[j].offset1 = x[1] / UCF(LENGTH); - Link[j].offset2 = Link[j].offset1; - Orifice[k].cDisch = x[2]; - Link[j].hasFlapGate = (x[3] > 0.0) ? 1 : 0; - Orifice[k].orate = x[4] * 3600.0; - break; - - case WEIR: - Weir[k].type = (int)x[0]; - Link[j].offset1 = x[1] / UCF(LENGTH); - Link[j].offset2 = Link[j].offset1; - Weir[k].cDisch1 = x[2]; - Link[j].hasFlapGate = (x[3] > 0.0) ? 1 : 0; - Weir[k].endCon = x[4]; - Weir[k].cDisch2 = x[5]; - Weir[k].canSurcharge = (int)x[6]; - Weir[k].roadWidth = x[7] / UCF(LENGTH); - Weir[k].roadSurface = (int)x[8]; - Weir[k].cdCurve = (int)x[9]; - break; - - case OUTLET: - Link[j].offset1 = x[0] / UCF(LENGTH); - Link[j].offset2 = Link[j].offset1; - Outlet[k].qCoeff = x[1]; - Outlet[k].qExpon = x[2]; - Outlet[k].qCurve = (int)x[3]; - Link[j].hasFlapGate = (x[4] > 0.0) ? 1 : 0; - Outlet[k].curveType = (int)x[5]; - - xsect_setParams(&Link[j].xsect, DUMMY, NULL, 0.0); - break; - - } -} - -//============================================================================= - -void link_validate(int j) -// -// Input: j = link index -// Output: none -// Purpose: validates a link's properties. -// -{ - int n; - - if ( LinkOffsets == ELEV_OFFSET ) link_convertOffsets(j); - switch ( Link[j].type ) - { - case CONDUIT: conduit_validate(j, Link[j].subIndex); break; - case PUMP: pump_validate(j, Link[j].subIndex); break; - case ORIFICE: orifice_validate(j, Link[j].subIndex); break; - case WEIR: weir_validate(j, Link[j].subIndex); break; - } - - // --- check if crest of regulator opening < invert of downstream node - switch ( Link[j].type ) - { - case ORIFICE: - case WEIR: - case OUTLET: - if ( Node[Link[j].node1].invertElev + Link[j].offset1 < - Node[Link[j].node2].invertElev ) - { - if (RouteModel == DW) - { - Link[j].offset1 = Node[Link[j].node2].invertElev - - Node[Link[j].node1].invertElev; - report_writeWarningMsg(WARN10b, Link[j].ID); - } - else report_writeWarningMsg(WARN10a, Link[j].ID); - } - } - - // --- force max. depth of end nodes to be >= link crown height - // at non-storage nodes - - // --- skip pumps and bottom orifices - if ( Link[j].type == PUMP || - (Link[j].type == ORIFICE && - Orifice[Link[j].subIndex].type == BOTTOM_ORIFICE) ) return; - - // --- extend upstream node's full depth to link's crown elevation - n = Link[j].node1; - if ( Node[n].type != STORAGE || Node[n].surDepth > 0.0 ) - { - Node[n].fullDepth = MAX(Node[n].fullDepth, - Link[j].offset1 + Link[j].xsect.yFull); - } - - // --- do same for downstream node only for conduit links - n = Link[j].node2; - if ( (Node[n].type != STORAGE || Node[n].surDepth > 0.0) && - Link[j].type == CONDUIT ) - { - Node[n].fullDepth = MAX(Node[n].fullDepth, - Link[j].offset2 + Link[j].xsect.yFull); - } -} - -//============================================================================= - -void link_convertOffsets(int j) -// -// Input: j = link index -// Output: none -// Purpose: converts offset elevations to offset heights for a link. -// -{ - double elev; - - elev = Node[Link[j].node1].invertElev; - Link[j].offset1 = link_getOffsetHeight(j, Link[j].offset1, elev); - if ( Link[j].type == CONDUIT ) - { - elev = Node[Link[j].node2].invertElev; - Link[j].offset2 = link_getOffsetHeight(j, Link[j].offset2, elev); - } - else Link[j].offset2 = Link[j].offset1; -} - -//============================================================================= - -double link_getOffsetHeight(int j, double offset, double elev) -// -// Input: j = link index -// offset = link elevation offset (ft) -// elev = node invert elevation (ft) -// Output: returns offset distance above node invert (ft) -// Purpose: finds offset height for one end of a link. -// -{ - if ( offset <= MISSING || Link[j].type == PUMP) return 0.0; - offset -= elev; - if ( offset >= 0.0 ) return offset; - if ( offset >= -MIN_DELTA_Z ) return 0.0; - report_writeWarningMsg(WARN03, Link[j].ID); - return 0.0; -} - -//============================================================================= - -void link_initState(int j) -// -// Input: j = link index -// Output: none -// Purpose: initializes a link's state variables at start of simulation. -// -{ - int p; - - // --- initialize hydraulic state - Link[j].oldFlow = Link[j].q0; - Link[j].newFlow = Link[j].q0; - Link[j].oldDepth = 0.0; - Link[j].newDepth = 0.0; - Link[j].oldVolume = 0.0; - Link[j].newVolume = 0.0; - Link[j].setting = 1.0; - Link[j].targetSetting = 1.0; - Link[j].timeLastSet = StartDate; - Link[j].inletControl = FALSE; - Link[j].normalFlow = FALSE; - if ( Link[j].type == CONDUIT ) conduit_initState(j, Link[j].subIndex); - if ( Link[j].type == PUMP ) pump_initState(j, Link[j].subIndex); - - // --- initialize water quality state - for (p = 0; p < Nobjects[POLLUT]; p++) - { - Link[j].oldQual[p] = 0.0; - Link[j].newQual[p] = 0.0; - Link[j].totalLoad[p] = 0.0; - } -} - -//============================================================================= - -double link_getInflow(int j) -// -// Input: j = link index -// Output: returns link flow rate (cfs) -// Purpose: finds total flow entering a link during current time step. -// -{ - if ( Link[j].setting == 0 ) return 0.0; - switch ( Link[j].type ) - { - case CONDUIT: return conduit_getInflow(j); - case PUMP: return pump_getInflow(j); - case ORIFICE: return orifice_getInflow(j); - case WEIR: return weir_getInflow(j); - case OUTLET: return outlet_getInflow(j); - default: return node_getOutflow(Link[j].node1, j); - } -} - -//============================================================================= - -void link_setOldHydState(int j) -// -// Input: j = link index -// Output: none -// Purpose: replaces link's old hydraulic state values with current ones. -// -{ - int k; - - Link[j].oldDepth = Link[j].newDepth; - Link[j].oldFlow = Link[j].newFlow; - Link[j].oldVolume = Link[j].newVolume; - - if ( Link[j].type == CONDUIT ) - { - k = Link[j].subIndex; - Conduit[k].q1Old = Conduit[k].q1; - Conduit[k].q2Old = Conduit[k].q2; - } -} - -//============================================================================= - -void link_setOldQualState(int j) -// -// Input: j = link index -// Output: none -// Purpose: replaces link's old water quality state values with current ones. -// -{ - int p; - for (p = 0; p < Nobjects[POLLUT]; p++) - { - Link[j].oldQual[p] = Link[j].newQual[p]; - Link[j].newQual[p] = 0.0; - } -} - -//============================================================================= - -void link_setTargetSetting(int j) -// -// Input: j = link index -// Output: none -// Purpose: updates a link's target setting. -// -{ - int k, n1; - if ( Link[j].type == PUMP ) - { - k = Link[j].subIndex; - n1 = Link[j].node1; - Link[j].targetSetting = Link[j].setting; - if ( Pump[k].yOff > 0.0 && - Link[j].setting > 0.0 && - Node[n1].newDepth < Pump[k].yOff ) Link[j].targetSetting = 0.0; - if ( Pump[k].yOn > 0.0 && - Link[j].setting == 0.0 && - Node[n1].newDepth > Pump[k].yOn ) Link[j].targetSetting = 1.0; - } -} - -//============================================================================= - -void link_setSetting(int j, double tstep) -// -// Input: j = link index -// tstep = time step over which setting is adjusted -// Output: none -// Purpose: updates a link's setting as a result of a control action. -// -{ - if ( Link[j].type == ORIFICE ) orifice_setSetting(j, tstep); - else if ( Link[j].type == WEIR ) weir_setSetting(j); - else Link[j].setting = Link[j].targetSetting; -} - -//============================================================================= - -int link_setFlapGate(int j, int n1, int n2, double q) -// -// Input: j = link index -// n1 = index of node on upstream end of link -// n2 = index of node on downstream end of link -// q = signed flow value (value and units don't matter) -// Output: returns TRUE if there is reverse flow through a flap gate -// associated with the link. -// Purpose: based on the sign of the flow, determines if a flap gate -// associated with the link should close or not. -// -{ - int n = -1; - - // --- check for reverse flow through link's flap gate - if ( Link[j].hasFlapGate ) - { - if ( q * (double)Link[j].direction < 0.0 ) return TRUE; - } - - // --- check for Outfall with flap gate node on inflow end of link - if ( q < 0.0 ) n = n2; - if ( q > 0.0 ) n = n1; - if ( n >= 0 && - Node[n].type == OUTFALL && - Outfall[Node[n].subIndex].hasFlapGate ) return TRUE; - return FALSE; -} - -//============================================================================= - -void link_getResults(int j, double f, float x[]) -// -// Input: j = link index -// f = time weighting factor -// Output: x = array of weighted results -// Purpose: retrieves time-weighted average of old and new results for a link. -// -{ - int p; // pollutant index - double y, // depth - q, // flow - u, // velocity - v, // volume - c; // capacity, setting or concentration - double f1 = 1.0 - f; - - y = f1*Link[j].oldDepth + f*Link[j].newDepth; - q = f1*Link[j].oldFlow + f*Link[j].newFlow; - v = f1*Link[j].oldVolume + f*Link[j].newVolume; - u = link_getVelocity(j, q, y); - c = 0.0; - if (Link[j].type == CONDUIT) - { - if (Link[j].xsect.type != DUMMY) - c = xsect_getAofY(&Link[j].xsect, y) / Link[j].xsect.aFull; - } - else c = Link[j].setting; - - // --- override time weighting for pump flow between on/off states - if (Link[j].type == PUMP && Link[j].oldFlow*Link[j].newFlow == 0.0) - { - if ( f >= f1 ) q = Link[j].newFlow; - else q = Link[j].oldFlow; - } - - y *= UCF(LENGTH); - v *= UCF(VOLUME); - q *= UCF(FLOW) * (double)Link[j].direction; - u *= UCF(LENGTH) * (double)Link[j].direction; - x[LINK_DEPTH] = (float)y; - x[LINK_FLOW] = (float)q; - x[LINK_VELOCITY] = (float)u; - x[LINK_VOLUME] = (float)v; - x[LINK_CAPACITY] = (float)c; - - if ( !IgnoreQuality ) for (p = 0; p < Nobjects[POLLUT]; p++) - { - c = f1*Link[j].oldQual[p] + f*Link[j].newQual[p]; - x[LINK_QUAL+p] = (float)c; - } -} - -//============================================================================= - -void link_setOutfallDepth(int j) -// -// Input: j = link index -// Output: none -// Purpose: sets depth at outfall node connected to link j. -// -{ - int k; // conduit index - int n; // outfall node index - double z; // invert offset height (ft) - double q; // flow rate (cfs) - double yCrit = 0.0; // critical flow depth (ft) - double yNorm = 0.0; // normal flow depth (ft) - - // --- find which end node of link is an outfall - if ( Node[Link[j].node2].type == OUTFALL ) - { - n = Link[j].node2; - z = Link[j].offset2; - } - else if ( Node[Link[j].node1].type == OUTFALL ) - { - n = Link[j].node1; - z = Link[j].offset1; - } - else return; - - // --- find both normal & critical depth for current flow - if ( Link[j].type == CONDUIT ) - { - k = Link[j].subIndex; - q = fabs(Link[j].newFlow / Conduit[k].barrels); - yNorm = link_getYnorm(j, q); - yCrit = link_getYcrit(j, q); - } - - // --- set new depth at node - node_setOutletDepth(n, yNorm, yCrit, z); -} - -//============================================================================= - -double link_getYcrit(int j, double q) -// -// Input: j = link index -// q = link flow rate (cfs) -// Output: returns critical depth (ft) -// Purpose: computes critical depth for given flow rate. -// -{ - return xsect_getYcrit(&Link[j].xsect, q); -} - -//============================================================================= - -double link_getYnorm(int j, double q) -// -// Input: j = link index -// q = link flow rate (cfs) -// Output: returns normal depth (ft) -// Purpose: computes normal depth for given flow rate. -// -{ - int k; - double s, a, y; - - if ( Link[j].type != CONDUIT ) return 0.0; - if ( Link[j].xsect.type == DUMMY ) return 0.0; - q = fabs(q); - k = Link[j].subIndex; - if ( q > Conduit[k].qMax ) q = Conduit[k].qMax; - if ( q <= 0.0 ) return 0.0; - s = q / Conduit[k].beta; - a = xsect_getAofS(&Link[j].xsect, s); - y = xsect_getYofA(&Link[j].xsect, a); - return y; -} - -//============================================================================= - -double link_getLength(int j) -// -// Input: j = link index -// Output: returns length (ft) -// Purpose: finds true length of a link. -// -{ - if ( Link[j].type == CONDUIT ) return conduit_getLength(j); - return 0.0; -} - -//============================================================================= - -double link_getVelocity(int j, double flow, double depth) -// -// Input: j = link index -// flow = link flow rate (cfs) -// depth = link flow depth (ft) -// Output: returns flow velocity (fps) -// Purpose: finds flow velocity given flow and depth. -// -{ - double area; - double veloc = 0.0; - int k; - - if ( depth <= 0.01 ) return 0.0; - if ( Link[j].type == CONDUIT ) - { - k = Link[j].subIndex; - flow /= Conduit[k].barrels; - area = xsect_getAofY(&Link[j].xsect, depth); - if (area > FUDGE ) veloc = flow / area; - } - return veloc; -} - -//============================================================================= - -double link_getFroude(int j, double v, double y) -// -// Input: j = link index -// v = flow velocity (fps) -// y = flow depth (ft) -// Output: returns Froude Number -// Purpose: computes Froude Number for given velocity and flow depth -// -{ - TXsect* xsect = &Link[j].xsect; - - // --- return 0 if link is not a conduit - if ( Link[j].type != CONDUIT ) return 0.0; - - // --- return 0 if link empty or closed conduit is full - if ( y <= FUDGE ) return 0.0; - if ( !xsect_isOpen(xsect->type) && - xsect->yFull - y <= FUDGE ) return 0.0; - - // --- compute hydraulic depth - y = xsect_getAofY(xsect, y) / xsect_getWofY(xsect, y); - - // --- compute Froude No. - return fabs(v) / sqrt(GRAVITY * y); -} - -//============================================================================= - -double link_getPower(int j) -// -// Input: j = link index -// Output: returns power consumed by link in kwatts -// Purpose: computes power consumed by head loss (or head gain) of -// water flowing through a link -// -{ - int n1 = Link[j].node1; - int n2 = Link[j].node2; - double dh = (Node[n1].invertElev + Node[n1].newDepth) - - (Node[n2].invertElev + Node[n2].newDepth); - double q = fabs(Link[j].newFlow); - return fabs(dh) * q / 8.814 * KWperHP; -} - -//============================================================================= - -double link_getLossRate(int j, double q) -// -// Input: j = link index -// q = flow rate (ft3/sec) -// tstep = time step (sec) -// Output: returns uniform loss rate in link (ft3/sec) -// Purpose: computes rate at which flow volume is lost in a link due to -// evaporation and seepage. -// -{ - if ( Link[j].type == CONDUIT ) return conduit_getLossRate(j, q); - else return 0.0; -} - -//============================================================================= - -char link_getFullState(double a1, double a2, double aFull) -// -// Input: a1 = upstream link area (ft2) -// a2 = downstream link area (ft2) -// aFull = area of full conduit -// Output: returns fullness state of a link -// Purpose: determines if a link is upstream, downstream or completely full. -// -{ - if ( a1 >= aFull ) - { - if ( a2 >= aFull ) return ALL_FULL; - else return UP_FULL; - } - if ( a2 >= aFull ) return DN_FULL; - return 0; -} - -//============================================================================= -// C O N D U I T M E T H O D S -//============================================================================= - -int conduit_readParams(int j, int k, char* tok[], int ntoks) -// -// Input: j = link index -// k = conduit index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads conduit parameters from a tokenzed line of input. -// -{ - int n1, n2; - double x[6]; - char* id; - - // --- check for valid ID and end node IDs - if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); - id = project_findID(LINK, tok[0]); // link ID - if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); - n1 = project_findObject(NODE, tok[1]); // upstrm. node - if ( n1 < 0 ) return error_setInpError(ERR_NAME, tok[1]); - n2 = project_findObject(NODE, tok[2]); // dwnstrm. node - if ( n2 < 0 ) return error_setInpError(ERR_NAME, tok[2]); - - // --- parse length & Mannings N - if ( !getDouble(tok[3], &x[0]) ) - return error_setInpError(ERR_NUMBER, tok[3]); - if ( !getDouble(tok[4], &x[1]) ) - return error_setInpError(ERR_NUMBER, tok[4]); - - // --- parse offsets - if ( LinkOffsets == ELEV_OFFSET && *tok[5] == '*' ) x[2] = MISSING; - else if ( !getDouble(tok[5], &x[2]) ) - return error_setInpError(ERR_NUMBER, tok[5]); - if ( LinkOffsets == ELEV_OFFSET && *tok[6] == '*' ) x[3] = MISSING; - else if ( !getDouble(tok[6], &x[3]) ) - return error_setInpError(ERR_NUMBER, tok[6]); - - // --- parse optional parameters - x[4] = 0.0; // init. flow - if ( ntoks >= 8 ) - { - if ( !getDouble(tok[7], &x[4]) ) - return error_setInpError(ERR_NUMBER, tok[7]); - } - x[5] = 0.0; - if ( ntoks >= 9 ) - { - if ( !getDouble(tok[8], &x[5]) ) - return error_setInpError(ERR_NUMBER, tok[8]); - } - - // --- add parameters to data base - Link[j].ID = id; - link_setParams(j, CONDUIT, n1, n2, k, x); - return 0; -} - -//============================================================================= - -void conduit_validate(int j, int k) -// -// Input: j = link index -// k = conduit index -// Output: none -// Purpose: validates a conduit's properties. -// -{ - double aa; - double lengthFactor, roughness, slope; - - // --- a storage node cannot have a dummy outflow link - if ( Link[j].xsect.type == DUMMY && RouteModel == DW ) - { - if ( Node[Link[j].node1].type == STORAGE ) - { - report_writeErrorMsg(ERR_DUMMY_LINK, Node[Link[j].node1].ID); - return; - } - } - - // --- if custom xsection, then set its parameters - if ( Link[j].xsect.type == CUSTOM ) - xsect_setCustomXsectParams(&Link[j].xsect); - - // --- if irreg. xsection, assign transect roughness to conduit - if ( Link[j].xsect.type == IRREGULAR ) - { - xsect_setIrregXsectParams(&Link[j].xsect); - Conduit[k].roughness = Transect[Link[j].xsect.transect].roughness; - } - - // --- if street xsection, then set its parameters - if (Link[j].xsect.type == STREET_XSECT) - { - xsect_setStreetXsectParams(&Link[j].xsect); - Conduit[k].roughness = Street[Link[j].xsect.transect].roughness; - } - - // --- if force main xsection, adjust units on D-W roughness height - if ( Link[j].xsect.type == FORCE_MAIN ) - { - if ( ForceMainEqn == D_W ) Link[j].xsect.rBot /= UCF(RAINDEPTH); - if ( Link[j].xsect.rBot <= 0.0 ) - report_writeErrorMsg(ERR_XSECT, Link[j].ID); - } - - // --- check for valid length & roughness - if ( Conduit[k].length <= 0.0 ) - report_writeErrorMsg(ERR_LENGTH, Link[j].ID); - if ( Conduit[k].roughness <= 0.0 ) - report_writeErrorMsg(ERR_ROUGHNESS, Link[j].ID); - if ( Conduit[k].barrels <= 0 ) - report_writeErrorMsg(ERR_BARRELS, Link[j].ID); - - // --- check for valid xsection - if ( Link[j].xsect.type != DUMMY ) - { - if ( Link[j].xsect.type < 0 ) - report_writeErrorMsg(ERR_NO_XSECT, Link[j].ID); - else if ( Link[j].xsect.aFull <= 0.0 ) - report_writeErrorMsg(ERR_XSECT, Link[j].ID); - } - if ( ErrorCode ) return; - - // --- check for negative offsets - if ( Link[j].offset1 < 0.0 ) - { - report_writeWarningMsg(WARN03, Link[j].ID); - Link[j].offset1 = 0.0; - } - if ( Link[j].offset2 < 0.0 ) - { - report_writeWarningMsg(WARN03, Link[j].ID); - Link[j].offset2 = 0.0; - } - - // --- adjust conduit offsets for partly filled circular xsection - if ( Link[j].xsect.type == FILLED_CIRCULAR ) - { - Link[j].offset1 += Link[j].xsect.yBot; - Link[j].offset2 += Link[j].xsect.yBot; - } - - // --- compute conduit slope - slope = conduit_getSlope(j); - Conduit[k].slope = slope; - - // --- reverse orientation of conduit if using dynamic wave routing - // and slope is negative - if ( RouteModel == DW && - slope < 0.0 && - Link[j].xsect.type != DUMMY ) - { - conduit_reverse(j, k); - } - - // --- get equivalent Manning roughness for Force Mains - // for use when pipe is partly full - roughness = Conduit[k].roughness; - if ( RouteModel == DW && Link[j].xsect.type == FORCE_MAIN ) - { - roughness = forcemain_getEquivN(j, k); - } - - // --- adjust roughness for meandering natural channels - if ( Link[j].xsect.type == IRREGULAR ) - { - lengthFactor = Transect[Link[j].xsect.transect].lengthFactor; - roughness *= sqrt(lengthFactor); - } - - // --- lengthen conduit if lengthening option is in effect - lengthFactor = 1.0; - if ( RouteModel == DW && - LengtheningStep > 0.0 && - Link[j].xsect.type != DUMMY ) - { - lengthFactor = conduit_getLengthFactor(j, k, roughness); - } - - if ( lengthFactor != 1.0 ) - { - Conduit[k].modLength = lengthFactor * conduit_getLength(j); - slope /= lengthFactor; - roughness = roughness / sqrt(lengthFactor); - } - - // --- compute roughness factor used when computing friction - // slope term in Dynamic Wave flow routing - - // --- special case for non-Manning Force Mains - // (roughness factor for full flow is saved in xsect.sBot) - if ( RouteModel == DW && Link[j].xsect.type == FORCE_MAIN ) - { - Link[j].xsect.sBot = - forcemain_getRoughFactor(j, lengthFactor); - } - Conduit[k].roughFactor = GRAVITY * SQR(roughness/PHI); - - // --- compute full flow through cross section - if ( Link[j].xsect.type == DUMMY ) Conduit[k].beta = 0.0; - else Conduit[k].beta = PHI * sqrt(fabs(slope)) / roughness; - Link[j].qFull = Link[j].xsect.sFull * Conduit[k].beta; - Conduit[k].qMax = Link[j].xsect.sMax * Conduit[k].beta; - - // --- see if flow is supercritical most of time - // by comparing normal & critical velocities. - // (factor of 0.3 is for circular pipe 95% full) - // NOTE: this factor was used in the past for a modified version of - // Kinematic Wave routing but is now deprecated. - aa = Conduit[k].beta / sqrt(32.2) * - pow(Link[j].xsect.yFull, 0.1666667) * 0.3; - if ( aa >= 1.0 ) Conduit[k].superCritical = TRUE; - else Conduit[k].superCritical = FALSE; - - // --- set value of hasLosses flag - if ( Link[j].cLossInlet == 0.0 && - Link[j].cLossOutlet == 0.0 && - Link[j].cLossAvg == 0.0 - ) Conduit[k].hasLosses = FALSE; - else Conduit[k].hasLosses = TRUE; -} - -//============================================================================= - -void conduit_reverse(int j, int k) -// -// Input: j = link index -// k = conduit index -// Output: none -// Purpose: reverses direction of a conduit -// -{ - int i; - double z; - double cLoss; - - // --- reverse end nodes - i = Link[j].node1; - Link[j].node1 = Link[j].node2; - Link[j].node2 = i; - - // --- reverse node offsets - z = Link[j].offset1; - Link[j].offset1 = Link[j].offset2; - Link[j].offset2 = z; - - // --- reverse loss coeffs. - cLoss = Link[j].cLossInlet; - Link[j].cLossInlet = Link[j].cLossOutlet; - Link[j].cLossOutlet = cLoss; - - // --- reverse direction & slope - Conduit[k].slope = -Conduit[k].slope; - Link[j].direction *= (signed char)-1; - - // --- reverse initial flow value - Link[j].q0 = -Link[j].q0; -} - -//============================================================================= - -double conduit_getLength(int j) -// -// Input: j = link index -// Output: returns conduit's length (ft) -// Purpose: finds true length of a conduit. -// -// Note: for irregular natural channels, user inputs length of main -// channel (for FEMA purposes) but program should use length -// associated with entire flood plain. Transect.lengthFactor -// is the ratio of these two lengths. -// -{ - int k = Link[j].subIndex; - int t; - if ( Link[j].xsect.type != IRREGULAR ) return Conduit[k].length; - t = Link[j].xsect.transect; - if ( t < 0 || t >= Nobjects[TRANSECT] ) return Conduit[k].length; - return Conduit[k].length / Transect[t].lengthFactor; -} - -//============================================================================= - -double conduit_getLengthFactor(int j, int k, double roughness) -// -// Input: j = link index -// k = conduit index -// roughness = conduit Manning's n -// Output: returns factor by which a conduit should be lengthened -// Purpose: computes amount of conduit lengthing to improve numerical stability. -// -// The following form of the Courant criterion is used: -// L = t * v * (1 + Fr) / Fr -// where L = conduit length, t = time step, v = velocity, & Fr = Froude No. -// After substituting Fr = v / sqrt(gy), where y = flow depth, we get: -// L = t * ( sqrt(gy) + v ) -// -{ - double ratio; - double yFull; - double vFull; - double tStep; - - // --- evaluate flow depth and velocity at full normal flow condition - yFull = Link[j].xsect.yFull; - if ( xsect_isOpen(Link[j].xsect.type) ) - { - yFull = Link[j].xsect.aFull / xsect_getWofY(&Link[j].xsect, yFull); - } - vFull = PHI / roughness * Link[j].xsect.sFull * - sqrt(fabs(Conduit[k].slope)) / Link[j].xsect.aFull; - - // --- determine ratio of Courant length to actual length - if ( LengtheningStep == 0.0 ) tStep = RouteStep; - else tStep = MIN(RouteStep, LengtheningStep); - ratio = (sqrt(GRAVITY*yFull) + vFull) * tStep / conduit_getLength(j); - - // --- return max. of 1.0 and ratio - if ( ratio > 1.0 ) return ratio; - else return 1.0; -} - -//============================================================================= - -double conduit_getSlope(int j) -// -// Input: j = link index -// Output: returns conduit slope -// Purpose: computes conduit slope. -// -{ - double elev1, elev2, delta, slope; - double length = conduit_getLength(j); - - // --- check that elevation drop > minimum allowable drop - elev1 = Link[j].offset1 + Node[Link[j].node1].invertElev; - elev2 = Link[j].offset2 + Node[Link[j].node2].invertElev; - delta = fabs(elev1 - elev2); - if ( delta < MIN_DELTA_Z ) - { - report_writeWarningMsg(WARN04, Link[j].ID); - delta = MIN_DELTA_Z; - } - - // --- elevation drop cannot exceed conduit length - if ( delta >= length ) - { - report_writeWarningMsg(WARN08, Link[j].ID); - slope = delta / length; - } - - // --- slope = elev. drop / horizontal distance - else slope = delta / sqrt(SQR(length) - SQR(delta)); - - // -- check that slope exceeds minimum allowable slope - if ( MinSlope > 0.0 && slope < MinSlope ) - { - report_writeWarningMsg(WARN05, Link[j].ID); - slope = MinSlope; - // keep min. slope positive for SF or KW routing - if (RouteModel == SF || RouteModel == KW) return slope; - } - - // --- change sign for adverse slope - if ( elev1 < elev2 ) slope = -slope; - return slope; -} - -//============================================================================= - -void conduit_initState(int j, int k) -// -// Input: j = link index -// k = conduit index -// Output: none -// Purpose: sets initial conduit depth to normal depth of initial flow -// -{ - Link[j].newDepth = link_getYnorm(j, Link[j].q0 / Conduit[k].barrels); - Link[j].oldDepth = Link[j].newDepth; - Conduit[k].evapLossRate = 0.0; - Conduit[k].seepLossRate = 0.0; -} - -//============================================================================= - -double conduit_getInflow(int j) -// -// Input: j = link index -// Output: returns flow in link (cfs) -// Purpose: finds inflow to conduit from upstream node. -// -{ - double qIn = node_getOutflow(Link[j].node1, j); - if ( Link[j].qLimit > 0.0 ) qIn = MIN(qIn, Link[j].qLimit); - return qIn; -} - -//============================================================================= - -double conduit_getLossRate(int j, double q) -// -// Input: j = link index -// 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). -// -{ - TXsect *xsect; - double depth = 0.5 * (Link[j].oldDepth + Link[j].newDepth); - double length; - double topWidth; - double evapLossRate = 0.0, - seepLossRate = 0.0, - totalLossRate = 0.0; - - if ( depth > FUDGE ) - { - xsect = &Link[j].xsect; - length = conduit_getLength(j); - - // --- find evaporation rate for open conduits - if ( xsect_isOpen(xsect->type) && Evap.rate > 0.0 ) - { - topWidth = xsect_getWofY(xsect, depth); - evapLossRate = topWidth * length * Evap.rate; - } - - // --- compute seepage loss rate - if ( Link[j].seepRate > 0.0 ) - { - // limit depth to depth at max width - if ( depth >= xsect->ywMax ) depth = xsect->ywMax; - - // compute seepage loss rate across length of conduit - seepLossRate = Link[j].seepRate * xsect_getWofY(xsect, depth) * - length; - seepLossRate *= Adjust.hydconFactor; - } - - // --- compute total loss rate - totalLossRate = evapLossRate + seepLossRate; - - // --- total loss rate cannot exceed flow rate - q = ABS(q); - if (totalLossRate > q) - { - evapLossRate = evapLossRate * q / totalLossRate; - seepLossRate = seepLossRate * q / totalLossRate; - totalLossRate = q; - } - } - - Conduit[Link[j].subIndex].evapLossRate = evapLossRate; - Conduit[Link[j].subIndex].seepLossRate = seepLossRate; - return totalLossRate; -} - - -//============================================================================= -// P U M P M E T H O D S -//============================================================================= - -int pump_readParams(int j, int k, char* tok[], int ntoks) -// -// Input: j = link index -// k = pump index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads pump parameters from a tokenized line of input. -// -{ - int m; - int n1, n2; - double x[4]; - char* id; - - // --- check for valid ID and end node IDs - if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); - id = project_findID(LINK, tok[0]); - if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); - n1 = project_findObject(NODE, tok[1]); - if ( n1 < 0 ) return error_setInpError(ERR_NAME, tok[1]); - n2 = project_findObject(NODE, tok[2]); - if ( n2 < 0 ) return error_setInpError(ERR_NAME, tok[2]); - - // --- parse curve name - x[0] = -1.; - if ( ntoks >= 4 ) - { - if ( !strcomp(tok[3],"*") ) - { - m = project_findObject(CURVE, tok[3]); - if ( m < 0 ) return error_setInpError(ERR_NAME, tok[3]); - x[0] = m; - } - } - - // --- parse init. status if present - x[1] = 1.0; - if ( ntoks >= 5 ) - { - m = findmatch(tok[4], OffOnWords); - if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[4]); - x[1] = m; - } - - // --- parse startup/shutoff depths if present - x[2] = 0.0; - if ( ntoks >= 6 ) - { - if ( !getDouble(tok[5], &x[2]) || x[2] < 0.0) - return error_setInpError(ERR_NUMBER, tok[5]); - } - x[3] = 0.0; - if ( ntoks >= 7 ) - { - if ( !getDouble(tok[6], &x[3]) || x[3] < 0.0 ) - return error_setInpError(ERR_NUMBER, tok[6]); - } - - // --- add parameters to pump object - Link[j].ID = id; - link_setParams(j, PUMP, n1, n2, k, x); - return 0; -} - -//============================================================================= - -void pump_validate(int j, int k) -// -// Input: j = link index -// k = pump index -// Output: none -// Purpose: validates a pump's properties -// -{ - int m, n1; - double x, y; - - Link[j].xsect.yFull = 0.0; - - // --- check for valid curve type - m = Pump[k].pumpCurve; - if ( m < 0 ) - { - Pump[k].type = IDEAL_PUMP; - } - else - { - if ( Curve[m].curveType < PUMP1_CURVE || - Curve[m].curveType > PUMP5_CURVE ) - report_writeErrorMsg(ERR_NO_CURVE, Link[j].ID); - - // --- store pump curve type with pump's parameters - else - { - Pump[k].type = Curve[m].curveType - PUMP1_CURVE; - if ( table_getFirstEntry(&Curve[m], &x, &y) ) - { - Link[j].qFull = y; - Pump[k].xMin = x; - Pump[k].xMax = x; - while ( table_getNextEntry(&Curve[m], &x, &y) ) - { - Link[j].qFull = MAX(y, Link[j].qFull); - Pump[k].xMax = x; - } - } - Link[j].qFull /= UCF(FLOW); - } - } - - // --- check that shutoff depth < startup depth - if ( Pump[k].yOn > 0.0 && Pump[k].yOn <= Pump[k].yOff ) - report_writeErrorMsg(ERR_PUMP_LIMITS, Link[j].ID); - - // --- assign wet well volume to inlet node of Type 1 pump - if ( Pump[k].type == TYPE1_PUMP ) - { - n1 = Link[j].node1; - if ( Node[n1].type != STORAGE ) - Node[n1].fullVolume = MAX(Node[n1].fullVolume, - Pump[k].xMax / UCF(VOLUME)); - } - -} - -//============================================================================= - -void pump_initState(int j, int k) -// -// Input: j = link index -// k = pump index -// Output: none -// Purpose: initializes pump conditions at start of a simulation -// -{ - Link[j].setting = Pump[k].initSetting; - Link[j].targetSetting = Pump[k].initSetting; -} - -//============================================================================= - -double pump_getInflow(int j) -// -// Input: j = link index -// Output: returns pump flow (cfs) -// Purpose: finds flow produced by a pump. -// -{ - int k, m; - int n1, n2; - double vol, depth, head; - double qIn, qIn1, dh = 0.001; - double s = 1.0; // speed setting - - k = Link[j].subIndex; - m = Pump[k].pumpCurve; - n1 = Link[j].node1; - n2 = Link[j].node2; - - // --- no flow if setting is closed - Link[j].flowClass = NO; - Link[j].setting = Link[j].targetSetting; - if ( Link[j].setting == 0.0 ) return 0.0; - - // --- pump flow = node inflow for IDEAL_PUMP - if ( Pump[k].type == IDEAL_PUMP ) - qIn = Node[n1].inflow + Node[n1].overflow; - - // --- pumping rate depends on pump curve type - else switch(Curve[m].curveType) - { - case PUMP1_CURVE: - vol = Node[n1].newVolume * UCF(VOLUME); - qIn = table_intervalLookup(&Curve[m], vol) / UCF(FLOW); - - // --- check if off of pump curve - if ( vol < Pump[k].xMin || vol > Pump[k].xMax ) - Link[j].flowClass = YES; - break; - - case PUMP2_CURVE: - depth = Node[n1].newDepth * UCF(LENGTH); - qIn = table_intervalLookup(&Curve[m], depth) / UCF(FLOW); - - // --- check if off of pump curve - if ( depth < Pump[k].xMin || depth > Pump[k].xMax ) - Link[j].flowClass = YES; - break; - - case PUMP3_CURVE: - case PUMP5_CURVE: - if (Curve[m].curveType == PUMP5_CURVE) s = Link[j].setting; - head = ((Node[n2].newDepth + Node[n2].invertElev) - - (Node[n1].newDepth + Node[n1].invertElev)) / s / s; - head = MAX(head, 0.0) * UCF(LENGTH); - qIn = table_lookup(&Curve[m], head) / UCF(FLOW); - - // --- compute dQ/dh (slope of pump curve) and - // reverse sign since flow decreases with increasing head - Link[j].dqdh = -table_getSlope(&Curve[m], head) * - UCF(LENGTH) / UCF(FLOW) / s; - - // --- check if off of pump curve - if (head < Pump[k].xMin || head > Pump[k].xMax) - Link[j].flowClass = YES; - break; - - case PUMP4_CURVE: - depth = Node[n1].newDepth; - qIn = table_lookup(&Curve[m], depth*UCF(LENGTH)) / UCF(FLOW); - - // --- compute dQ/dh (slope of pump curve) - qIn1 = table_lookup(&Curve[m], (depth+dh)*UCF(LENGTH)) / UCF(FLOW); - Link[j].dqdh = (qIn1 - qIn) / dh; - - // --- check if off of pump curve - depth *= UCF(LENGTH); - if ( depth < Pump[k].xMin ) Link[j].flowClass = DN_DRY; - if ( depth > Pump[k].xMax ) Link[j].flowClass = UP_DRY; - break; - - default: qIn = 0.0; - } - - // --- do not allow reverse flow through pump - if ( qIn < 0.0 ) qIn = 0.0; - return qIn * Link[j].setting; -} - - -//============================================================================= -// O R I F I C E M E T H O D S -//============================================================================= - -int orifice_readParams(int j, int k, char* tok[], int ntoks) -// -// Input: j = link index -// k = orifice index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads orifice parameters from a tokenized line of input. -// -{ - int m; - int n1, n2; - double x[5]; - char* id; - - // --- check for valid ID and end node IDs - if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); - id = project_findID(LINK, tok[0]); - if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); - n1 = project_findObject(NODE, tok[1]); - if ( n1 < 0 ) return error_setInpError(ERR_NAME, tok[1]); - n2 = project_findObject(NODE, tok[2]); - if ( n2 < 0 ) return error_setInpError(ERR_NAME, tok[2]); - - // --- parse orifice parameters - m = findmatch(tok[3], OrificeTypeWords); - if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[3]); - x[0] = m; // type - if ( LinkOffsets == ELEV_OFFSET && *tok[4] == '*' ) x[1] = MISSING; - else if ( ! getDouble(tok[4], &x[1]) ) // crest height - return error_setInpError(ERR_NUMBER, tok[4]); - if ( ! getDouble(tok[5], &x[2]) || x[2] < 0.0 ) // cDisch - return error_setInpError(ERR_NUMBER, tok[5]); - x[3] = 0.0; - if ( ntoks >= 7 ) - { - m = findmatch(tok[6], NoYesWords); - if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[6]); - x[3] = m; // flap gate - } - x[4] = 0.0; - if ( ntoks >= 8 ) - { - if ( ! getDouble(tok[7], &x[4]) || x[4] < 0.0 ) // orate - return error_setInpError(ERR_NUMBER, tok[7]); - } - - // --- add parameters to orifice object - Link[j].ID = id; - link_setParams(j, ORIFICE, n1, n2, k, x); - return 0; -} - -//============================================================================= - -void orifice_validate(int j, int k) -// -// Input: j = link index -// k = orifice index -// Output: none -// Purpose: validates an orifice's properties -// -{ - int err = 0; - - // --- check for valid xsection - if ( Link[j].xsect.type != RECT_CLOSED - && Link[j].xsect.type != CIRCULAR ) err = ERR_REGULATOR_SHAPE; - if ( err > 0 ) - { - report_writeErrorMsg(err, Link[j].ID); - return; - } - - // --- check for negative offset - if ( Link[j].offset1 < 0.0 ) Link[j].offset1 = 0.0; - - // --- compute partial flow adjustment - orifice_setSetting(j, 0.0); - - // --- compute an equivalent length - Orifice[k].length = 2.0 * RouteStep * sqrt(GRAVITY * Link[j].xsect.yFull); - Orifice[k].length = MAX(200.0, Orifice[k].length); - Orifice[k].surfArea = 0.0; -} - -//============================================================================= - -void orifice_setSetting(int j, double tstep) -// -// Input: j = link index -// tstep = time step over which setting is adjusted (sec) -// Output: none -// Purpose: updates an orifice's setting as a result of a control action. -// -{ - int k = Link[j].subIndex; - double delta, step; - double h, f; - - // --- case where adjustment rate is instantaneous - if ( Orifice[k].orate == 0.0 || tstep == 0.0) - Link[j].setting = Link[j].targetSetting; - - // --- case where orifice setting depends on time step - else - { - delta = Link[j].targetSetting - Link[j].setting; - step = tstep / Orifice[k].orate; - if ( step + 0.001 >= fabs(delta) ) - Link[j].setting = Link[j].targetSetting; - else Link[j].setting += SGN(delta) * step; - } - - // --- find effective orifice discharge coeff. - h = Link[j].setting * Link[j].xsect.yFull; - f = xsect_getAofY(&Link[j].xsect, h) * sqrt(2.0 * GRAVITY); - Orifice[k].cOrif = Orifice[k].cDisch * f; - - // --- find equiv. discharge coeff. for when weir flow occurs - Orifice[k].cWeir = orifice_getWeirCoeff(j, k, h) * f; -} - -//============================================================================= - -double orifice_getWeirCoeff(int j, int k, double h) -// -// Input: j = link index -// k = orifice index -// h = height of orifice opening (ft) -// Output: returns a discharge coefficient (ft^1/2) -// Purpose: computes the discharge coefficient for an orifice -// at the critical depth where weir flow begins. -// -{ - double w, aOverL; - - // --- this is for bottom orifices - if ( Orifice[k].type == BOTTOM_ORIFICE ) - { - // --- find critical height above opening where orifice flow - // turns into weir flow. It equals (Co/Cw)*(Area/Length) - // where Co is the orifice coeff., Cw is the weir coeff/sqrt(2g), - // Area is the area of the opening, and Length = circumference - // of the opening. For a basic sharp crested weir, Cw = 0.414. - if (Link[j].xsect.type == CIRCULAR) aOverL = h / 4.0; - else - { - w = Link[j].xsect.wMax; - aOverL = (h*w) / (2.0*(h+w)); - } - h = Orifice[k].cDisch / 0.414 * aOverL; - Orifice[k].hCrit = h; - } - - // --- this is for side orifices - else - { - // --- critical height is simply height of opening - Orifice[k].hCrit = h; - - // --- head on orifice is distance to center line - h = h / 2.0; - } - - // --- return a coefficient for the critical depth - return Orifice[k].cDisch * sqrt(h); -} - -//============================================================================= - -double orifice_getInflow(int j) -// -// Input: j = link index -// Output: returns orifice flow rate (cfs) -// Purpose: finds the flow through an orifice. -// -{ - int k, n1, n2; - double head, h1, h2, y1, dir; - double f; - double hcrest = 0.0; - double hcrown = 0.0; - double hmidpt; - double q, ratio; - - // --- get indexes of end nodes and link's orifice - n1 = Link[j].node1; - n2 = Link[j].node2; - k = Link[j].subIndex; - - // --- find heads at upstream & downstream nodes - if ( RouteModel == DW ) - { - h1 = Node[n1].newDepth + Node[n1].invertElev; - h2 = Node[n2].newDepth + Node[n2].invertElev; - } - else - { - h1 = Node[n1].newDepth + Node[n1].invertElev; - h2 = Node[n1].invertElev; - } - dir = (h1 >= h2) ? +1.0 : -1.0; - - // --- exchange h1 and h2 for reverse flow - y1 = Node[n1].newDepth; - if ( dir < 0.0 ) - { - head = h1; - h1 = h2; - h2 = head; - y1 = Node[n2].newDepth; - } - - // --- orifice is a bottom orifice (oriented in horizontal plane) - if ( Orifice[k].type == BOTTOM_ORIFICE ) - { - // --- compute crest elevation - hcrest = Node[n1].invertElev + Link[j].offset1; - - // --- compute head on orifice - if (h1 < hcrest) head = 0.0; - else if (h2 > hcrest) head = h1 - h2; - else head = h1 - hcrest; - - // --- find fraction of critical height for which weir flow occurs - f = head / Orifice[k].hCrit; - f = MIN(f, 1.0); - } - - // --- otherwise orifice is a side orifice (oriented in vertical plane) - else - { - // --- compute elevations of orifice crest and crown - hcrest = Node[n1].invertElev + Link[j].offset1; - hcrown = hcrest + Link[j].xsect.yFull * Link[j].setting; - hmidpt = (hcrest + hcrown) / 2.0; - - // --- compute degree of inlet submergence - if ( h1 < hcrown && hcrown > hcrest ) - f = (h1 - hcrest) / (hcrown - hcrest); - else f = 1.0; - - // --- compute head on orifice - if ( f < 1.0 ) head = h1 - hcrest; - else if ( h2 < hmidpt ) head = h1 - hmidpt; - else head = h1 - h2; - } - - // --- return if head is negligible or flap gate closed - if ( head <= FUDGE || y1 <= FUDGE || - link_setFlapGate(j, n1, n2, dir) ) - { - Link[j].newDepth = 0.0; - Link[j].flowClass = DRY; - Orifice[k].surfArea = FUDGE * Orifice[k].length; - Link[j].dqdh = 0.0; - return 0.0; - } - - // --- determine flow class - Link[j].flowClass = SUBCRITICAL; - if ( hcrest > h2 ) - { - if ( dir == 1.0 ) Link[j].flowClass = DN_CRITICAL; - else Link[j].flowClass = UP_CRITICAL; - } - - // --- compute flow depth and surface area - y1 = Link[j].xsect.yFull * Link[j].setting; - if ( Orifice[k].type == SIDE_ORIFICE ) - { - Link[j].newDepth = y1 * f; - Orifice[k].surfArea = - xsect_getWofY(&Link[j].xsect, Link[j].newDepth) * - Orifice[k].length; - } - else - { - Link[j].newDepth = y1; - Orifice[k].surfArea = xsect_getAofY(&Link[j].xsect, y1); - } - - // --- find flow through the orifice - q = dir * orifice_getFlow(j, k, head, f, Link[j].hasFlapGate); - - // --- apply Villemonte eqn. to correct for submergence - if ( f < 1.0 && h2 > hcrest ) - { - ratio = (h2 - hcrest) / (h1 - hcrest); - q *= pow( (1.0 - pow(ratio, 1.5)), 0.385); - } - return q; -} - -//============================================================================= - -double orifice_getFlow(int j, int k, double head, double f, int hasFlapGate) -// -// Input: j = link index -// k = orifice index -// head = head across orifice -// f = fraction of critical depth filled -// hasFlapGate = flap gate indicator -// Output: returns flow through an orifice -// Purpose: computes flow through an orifice as a function of head. -// -{ - double area, q; - double veloc, hLoss; - - // --- case where orifice is closed - if ( head == 0.0 || f <= 0.0 ) - { - Link[j].dqdh = 0.0; - return 0.0; - } - - // --- case where inlet depth is below critical depth; - // orifice behaves as a weir - else if ( f < 1.0 ) - { - q = Orifice[k].cWeir * pow(f, 1.5); - Link[j].dqdh = 1.5 * q / (f * Orifice[k].hCrit); - } - - // --- case where normal orifice flow applies - else - { - q = Orifice[k].cOrif * sqrt(head); - Link[j].dqdh = q / (2.0 * head); - } - - // --- apply ARMCO adjustment for headloss from flap gate - if ( hasFlapGate ) - { - // --- compute velocity for current orifice flow - area = xsect_getAofY(&Link[j].xsect, - Link[j].setting * Link[j].xsect.yFull); - veloc = q / area; - - // --- compute head loss from gate - hLoss = (4.0 / GRAVITY) * veloc * veloc * - exp(-1.15 * veloc / sqrt(head) ); - - // --- update head (for orifice flow) - // or critical depth fraction (for weir flow) - if ( f < 1.0 ) - { - f = f - hLoss/Orifice[k].hCrit; - if ( f < 0.0 ) f = 0.0; - } - else - { - head = head - hLoss; - if ( head < 0.0 ) head = 0.0; - } - - // --- make recursive call to this function, with hasFlapGate - // set to false, to find flow values at adjusted head value - q = orifice_getFlow(j, k, head, f, FALSE); - } - return q; -} - -//============================================================================= -// W E I R M E T H O D S -//============================================================================= - -int weir_readParams(int j, int k, char* tok[], int ntoks) -// -// Input: j = link index -// k = weir index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads weir parameters from a tokenized line of input. -// -{ - int m; - int n1, n2; - double x[10]; - char* id; - - // --- check for valid ID and end node IDs - if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); - id = project_findID(LINK, tok[0]); - if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); - n1 = project_findObject(NODE, tok[1]); - if ( n1 < 0 ) return error_setInpError(ERR_NAME, tok[1]); - n2 = project_findObject(NODE, tok[2]); - if ( n2 < 0 ) return error_setInpError(ERR_NAME, tok[2]); - - // --- parse weir parameters - m = findmatch(tok[3], WeirTypeWords); - if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[3]); - x[0] = m; // type - if ( LinkOffsets == ELEV_OFFSET && *tok[4] == '*' ) x[1] = MISSING; - else if ( ! getDouble(tok[4], &x[1]) ) // height - return error_setInpError(ERR_NUMBER, tok[4]); - if ( ! getDouble(tok[5], &x[2]) || x[2] < 0.0 ) // cDisch1 - return error_setInpError(ERR_NUMBER, tok[5]); - x[3] = 0.0; - x[4] = 0.0; - x[5] = 0.0; - x[6] = 1.0; - x[7] = 0.0; - x[8] = 0.0; - x[9] = -1.0; - if ( ntoks >= 7 && *tok[6] != '*' ) - { - m = findmatch(tok[6], NoYesWords); - if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[6]); - x[3] = m; // flap gate - } - if ( ntoks >= 8 && *tok[7] != '*' ) - { - if ( ! getDouble(tok[7], &x[4]) || x[4] < 0.0 ) // endCon - return error_setInpError(ERR_NUMBER, tok[7]); - } - if ( ntoks >= 9 && *tok[8] != '*' ) - { - if ( ! getDouble(tok[8], &x[5]) || x[5] < 0.0 ) // cDisch2 - return error_setInpError(ERR_NUMBER, tok[8]); - } - - if ( ntoks >= 10 && *tok[9] != '*' ) - { - m = findmatch(tok[9], NoYesWords); - if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[9]); - x[6] = m; // canSurcharge - } - - if ( (m = (int)x[0]) == ROADWAY_WEIR ) - { - if ( ntoks >= 11 ) // road width - { - if ( ! getDouble(tok[10], &x[7]) || x[7] < 0.0 ) - return error_setInpError(ERR_NUMBER, tok[10]); - } - if ( ntoks >= 12 ) // road surface - { - if ( strcomp(tok[11], "PAVED") ) x[8] = 1.0; - else if ( strcomp(tok[11], "GRAVEL") ) x[8] = 2.0; - } - } - - if (ntoks >= 13 && *tok[12] != '*') - { - m = project_findObject(CURVE, tok[12]); // coeff. curve - if (m < 0) return error_setInpError(ERR_NAME, tok[12]); - x[9] = m; - } - - // --- add parameters to weir object - Link[j].ID = id; - link_setParams(j, WEIR, n1, n2, k, x); - return 0; -} - -//============================================================================= - -void weir_validate(int j, int k) -// -// Input: j = link index -// k = weir index -// Output: none -// Purpose: validates a weir's properties -// -{ - int err = 0; - double q, q1, q2, head; - - // --- check for valid cross section - switch ( Weir[k].type) - { - case TRANSVERSE_WEIR: - case SIDEFLOW_WEIR: - case ROADWAY_WEIR: - if ( Link[j].xsect.type != RECT_OPEN ) err = ERR_REGULATOR_SHAPE; - Weir[k].slope = 0.0; - break; - - case VNOTCH_WEIR: - if ( Link[j].xsect.type != TRIANGULAR ) err = ERR_REGULATOR_SHAPE; - else - { - Weir[k].slope = Link[j].xsect.sBot; - } - break; - - case TRAPEZOIDAL_WEIR: - if ( Link[j].xsect.type != TRAPEZOIDAL ) err = ERR_REGULATOR_SHAPE; - else - { - Weir[k].slope = Link[j].xsect.sBot; - } - break; - } - if ( err > 0 ) - { - report_writeErrorMsg(err, Link[j].ID); - return; - } - - // --- check for negative offset - if ( Link[j].offset1 < 0.0 ) Link[j].offset1 = 0.0; - - // --- compute an equivalent length - Weir[k].length = 2.0 * RouteStep * sqrt(GRAVITY * Link[j].xsect.yFull); - Weir[k].length = MAX(200.0, Weir[k].length); - Weir[k].surfArea = 0.0; - - // --- find flow through weir when water level equals weir height - head = Link[j].xsect.yFull; - weir_getFlow(j, k, head, 1.0, FALSE, &q1, &q2); - q = q1 + q2; - - // --- compute equivalent orifice coeff. (for CFS flow units) - head = head / 2.0; // head seen by equivalent orifice - Weir[k].cSurcharge = q / sqrt(head); -} - -//============================================================================= - -void weir_setSetting(int j) -// -// Input: j = link index -// Output: none -// Purpose: updates a weir's setting as a result of a control action. -// -{ - int k = Link[j].subIndex; - double h, q, q1, q2; - - // --- adjust weir setting - Link[j].setting = Link[j].targetSetting; - if ( !Weir[k].canSurcharge ) return; - if ( Weir[k].type == ROADWAY_WEIR ) return; - - // --- find orifice coeff. for surcharged flow - if ( Link[j].setting == 0.0 ) Weir[k].cSurcharge = 0.0; - else - { - // --- find flow through weir when water level equals weir height - h = Link[j].setting * Link[j].xsect.yFull; - weir_getFlow(j, k, h, 1.0, FALSE, &q1, &q2); - q = q1 + q2; - - // --- compute equivalent orifice coeff. (for CFS flow units) - h = h / 2.0; // head seen by equivalent orifice - Weir[k].cSurcharge = q / sqrt(h); - } -} - -//============================================================================= - -double weir_getInflow(int j) -// -// Input: j = link index -// Output: returns weir flow rate (cfs) -// Purpose: finds the flow over a weir. -// -{ - int n1; // index of upstream node - int n2; // index of downstream node - int k; // index of weir - double q1; // flow through central part of weir (cfs) - double q2; // flow through end sections of weir (cfs) - double head; // head on weir (ft) - double h1; // upstrm nodal head (ft) - double h2; // downstrm nodal head (ft) - double hcrest; // head at weir crest (ft) - double hcrown; // head at weir crown (ft) - double y; // water depth in weir (ft) - double dir; // direction multiplier - double ratio; - double weirPower[] = {1.5, // transverse weir - 5./3., // side flow weir - 2.5, // v-notch weir - 1.5}; // trapezoidal weir - - n1 = Link[j].node1; - n2 = Link[j].node2; - k = Link[j].subIndex; - if ( RouteModel == DW ) - { - h1 = Node[n1].newDepth + Node[n1].invertElev; - h2 = Node[n2].newDepth + Node[n2].invertElev; - } - else - { - h1 = Node[n1].newDepth + Node[n1].invertElev; - h2 = Node[n1].invertElev; - } - dir = (h1 > h2) ? +1.0 : -1.0; - - // --- exchange h1 and h2 for reverse flow - if ( dir < 0.0 ) - { - head = h1; - h1 = h2; - h2 = head; - } - - // --- find head of weir's crest and crown - hcrest = Node[n1].invertElev + Link[j].offset1; - hcrown = hcrest + Link[j].xsect.yFull; - - // --- treat a roadway weir as a special case - if ( Weir[k].type == ROADWAY_WEIR ) - return roadway_getInflow(j, dir, hcrest, h1, h2); - - // --- adjust crest ht. for partially open weir - hcrest += (1.0 - Link[j].setting) * Link[j].xsect.yFull; - - // --- compute head relative to weir crest - head = h1 - hcrest; - - // --- return if head is negligible or flap gate closed - Link[j].dqdh = 0.0; - if ( head <= FUDGE || hcrest >= hcrown || - link_setFlapGate(j, n1, n2, dir) ) - { - Link[j].newDepth = 0.0; - Link[j].flowClass = DRY; - return 0.0; - } - - // --- determine flow class - Link[j].flowClass = SUBCRITICAL; - if ( hcrest > h2 ) - { - if ( dir == 1.0 ) Link[j].flowClass = DN_CRITICAL; - else Link[j].flowClass = UP_CRITICAL; - } - - // --- compute new equivalent surface area - y = Link[j].xsect.yFull - (hcrown - MIN(h1, hcrown)); - Weir[k].surfArea = xsect_getWofY(&Link[j].xsect, y) * Weir[k].length; - - // --- head is above crown - if ( h1 >= hcrown ) - { - // --- use equivalent orifice if weir can surcharge - if ( Weir[k].canSurcharge ) - { - y = (hcrest + hcrown) / 2.0; - if ( h2 < y ) head = h1 - y; - else head = h1 - h2; - y = hcrown - hcrest; - q1 = weir_getOrificeFlow(j, head, y, Weir[k].cSurcharge); - Link[j].newDepth = y; - return dir * q1; - } - - // --- otherwise limit head to height of weir opening - else head = hcrown - hcrest; - } - - // --- use weir eqn. to find flows through central (q1) - // and end sections (q2) of weir - weir_getFlow(j, k, head, dir, Link[j].hasFlapGate, &q1, &q2); - - // --- apply Villemonte eqn. to correct for submergence - if ( h2 > hcrest ) - { - ratio = (h2 - hcrest) / (h1 - hcrest); - q1 *= pow( (1.0 - pow(ratio, weirPower[Weir[k].type])), 0.385); - if ( q2 > 0.0 ) - q2 *= pow( (1.0 - pow(ratio, weirPower[VNOTCH_WEIR])), 0.385); - } - - // --- return total flow through weir - Link[j].newDepth = MIN((h1 - hcrest), Link[j].xsect.yFull); - return dir * (q1 + q2); -} - -//============================================================================= - -void weir_getFlow(int j, int k, double head, double dir, int hasFlapGate, - double* q1, double* q2) -// -// Input: j = link index -// k = weir index -// head = head across weir (ft) -// dir = flow direction indicator -// hasFlapGate = flap gate indicator -// Output: q1 = flow through central portion of weir (cfs) -// q2 = flow through end sections of weir (cfs) -// Purpose: computes flow over weir given head. -// -{ - double length; - double h; - double y; - double hLoss; - double area; - double veloc; - int wType; - int cdCurve = Weir[k].cdCurve; - double cDisch1 = Weir[k].cDisch1; - - // --- q1 = flow through central portion of weir, - // q2 = flow through end sections of trapezoidal weir - *q1 = 0.0; - *q2 = 0.0; - Link[j].dqdh = 0.0; - if ( head <= 0.0 ) return; - - // --- convert weir length & head to original units - length = Link[j].xsect.wMax * UCF(LENGTH); - h = head * UCF(LENGTH); - - // --- lookup tabulated discharge coeff. - if ( cdCurve >= 0 ) cDisch1 = table_lookup(&Curve[cdCurve], h); - - // --- use appropriate formula for weir flow - wType = Weir[k].type; - if ( wType == VNOTCH_WEIR && - Link[j].setting < 1.0 ) wType = TRAPEZOIDAL_WEIR; - switch (wType) - { - case TRANSVERSE_WEIR: - - // --- reduce length when end contractions present - length -= 0.1 * Weir[k].endCon * h; - length = MAX(length, 0.0); - *q1 = cDisch1 * length * pow(h, 1.5); - break; - - case SIDEFLOW_WEIR: - - // --- reduce length when end contractions present - length -= 0.1 * Weir[k].endCon * h; - length = MAX(length, 0.0); - - // --- weir behaves as a transverse weir under reverse flow - if ( dir < 0.0 ) - *q1 = cDisch1 * length * pow(h, 1.5); - else - - // Corrected formula (see Metcalf & Eddy, Inc., - // Wastewater Engineering, McGraw-Hill, 1972 p. 164). - *q1 = cDisch1 * pow(length, 0.83) * pow(h, 1.67); - - break; - - case VNOTCH_WEIR: - *q1 = cDisch1 * Weir[k].slope * pow(h, 2.5); - break; - - case TRAPEZOIDAL_WEIR: - y = (1.0 - Link[j].setting) * Link[j].xsect.yFull; - length = xsect_getWofY(&Link[j].xsect, y) * UCF(LENGTH); - *q1 = cDisch1 * length * pow(h, 1.5); - *q2 = Weir[k].cDisch2 * Weir[k].slope * pow(h, 2.5); - } - - // --- convert CMS flows to CFS - if ( UnitSystem == SI ) - { - *q1 /= M3perFT3; - *q2 /= M3perFT3; - } - - // --- apply ARMCO adjustment for headloss from flap gate - if ( hasFlapGate ) - { - // --- compute flow area & velocity for current weir flow - area = weir_getOpenArea(j, head); - if ( area > TINY ) - { - veloc = (*q1 + *q2) / area; - - // --- compute headloss and subtract from original head - hLoss = (4.0 / GRAVITY) * veloc * veloc * - exp(-1.15 * veloc / sqrt(head) ); - head = head - hLoss; - if ( head < 0.0 ) head = 0.0; - - // --- make recursive call to this function, with hasFlapGate - // set to false, to find flow values at adjusted head value - weir_getFlow(j, k, head, dir, FALSE, q1, q2); - } - } - Link[j].dqdh = weir_getdqdh(k, dir, head, *q1, *q2); -} - -//============================================================================= - -double weir_getOrificeFlow(int j, double head, double y, double cOrif) -// -// Input: j = link index -// head = head across weir (ft) -// y = height of upstream water level above weir crest (ft) -// cOrif = orifice flow coefficient -// Output: returns flow through weir -// Purpose: finds flow through a surcharged weir using the orifice equation. -// -{ - double a, q, v, hloss; - - // --- evaluate the orifice flow equation - q = cOrif * sqrt(head); - - // --- apply Armco adjustment if weir has a flap gate - if ( Link[j].hasFlapGate ) - { - a = weir_getOpenArea(j, y); - if ( a > 0.0 ) - { - v = q / a; - hloss = (4.0 / GRAVITY) * v * v * exp(-1.15 * v / sqrt(y) ); - head -= hloss; - head = MAX(head, 0.0); - q = cOrif * sqrt(head); - } - } - if ( head > 0.0 ) Link[j].dqdh = q / (2.0 * head); - else Link[j].dqdh = 0.0; - return q; -} - -//============================================================================= - -double weir_getOpenArea(int j, double y) -// -// Input: j = link index -// y = depth of water above weir crest (ft) -// Output: returns area between weir crest and y (ft2) -// Purpose: finds flow area through a weir. -// -{ - double z, zy; - - // --- find offset of weir crest due to control setting - z = (1.0 - Link[j].setting) * Link[j].xsect.yFull; - - // --- ht. of crest + ht of water above crest - zy = z + y; - zy = MIN(zy, Link[j].xsect.yFull); - - // --- return difference between area of offset + water depth - // and area of just the offset - return xsect_getAofY(&Link[j].xsect, zy) - - xsect_getAofY(&Link[j].xsect, z); -} - -//============================================================================= - -double weir_getdqdh(int k, double dir, double h, double q1, double q2) -{ - double q1h; - double q2h; - - if ( fabs(h) < FUDGE ) return 0.0; - q1h = fabs(q1/h); - q2h = fabs(q2/h); - - switch (Weir[k].type) - { - case TRANSVERSE_WEIR: return 1.5 * q1h; - - case SIDEFLOW_WEIR: - // --- weir behaves as a transverse weir under reverse flow - if ( dir < 0.0 ) return 1.5 * q1h; - else return 1.67 * q1h; - - case VNOTCH_WEIR: - if ( q2h == 0.0 ) return 2.5 * q1h; // Fully open - else return 1.5 * q1h + 2.5 * q2h; // Partly open - - case TRAPEZOIDAL_WEIR: return 1.5 * q1h + 2.5 * q2h; - } - return 0.0; -} - - -//============================================================================= -// O U T L E T D E V I C E M E T H O D S -//============================================================================= - -int outlet_readParams(int j, int k, char* tok[], int ntoks) -// -// Input: j = link index -// k = outlet index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads outlet parameters from a tokenized line of input. -// -{ - int i, m, n; - int n1, n2; - double x[6]; - char* id; - char* s; - - // --- check for valid ID and end node IDs - if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); - id = project_findID(LINK, tok[0]); - if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); - n1 = project_findObject(NODE, tok[1]); - if ( n1 < 0 ) return error_setInpError(ERR_NAME, tok[1]); - n2 = project_findObject(NODE, tok[2]); - if ( n2 < 0 ) return error_setInpError(ERR_NAME, tok[2]); - - // --- get height above invert - if ( LinkOffsets == ELEV_OFFSET && *tok[3] == '*' ) x[0] = MISSING; - else - { - if ( ! getDouble(tok[3], &x[0]) ) - return error_setInpError(ERR_NUMBER, tok[3]); - if ( LinkOffsets == DEPTH_OFFSET && x[0] < 0.0 ) x[0] = 0.0; - } - - // --- see if outlet flow relation is tabular or functional - m = findmatch(tok[4], RelationWords); - if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[4]); - x[1] = 0.0; - x[2] = 0.0; - x[3] = -1.0; - x[4] = 0.0; - - // --- see if rating curve is head or depth based - x[5] = NODE_DEPTH; //default is depth-based - s = strtok(tok[4], "/"); //parse token for - s = strtok(NULL, "/"); // qualifier term - if ( strcomp(s, w_HEAD) ) x[5] = NODE_HEAD; //check if its "HEAD" - - // --- get params. for functional outlet device - if ( m == FUNCTIONAL ) - { - if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); - if ( ! getDouble(tok[5], &x[1]) ) - return error_setInpError(ERR_NUMBER, tok[5]); - if ( ! getDouble(tok[6], &x[2]) ) - return error_setInpError(ERR_NUMBER, tok[6]); - n = 7; - } - - // --- get name of outlet rating curve - else - { - i = project_findObject(CURVE, tok[5]); - if ( i < 0 ) return error_setInpError(ERR_NAME, tok[5]); - x[3] = i; - n = 6; - } - - // --- check if flap gate specified - if ( ntoks > n) - { - i = findmatch(tok[n], NoYesWords); - if ( i < 0 ) return error_setInpError(ERR_KEYWORD, tok[n]); - x[4] = i; - } - - // --- add parameters to outlet object - Link[j].ID = id; - link_setParams(j, OUTLET, n1, n2, k, x); - return 0; -} - -//============================================================================= - -double outlet_getInflow(int j) -// -// Input: j = link index -// Output: outlet flow rate (cfs) -// Purpose: finds the flow through an outlet. -// -{ - int k, n1, n2; - double head, hcrest, h1, h2, y1, dir; - - // --- get indexes of end nodes - n1 = Link[j].node1; - n2 = Link[j].node2; - k = Link[j].subIndex; - - // --- find heads at upstream & downstream nodes - if ( RouteModel == DW ) - { - h1 = Node[n1].newDepth + Node[n1].invertElev; - h2 = Node[n2].newDepth + Node[n2].invertElev; - } - else - { - h1 = Node[n1].newDepth + Node[n1].invertElev; - h2 = Node[n1].invertElev; - } - dir = (h1 >= h2) ? +1.0 : -1.0; - - // --- exchange h1 and h2 for reverse flow - y1 = Node[n1].newDepth; - if ( dir < 0.0 ) - { - y1 = h1; - h1 = h2; - h2 = y1; - y1 = Node[n2].newDepth; - } - - // --- for a NODE_DEPTH rating curve the effective head across the - // outlet is the depth above the crest elev. while for a NODE_HEAD - // curve it is the difference between upstream & downstream heads - hcrest = Node[n1].invertElev + Link[j].offset1; - if ( Outlet[k].curveType == NODE_HEAD && RouteModel == DW ) - head = h1 - MAX(h2, hcrest); - else head = h1 - hcrest; - - // --- no flow if either no effective head difference, - // no upstream water available, or closed flap gate - if ( head <= FUDGE || y1 <= FUDGE || - link_setFlapGate(j, n1, n2, dir) ) - { - Link[j].newDepth = 0.0; - Link[j].flowClass = DRY; - return 0.0; - } - - // --- otherwise use rating curve to compute flow - Link[j].newDepth = head; - Link[j].flowClass = SUBCRITICAL; - return dir * Link[j].setting * outlet_getFlow(k, head); -} - -//============================================================================= - -double outlet_getFlow(int k, double head) -// -// Input: k = outlet index -// head = head across outlet (ft) -// Output: returns outlet flow rate (cfs) -// Purpose: computes flow rate through an outlet given head. -// -{ - int m; - double h; - - // --- convert head to original units - h = head * UCF(LENGTH); - - // --- look-up flow in rating curve table if provided - m = Outlet[k].qCurve; - if ( m >= 0 ) return table_lookup(&Curve[m], h) / UCF(FLOW); - - // --- otherwise use function to find flow - else return Outlet[k].qCoeff * pow(h, Outlet[k].qExpon) / UCF(FLOW); -} diff --git a/src/macros.h b/src/macros.h deleted file mode 100644 index c15749e4b..000000000 --- a/src/macros.h +++ /dev/null @@ -1,45 +0,0 @@ -//----------------------------------------------------------------------------- -// macros.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -//----------------------------------------------------------------------------- - -#ifndef MACROS_H -#define MACROS_H - - -//-------------------------------------------------- -// Macro to test for successful allocation of memory -//-------------------------------------------------- -#define MEMCHECK(x) (((x) == NULL) ? 101 : 0 ) - -//-------------------------------------------------- -// Macro to free a non-null pointer -//-------------------------------------------------- -#define FREE(x) { if (x) { free(x); x = NULL; } } - -//--------------------------------------------------- -// Conversion macros to be used in place of functions -//--------------------------------------------------- -#define ABS(x) (((x)<0) ? -(x) : (x)) /* absolute value of x */ -#define MIN(x,y) (((x)<=(y)) ? (x) : (y)) /* minimum of x and y */ -#define MAX(x,y) (((x)>=(y)) ? (x) : (y)) /* maximum of x and y */ -#define MOD(x,y) ((x)%(y)) /* x modulus y */ -#define LOG10(x) ((x) > 0.0 ? log10((x)) : (x)) /* safe log10 of x */ -#define SQR(x) ((x)*(x)) /* x-squared */ -#define SGN(x) (((x)<0) ? (-1) : (1)) /* sign of x */ -#define SIGN(x,y) ((y) >= 0.0 ? fabs(x) : -fabs(x)) -#define UCHAR(x) (((x) >= 'a' && (x) <= 'z') ? ((x)&~32) : (x)) - /* uppercase char of x */ -#define ARRAY_LENGTH(x) (sizeof(x)/sizeof(x[0])) /* length of array x */ - -//------------------------------------------------- -// Macro to evaluate function x with error checking -//------------------------------------------------- -#define CALL(x) (ErrorCode = ((ErrorCode>0) ? (ErrorCode) : (x))) - - -#endif //MACROS_H diff --git a/src/main.c b/src/main.c deleted file mode 100644 index 0fe1b40b7..000000000 --- a/src/main.c +++ /dev/null @@ -1,104 +0,0 @@ -//----------------------------------------------------------------------------- -// main.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 03/24/2021 -// Author: L. Rossman - -// Main stub for the command line version of EPA SWMM 5.2 -// to be run with swmm5.dll. - -#include -#include -#include -#include "swmm5.h" - -int main(int argc, char *argv[]) -// -// Input: argc = number of command line arguments -// argv = array of command line arguments -// Output: returns error status -// Purpose: runs the command line version of EPA SWMM 5.2. -// -// Command line is: runswmm f1 f2 f3 -// where f1 = name of input file, f2 = name of report file, and -// 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"); - } - else if (argc == 2) - { - // --- extract first argument - arg1 = argv[1]; - - if (strcmp(arg1, "--help") == 0 || strcmp(arg1, "-h") == 0) - { - // Help - printf("\n\nSTORMWATER MANAGEMENT MODEL (SWMM) HELP\n\n"); - printf("COMMANDS:\n"); - printf("\t--help (-h) SWMM Help\n"); - printf("\t--version (-v) Build Version\n"); - printf("\nRUNNING A SIMULATION:\n"); - printf("\t runswmm