Files
EPANET/src/qualreact.c
Sam Hatchett 4d8d82ddc2 v2.2
* removing reference to strncpy

* Fixing memory problems with test_toolkit

Fixes memory leaks and some minor refactoring.

* Update test_toolkit.hpp

removing crtdbg.h from header

* Update CMakeLists.txt

Restoring test_net_builder to test_toolkit.exe

* Cleaning up include statements adding crtdbg.h

* Fixing index error in test

* Add more analysis options to the API (issue #425)

* Fixed epanet2_enums.h

* Eliminates use of temporary linked lists to process Patterns & Curves (issue #449)

* Update input2.c

* Bug fix for 2Comp and LIFO tank mixing models (issue #448)

* Triggering build to update benchmarks

* Added new reg tests

Updating reference build id

* Initial commit list

generic linked list

* Update test_list.cpp

Tests are passing

* Update list.h

Adding documentation

* Fix typo

* Fixing bug in head_list

* Fixing indentation

* Fixed memory leak

Fixed memory leak in test_head_list

* Clean up and inline comments

* Updating file headers

* Update list.c

Updating in line comments.

* Update test_list.cpp

* Fixing indent

Spaces not tabs

* Update list.c

Fixing indent

* Update test_list.cpp

Updating file header to reflect proper attribution

* Expanding test

Added test where data is a struct

* Fixing indent

* Work in progress

* Reorganized to contain list abstraction

* Update list.c

* Refactoring head_list and tail_list

Simplifying head and tail list. Adding delete_node() to list API.

* Update test_list.cpp

* Update test_list.cpp

Fixing bug on gcc

* Fixing bug

* Fixing bug on gcc

* Update CMakeLists.txt

Adding test_list to ctest

* Fixes memory leak in EN_addnode() (#455)


* Fixing memory leak in EN_addnode()

* Separating test_net_builder from test_toolkit

Making test_net_builder a standalone test

* Removing BOOST_TEST_MAIN

* Work in progress

* Updating unit tests

* Fixing compilation bug on gcc

* Work in progress

compiles with warnings, definitely not working

* Update demand.h

* Work in progress

Implementing demand_list

* Work in progress

Creating function for validateing element ID strings

* Work in progress

Refactoring cstr_copy and adding test

* Update cstr_helper.c

fixing indentation

* Update cstr_helper.c

Fixing indentation

* Update test_cstrhelper.cpp

Fixed mem leak

* Adding element id validity checks

* Adding element id validity check

Adding checks for element set id functions

* Fixing build warnings on gcc

* Update errror code from 250 to 252

* Work in progress

Implementing generic demand pattern lists. Compiles but does not run.

* Update demand.c

Work in progress

* Return object index from EN_addnode and EN_addlink (issue #432)

Adds an output argument to EN_addnode and EN_addlink that returns the index of the newly added object.
Also refactors the validity check on object ID names.

* Fixed compilation errors

* Update test_node.cpp

* Create test_demand_data.cpp

* test demand data passing

* Work in progress

Fixing problems when demand lists are null

* Passing open and close test

* get/set demand name are passing

* Updated criteria for valid object ID name

* Work in progress

* Work in progress

Working on demand lists

* Work in progress

Fixing memory leaks
Unit tests passing

* Cleaning up build on gcc

* Cleaning up gcc build

* Fixing bug

* Working on gcc bug

Tests are passing on Appveyor

* Update inpfile.c

Trying to isolate bug

* GCC Bug

* Refactored xstrcpy function

* Update inpfile.c

Testing linux build

* Update epanet.c

Trying to isolate bug

* updating get demand name and write demands

Everything passing locally

* Update test_project.cpp

Isolating bug on gcc

* Isolating bug

Not writing demand section of input file should eliminate it

* Update demand.c

Fixing bug in get_category_name when category_name is NULL

* Restoring write_demands section in saveinpfile

* Update test_demand_data.cpp

Adding index to addnode calls. Fixing indent

* Update demand.c

* Reverted handling of default pattern

When creating demands, no pattern is marked with a zero. Then when data is adjusted it gets updated to default.

* Update epanet.c

Updating EN_getnodevalue() and EN_setnodevalue() to process the primary demand located at the head of the demand list

* Update demand.c

* Work in progress

code cleanup, addressed issue raised in review, and implemented EN_adddemand()

* Adding key and search to list

* Adding remove node method to generic list

* Adding remove demand method to toolkit

* Fix bug and test remove demand

* Fix problems with setting tank parameters (issue #464 )

* Fixed NULL pointer error, if no label is provided after the rule keyword.

* Create Makefile2.bat

Co-Authored-By: Demetrios G. Eliades <eldemet@users.noreply.github.com>
Co-Authored-By: Elad Salomons <selad@optiwater.com>

* Create LICENSE

* Fixed NULL pointer error, if no label is provided after the rule keyword.
Add NULL guard in freerules function. Use strncat and strncpy to ensure
the buffer lengths are adhered to.

* For "conditional" do delete a node connected to a link

For "conditional" deletion the node is deleted only if all of its links have been explicitly deleted beforehand #473

Co-Authored-By: Lew Rossman <lrossman@outlook.com>

* Create CODE_OF_CONDUCT.md

* Refactors the API's demand editing functions

* Update test_demand.cpp

* Update CODE_OF_CONDUCT.md

* Update rules.c

Fix broken win build script

* Updates to doc files

* Documentation edits

* Update Makefile.bat

Updates on the Microsoft SDK 7.1 compilation script to generate runepanet.exe and to use the \include\epanet2.def

* Update Makefile2.bat

Modified epanet2.exe to runepanet.exe, for consistency.

* Delete epanet2.def

Deleted the redundant `epanet2.def` file in the WindSDK folder

* Minor format change to status report

* Removing status reports from CI testing

* rm WinSDK folder and update Makefiles

Co-Authored-By: Demetrios G. Eliades <eldemet@users.noreply.github.com>

* Restored CI testing of status reports

* Removes _DEBUG directives from all source files

This commit removes the #ifdef _DEBUG statements at the top of all source code files per issue #482. It also updates the doc files to stress that the speedup observed for hydraulic analysis with the MMD node re-ordering method only applies to single period runs.

* Fix refactor of types.h

* updates authors

* updates AUTHORS and generator script

* Update run\CMakeLists.txt

* add help file win_build.md

Co-Authored-By: Elad Salomons <selad@optiwater.com>

* move win_build.md to root dir and renaiming to BUILDING.md

* Move BuildAndTest.md to the tools directory

* Update BUILDING.md

* Update BUILDING.md

* Update BUILDING.md

* Fixes problem with findpattern() function (issue #498)

* Change default properties for new pipe created with EN_addlink (issue #500)

* Numerous updates to project documentation

* Adds tank overflow feature

* Updating docs for tank overflow feature

* Updating VB include files

* Update input3.c

* Identifies overflowing tank in Status Report

* Update Makefile.bat

* Update Makefile2.bat

#508

* rethinking the python wrapper (#511)

* renames certain function parameter declarations and removes double pointer call from the deleteproject function

* deprecates conditonal compilation, removes python-specific headers and function renaming

* fixes tests and docs

* fixes test

* PDA fixes

* Minor update to force new CI test

* Another minor change to force another CI test

* Fixes Overflow and PDA tests not being run

* Fix EN_getElseaction and EN_setelseaction

Co-Authored-By: Andreas Ashikkis <andreasashikkis@users.noreply.github.com>

* Add -MT switch for CMake Windows build

* Updates to the docs

* Update BUILDING.md

* Build script updates

* Fixes EN_setlinkvalue bug

* fix in EN_deleteLink

when pipes are deleted via deletelink it also deletes comment of last link

Co-Authored-By: Pavlos Pavlou <pavlou.v.pavlos@ucy.ac.cy>

* rm set to null in functions EN_deletenode, EN_deletelink

* trial actions config

* Update ccpp.yml

* welcome to the Actions beta

* fixes mkstemp file handle-leaking behavior (#529)

* reverts posix include (#533)

... because it is not needed

* Fixes bugs in pump and demand head loss gradients

* Removed dependence on unistd.h in project.c

Travis CI failed because system could not find unistd.h.

* getTmpName() and xstrcpy() made safer

* Fixed use of strncpy in xstrcpy()

* Refactor of hydcoeffs.c

Simplifies and unifies how limit on head gradient at low flow is handled.

* Update ReleaseNotes2_2.md

* Return error if node/link name is too long (#535)

* co-authored with @ehsan-shafiee

* removes errant slashes

* Throws correct error for ID name too long

* Revert "Throws correct error for ID name too long"

This reverts commit 57b4873f5882cb9fd983f7e1e5a703b9e442cd74.

* fixes #534 by bubbling error codes up from add node/link internal functions

* fixes tests on Mac at least

* fixes improper success code

* Error 252 (not 250) returned for ID name too long.

From errors.dat: DAT(252,"invalid ID name")

* Fixes problems with EN_addnode() (#543)

See issue #542 . Also modifies unit test test_node to check that fixup works.

* Adds EN_getresultindex function to the API

See issue #546 . Also fixes a small bug in project.c.

* Adds link vertex get/set functions to the API

* Fixes to EN_addlink and EN_deletelink

* Updates the docs

* Bug fix for EN_setcurve

Adjusts params of any pump that uses the curve whose data is modified by EN_setcurve or EN_setcurvevalue (issue #550 ).

* Bug fix for EN_getrule

Fixes possible seg fault condition in EN_getrule. Also defines EN_MISSING as an API constant since it can be assigned internally to several variables that are retrievable by the API.

* Updating the docs

* Adds error check to EN_setheadcurveindex

See issue #556 .

* Update epanet2.pas

* Incorrect characterd

There was a character ’ instead of ' which created an error when compiling LaTeX.

* fixes a crashing issue in freedata (#559)

The freedata function used cached values for sizes of certain arrays found in the parser struct. However, now that the network is mutable, those values can become invalid. Relying instead on the actual array lengths prevents freeing unallocated memory, or ignoring cleanup on newly created elements.

* Bug fix for valvecheck function

See issue #561

* Restored prior update to project.c that got overwritten

* Fixed editing errors made to project.c

* PDF Guide

PDF users' guide for EPANET, and some minor corrections to readme.md to fix some formatting issues.

* HTML Users Guide

* Fixes a "copy over" bug in input3.c

The copying of one input line token over another was causing a compilation error under Clang. With v2.2 this copying is no longer needed so the line of code in question was simply deleted.

This commit also deletes the HTML and Latex output generated by running Doxygen that got added from the previous update to dev since they don't really belong in a source code repo.

* Correction made to doc files

The output-format.dox file was deprecated and not included in the doxyfile so it was deleted. The description of the format of of the Energy Usage section of the binary output in toolkit-files.dox was corrected.

* Update ReleaseNotes2_2.md

I added the v2.2 contributing authors to the notes. I checked PR's from 2017 and beyond and these were the only names I could find. Please append any one I might have missed.

* Fixes problem with re-opening const. HP pumps

See latest comments in issue #528. Also, the setlinkflow() function was deleted as it was never called anywhere.

* Update README.md (#539)

* Update README.md

* Update README.md

Some section titles were re-named to conform to GitHub guidelines and the OWA info was moved to a CREDITS section.

* Update README.md

Added link to the Community Forum page.

* Replaced OWA copyright with "(see AUTHORS)".

* Update AUTHORS

Copied format used by the OWA-SWMM project.

* Update README.md

The Disclaimer section was edited to reflect that there actually is a "collaborative" connection between USEPA and OWA.

* updates CI badges

* cleanup of readme links and unused files

* possessive vs contraction

* adding contributor to notes
2019-12-10 10:19:36 -05:00

744 lines
21 KiB
C

/*
******************************************************************************
Project: OWA EPANET
Version: 2.2
Module: qualreact.c
Description: computes water quality reactions within pipes and tanks
Authors: see AUTHORS
Copyright: see AUTHORS
License: see LICENSE
Last Updated: 05/15/2019
******************************************************************************
*/
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include "types.h"
// Exported functions
char setreactflag(Project *);
double getucf(double);
void ratecoeffs(Project *);
void reactpipes(Project *, long);
void reacttanks(Project *, long);
double mixtank(Project *, int, double, double ,double);
// Imported functions
extern void addseg(Project *, int, double, double);
extern void reversesegs(Project *, int);
// Local functions
static double piperate(Project *, int);
static double pipereact(Project *, int, double, double, long);
static double tankreact(Project *, double, double, double, long);
static double bulkrate(Project *, double, double, double);
static double wallrate(Project *, double, double, double, double);
static void tankmix1(Project *, int, double, double, double);
static void tankmix2(Project *, int, double, double, double);
static void tankmix3(Project *, int, double, double, double);
static void tankmix4(Project *, int, double, double, double);
char setreactflag(Project *pr)
/*
**-----------------------------------------------------------
** Input: none
** Output: returns 1 for reactive WQ constituent, 0 otherwise
** Purpose: checks if reactive chemical being simulated
**-----------------------------------------------------------
*/
{
Network *net = &pr->network;
int i;
if (pr->quality.Qualflag == TRACE) return 0;
else if (pr->quality.Qualflag == AGE) return 1;
else
{
for (i = 1; i <= net->Nlinks; i++)
{
if (net->Link[i].Type <= PIPE)
{
if (net->Link[i].Kb != 0.0 || net->Link[i].Kw != 0.0) return 1;
}
}
for (i = 1; i <= net->Ntanks; i++)
{
if (net->Tank[i].Kb != 0.0) return 1;
}
}
return 0;
}
double getucf(double order)
/*
**--------------------------------------------------------------
** Input: order = bulk reaction order
** Output: returns a unit conversion factor
** Purpose: converts bulk reaction rates from per Liter to
** per FT3 basis
**--------------------------------------------------------------
*/
{
if (order < 0.0) order = 0.0;
if (order == 1.0) return (1.0);
else return (1. / pow(LperFT3, (order - 1.0)));
}
void ratecoeffs(Project *pr)
/*
**--------------------------------------------------------------
** Input: none
** Output: none
** Purpose: determines wall reaction coeff. for each pipe
**--------------------------------------------------------------
*/
{
Network *net = &pr->network;
Quality *qual = &pr->quality;
int k;
double kw;
for (k = 1; k <= net->Nlinks; k++)
{
kw = net->Link[k].Kw;
if (kw != 0.0) kw = piperate(pr, k);
net->Link[k].Rc = kw;
qual->PipeRateCoeff[k] = 0.0;
}
}
void reactpipes(Project *pr, long dt)
/*
**--------------------------------------------------------------
** Input: dt = time step
** Output: none
** Purpose: reacts water within each pipe over a time step.
**--------------------------------------------------------------
*/
{
Network *net = &pr->network;
Quality *qual = &pr->quality;
int k;
Pseg seg;
double cseg, rsum, vsum;
// Examine each link in network
for (k = 1; k <= net->Nlinks; k++)
{
// Skip non-pipe links (pumps & valves)
if (net->Link[k].Type != PIPE) continue;
rsum = 0.0;
vsum = 0.0;
// Examine each segment of the pipe
seg = qual->FirstSeg[k];
while (seg != NULL)
{
// React segment over time dt
cseg = seg->c;
seg->c = pipereact(pr, k, seg->c, seg->v, dt);
// Update reaction component of mass balance
qual->MassBalance.reacted += (cseg - seg->c) * seg->v;
// Accumulate volume-weighted reaction rate
if (qual->Qualflag == CHEM)
{
rsum += fabs(seg->c - cseg) * seg->v;
vsum += seg->v;
}
seg = seg->prev;
}
// Normalize volume-weighted reaction rate
if (vsum > 0.0) qual->PipeRateCoeff[k] = rsum / vsum / dt * SECperDAY;
else qual->PipeRateCoeff[k] = 0.0;
}
}
void reacttanks(Project *pr, long dt)
/*
**--------------------------------------------------------------
** Input: dt = time step
** Output: none
** Purpose: reacts water within each tank over a time step.
**--------------------------------------------------------------
*/
{
Network *net = &pr->network;
Quality *qual = &pr->quality;
int i, k;
double c;
Pseg seg;
Stank *tank;
// Examine each tank in network
for (i = 1; i <= net->Ntanks; i++)
{
// Skip reservoirs
tank = &net->Tank[i];
if (tank->A == 0.0) continue;
// k is segment chain belonging to tank i
k = net->Nlinks + i;
// React each volume segment in the chain
seg = qual->FirstSeg[k];
while (seg != NULL)
{
c = seg->c;
seg->c = tankreact(pr, seg->c, seg->v, tank->Kb, dt);
qual->MassBalance.reacted += (c - seg->c) * seg->v;
seg = seg->prev;
}
}
}
double piperate(Project *pr, int k)
/*
**--------------------------------------------------------------
** Input: k = link index
** Output: returns reaction rate coeff. for 1st-order wall
** reactions or mass transfer rate coeff. for 0-order
** reactions
** Purpose: finds wall reaction rate coeffs.
**--------------------------------------------------------------
*/
{
Network *net = &pr->network;
Hydraul *hyd = &pr->hydraul;
Quality *qual = &pr->quality;
double a, d, u, q, kf, kw, y, Re, Sh;
d = net->Link[k].Diam; // Pipe diameter, ft
// Ignore mass transfer if Schmidt No. is 0
if (qual->Sc == 0.0)
{
if (qual->WallOrder == 0.0) return BIG;
else return (net->Link[k].Kw * (4.0 / d) / pr->Ucf[ELEV]);
}
// Compute Reynolds No.
// Flow rate made consistent with how its saved to hydraulics file
q = (hyd->LinkStatus[k] <= CLOSED) ? 0.0 : hyd->LinkFlow[k];
a = PI * d * d / 4.0; // pipe area
u = fabs(q) / a; // flow velocity
Re = u * d / hyd->Viscos; // Reynolds number
// Compute Sherwood No. for stagnant flow
// (mass transfer coeff. = Diffus./radius)
if (Re < 1.0) Sh = 2.0;
// Compute Sherwood No. for turbulent flow using the Notter-Sleicher formula.
else if (Re >= 2300.0) Sh = 0.0149 * pow(Re, 0.88) * pow(qual->Sc, 0.333);
// Compute Sherwood No. for laminar flow using Graetz solution formula.
else
{
y = d / net->Link[k].Len * Re * qual->Sc;
Sh = 3.65 + 0.0668 * y / (1.0 + 0.04 * pow(y, 0.667));
}
// Compute mass transfer coeff. (in ft/sec)
kf = Sh * qual->Diffus / d;
// For zero-order reaction, return mass transfer coeff.
if (qual->WallOrder == 0.0) return kf;
// For first-order reaction, return apparent wall coeff.
kw = net->Link[k].Kw / pr->Ucf[ELEV]; // Wall coeff, ft/sec
kw = (4.0 / d) * kw * kf / (kf + fabs(kw)); // Wall coeff, 1/sec
return kw;
}
double pipereact(Project *pr, int k, double c, double v, long dt)
/*
**------------------------------------------------------------
** Input: k = link index
** c = current quality in segment
** v = segment volume
** dt = time step
** Output: returns new WQ value
** Purpose: computes new quality in a pipe segment after
** reaction occurs
**------------------------------------------------------------
*/
{
Network *net = &pr->network;
Quality *qual = &pr->quality;
double cnew, dc, dcbulk, dcwall, rbulk, rwall;
// For water age (hrs), update concentration by timestep
if (qual->Qualflag == AGE)
{
dc = (double)dt / 3600.0;
cnew = c + dc;
cnew = MAX(0.0, cnew);
return cnew;
}
// Otherwise find bulk & wall reaction rates
rbulk = bulkrate(pr, c, net->Link[k].Kb, qual->BulkOrder) * qual->Bucf;
rwall = wallrate(pr, c, net->Link[k].Diam, net->Link[k].Kw, net->Link[k].Rc);
// Find change in concentration over timestep
dcbulk = rbulk * (double)dt;
dcwall = rwall * (double)dt;
// Update cumulative mass reacted
if (pr->times.Htime >= pr->times.Rstart)
{
qual->Wbulk += fabs(dcbulk) * v;
qual->Wwall += fabs(dcwall) * v;
}
// Update concentration
dc = dcbulk + dcwall;
cnew = c + dc;
cnew = MAX(0.0, cnew);
return cnew;
}
double tankreact(Project *pr, double c, double v, double kb, long dt)
/*
**-------------------------------------------------------
** Input: c = current quality in tank
** v = tank volume
** kb = reaction coeff.
** dt = time step
** Output: returns new WQ value
** Purpose: computes new quality in a tank after
** reaction occurs
**-------------------------------------------------------
*/
{
Quality *qual = &pr->quality;
double cnew, dc, rbulk;
// For water age, update concentration by timestep
if (qual->Qualflag == AGE)
{
dc = (double)dt / 3600.0;
}
// For chemical analysis apply bulk reaction rate
else
{
// Find bulk reaction rate
rbulk = bulkrate(pr, c, kb, qual->TankOrder) * qual->Tucf;
// Find concentration change & update quality
dc = rbulk * (double)dt;
if (pr->times.Htime >= pr->times.Rstart)
{
qual->Wtank += fabs(dc) * v;
}
}
cnew = c + dc;
cnew = MAX(0.0, cnew);
return cnew;
}
double bulkrate(Project *pr, double c, double kb, double order)
/*
**-----------------------------------------------------------
** Input: c = current WQ concentration
** kb = bulk reaction coeff.
** order = bulk reaction order
** Output: returns bulk reaction rate
** Purpose: computes bulk reaction rate (mass/volume/time)
**-----------------------------------------------------------
*/
{
Quality *qual = &pr->quality;
double c1;
// Find bulk reaction potential taking into account
// limiting potential & reaction order.
// Zero-order kinetics:
if (order == 0.0) c = 1.0;
// Michaelis-Menton kinetics:
else if (order < 0.0)
{
c1 = qual->Climit + SGN(kb) * c;
if (fabs(c1) < TINY) c1 = SGN(c1) * TINY;
c = c / c1;
}
// N-th order kinetics:
else
{
// Account for limiting potential
if (qual->Climit == 0.0) c1 = c;
else c1 = MAX(0.0, SGN(kb) * (qual->Climit - c));
// Compute concentration potential
if (order == 1.0) c = c1;
else if (order == 2.0) c = c1 * c;
else c = c1 * pow(MAX(0.0, c), order - 1.0);
}
// Reaction rate = bulk coeff. * potential
if (c < 0) c = 0;
return kb * c;
}
double wallrate(Project *pr, double c, double d, double kw, double kf)
/*
**------------------------------------------------------------
** Input: c = current WQ concentration
** d = pipe diameter
** kw = intrinsic wall reaction coeff.
** kf = mass transfer coeff. for 0-order reaction
** (ft/sec) or apparent wall reaction coeff.
** for 1-st order reaction (1/sec)
** Output: returns wall reaction rate in mass/ft3/sec
** Purpose: computes wall reaction rate
**------------------------------------------------------------
*/
{
Quality *qual = &pr->quality;
if (kw == 0.0 || d == 0.0) return (0.0);
if (qual->WallOrder == 0.0) // 0-order reaction */
{
kf = SGN(kw) * c * kf; //* Mass transfer rate (mass/ft2/sec)
kw = kw * SQR(pr->Ucf[ELEV]); // Reaction rate (mass/ft2/sec)
if (fabs(kf) < fabs(kw)) kw = kf; // Reaction mass transfer limited
return (kw * 4.0 / d); // Reaction rate (mass/ft3/sec)
}
else return (c * kf); // 1st-order reaction
}
double mixtank(Project *pr, int n, double volin, double massin, double volout)
/*
**------------------------------------------------------------
** Input: n = node index
** volin = inflow volume to tank over time step
** massin = mass inflow to tank over time step
** volout = outflow volume from tank over time step
** Output: returns new quality for tank
** Purpose: mixes inflow with tank's contents to update its quality.
**------------------------------------------------------------
*/
{
Network *net = &pr->network;
int i;
double vnet;
i = n - net->Njuncs;
vnet = volin - volout;
switch (net->Tank[i].MixModel)
{
case MIX1: tankmix1(pr, i, volin, massin, vnet); break;
case MIX2: tankmix2(pr, i, volin, massin, vnet); break;
case FIFO: tankmix3(pr, i, volin, massin, vnet); break;
case LIFO: tankmix4(pr, i, volin, massin, vnet); break;
}
return net->Tank[i].C;
}
void tankmix1(Project *pr, int i, double vin, double win, double vnet)
/*
**---------------------------------------------
** Input: i = tank index
** vin = inflow volume
** win = mass inflow
** vnet = inflow - outflow
** Output: none
** Purpose: updates quality in a complete mix tank model
**---------------------------------------------
*/
{
Network *net = &pr->network;
Quality *qual = &pr->quality;
int k;
double vnew;
Pseg seg;
Stank *tank = &net->Tank[i];
k = net->Nlinks + i;
seg = qual->FirstSeg[k];
if (seg)
{
vnew = seg->v + vin;
if (vnew > 0.0) seg->c = (seg->c * seg->v + win) / vnew;
seg->v += vnet;
seg->v = MAX(0.0, seg->v);
tank->C = seg->c;
}
}
void tankmix2(Project *pr, int i, double vin, double win, double vnet)
/*
**------------------------------------------------
** Input: i = tank index
** vin = inflow volume
** win = mass inflow
** vnet = inflow - outflow
** Output: none
** Purpose: updates quality in a 2-compartment tank model
**------------------------------------------------
*/
{
Network *net = &pr->network;
Quality *qual = &pr->quality;
int k;
double vt, // Transferred volume
vmz; // Full mixing zone volume
Pseg mixzone, // Mixing zone segment
stagzone; // Stagnant zone segment
Stank *tank = &pr->network.Tank[i];
// Identify segments for each compartment
k = net->Nlinks + i;
mixzone = qual->LastSeg[k];
stagzone = qual->FirstSeg[k];
if (mixzone == NULL || stagzone == NULL) return;
// Full mixing zone volume
vmz = tank->V1max;
// Tank is filling
vt = 0.0;
if (vnet > 0.0)
{
vt = MAX(0.0, (mixzone->v + vnet - vmz));
if (vin > 0.0)
{
mixzone->c = ((mixzone->c) * (mixzone->v) + win) /
(mixzone->v + vin);
}
if (vt > 0.0)
{
stagzone->c = ((stagzone->c) * (stagzone->v) +
(mixzone->c) * vt) / (stagzone->v + vt);
}
}
// Tank is emptying
else if (vnet < 0.0)
{
if (stagzone->v > 0.0) vt = MIN(stagzone->v, (-vnet));
if (vin + vt > 0.0)
{
mixzone->c = ((mixzone->c) * (mixzone->v) + win +
(stagzone->c) * vt) / (mixzone->v + vin + vt);
}
}
// Update segment volumes
if (vt > 0.0)
{
mixzone->v = vmz;
if (vnet > 0.0) stagzone->v += vt;
else stagzone->v = MAX(0.0, ((stagzone->v) - vt));
}
else
{
mixzone->v += vnet;
mixzone->v = MIN(mixzone->v, vmz);
mixzone->v = MAX(0.0, mixzone->v);
stagzone->v = 0.0;
}
// Use quality of mixing zone to represent quality of
// tank since this is where outflow begins to flow from
tank->C = mixzone->c;
}
void tankmix3(Project *pr, int i, double vin, double win, double vnet)
/*
**----------------------------------------------------------
** Input: i = tank index
** vin = inflow volume
** win = mass inflow
** vnet = inflow - outflow
** Output: none
** Purpose: Updates quality in a First-In-First-Out (FIFO) tank model.
**----------------------------------------------------------
*/
{
Network *net = &pr->network;
Quality *qual = &pr->quality;
int k;
double vout, vseg;
double cin, vsum, wsum;
Pseg seg;
Stank *tank = &pr->network.Tank[i];
k = net->Nlinks + i;
if (qual->LastSeg[k] == NULL || qual->FirstSeg[k] == NULL) return;
// Add new last segment for flow entering the tank
if (vin > 0.0)
{
// ... increase segment volume if inflow has same quality as segment
cin = win / vin;
seg = qual->LastSeg[k];
if (fabs(seg->c - cin) < qual->Ctol) seg->v += vin;
// ... otherwise add a new last segment to the tank
else addseg(pr, k, vin, cin);
}
// Withdraw flow from first segment
vsum = 0.0;
wsum = 0.0;
vout = vin - vnet;
while (vout > 0.0)
{
seg = qual->FirstSeg[k];
if (seg == NULL) break;
vseg = seg->v; // Flow volume from leading seg
vseg = MIN(vseg, vout);
if (seg == qual->LastSeg[k]) vseg = vout;
vsum += vseg;
wsum += (seg->c) * vseg;
vout -= vseg; // Remaining flow volume
if (vout >= 0.0 && vseg >= seg->v) // Seg used up
{
if (seg->prev)
{
qual->FirstSeg[k] = seg->prev;
seg->prev = qual->FreeSeg;
qual->FreeSeg = seg;
}
}
else seg->v -= vseg; // Remaining volume in segment
}
// Use quality withdrawn from 1st segment
// to represent overall quality of tank
if (vsum > 0.0) tank->C = wsum / vsum;
else if (qual->FirstSeg[k] == NULL) tank->C = 0.0;
else tank->C = qual->FirstSeg[k]->c;
}
void tankmix4(Project *pr, int i, double vin, double win, double vnet)
/*
**----------------------------------------------------------
** Input: i = tank index
** vin = inflow volume
** win = mass inflow
** vnet = inflow - outflow
** Output: none
** Purpose: Updates quality in a Last In-First Out (LIFO) tank model.
**----------------------------------------------------------
*/
{
Network *net = &pr->network;
Quality *qual = &pr->quality;
int k;
double cin, vsum, wsum, vseg;
Pseg seg;
Stank *tank = &pr->network.Tank[i];
k = net->Nlinks + i;
if (qual->LastSeg[k] == NULL || qual->FirstSeg[k] == NULL) return;
// Find inflows & outflows
if (vin > 0.0) cin = win / vin;
else cin = 0.0;
// If tank filling, then create new last seg
tank->C = qual->LastSeg[k]->c;
seg = qual->LastSeg[k];
if (vnet > 0.0)
{
// ... inflow quality is same as last segment's quality,
// so just add inflow volume to last segment
if (fabs(seg->c - cin) < qual->Ctol) seg->v += vnet;
// ... otherwise add a new last segment with inflow quality
else addseg(pr, k, vnet, cin);
// Update reported tank quality
tank->C = qual->LastSeg[k]->c;
}
// If tank emptying then remove last segments until vnet consumed
else if (vnet < 0.0)
{
vsum = 0.0;
wsum = 0.0;
vnet = -vnet;
// Reverse segment chain so segments are processed from last to first
reversesegs(pr, k);
// While there is still volume to remove
while (vnet > 0.0)
{
// ... start with reversed first segment
seg = qual->FirstSeg[k];
if (seg == NULL) break;
// ... find volume to remove from it
vseg = seg->v;
vseg = MIN(vseg, vnet);
if (seg == qual->LastSeg[k]) vseg = vnet;
// ... update total volume & mass removed
vsum += vseg;
wsum += (seg->c) * vseg;
// ... update remiaing volume to remove
vnet -= vseg;
// ... if no more volume left in current segment
if (vnet >= 0.0 && vseg >= seg->v)
{
// ... replace current segment with previous one
if (seg->prev)
{
qual->FirstSeg[k] = seg->prev;
seg->prev = qual->FreeSeg;
qual->FreeSeg = seg;
}
}
// ... otherwise reduce volume of current segment
else seg->v -= vseg;
}
// Restore original orientation of segment chain
reversesegs(pr, k);
// Reported tank quality is mixture of flow released and any inflow
tank->C = (wsum + win) / (vsum + vin);
}
}