GCC Code Coverage Report


Directory: src/solver/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 92.4% 549 / 0 / 594
Functions: 100.0% 30 / 0 / 30
Branches: 71.1% 249 / 0 / 350

node.c
Line Branch Exec Source
1 //-----------------------------------------------------------------------------
2 // node.c
3 //
4 // Project: EPA SWMM5
5 // Version: 5.2
6 // Date: 10/29/22 (Build 5.2.2)
7 // Author: L. Rossman
8 //
9 // Conveyance system node functions.
10 //
11 // Update History
12 // ==============
13 // Build 5.1.007:
14 // - Ponded area property for storage nodes deprecated.
15 // - Support for Green-Ampt seepage from bottom and sides of storage node added.
16 // - Storage node evap. & seepage losses now computed at start of each routing
17 // time step.
18 // Build 5.1.008:
19 // - Support added for sending outfall discharge to a subcatchment.
20 // Build 5.1.010:
21 // - Storage losses now based on node's new volume instead of old volume.
22 // Build 5.1.013:
23 // - A surcharge depth can now be applied to storage nodes.
24 // - A negative inflow is now assigned to an Outfall node with backflow.
25 // Build 5.1.014:
26 // - Fixed bug in storage_losses() that affected storage exfiltration.
27 // Build 5.1.015:
28 // - Fatal error issued if a storage node's area curve produces a negative
29 // volume when extrapolated to the node's full depth.
30 // Build 5.2.0:
31 // - Support added Streets and Inlets.
32 // - Support added for analytical storage shapes.
33 // Build 5.2.1:
34 // - Warning no longer issued when node full depth is increased to match
35 // crown of highest connecting link.
36 // - a2 term for paraboloid shaped storage units was corrected
37 // Build 5.2.2:
38 // - Warning restored for node full depth being increased to crown of highest
39 // connecting link.
40 //-----------------------------------------------------------------------------
41 #define _CRT_SECURE_NO_DEPRECATE
42
43 #include <stdlib.h>
44 #include <string.h>
45 #include <math.h>
46 #include "headers.h"
47 #include "findroot.h"
48
49 //-----------------------------------------------------------------------------
50 // Local Declarations
51 //-----------------------------------------------------------------------------
52 typedef struct
53 {
54 int k; // storage unit index
55 double v; // storage unit volume (ft3)
56 } TStorageVol;
57
58 //-----------------------------------------------------------------------------
59 // External functions (declared in funcs.h)
60 //-----------------------------------------------------------------------------
61 // node_readParams (called from readNode in input.c)
62 // node_validate (called from project_validate)
63 // node_initState (called from project_init)
64 // node_setOldHydState (called from routing_execute)
65 // node_setOldQualState (called from routing_execute)
66 // node_initFlows (called from routing_execute)
67 // node_setOutletDepth (called from routing_execute)
68 // node_getLosses (called from routing_execute)
69 // node_getSystemOutflow (called from removeOutflows in routing.c)
70 // node_getResults (called from output_saveNodeResults)
71 // node_getPondedArea (called from initNodeStates in dynwave.c)
72 // node_getOutflow (called from link_getInflow & conduit_getInflow)
73 // node_getMaxOutflow (called from flowrout.c and dynwave.c)
74 // node_getSurfArea
75 // node_getDepth
76 // node_getVolume
77
78 //-----------------------------------------------------------------------------
79 // Local functions
80 //-----------------------------------------------------------------------------
81 static void node_setParams(int j, int type, int k, double x[]);
82 static int junc_readParams(int j, int k, char* tok[], int ntoks);
83
84 static int outfall_readParams(int j, int k, char* tok[], int ntoks);
85 static void outfall_setOutletDepth(int j, double yNorm, double yCrit, double z);
86
87 static int storage_readParams(int j, int k, char* tok[], int ntoks);
88 static double storage_getDepth(int j, double v);
89 static double storage_getVolume(int j, double d);
90 static double storage_getSurfArea(int j, double d);
91 static void storage_getVolDiff(double y, double* f, double* df, void* p);
92 static double storage_getOutflow(int j, int i);
93 static double storage_getLosses(int j, double tStep);
94
95 static int divider_readParams(int j, int k, char* tok[], int ntoks);
96 static void divider_validate(int j);
97 static double divider_getOutflow(int j, int link);
98
99
100 //=============================================================================
101
102 10183 int node_readParams(int j, int type, int k, char* tok[], int ntoks)
103 //
104 // Input: j = node index
105 // type = node type code
106 // k = index of node type
107 // tok[] = array of string tokens
108 // ntoks = number of tokens
109 // Output: returns an error code
110 // Purpose: reads node properties from a tokenized line of input.
111 //
112 {
113
4/5
✓ Branch 0 taken 9714 times.
✓ Branch 1 taken 348 times.
✓ Branch 2 taken 117 times.
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
10183 switch ( type )
114 {
115 9714 case JUNCTION: return junc_readParams(j, k, tok, ntoks);
116 348 case OUTFALL: return outfall_readParams(j, k, tok, ntoks);
117 117 case STORAGE: return storage_readParams(j, k, tok, ntoks);
118 4 case DIVIDER: return divider_readParams(j, k, tok, ntoks);
119 default: return 0;
120 }
121 }
122
123 //=============================================================================
124
125 10183 void node_setParams(int j, int type, int k, double x[])
126 //
127 // Input: j = node index
128 // type = node type code
129 // k = index of node type
130 // x[] = array of property values
131 // Output: none
132 // Purpose: assigns property values to a node.
133 //
134 {
135 10183 Node[j].type = type;
136 10183 Node[j].subIndex = k;
137 10183 Node[j].invertElev = x[0] / UCF(LENGTH);
138 10183 Node[j].crownElev = Node[j].invertElev;
139 10183 Node[j].initDepth = 0.0;
140 10183 Node[j].newVolume = 0.0;
141 10183 Node[j].fullVolume = 0.0;
142 10183 Node[j].fullDepth = 0.0;
143 10183 Node[j].surDepth = 0.0;
144 10183 Node[j].pondedArea = 0.0;
145 10183 Node[j].degree = 0;
146 10183 Node[j].inlet = NO_INLET;
147
4/5
✓ Branch 0 taken 9714 times.
✓ Branch 1 taken 348 times.
✓ Branch 2 taken 117 times.
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
10183 switch (type)
148 {
149 9714 case JUNCTION:
150 9714 Node[j].fullDepth = x[1] / UCF(LENGTH);
151 9714 Node[j].initDepth = x[2] / UCF(LENGTH);
152 9714 Node[j].surDepth = x[3] / UCF(LENGTH);
153 9714 Node[j].pondedArea = x[4] / (UCF(LENGTH)*UCF(LENGTH));
154 9714 break;
155
156 348 case OUTFALL:
157 348 Outfall[k].type = (int)x[1];
158 348 Outfall[k].fixedStage = x[2] / UCF(LENGTH);
159 348 Outfall[k].tideCurve = (int)x[3];
160 348 Outfall[k].stageSeries = (int)x[4];
161 348 Outfall[k].hasFlapGate = (char)x[5];
162 348 Outfall[k].routeTo = (int)x[6];
163 348 Outfall[k].wRouted = NULL;
164
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 347 times.
348 if ( Outfall[k].routeTo >= 0 )
165 {
166 1 Outfall[k].wRouted =
167 1 (double *) calloc(Nobjects[POLLUT], sizeof(double));
168 }
169 348 break;
170
171 117 case STORAGE:
172 117 Node[j].fullDepth = x[1] / UCF(LENGTH);
173 117 Node[j].initDepth = x[2] / UCF(LENGTH);
174 117 Storage[k].shape = (int)x[3];
175 117 Storage[k].a1 = x[4];
176 117 Storage[k].a2 = x[5];
177 117 Storage[k].a0 = x[6];
178 117 Storage[k].aCurve = (int)x[7];
179 117 Node[j].surDepth = x[8] / UCF(LENGTH);
180 117 Storage[k].fEvap = x[9];
181 117 break;
182
183 4 case DIVIDER:
184 4 Divider[k].link = (int)x[1];
185 4 Divider[k].type = (int)x[2];
186 4 Divider[k].flowCurve = (int)x[3];
187 4 Divider[k].qMin = x[4] / UCF(FLOW);
188 4 Divider[k].dhMax = x[5];
189 4 Divider[k].cWeir = x[6];
190 4 Node[j].fullDepth = x[7] / UCF(LENGTH);
191 4 Node[j].initDepth = x[8] / UCF(LENGTH);
192 4 Node[j].surDepth = x[9] / UCF(LENGTH);
193 4 Node[j].pondedArea = x[10] / (UCF(LENGTH)*UCF(LENGTH));
194 4 break;
195 }
196 10183 }
197
198 //=============================================================================
199
200 10183 void node_validate(int j)
201 //
202 // Input: j = node index
203 // Output: none
204 // Purpose: validates a node's properties.
205 //
206 {
207 TDwfInflow* inflow;
208
209 // --- see if full depth was increased to accommodate conduit crown
210
4/4
✓ Branch 0 taken 434 times.
✓ Branch 1 taken 9749 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 433 times.
10183 if ( Node[j].fullDepth > Node[j].oldDepth && Node[j].oldDepth > 0.0 )
211 {
212 1 report_writeWarningMsg(WARN02, Node[j].ID);
213 }
214
215 // --- check that initial depth does not exceed max. depth
216
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10183 times.
10183 if ( Node[j].initDepth > Node[j].fullDepth + Node[j].surDepth )
217 report_writeErrorMsg(ERR_NODE_DEPTH, Node[j].ID);
218
219 // --- check for negative volume for storage node at full depth
220
2/2
✓ Branch 0 taken 117 times.
✓ Branch 1 taken 10066 times.
10183 if (Node[j].type == STORAGE)
221
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 117 times.
117 if (node_getVolume(j, Node[j].fullDepth) < 0.0)
222 report_writeErrorMsg(ERR_STORAGE_VOLUME, Node[j].ID);
223
224
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 10179 times.
10183 if ( Node[j].type == DIVIDER ) divider_validate(j);
225
226 // --- initialize dry weather inflows
227 10183 inflow = Node[j].dwfInflow;
228
2/2
✓ Branch 0 taken 3943 times.
✓ Branch 1 taken 10183 times.
14126 while (inflow)
229 {
230 3943 inflow_initDwfInflow(inflow);
231 3943 inflow = inflow->next;
232 }
233 10183 }
234
235 //=============================================================================
236
237 10183 void node_initState(int j)
238 //
239 // Input: j = node index
240 // Output: none
241 // Purpose: initializes a node's state variables at start of simulation.
242 //
243 {
244 int p, k;
245
246 // --- initialize depth
247 10183 Node[j].oldDepth = Node[j].initDepth;
248 10183 Node[j].newDepth = Node[j].oldDepth;
249 10183 Node[j].crownElev = Node[j].invertElev;
250
251 10183 Node[j].fullVolume = node_getVolume(j, Node[j].fullDepth);
252 10183 Node[j].oldVolume = node_getVolume(j, Node[j].oldDepth);
253 10183 Node[j].newVolume = Node[j].oldVolume;
254
255 // --- initialize water quality state
256
2/2
✓ Branch 0 taken 25771 times.
✓ Branch 1 taken 10183 times.
35954 for (p = 0; p < Nobjects[POLLUT]; p++)
257 {
258 25771 Node[j].oldQual[p] = 0.0;
259 25771 Node[j].newQual[p] = 0.0;
260 }
261
262 // --- initialize any inflow
263 10183 Node[j].oldLatFlow = 0.0;
264 10183 Node[j].newLatFlow = 0.0;
265 10183 Node[j].apiExtInflow = 0.0;
266 10183 Node[j].losses = 0.0;
267
268 // --- initialize storage nodes
269
2/2
✓ Branch 0 taken 117 times.
✓ Branch 1 taken 10066 times.
10183 if ( Node[j].type == STORAGE )
270 {
271 // --- set hydraulic residence time to 0
272 117 k = Node[j].subIndex;
273 117 Storage[k].hrt = 0.0;
274
275 // --- initialize exfiltration properties
276
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 114 times.
117 if ( Storage[k].exfil ) exfil_initState(k);
277 }
278
279 // --- initialize flow stream routed from outfall onto a subcatchment
280
2/2
✓ Branch 0 taken 348 times.
✓ Branch 1 taken 9835 times.
10183 if ( Node[j].type == OUTFALL )
281 {
282 348 k = Node[j].subIndex;
283
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 347 times.
348 if ( Outfall[k].routeTo >= 0 )
284 {
285 1 Outfall[k].vRouted = 0.0;
286
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 for (p = 0; p < Nobjects[POLLUT]; p++) Outfall[k].wRouted[p] = 0.0;
287 }
288 }
289 10183 }
290
291 //=============================================================================
292
293 37894467 void node_setOldHydState(int j)
294 //
295 // Input: j = node index
296 // Output: none
297 // Purpose: replaces a node's old hydraulic state values with new ones.
298 //
299 {
300 37894467 Node[j].oldDepth = Node[j].newDepth;
301 37894467 Node[j].oldVolume = Node[j].newVolume;
302 37894467 Node[j].oldFlowInflow = Node[j].inflow;
303 37894467 Node[j].oldNetInflow = Node[j].inflow - Node[j].outflow;
304 37894467 }
305
306 //=============================================================================
307
308 20940478 void node_setOldQualState(int j)
309 //
310 // Input: j = node index
311 // Output: none
312 // Purpose: replaces a node's old water quality state values with new ones.
313 //
314 {
315 int p;
316
2/2
✓ Branch 0 taken 78198686 times.
✓ Branch 1 taken 20940478 times.
99139164 for (p = 0; p < Nobjects[POLLUT]; p++)
317 {
318 78198686 Node[j].oldQual[p] = Node[j].newQual[p];
319 78198686 Node[j].newQual[p] = 0.0;
320 }
321 20940478 }
322
323 //=============================================================================
324
325 37894467 void node_initFlows(int j, double tStep)
326 //
327 // Input: j = node index
328 // tStep = time step (sec)
329 // Output: none
330 // Purpose: initializes a node's inflow/outflow/overflow at start of time step.
331 //
332 {
333 // --- initialize inflow & outflow
334 37894467 Node[j].inflow = Node[j].newLatFlow;
335 37894467 Node[j].outflow = Node[j].losses;
336
337 // --- set overflow to any excess stored volume
338
2/2
✓ Branch 0 taken 88273 times.
✓ Branch 1 taken 37806194 times.
37894467 if ( Node[j].newVolume > Node[j].fullVolume )
339 88273 Node[j].overflow = (Node[j].newVolume - Node[j].fullVolume) / tStep;
340 37806194 else Node[j].overflow = 0.0;
341 37894467 }
342
343 //=============================================================================
344
345 12629377 double node_getDepth(int j, double v)
346 //
347 // Input: j = node index
348 // v = volume (ft3)
349 // Output: returns depth of water at a node (ft)
350 // Purpose: computes a node's water depth from its volume.
351 //
352 {
353
2/2
✓ Branch 0 taken 14159 times.
✓ Branch 1 taken 12615218 times.
12629377 switch ( Node[j].type )
354 {
355 14159 case STORAGE: return storage_getDepth(j, v);
356 12615218 default: return 0.0;
357 }
358 }
359
360 //=============================================================================
361
362 65116604 double node_getVolume(int j, double d)
363 //
364 // Input: j = node index
365 // d = water depth (ft)
366 // Output: returns volume of water at a node (ft3)
367 // Purpose: computes volume stored at a node from its water depth.
368 //
369 {
370
2/2
✓ Branch 0 taken 485671 times.
✓ Branch 1 taken 64630933 times.
65116604 switch ( Node[j].type )
371 {
372 485671 case STORAGE: return storage_getVolume(j, d);
373
374 64630933 default:
375
2/2
✓ Branch 0 taken 64630825 times.
✓ Branch 1 taken 108 times.
64630933 if ( Node[j].fullDepth > 0.0 )
376 64630825 return Node[j].fullVolume * (d / Node[j].fullDepth);
377 108 else return 0.0;
378 }
379 }
380
381 //=============================================================================
382
383 67143458 double node_getSurfArea(int j, double d)
384 //
385 // Input: j = node index
386 // d = water depth (ft)
387 // Output: returns surface area of water at a node (ft2)
388 // Purpose: computes surface area of water stored at a node from water depth.
389 //
390 {
391
2/2
✓ Branch 0 taken 495707 times.
✓ Branch 1 taken 66647751 times.
67143458 switch (Node[j].type)
392 {
393 495707 case STORAGE: return storage_getSurfArea(j, d);
394 66647751 default: return 0.0;
395 }
396 }
397
398 //=============================================================================
399
400 11998612 double node_getOutflow(int j, int k)
401 //
402 // Input: j = node index
403 // k = link index
404 // Output: returns flow rate (cfs)
405 // Purpose: computes outflow from node available for inflow into a link.
406 //
407 {
408
3/3
✓ Branch 0 taken 1920 times.
✓ Branch 1 taken 17353 times.
✓ Branch 2 taken 11979339 times.
11998612 switch ( Node[j].type )
409 {
410 1920 case DIVIDER: return divider_getOutflow(j, k);
411 17353 case STORAGE: return storage_getOutflow(j, k);
412 11979339 default: return Node[j].inflow + Node[j].overflow;
413 }
414 }
415
416 //=============================================================================
417
418 12035293 double node_getMaxOutflow(int j, double q, double tStep)
419 //
420 // Input: j = node index
421 // q = original outflow rate (cfs)
422 // tStep = time step (sec)
423 // Output: returns modified flow rate (cfs)
424 // Purpose: limits outflow rate from a node with storage volume.
425 //
426 {
427 double qMax;
428
2/2
✓ Branch 0 taken 54034 times.
✓ Branch 1 taken 11981259 times.
12035293 if ( Node[j].fullVolume > 0.0 )
429 {
430 54034 qMax = Node[j].inflow + Node[j].oldVolume / tStep;
431
2/2
✓ Branch 0 taken 2731 times.
✓ Branch 1 taken 51303 times.
54034 if ( q > qMax ) q = qMax;
432 }
433
2/2
✓ Branch 0 taken 11315709 times.
✓ Branch 1 taken 719584 times.
12035293 return MAX(0.0, q);
434 }
435
436 //=============================================================================
437
438 37894467 double node_getSystemOutflow(int j, int *isFlooded)
439 //
440 // Input: j = node index
441 // isFlooded = TRUE if node becomes flooded
442 // Output: returns flow rate lost from system (cfs)
443 // Purpose: computes flow rate at outfalls and flooded nodes.
444 //
445 {
446 37894467 double outflow = 0.0;;
447
448 // --- assume there is no flooding
449 37894467 *isFlooded = FALSE;
450
451 // --- if node is an outfall
452
2/2
✓ Branch 0 taken 1309094 times.
✓ Branch 1 taken 36585373 times.
37894467 if ( Node[j].type == OUTFALL )
453 {
454 // --- node receives inflow from outfall conduit
455
2/2
✓ Branch 0 taken 1308539 times.
✓ Branch 1 taken 555 times.
1309094 if ( Node[j].outflow == 0.0 ) outflow = Node[j].inflow;
456
457 // --- node sends flow into outfall conduit
458 // (therefore it has a negative outflow)
459 else
460 {
461
1/2
✓ Branch 0 taken 555 times.
✗ Branch 1 not taken.
555 if ( Node[j].inflow == 0.0 )
462 {
463 555 outflow = -Node[j].outflow;
464 555 Node[j].inflow = fabs(outflow);
465 }
466 }
467
468 // --- set overflow and volume to 0
469 1309094 Node[j].overflow = 0.0;
470 1309094 Node[j].newVolume = 0.0;
471 }
472
473 // --- node is a terminal node under Steady or Kin. Wave routing
474
2/2
✓ Branch 0 taken 11994339 times.
✓ Branch 1 taken 24591034 times.
36585373 else if ( RouteModel != DW &&
475
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 11994339 times.
11994339 Node[j].degree == 0 &&
476 Node[j].type != STORAGE
477 )
478 {
479 if ( Node[j].outflow == 0.0 ) outflow = Node[j].inflow;
480 Node[j].overflow = 0.0;
481 Node[j].newVolume = 0.0;
482 }
483
484 // --- otherwise node is an interior node and any
485 // overflow is considered as system outflow and flooding
486 else
487 {
488
2/2
✓ Branch 0 taken 36497100 times.
✓ Branch 1 taken 88273 times.
36585373 if ( Node[j].newVolume <= Node[j].fullVolume)
489 36497100 outflow = Node[j].overflow;
490
2/2
✓ Branch 0 taken 37764 times.
✓ Branch 1 taken 36547609 times.
36585373 if ( outflow > 0.0 ) *isFlooded = TRUE;
491 }
492 37894467 return outflow;
493 }
494
495 //=============================================================================
496
497 1931254 void node_getResults(int j, double f, float x[])
498 //
499 // Input: j = node index
500 // f = weighting factor
501 // x[] = array of nodal reporting variables
502 // Output: none
503 // Purpose: computes weighted average of old and new results at a node.
504 //
505 {
506 int p;
507 double z;
508 1931254 double f1 = 1.0 - f;
509
510 1931254 z = (f1 * Node[j].oldDepth + f * Node[j].newDepth) * UCF(LENGTH);
511 1931254 x[NODE_DEPTH] = (float)z;
512 1931254 z = Node[j].invertElev * UCF(LENGTH);
513 1931254 x[NODE_HEAD] = x[NODE_DEPTH] + (float)z;
514 1931254 z = (f1*Node[j].oldVolume + f*Node[j].newVolume) * UCF(VOLUME);
515 1931254 x[NODE_VOLUME] = (float)z;
516 1931254 z = (f1*Node[j].oldLatFlow + f*Node[j].newLatFlow) * UCF(FLOW);
517 1931254 x[NODE_LATFLOW] = (float)z;
518 1931254 z = (f1*Node[j].oldFlowInflow + f*Node[j].inflow) * UCF(FLOW);
519 1931254 x[NODE_INFLOW] = (float)z;
520 1931254 z = Node[j].overflow * UCF(FLOW);
521 1931254 x[NODE_OVERFLOW] = (float)z;
522
523
4/4
✓ Branch 0 taken 1927798 times.
✓ Branch 1 taken 3456 times.
✓ Branch 2 taken 827538 times.
✓ Branch 3 taken 1927798 times.
2758792 if ( !IgnoreQuality ) for (p = 0; p < Nobjects[POLLUT]; p++)
524 {
525 827538 z = f1*Node[j].oldQual[p] + f*Node[j].newQual[p];
526 827538 x[NODE_QUAL+p] = (float)z;
527 }
528 1931254 }
529
530 //=============================================================================
531
532 1979248 void node_setOutletDepth(int j, double yNorm, double yCrit, double z)
533 //
534 // Input: j = node index
535 // yNorm = normal flow depth (ft)
536 // yCrit = critical flow depth (ft)
537 // z = offset of connecting outfall link from node invert (ft)
538 // Output: none
539 // Purpose: sets water depth at a node that serves as an outlet point.
540 //
541 {
542
1/3
✗ Branch 0 not taken.
✓ Branch 1 taken 1979248 times.
✗ Branch 2 not taken.
1979248 switch (Node[j].type)
543 {
544 // --- do nothing if outlet is a storage unit
545 case STORAGE:
546 return;
547
548 // --- if outlet is a designated outfall then use outfall's specs
549 1979248 case OUTFALL:
550 1979248 outfall_setOutletDepth(j, yNorm, yCrit, z);
551 1979248 break;
552
553 // --- for all other nodes, use min. of critical & normal depths
554 default:
555 if ( z > 0.0 ) Node[j].newDepth = 0.0;
556 else Node[j].newDepth = MIN(yNorm, yCrit);
557 }
558 }
559
560 //=============================================================================
561
562 2800436 double node_getPondedArea(int j, double d)
563 //
564 // Input: j = node index
565 // d = water depth (ft)
566 // Output: returns surface area of water at a node (ft2)
567 // Purpose: computes surface area of water at a node based on depth.
568 //
569 {
570 double a;
571
572 // --- use regular getSurfArea function if node not flooded
573
4/4
✓ Branch 0 taken 188112 times.
✓ Branch 1 taken 2612324 times.
✓ Branch 2 taken 10929 times.
✓ Branch 3 taken 177183 times.
2800436 if ( d <= Node[j].fullDepth || Node[j].pondedArea == 0.0 )
574 {
575 2623253 return node_getSurfArea(j, d);
576 }
577
578 // --- compute ponded depth
579 177183 d = d - Node[j].fullDepth;
580
581 // --- use ponded area for flooded node
582 177183 a = Node[j].pondedArea;
583
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 177183 times.
177183 if ( a <= 0.0 ) a = node_getSurfArea(j, Node[j].fullDepth);
584 177183 return a;
585 }
586
587 //=============================================================================
588
589 37894467 double node_getLosses(int j, double tStep)
590 //
591 // Input: j = node index
592 // tStep = time step (sec)
593 // Output: returns water loss rate at node (ft3)
594 // Purpose: computes the rates of evaporation and infiltration over a given
595 // time step for a node.
596 //
597 {
598
2/2
✓ Branch 0 taken 143130 times.
✓ Branch 1 taken 37751337 times.
37894467 if ( Node[j].type == STORAGE ) return storage_getLosses(j, tStep);
599 37751337 else return 0.0;
600 }
601
602 //=============================================================================
603 // J U N C T I O N M E T H O D S
604 //=============================================================================
605
606 9714 int junc_readParams(int j, int k, char* tok[], int ntoks)
607 //
608 // Input: j = node index
609 // k = junction index
610 // tok[] = array of string tokens
611 // ntoks = number of tokens
612 // Output: returns an error message
613 // Purpose: reads a junction's properties from a tokenized line of input.
614 //
615 // Format of input line is:
616 // nodeID elev maxDepth initDepth surDepth aPond
617 {
618 int i;
619 double x[6];
620 char* id;
621
622
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9714 times.
9714 if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, "");
623 9714 id = project_findID(NODE, tok[0]);
624
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9714 times.
9714 if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]);
625
626 // --- parse invert elev., max. depth, init. depth, surcharged depth,
627 // & ponded area values
628
2/2
✓ Branch 0 taken 48570 times.
✓ Branch 1 taken 9714 times.
58284 for ( i = 1; i <= 5; i++ )
629 {
630 48570 x[i-1] = 0.0;
631
1/2
✓ Branch 0 taken 48570 times.
✗ Branch 1 not taken.
48570 if ( i < ntoks )
632 {
633
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 48570 times.
48570 if ( ! getDouble(tok[i], &x[i-1]) )
634 return error_setInpError(ERR_NUMBER, tok[i]);
635 }
636 }
637
638 // --- check for non-negative values (except for invert elev.)
639
2/2
✓ Branch 0 taken 38856 times.
✓ Branch 1 taken 9714 times.
48570 for ( i = 1; i <= 4; i++ )
640 {
641
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 38856 times.
38856 if ( x[i] < 0.0 ) return error_setInpError(ERR_NUMBER, tok[i+1]);
642 }
643
644 // --- add parameters to junction object
645 9714 Node[j].ID = id;
646 9714 node_setParams(j, JUNCTION, k, x);
647 9714 return 0;
648 }
649
650 //=============================================================================
651 // S T O R A G E M E T H O D S
652 //=============================================================================
653
654 117 int storage_readParams(int j, int k, char* tok[], int ntoks)
655 //
656 // Input: j = node index
657 // k = storage unit index
658 // tok[] = array of string tokens
659 // ntoks = number of tokens
660 // Output: returns an error message
661 // Purpose: reads a storage unit's properties from a tokenized line of input.
662 //
663 // Format of input line is:
664 // nodeID elev maxDepth initDepth curveType a1 a2 a0 surDepth fEvap (infil)
665 // nodeID elev maxDepth initDepth TABULAR curveID surDepth fEvap (infil)
666 // x[0] x[1] x[2] x[3] x[4..7] x[8] x[9]
667 {
668 int i, m, n;
669 double x[10], y[3];
670 double A, B; //base semi-axis length & width for conical shape
671 double L, W; //base length & width for pyramidal shape
672 double Z; //run over rise for conical & pyramidal sides
673 char* id;
674
675 // --- get ID name
676
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 117 times.
117 if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, "");
677 117 id = project_findID(NODE, tok[0]);
678
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 117 times.
117 if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]);
679
680 // --- get invert elev, max. depth, & init. depth
681
2/2
✓ Branch 0 taken 351 times.
✓ Branch 1 taken 117 times.
468 for ( i = 1; i <= 3; i++ )
682 {
683
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 351 times.
351 if ( ! getDouble(tok[i], &x[i-1]) )
684 return error_setInpError(ERR_NUMBER, tok[i]);
685 }
686
687 // --- get surf. area relation type
688 117 m = findmatch(tok[4], RelationWords);
689
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 117 times.
117 if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[4]);
690 117 x[3] = m;
691 117 x[4] = 0.0; // a1
692 117 x[5] = 0.0; // a2
693 117 x[6] = 0.0; // a0
694 117 x[7] = -1.0; // curveID
695 117 x[8] = 0.0; // surDepth
696 117 x[9] = 0.0; // fEvap
697
698 // --- get surf. area curve name
699
2/2
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 61 times.
117 if (m == TABULAR)
700 {
701 56 i = project_findObject(CURVE, tok[5]);
702
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 56 times.
56 if (i < 0) return error_setInpError(ERR_NAME, tok[5]);
703 56 x[7] = i;
704 56 n = 6;
705 }
706
707 // --- get surf. area function coeffs.
708 else
709 {
710
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 61 times.
61 if (ntoks < 8) return error_setInpError(ERR_ITEMS, "");
711
2/2
✓ Branch 0 taken 183 times.
✓ Branch 1 taken 61 times.
244 for (i=5; i<=7; i++)
712 {
713
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 183 times.
183 if ( ! getDouble(tok[i], &y[i-5]) )
714 return error_setInpError(ERR_NUMBER, tok[i]);
715 }
716 61 n = 8;
717 }
718
719 // --- check for valid data
720
3/3
✓ Branch 0 taken 53 times.
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 56 times.
117 switch (m)
721 {
722 53 case FUNCTIONAL:
723 // area at 0 depth can't be negative
724
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 53 times.
53 if (y[2] < 0.0) return error_setInpError(ERR_NUMBER, tok[7]);
725 53 break;
726
727 8 case CYLINDRICAL:
728 case CONICAL:
729 case PARABOLOID:
730 case PYRAMIDAL:
731 // length or width can't be <= 0, slope can't be < 0
732
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (y[0] <= 0.0 ) return error_setInpError(ERR_NUMBER, tok[5]);
733
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (y[1] <= 0.0) return error_setInpError(ERR_NUMBER, tok[6]);
734
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (y[2] < 0.0) return error_setInpError(ERR_NUMBER, tok[7]);
735 8 break;
736 }
737 // --- height of paraboloid shape can't be 0
738
3/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 115 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
117 if (m == PARABOLOID && y[2] == 0.0)
739 return error_setInpError(ERR_NUMBER, tok[7]);
740
741 // --- convert supplied parameters to coeffs. in surface area equation
742
6/6
✓ Branch 0 taken 53 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 56 times.
117 switch (m)
743 {
744 53 case FUNCTIONAL:
745 53 x[4] = y[0];
746 53 x[5] = y[1];
747 53 x[6] = y[2];
748 53 break;
749
750 2 case CYLINDRICAL:
751 2 A = y[0] / 2.; // base semi-axis length
752 2 B = y[1] / 2.; // base semi axis width
753 2 x[4] = 0.0; // a1 term
754 2 x[5] = 0.0; // a2 term
755 2 x[6] = PI * A * B; // a0 term
756 2 break;
757
758 2 case CONICAL:
759 2 A = y[0] / 2.; // base semi-axis length
760 2 B = y[1] / 2.; // base semi axis width
761 2 Z = y[2]; // side slope
762 2 x[4] = 2.0 * PI * B * Z; // linear coeff.
763 2 x[5] = PI * B / A * Z * Z; // quadratic coeff.
764 2 x[6] = PI * A * B; // constant
765 2 break;
766
767 2 case PARABOLOID:
768 2 A = y[0] / 2.; // top semi-axis length
769 2 B = y[1] / 2.; // top semi-axis width
770 2 Z = y[2]; // top height
771 2 x[4] = PI * A * B / Z; // a1 term
772 2 x[5] = 0.0; // a2 term
773 2 x[6] = 0.0; // a0 term
774 2 break;
775
776 2 case PYRAMIDAL:
777 2 L = y[0];
778 2 W = y[1];
779 2 Z = y[2];
780 2 x[4] = 2.0 * (L + W) * Z; // linear coeff.
781 2 x[5] = 4.0 * Z * Z; // quadratic coeff.
782 2 x[6] = L * W; // constant
783 2 break;
784 }
785
786 // --- get surcharge depth if present
787
1/2
✓ Branch 0 taken 117 times.
✗ Branch 1 not taken.
117 if ( ntoks > n)
788 {
789
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 117 times.
117 if ( ! getDouble(tok[n], &x[8]) )
790 return error_setInpError(ERR_NUMBER, tok[n]);
791 117 n++;
792 }
793
794 // --- get evaporation fraction if present
795
1/2
✓ Branch 0 taken 117 times.
✗ Branch 1 not taken.
117 if ( ntoks > n )
796 {
797
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 117 times.
117 if ( ! getDouble(tok[n], &x[9]) )
798 return error_setInpError(ERR_NUMBER, tok[n]);
799 117 n++;
800 }
801
802 // --- add parameters to storage unit object
803 117 Node[j].ID = id;
804 117 node_setParams(j, STORAGE, k, x);
805
806 // --- read exfiltration parameters if present
807
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 114 times.
117 if ( ntoks > n ) return exfil_readStorageParams(k, tok, ntoks, n);
808 114 return 0;
809 }
810
811 //=============================================================================
812
813 14159 double storage_getDepth(int j, double v)
814 //
815 // Input: j = node index
816 // v = volume (ft3)
817 // Output: returns depth of water at a storage node (ft)
818 // Purpose: computes a storage node's water depth from its volume.
819 //
820 {
821 14159 int k = Node[j].subIndex;
822 14159 int i = Storage[k].aCurve;
823 14159 int shape = Storage[k].shape;
824 14159 double a0 = Storage[k].a0;
825 14159 double a1 = Storage[k].a1;
826 14159 double a2 = Storage[k].a2;
827 double d, e;
828 TStorageVol storageVol;
829
830 // --- return max depth if a max. volume has been computed
831 // and volume is > max. volume
832
1/2
✓ Branch 0 taken 14159 times.
✗ Branch 1 not taken.
14159 if ( Node[j].fullVolume > 0.0
833
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14159 times.
14159 && v >= Node[j].fullVolume ) return Node[j].fullDepth;
834
2/2
✓ Branch 0 taken 1230 times.
✓ Branch 1 taken 12929 times.
14159 if ( v == 0.0 ) return 0.0;
835
836 // --- convert volume to user's units
837 12929 v *= UCF(VOLUME);
838 12929 storageVol.k = k;
839 12929 storageVol.v = v;
840
841
5/6
✓ Branch 0 taken 5669 times.
✓ Branch 1 taken 1452 times.
✓ Branch 2 taken 1453 times.
✓ Branch 3 taken 1453 times.
✓ Branch 4 taken 2902 times.
✗ Branch 5 not taken.
12929 switch (shape)
842 {
843 5669 case TABULAR:
844 5669 i = Storage[k].aCurve;
845
1/2
✓ Branch 0 taken 5669 times.
✗ Branch 1 not taken.
5669 if (i >= 0)
846 5669 d = table_getStorageDepth(&Curve[i], v);
847 else d = 0.0;
848 5669 break;
849
850 1452 case CYLINDRICAL:
851 // area = a0; v = a0*d;
852 1452 d = v / a0;
853 1452 break;
854
855 1453 case PARABOLOID:
856 // area = a1*d; v = (a1/2)*d^2
857 1453 d = sqrt(2.0 * v / a1);
858 1453 break;
859
860 1453 case FUNCTIONAL:
861 // area = a0 + a1; v = (a0 + a1) * d
862
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1453 times.
1453 if (a2 == 0.0)
863 {
864 d = v / (a0 + a1);
865 }
866 // area = a1*d^a2; v = a1/(a2+1)*d^(a2+1)
867
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1453 times.
1453 else if (a0 == 0.0)
868 {
869 e = 1.0 / (a2 + 1.0);
870 d = pow(v / (a1 * e), e);
871 }
872 // area = a0 + a1*d; v = a0*d + (a1/2)*d^2
873
2/4
✓ Branch 0 taken 1453 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1453 times.
✗ Branch 3 not taken.
1453 else if (a2 == 1.0 && a1 > 0.0)
874 {
875 1453 d = (sqrt(a0*a0 + 2.*a1*v) - a0) / a1;
876 }
877 else
878 // area = a0 + a1*d^a2
879 {
880 d = v / (a0 + a1);
881 findroot_Newton(0.0, Node[j].fullDepth*UCF(LENGTH), &d,
882 0.001, storage_getVolDiff, &storageVol);
883 }
884 1453 break;
885
886 2902 case CONICAL:
887 case PYRAMIDAL:
888 // area = a0 + a1*d + a2*d^2; v = a0*d + (a1/2)*d^2 + (a2/3)*d^3
889 2902 d = v / a0;
890 2902 findroot_Newton(0.0, Node[j].fullDepth*UCF(LENGTH), &d,
891 0.001, storage_getVolDiff, &storageVol);
892 2902 break;
893
894 default:
895 d = 0.0;
896 }
897
898 12929 d /= UCF(LENGTH);
899
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12929 times.
12929 if ( d > Node[j].fullDepth )
900 d = Node[j].fullDepth;
901 12929 return d;
902 }
903
904 //=============================================================================
905
906 5804 void storage_getVolDiff(double y, double* f, double* df, void* p)
907 //
908 // Input: y = depth of water (ft)
909 // p = pointer to a TStorageVol object
910 // Output: f = volume of water (ft3)
911 // df = dVolume/dDepth ( = surface area)(ft2)
912 // Purpose: computes volume difference and its derivative at a storage node
913 // using the node's area versus depth function.
914 //
915 {
916 int k;
917 TStorageVol* storageVol;
918
919 // --- cast void pointer p to a TStorageVol object
920 5804 storageVol = (TStorageVol *)p;
921 5804 k = storageVol->k;
922
923 // --- compute volume & surface area at depth y
924 5804 *f = storage_getVolume(k, y) - storageVol->v;
925 5804 *df = storage_getSurfArea(k, y);
926 5804 }
927
928 //=============================================================================
929
930 491475 double storage_getVolume(int j, double d)
931 //
932 // Input: j = node index
933 // d = depth (ft)
934 // Output: returns volume of stored water (ft3)
935 // Purpose: computes a storage node's water volume from its depth.
936 //
937 {
938 491475 int k = Node[j].subIndex;
939 int i;
940 double n, v;
941
942 // --- return full volume if depth >= max. depth
943
2/2
✓ Branch 0 taken 3990 times.
✓ Branch 1 taken 487485 times.
491475 if ( d == 0.0 ) return 0.0;
944
2/2
✓ Branch 0 taken 234 times.
✓ Branch 1 taken 487251 times.
487485 if ( d >= Node[j].fullDepth
945
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 234 times.
234 && Node[j].fullVolume > 0.0 ) return Node[j].fullVolume;
946
947
3/4
✓ Branch 0 taken 44927 times.
✓ Branch 1 taken 379114 times.
✓ Branch 2 taken 63444 times.
✗ Branch 3 not taken.
487485 switch (Storage[k].shape)
948 {
949 // --- for tabular shape function, use end area method
950 44927 case TABULAR:
951 44927 i = Storage[k].aCurve;
952
1/2
✓ Branch 0 taken 44927 times.
✗ Branch 1 not taken.
44927 if (i >= 0)
953 89854 return table_getStorageVolume(&Curve[i], d*UCF(LENGTH)) /
954 44927 UCF(VOLUME);
955 else return 0.0;
956
957 // --- for FUNCTIONAL relation, integrate a0 + a1*d^a2
958 379114 case FUNCTIONAL:
959 379114 d *= UCF(LENGTH);
960 379114 n = Storage[k].a2 + 1.0;
961 379114 v = (Storage[k].a0 * d) + Storage[k].a1 / n * pow(d, n);
962 379114 return v / UCF(VOLUME);
963
964 // --- for other shapes evaluate cubic eqn. a0*d + (a1/2)*d^2 + (a2/3)*d^3
965 63444 case CYLINDRICAL:
966 case CONICAL:
967 case PARABOLOID:
968 case PYRAMIDAL:
969 63444 d *= UCF(LENGTH);
970 63444 v = d * (Storage[k].a0 + d * (Storage[k].a1 / 2.0 + d * Storage[k].a2 / 3.0));
971 63444 return v / UCF(VOLUME);
972
973 default: return 0.0;
974 }
975 }
976
977 //=============================================================================
978
979 577948 double storage_getSurfArea(int j, double d)
980 //
981 // Input: j = node index
982 // d = depth (ft)
983 // Output: returns surface area (ft2)
984 // Purpose: computes a storage node's surface area from its water depth.
985 //
986 {
987 577948 double area = 0.0;
988 577948 int k = Node[j].subIndex;
989 int i;
990
991
3/4
✓ Branch 0 taken 66696 times.
✓ Branch 1 taken 413276 times.
✓ Branch 2 taken 97976 times.
✗ Branch 3 not taken.
577948 switch (Storage[k].shape)
992 {
993 // --- for tabular shape function, use table look-up
994 66696 case TABULAR:
995 66696 i = Storage[k].aCurve;
996
1/2
✓ Branch 0 taken 66696 times.
✗ Branch 1 not taken.
66696 if (i >= 0)
997 66696 area = table_lookupEx(&Curve[i], d*UCF(LENGTH));
998 66696 break;
999
1000 // --- for FUNCTIONAL relation, evaluate a0 + a1*d^a2
1001 413276 case FUNCTIONAL:
1002 826552 area = Storage[k].a0 + Storage[k].a1 *
1003 413276 pow(d*UCF(LENGTH), Storage[k].a2);
1004 413276 break;
1005
1006 // --- for other shapes, evaluate quadratic a0 + a1*d + a2*d^2
1007 97976 case CYLINDRICAL:
1008 case CONICAL:
1009 case PARABOLOID:
1010 case PYRAMIDAL:
1011 97976 d *= UCF(LENGTH);
1012 97976 area = Storage[k].a0 + d * (Storage[k].a1 + d * Storage[k].a2);
1013 97976 break;
1014
1015 default: return 0.0;
1016 }
1017 577948 return area / UCF(LENGTH) / UCF(LENGTH);
1018 }
1019
1020 //=============================================================================
1021
1022 17353 double storage_getOutflow(int j, int i)
1023 //
1024 // Input: j = node index
1025 // i = link index
1026 // Output: returns flow from storage node into conduit link (cfs)
1027 // Purpose: finds outflow from a storage node into its connecting conduit link
1028 // ( non-conduit links have their own getInflow functions).
1029 //
1030 {
1031 int k;
1032 double a, y;
1033
1034 // --- link must be a conduit
1035
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 17353 times.
17353 if ( Link[i].type != CONDUIT ) return 0.0;
1036
1037 // --- find depth of water in conduit
1038 17353 y = Node[j].newDepth - Link[i].offset1;
1039
1040 // --- return 0 if conduit empty or full flow if full
1041
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 17353 times.
17353 if ( y <= 0.0 ) return 0.0;
1042
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 17353 times.
17353 if ( y >= Link[i].xsect.yFull ) return Link[i].qFull;
1043
1044 // --- if partially full, return normal flow
1045 17353 k = Link[i].subIndex;
1046 17353 a = xsect_getAofY(&Link[i].xsect, y);
1047 17353 return Conduit[k].beta * xsect_getSofA(&Link[i].xsect, a);
1048 }
1049
1050 //=============================================================================
1051
1052 143130 double storage_getLosses(int j, double tStep)
1053 //
1054 // Input: j = node index
1055 // tStep = time step (sec)
1056 // Output: returns evaporation + infiltration rate (cfs)
1057 // Purpose: computes combined rate of water evaporated & infiltrated from
1058 // a storage node.
1059 //
1060 {
1061 int k;
1062 double depth;
1063 double area;
1064 143130 double evapRate = 0.0;
1065 143130 double exfilRate = 0.0;
1066 143130 double totalLoss = 0.0;
1067 double lossRatio;
1068 TExfil* exfil;
1069
1070 // --- get node's evap. rate (ft/s) & exfiltration object
1071 143130 k = Node[j].subIndex;
1072 143130 evapRate = Evap.rate * Storage[k].fEvap;
1073 143130 exfil = Storage[k].exfil;
1074
1075 // --- if either of these apply
1076
4/4
✓ Branch 0 taken 85776 times.
✓ Branch 1 taken 57354 times.
✓ Branch 2 taken 19083 times.
✓ Branch 3 taken 66693 times.
143130 if ( evapRate > 0.0 || exfil != NULL)
1077 {
1078 // --- obtain storage depth & surface area
1079 76437 depth = Node[j].newDepth;
1080 76437 area = storage_getSurfArea(j, depth);
1081
1082 // --- compute evap rate over this area (cfs)
1083
2/2
✓ Branch 0 taken 75985 times.
✓ Branch 1 taken 452 times.
76437 if (Node[j].newVolume > FUDGE)
1084 75985 evapRate = area * evapRate;
1085
1086 // --- find exfiltration rate (cfs) through bottom and side banks
1087
2/2
✓ Branch 0 taken 19083 times.
✓ Branch 1 taken 57354 times.
76437 if ( exfil != NULL )
1088 {
1089 19083 exfilRate = exfil_getLoss(exfil, tStep, depth, area);
1090 }
1091
1092 // --- total loss over time step cannot exceed stored volume
1093 76437 totalLoss = (evapRate + exfilRate) * tStep;
1094
2/2
✓ Branch 0 taken 450 times.
✓ Branch 1 taken 75987 times.
76437 if ( totalLoss > Node[j].newVolume )
1095 {
1096 450 lossRatio = Node[j].newVolume / totalLoss;
1097 450 evapRate *= lossRatio;
1098 450 exfilRate *= lossRatio;
1099 }
1100 }
1101
1102 // --- save evap & infil losses at the node
1103 143130 Storage[Node[j].subIndex].evapLoss = evapRate * tStep;
1104 143130 Storage[Node[j].subIndex].exfilLoss = exfilRate * tStep;
1105 143130 return evapRate + exfilRate;
1106 }
1107
1108
1109 //=============================================================================
1110 // D I V I D E R M E T H O D S
1111 //=============================================================================
1112
1113 4 int divider_readParams(int j, int k, char* tok[], int ntoks)
1114 //
1115 // Input: j = node index
1116 // k = divider index
1117 // tok[] = array of string tokens
1118 // ntoks = number of tokens
1119 // Output: returns an error message
1120 // Purpose: reads a flow divider's properties from a tokenized line of input.
1121 //
1122 // Format of input line is:
1123 // nodeID elev divLink TABULAR curveID (optional params)
1124 // nodeID elev divLink OVERFLOW (optional params)
1125 // nodeID elev divLink CUTOFF qCutoff (optional params)
1126 // nodeID elev divLink WEIR qMin dhMax cWeir (optional params)
1127 // where optional params are:
1128 // maxDepth initDepth surDepth aPond
1129 //
1130 {
1131 int i, m, m1, m2, n;
1132 double x[11];
1133 char* id;
1134
1135 // --- get ID name
1136
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if ( ntoks < 4 ) return error_setInpError(ERR_ITEMS, "");
1137 4 id = project_findID(NODE, tok[0]);
1138
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]);
1139
1140 // --- get invert elev.
1141
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
4 if ( ! getDouble(tok[1], &x[0]) ) return error_setInpError(ERR_NUMBER, tok[1]);
1142
1143 // --- initialize parameter values
1144
2/2
✓ Branch 0 taken 40 times.
✓ Branch 1 taken 4 times.
44 for ( i=1; i<11; i++) x[i] = 0.0;
1145
1146 // --- check if no diverted link supplied
1147
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
4 if ( strlen(tok[2]) == 0 || strcmp(tok[2], "*") == 0 ) x[1] = -1.0;
1148
1149 // --- otherwise get index of diverted link
1150 else
1151 {
1152 4 m1 = project_findObject(LINK, tok[2]);
1153
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if ( m1 < 0 ) return error_setInpError(ERR_NAME, tok[2]);
1154 4 x[1] = m1;
1155 }
1156
1157 // --- get divider type
1158 4 n = 4;
1159 4 m1 = findmatch(tok[3], DividerTypeWords);
1160
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if ( m1 < 0 ) return error_setInpError(ERR_KEYWORD, tok[3]);
1161 4 x[2] = m1;
1162
1163 // --- get index of flow diversion curve for Tabular divider
1164 4 x[3] = -1;
1165
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 3 times.
4 if ( m1 == TABULAR_DIVIDER )
1166 {
1167
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 if ( ntoks < 5 ) return error_setInpError(ERR_ITEMS, "");
1168 1 m2 = project_findObject(CURVE, tok[4]);
1169
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 if ( m2 < 0 ) return error_setInpError(ERR_NAME, tok[4]);
1170 1 x[3] = m2;
1171 1 n = 5;
1172 }
1173
1174 // --- get cutoff flow for Cutoff divider
1175
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 3 times.
4 if ( m1 == CUTOFF_DIVIDER )
1176 {
1177
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 if ( ntoks < 5 ) return error_setInpError(ERR_ITEMS, "");
1178
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
1 if ( ! getDouble(tok[4], &x[4]) )
1179 return error_setInpError(ERR_NUMBER, tok[4]);
1180 1 n = 5;
1181 }
1182
1183 // --- get qmin, dhMax, & cWeir for Weir divider
1184
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 3 times.
4 if ( m1 == WEIR_DIVIDER )
1185 {
1186
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, "");
1187
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
4 for (i=4; i<7; i++)
1188
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if ( ! getDouble(tok[i], &x[i]) )
1189 return error_setInpError(ERR_NUMBER, tok[i]);
1190 1 n = 7;
1191 }
1192
1193 // --- no parameters needed for Overflow divider
1194
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 3 times.
4 if ( m1 == OVERFLOW_DIVIDER ) n = 4;
1195
1196 // --- retrieve optional full depth, init. depth, surcharged depth
1197 // & ponded area
1198 4 m = 7;
1199
3/4
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
20 for (i=n; i<ntoks && m<11; i++)
1200 {
1201
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
16 if ( ! getDouble(tok[i], &x[m]) )
1202 {
1203 return error_setInpError(ERR_NUMBER, tok[i]);
1204 }
1205 16 m++;
1206 }
1207
1208 // --- add parameters to data base
1209 4 Node[j].ID = id;
1210 4 node_setParams(j, DIVIDER, k, x);
1211 4 return 0;
1212 }
1213
1214 //=============================================================================
1215
1216 4 void divider_validate(int j)
1217 //
1218 // Input: j = node index
1219 // Output: none
1220 // Purpose: validates a flow divider's properties.
1221 //
1222 {
1223 int i, k;
1224
1225 // --- check that diverted link is attached to divider
1226 4 k = Node[j].subIndex;
1227 4 i = Divider[k].link;
1228
2/6
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
4 if ( i < 0 || ( Link[i].node1 != j && Link[i].node2 != j) )
1229 {
1230 report_writeErrorMsg(ERR_DIVIDER_LINK, Node[j].ID);
1231 }
1232
1233 // --- validate parameters supplied for weir-type divider
1234
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 3 times.
4 if ( Divider[k].type == WEIR_DIVIDER )
1235 {
1236
2/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
1 if ( Divider[k].dhMax <= 0.0 || Divider[k].cWeir <= 0.0 )
1237 report_writeErrorMsg(ERR_WEIR_DIVIDER, Node[j].ID);
1238 else
1239 {
1240 // --- find flow when weir is full
1241 2 Divider[k].qMax = Divider[k].cWeir * pow(Divider[k].dhMax, 1.5)
1242 1 / UCF(FLOW);
1243
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 if ( Divider[k].qMin > Divider[k].qMax )
1244 report_writeErrorMsg(ERR_WEIR_DIVIDER, Node[j].ID);
1245 }
1246 }
1247 4 }
1248
1249 //=============================================================================
1250
1251 1920 double divider_getOutflow(int j, int k)
1252 //
1253 // Input: j = node index
1254 // k = index of diversion link
1255 // Output: returns diverted flow rate (cfs)
1256 // Purpose: computes flow sent through divider node into its diversion link.
1257 //
1258 // NOTE: requires that links be previously sorted so that the non-diversion
1259 // link always gets evaluated before the diversion link
1260 {
1261 int i; // index of divider node
1262 int m; // index of diverted flow table
1263 double qIn; // inflow to divider
1264 double qOut; // diverted outflow
1265 double f; // fraction of weir divider full
1266
1267 1920 qIn = Node[j].inflow + Node[j].overflow;
1268 1920 i = Node[j].subIndex;
1269
4/5
✓ Branch 0 taken 480 times.
✓ Branch 1 taken 480 times.
✓ Branch 2 taken 480 times.
✓ Branch 3 taken 480 times.
✗ Branch 4 not taken.
1920 switch ( Divider[i].type )
1270 {
1271 480 case CUTOFF_DIVIDER:
1272
2/2
✓ Branch 0 taken 200 times.
✓ Branch 1 taken 280 times.
480 if ( qIn <= Divider[i].qMin ) qOut = 0.0;
1273 280 else qOut = qIn - Divider[i].qMin;
1274 480 break;
1275
1276 480 case OVERFLOW_DIVIDER:
1277 // --- outflow sent into non-diversion link is simply node's inflow
1278
2/2
✓ Branch 0 taken 240 times.
✓ Branch 1 taken 240 times.
480 if ( k != Divider[i].link ) qOut = qIn;
1279
1280 // --- diversion link receives any excess of node's inflow and
1281 // outflow sent previously into non-diversion link
1282 240 else qOut = qIn - Node[j].outflow;
1283
2/2
✓ Branch 0 taken 241 times.
✓ Branch 1 taken 239 times.
480 if ( qOut < FLOW_TOL ) qOut = 0.0;
1284 480 return qOut;
1285
1286 480 case WEIR_DIVIDER:
1287 // --- no flow if inflow < qMin
1288
2/2
✓ Branch 0 taken 120 times.
✓ Branch 1 taken 360 times.
480 if ( qIn <= Divider[i].qMin ) qOut = 0.0;
1289
1290 // --- otherwise use weir eqn.
1291 else
1292 {
1293 // --- find fractional depth of flow over weir
1294 360 f = (qIn - Divider[i].qMin) /
1295 360 (Divider[i].qMax - Divider[i].qMin);
1296
1297 // --- if weir surcharged, use orifice eqn.
1298
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 360 times.
360 if ( f > 1.0 ) qOut = Divider[i].qMax * sqrt(f);
1299
1300 // --- otherwise use weir eqn.
1301 360 else qOut = Divider[i].cWeir *
1302 360 pow(f*Divider[i].dhMax, 1.5) / UCF(FLOW);
1303 }
1304 480 break;
1305
1306 480 case TABULAR_DIVIDER:
1307 480 m = Divider[i].flowCurve;
1308
1/2
✓ Branch 0 taken 480 times.
✗ Branch 1 not taken.
480 if ( m >= 0 )
1309 480 qOut = table_lookup(&Curve[m], qIn * UCF(FLOW)) / UCF(FLOW);
1310 else qOut = 0.0;
1311 480 break;
1312
1313 default: qOut = 0.0;
1314 }
1315
1316 // --- make sure outflow doesn't exceed inflow
1317
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1440 times.
1440 if ( qOut > qIn ) qOut = qIn;
1318
1319 // --- if link k not the diversion link, then re-define qOut as
1320 // the undiverted flow
1321
2/2
✓ Branch 0 taken 720 times.
✓ Branch 1 taken 720 times.
1440 if ( k != Divider[i].link )
1322 {
1323 720 qOut = qIn - qOut;
1324 }
1325 1440 return qOut;
1326 }
1327
1328
1329 //=============================================================================
1330 // O U T F A L L M E T H O D S
1331 //=============================================================================
1332
1333 348 int outfall_readParams(int j, int k, char* tok[], int ntoks)
1334 //
1335 // Input: j = node index
1336 // k = outfall index
1337 // tok[] = array of string tokens
1338 // ntoks = number of tokens
1339 // Output: returns an error message
1340 // Purpose: reads an outfall's properties from a tokenized line of input.
1341 //
1342 // Format of input line is:
1343 // nodeID elev FIXED fixedStage (flapGate) (routeTo)
1344 // nodeID elev TIDAL curveID (flapGate) (routeTo)
1345 // nodeID elev TIMESERIES tseriesID (flapGate) (routTo)
1346 // nodeID elev FREE (flapGate) (routeTo)
1347 // nodeID elev NORMAL (flapGate) (routeTo)
1348 //
1349 {
1350 int i, m, n;
1351 double x[7];
1352 char* id;
1353
1354
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 348 times.
348 if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, "");
1355 348 id = project_findID(NODE, tok[0]); // node ID
1356
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 348 times.
348 if ( id == NULL )
1357 return error_setInpError(ERR_NAME, tok[0]);
1358
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 348 times.
348 if ( ! getDouble(tok[1], &x[0]) ) // invert elev.
1359 return error_setInpError(ERR_NUMBER, tok[1]);
1360 348 i = findmatch(tok[2], OutfallTypeWords); // outfall type
1361
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 348 times.
348 if ( i < 0 ) return error_setInpError(ERR_KEYWORD, tok[2]);
1362 348 x[1] = i; // outfall type
1363 348 x[2] = 0.0; // fixed stage
1364 348 x[3] = -1.; // tidal curve
1365 348 x[4] = -1.; // tide series
1366 348 x[5] = 0.; // flap gate
1367 348 x[6] = -1.; // route to subcatch
1368
1369 348 n = 4;
1370
2/2
✓ Branch 0 taken 247 times.
✓ Branch 1 taken 101 times.
348 if ( i >= FIXED_OUTFALL )
1371 {
1372
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 247 times.
247 if ( ntoks < 4 ) return error_setInpError(ERR_ITEMS, "");
1373 247 n = 5;
1374
3/4
✓ Branch 0 taken 19 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 226 times.
✗ Branch 3 not taken.
247 switch ( i )
1375 {
1376 19 case FIXED_OUTFALL: // fixed stage
1377
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 19 times.
19 if ( ! getDouble(tok[3], &x[2]) )
1378 return error_setInpError(ERR_NUMBER, tok[3]);
1379 19 break;
1380 2 case TIDAL_OUTFALL: // tidal curve
1381 2 m = project_findObject(CURVE, tok[3]);
1382
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if ( m < 0 ) return error_setInpError(ERR_NAME, tok[3]);
1383 2 x[3] = m;
1384 2 break;
1385 226 case TIMESERIES_OUTFALL: // stage time series
1386 226 m = project_findObject(TSERIES, tok[3]);
1387
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 226 times.
226 if ( m < 0 ) return error_setInpError(ERR_NAME, tok[3]);
1388 226 x[4] = m;
1389 226 Tseries[m].refersTo = TIMESERIES_OUTFALL;
1390 }
1391 }
1392
2/2
✓ Branch 0 taken 347 times.
✓ Branch 1 taken 1 time.
348 if ( ntoks == n )
1393 {
1394 347 m = findmatch(tok[n-1], NoYesWords); // flap gate
1395
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 347 times.
347 if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[n-1]);
1396 347 x[5] = m;
1397 }
1398
1399
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 347 times.
348 if ( ntoks == n+1)
1400 {
1401 1 m = project_findObject(SUBCATCH, tok[n]);
1402
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 if ( m < 0 ) return error_setInpError(ERR_NAME, tok[n]);
1403 1 x[6] = m;
1404 }
1405
1406 348 Node[j].ID = id;
1407 348 node_setParams(j, OUTFALL, k, x);
1408 348 return 0;
1409 }
1410
1411 //=============================================================================
1412
1413 1979248 void outfall_setOutletDepth(int j, double yNorm, double yCrit, double z)
1414 //
1415 // Input: j = node index
1416 // yNorm = normal flow depth in outfall conduit (ft)
1417 // yCrit = critical flow depth in outfall conduit (ft)
1418 // z = height to outfall conduit invert (ft)
1419 // Output: none
1420 // Purpose: sets water depth at an outfall node.
1421 //
1422 {
1423 double x, y; // x,y values in table
1424 double yNew; // new depth above invert elev. (ft)
1425 double stage; // water elevation at outfall (ft)
1426 int k; // table index
1427 1979248 int i = Node[j].subIndex; // outfall index
1428 DateTime currentDate; // current date/time in days
1429
1430
5/6
✓ Branch 0 taken 1651815 times.
✓ Branch 1 taken 48901 times.
✓ Branch 2 taken 223052 times.
✓ Branch 3 taken 2892 times.
✓ Branch 4 taken 52588 times.
✗ Branch 5 not taken.
1979248 switch ( Outfall[i].type )
1431 {
1432 1651815 case FREE_OUTFALL:
1433
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1651815 times.
1651815 if ( z > 0.0 ) Node[j].newDepth = 0.0;
1434
2/2
✓ Branch 0 taken 508221 times.
✓ Branch 1 taken 1143594 times.
1651815 else Node[j].newDepth = MIN(yNorm, yCrit);
1435 1700716 return;
1436
1437 48901 case NORMAL_OUTFALL:
1438
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48901 times.
48901 if ( z > 0.0 ) Node[j].newDepth = 0.0;
1439 48901 else Node[j].newDepth = yNorm;
1440 48901 return;
1441
1442 223052 case FIXED_OUTFALL:
1443 223052 stage = Outfall[i].fixedStage;
1444 223052 break;
1445
1446 2892 case TIDAL_OUTFALL:
1447 2892 k = Outfall[i].tideCurve;
1448 2892 table_getFirstEntry(&Curve[k], &x, &y);
1449 2892 currentDate = NewRoutingTime / MSECperDAY;
1450 2892 x += ( currentDate - floor(currentDate) ) * 24.0;
1451 2892 stage = table_lookup(&Curve[k], x) / UCF(LENGTH);
1452 2892 break;
1453
1454 52588 case TIMESERIES_OUTFALL:
1455 52588 k = Outfall[i].stageSeries;
1456 52588 currentDate = StartDateTime + NewRoutingTime / MSECperDAY;
1457 52588 stage = table_tseriesLookup(&Tseries[k], currentDate, TRUE) /
1458 52588 UCF(LENGTH);
1459 52588 break;
1460 default: stage = Node[j].invertElev;
1461 }
1462
1463 // --- now determine depth at node given outfall stage elev.
1464
1465 // --- let critical flow depth be min. of critical & normal depth
1466
2/2
✓ Branch 0 taken 242939 times.
✓ Branch 1 taken 35593 times.
278532 yCrit = MIN(yCrit, yNorm);
1467
1468 // --- if elev. of critical depth is below outfall stage elev. then
1469 // the outfall stage determines node depth
1470
2/2
✓ Branch 0 taken 261290 times.
✓ Branch 1 taken 17242 times.
278532 if ( yCrit + z + Node[j].invertElev < stage )
1471 {
1472 261290 yNew = stage - Node[j].invertElev;
1473 }
1474
1475 // --- otherwise if the outfall conduit lies above the outfall invert
1476
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 17242 times.
17242 else if ( z > 0.0 )
1477 {
1478 // --- if the outfall stage lies below the bottom of the outfall
1479 // conduit then the result is distance from node invert to stage
1480 if ( stage < Node[j].invertElev + z )
1481 yNew = MAX(0.0, (stage - Node[j].invertElev));
1482
1483 // --- otherwise stage lies between bottom of conduit and critical
1484 // depth in conduit so result is elev. of critical depth
1485 else yNew = z + yCrit;
1486 }
1487
1488 // --- and for case where there is no conduit offset and outfall stage
1489 // lies below critical depth, then node depth = critical depth
1490 17242 else yNew = yCrit;
1491 278532 Node[j].newDepth = yNew;
1492 }
1493
1494 //=============================================================================
1495