GCC Code Coverage Report


Directory: src/solver/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 81.4% 508 / 0 / 624
Functions: 100.0% 30 / 0 / 30
Branches: 60.2% 315 / 0 / 523

controls.c
Line Branch Exec Source
1 //-----------------------------------------------------------------------------
2 // controls.c
3 //
4 // Project: EPA SWMM5
5 // Version: 5.2
6 // Date: 06/01/22 (Build 5.2.1)
7 // Author: L. Rossman
8 //
9 // Rule-based controls functions.
10 //
11 // Control rules have the format:
12 // RULE name
13 // IF <premise>
14 // AND / OR <premise>
15 // etc.
16 // THEN <action>
17 // AND <action>
18 // etc.
19 // ELSE <action>
20 // AND <action>
21 // etc.
22 // PRIORITY <p>
23 //
24 // <premise> consists of:
25 // <variable> <relational operator> value / <variable>
26 // where <variable> is <object type> <id name> <attribute>
27 // E.g.: Node 123 Depth > 4.5
28 // Node 456 Depth < Node 123 Depth
29 //
30 // <action> consists of:
31 // <variable> = setting
32 // E.g.: Pump abc status = OFF
33 // Weir xyz setting = 0.5
34 //
35 // Update History
36 // ==============
37 // Build 5.1.008:
38 // - Support added for r.h.s. variables in rule premises.
39 // - Node volume added as a premise variable.
40 // Build 5.1.009:
41 // - Fixed problem with parsing a RHS premise variable.
42 // Build 5.1.010:
43 // - Support added for link TIMEOPEN & TIMECLOSED premises.
44 // Build 5.1.011:
45 // - Support added for DAYOFYEAR attribute.
46 // - Modulated controls no longer included in reported control actions.
47 // Build 5.2.0:
48 // - Additional attributes added to condition clauses.
49 // - Support added for named variables in condition clauses.
50 // - Support added for math expressions in condition clauses.
51 // Build 5.2.1:
52 // - A refactoring bug from 5.2.0 causing duplicate actions to be added
53 // to the list of control actions to take was fixed.
54 //-----------------------------------------------------------------------------
55 #define _CRT_SECURE_NO_DEPRECATE
56
57 #include <string.h>
58 #include <stdlib.h>
59 #include <math.h>
60 #include "headers.h"
61
62 //-----------------------------------------------------------------------------
63 // Constants
64 //-----------------------------------------------------------------------------
65 enum RuleState {r_RULE, r_IF, r_AND, r_OR, r_THEN, r_ELSE, r_PRIORITY,
66 r_VARIABLE, r_EXPRESSION, r_ERROR};
67 enum RuleObject {r_GAGE, r_NODE, r_LINK, r_CONDUIT, r_PUMP, r_ORIFICE,
68 r_WEIR, r_OUTLET, r_SIMULATION};
69 enum RuleAttrib {r_DEPTH, r_MAXDEPTH, r_HEAD, r_VOLUME, r_INFLOW,
70 r_FLOW, r_FULLFLOW, r_FULLDEPTH, r_STATUS, r_SETTING,
71 r_LENGTH, r_SLOPE, r_VELOCITY, r_TIMEOPEN, r_TIMECLOSED,
72 r_TIME, r_DATE, r_CLOCKTIME, r_DAYOFYEAR, r_DAY, r_MONTH};
73 enum RuleRelation {EQ, NE, LT, LE, GT, GE};
74 enum RuleSetting {r_CURVE, r_TIMESERIES, r_PID, r_NUMERIC};
75
76 #define MAXVARNAME 32
77
78 static char* ObjectWords[] =
79 {"GAGE", "NODE", "LINK", "CONDUIT", "PUMP", "ORIFICE", "WEIR", "OUTLET",
80 "SIMULATION", NULL};
81 static char* AttribWords[] =
82 {"DEPTH", "MAXDEPTH", "HEAD", "VOLUME", "INFLOW",
83 "FLOW", "FULLFLOW", "FULLDEPTH", "STATUS", "SETTING",
84 "LENGTH", "SLOPE", "VELOCITY", "TIMEOPEN", "TIMECLOSED",
85 "TIME", "DATE", "CLOCKTIME", "DAYOFYEAR", "DAY", "MONTH", NULL};
86 static char* RelOpWords[] = {"=", "<>", "<", "<=", ">", ">=", NULL};
87 static char* StatusWords[] = {"OFF", "ON", NULL};
88 static char* ConduitWords[] = {"CLOSED", "OPEN", NULL};
89 static char* SettingTypeWords[] = {"CURVE", "TIMESERIES", "PID", NULL};
90 static char* IntensityWord = "INTENSITY";
91
92 //-----------------------------------------------------------------------------
93 // Data Structures
94 //-----------------------------------------------------------------------------
95 // Rule Premise Variable
96 struct TVariable
97 {
98 int object; // type of object
99 int index; // index in object's array
100 int attribute; // object's attribute
101 };
102
103 // Named Variable
104 struct TNamedVariable
105 {
106 struct TVariable variable; // a rule premise variable
107 char name[MAXVARNAME+1]; // name used in math expression
108 };
109
110 // Rule Premise Function
111 struct TExpression
112 {
113 MathExpr* expression; // tokenized math expression
114 char name[MAXVARNAME+1]; // expression name
115 };
116
117 // Rule Premise Clause
118 struct TPremise
119 {
120 int type; // clause type (IF/AND/OR)
121 int exprIndex; // expression index (-1 if N/A)
122 struct TVariable lhsVar; // left hand side variable
123 struct TVariable rhsVar; // right hand side variable
124 int relation; // relational operator (>, <, =, etc)
125 double value; // right hand side value
126 struct TPremise *next; // next premise clause of rule
127 };
128
129 // Rule Action Clause
130 struct TAction
131 {
132 int rule; // index of rule that action belongs to
133 int link; // index of link being controlled
134 int attribute; // attribute of link being controlled
135 int curve; // index of curve for modulated control
136 int tseries; // index of time series for modulated control
137 double value; // control setting for link attribute
138 double kp, ki, kd; // coeffs. for PID modulated control
139 double e1, e2; // PID set point error from previous time steps
140 struct TAction *next; // next action clause of rule
141 };
142
143 // List of Control Actions
144 struct TActionList
145 {
146 struct TAction* action;
147 struct TActionList* next;
148 };
149
150 // Control Rule
151 struct TRule
152 {
153 char* ID; // rule ID
154 double priority; // priority level
155 struct TPremise* firstPremise; // pointer to first premise of rule
156 struct TPremise* lastPremise; // pointer to last premise of rule
157 struct TAction* thenActions; // linked list of actions if true
158 struct TAction* elseActions; // linked list of actions if false
159 };
160
161 //-----------------------------------------------------------------------------
162 // Shared variables
163 //-----------------------------------------------------------------------------
164 struct TRule* Rules; // array of control rules
165 struct TActionList* ActionList; // linked list of control actions
166 int InputState; // state of rule interpreter
167 int RuleCount; // total number of rules
168 double ControlValue; // value of controller variable
169 double SetPoint; // value of controller setpoint
170 DateTime CurrentDate; // current date in whole days
171 DateTime CurrentTime; // current time of day (decimal)
172
173 int VariableCount;
174 int ExpressionCount;
175 int CurrentVariable;
176 int CurrentExpression;
177 struct TNamedVariable* NamedVariable; // array of named variables
178 struct TExpression* Expression; // array of math expressions
179
180 //-----------------------------------------------------------------------------
181 // External functions (declared in funcs.h)
182 //-----------------------------------------------------------------------------
183 // controls_create
184 // controls_delete
185 // controls_init
186 // controls_addToCount
187 // controls_addVariable
188 // controls_addExpression
189 // controls_addRuleClause
190 // controls_evaluate
191
192 //-----------------------------------------------------------------------------
193 // Local functions
194 //-----------------------------------------------------------------------------
195 int addPremise(int r, int type, char* Tok[], int nToks);
196 int getPremiseVariable(char* tok[], int nToks, int* k, struct TVariable* v);
197 int getPremiseValue(char* token, int attrib, double* value);
198 int addAction(int r, char* Tok[], int nToks);
199
200 int evaluatePremise(struct TPremise* p, double tStep);
201 double getVariableValue(struct TVariable v);
202 int compareTimes(double lhsValue, int relation, double rhsValue,
203 double halfStep);
204 int compareValues(double lhsValue, int relation, double rhsValue);
205
206 void updateActionList(struct TAction* a);
207 int executeActionList(DateTime currentTime);
208 void clearActionList(void);
209 void deleteActionList(void);
210 void deleteRules(void);
211
212 int findExactMatch(char *s, char *keyword[]);
213 int setActionSetting(char* tok[], int nToks, int* curve, int* tseries,
214 int* attrib, double value[]);
215 void updateActionValue(struct TAction* a, DateTime currentTime, double dt);
216 double getPIDSetting(struct TAction* a, double dt);
217
218 int getVariableIndex(char* varName);
219 double getNamedVariableValue(int varIndex);
220 int getExpressionIndex(char* exprName);
221 int getGageAttrib(char* token);
222 double getRainValue(struct TVariable v);
223
224 //=============================================================================
225
226 58 void controls_init()
227 //
228 // Input: none
229 // Output: none
230 // Purpose: initializes the control rule system.
231 //
232 {
233 58 Rules = NULL;
234 58 NamedVariable = NULL;
235 58 Expression = NULL;
236 58 RuleCount = 0;
237 58 VariableCount = 0;
238 58 ExpressionCount = 0;
239 58 }
240
241 //=============================================================================
242
243 267 void controls_addToCount(char* s)
244 //
245 // Input: s = either VARIABLE or EXPRESSION
246 // Output: none
247 // Purpose: updates the number of named variables or math expressions used
248 // by control rules.
249 //
250 {
251
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 264 times.
267 if (match(s, w_VARIABLE)) VariableCount++;
252
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 258 times.
264 else if (match(s, w_EXPRESSION)) ExpressionCount++;
253 267 }
254
255 //=============================================================================
256
257 58 int controls_create(int n)
258 //
259 // Input: n = total number of control rules
260 // Output: returns error code
261 // Purpose: creates an array of control rules.
262 //
263 {
264 int r;
265 58 ActionList = NULL;
266 58 InputState = r_PRIORITY;
267 58 RuleCount = n;
268
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 46 times.
58 if (RuleCount > 0)
269 {
270 12 Rules = (struct TRule *) calloc(RuleCount, sizeof(struct TRule));
271
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (Rules == NULL) return ERR_MEMORY;
272
2/2
✓ Branch 0 taken 76 times.
✓ Branch 1 taken 12 times.
88 for ( r=0; r<RuleCount; r++ )
273 {
274 76 Rules[r].ID = NULL;
275 76 Rules[r].firstPremise = NULL;
276 76 Rules[r].lastPremise = NULL;
277 76 Rules[r].thenActions = NULL;
278 76 Rules[r].elseActions = NULL;
279 76 Rules[r].priority = 0.0;
280 }
281 }
282
283 58 CurrentVariable = -1;
284 58 CurrentExpression = -1;
285
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 56 times.
58 if (VariableCount > 0)
286 {
287 2 NamedVariable = (struct TNamedVariable *) calloc(VariableCount,
288 sizeof(struct TNamedVariable));
289
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (NamedVariable == NULL) return ERR_MEMORY;
290 }
291
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 56 times.
58 if (ExpressionCount > 0)
292 {
293 2 Expression = (struct TExpression *) calloc(ExpressionCount,
294 sizeof(struct TExpression));
295
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (Expression == NULL) return ERR_MEMORY;
296 }
297 58 return 0;
298 }
299
300 //=============================================================================
301
302 58 void controls_delete(void)
303 //
304 // Input: none
305 // Output: none
306 // Purpose: deletes all control rules.
307 //
308 {
309 int i;
310
311
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 58 times.
64 for (i = 0; i < ExpressionCount; i++)
312 {
313 6 mathexpr_delete(Expression[i].expression);
314 6 Expression[i].expression = NULL;
315 }
316
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 56 times.
58 FREE(Expression);
317
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 56 times.
58 FREE(NamedVariable);
318
319
2/2
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 12 times.
58 if ( RuleCount == 0 ) return;
320 12 deleteActionList();
321 12 deleteRules();
322 }
323
324 //=============================================================================
325
326 3 int controls_addVariable(char* tok[], int nToks)
327 //
328 // Input: tok = an array of string tokens
329 // n = the size of tok[]
330 // Output: returns error code
331 // Purpose: adds a named variable to the control rule system from a
332 // tokenized line of input with formats:
333 // VARIABLE name = Object id attribute
334 // VARIABLE name = SIMULATION attribute
335 //
336 {
337 struct TVariable v1;
338 int k, err;
339
340 3 CurrentVariable++;
341
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (nToks < 5) return ERR_ITEMS;
342
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if (findExactMatch(tok[1], AttribWords) >= 0)
343 return error_setInpError(ERR_KEYWORD, tok[1]);
344
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if (!match(tok[2], "=")) return error_setInpError(ERR_KEYWORD, tok[2]);
345
2/4
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
3 if (!match(tok[3], "SIMULATION") && nToks < 6) return ERR_ITEMS;
346 3 k = 3;
347 3 err = getPremiseVariable(tok, nToks, &k, &v1);
348
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (err > 0) return err;
349 3 k = CurrentVariable;
350 3 NamedVariable[k].variable = v1;
351 3 sstrncpy(NamedVariable[k].name, tok[1], MAXVARNAME);
352 3 return 0;
353 }
354
355 //=============================================================================
356
357 6 int controls_addExpression(char* tok[], int nToks)
358 //
359 // Input: tok = an array of string tokens
360 // n = number of tokens
361 // Output: returns error code
362 // Purpose: adds a math expression to the control rule system from a
363 // a tokenized line of input with format:
364 // EXPRESSION name = <math expression containing VARIABLE names>
365 //
366 {
367 int i, k;
368 char s[MAXLINE + 1];
369 MathExpr* expr;
370
371 6 CurrentExpression++;
372
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (nToks < 4) return ERR_ITEMS;
373 6 k = CurrentExpression;
374 6 Expression[k].expression = NULL;
375 6 sstrncpy(Expression[k].name, tok[1], MAXVARNAME);
376 6 sstrncpy(s, tok[3], MAXLINE);
377
2/2
✓ Branch 0 taken 124 times.
✓ Branch 1 taken 6 times.
130 for (i = 4; i < nToks; i++)
378 {
379 124 sstrcat(s, " ", MAXLINE);
380 124 sstrcat(s, tok[i], MAXLINE);
381 }
382
383 6 expr = mathexpr_create(s, getVariableIndex);
384
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (expr == NULL)
385 return error_setInpError(ERR_MATH_EXPR, "");
386
387 6 Expression[k].expression = expr;
388 6 return 0;
389 }
390
391 //=============================================================================
392
393 207 int getVariableIndex(char* varName)
394 //
395 // Input: varName = string containing a variable name
396 // Output: returns the index of the named variable or -1 if not found
397 // Purpose: finds the array index of a named variable.
398 //
399 {
400 int i;
401
2/2
✓ Branch 0 taken 52 times.
✓ Branch 1 taken 178 times.
230 for (i = 0; i < VariableCount; i++)
402 {
403
2/2
✓ Branch 1 taken 29 times.
✓ Branch 2 taken 23 times.
52 if (match(varName, NamedVariable[i].name)) return i;
404 }
405 178 return -1;
406 }
407
408 //=============================================================================
409
410 6960 double getNamedVariableValue(int varIndex)
411 //
412 // Input: varIndex = index of a named variable
413 // Output: returns the current value of the variable
414 // Purpose: finds the value of a named variable.
415 //
416 {
417 6960 return getVariableValue(NamedVariable[varIndex].variable);
418 }
419
420 //=============================================================================
421
422 92 int getExpressionIndex(char* exprName)
423 //
424 // Input: exprName = string containing an expression name
425 // Output: returns the index of the expression or -1 if not found
426 // Purpose: finds the array index of a math expression
427 //
428 {
429 int i;
430
2/2
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 86 times.
107 for (i = 0; i < ExpressionCount; i++)
431 {
432
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 15 times.
21 if (match(exprName, Expression[i].name)) return i;
433 }
434 86 return -1;
435 }
436
437 //=============================================================================
438
439 334 int controls_addRuleClause(int r, int keyword, char* tok[], int nToks)
440 //
441 // Input: r = rule index
442 // keyword = the clause's keyword code (IF, THEN, etc.)
443 // tok = an array of string tokens that comprises the clause
444 // nToks = number of tokens
445 // Output: returns an error code
446 // Purpose: addd a new clause to a control rule.
447 //
448 {
449
7/8
✓ Branch 0 taken 76 times.
✓ Branch 1 taken 76 times.
✓ Branch 2 taken 9 times.
✓ Branch 3 taken 8 times.
✓ Branch 4 taken 76 times.
✓ Branch 5 taken 20 times.
✓ Branch 6 taken 69 times.
✗ Branch 7 not taken.
334 switch (keyword)
450 {
451 76 case r_RULE:
452
1/2
✓ Branch 0 taken 76 times.
✗ Branch 1 not taken.
76 if ( Rules[r].ID == NULL )
453 76 Rules[r].ID = project_findID(CONTROL, tok[1]);
454 76 InputState = r_RULE;
455
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 76 times.
76 if ( nToks > 2 ) return ERR_RULE;
456 76 return 0;
457
458 76 case r_IF:
459
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 76 times.
76 if ( InputState != r_RULE ) return ERR_RULE;
460 76 InputState = r_IF;
461 76 return addPremise(r, r_AND, tok, nToks);
462
463 9 case r_AND:
464
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 1 time.
9 if ( InputState == r_IF ) return addPremise(r, r_AND, tok, nToks);
465
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 else if ( InputState == r_THEN || InputState == r_ELSE )
466 1 return addAction(r, tok, nToks);
467 else return ERR_RULE;
468
469 8 case r_OR:
470
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if ( InputState != r_IF ) return ERR_RULE;
471 8 return addPremise(r, r_OR, tok, nToks);
472
473 76 case r_THEN:
474
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 76 times.
76 if ( InputState != r_IF ) return ERR_RULE;
475 76 InputState = r_THEN;
476 76 return addAction(r, tok, nToks);
477
478 20 case r_ELSE:
479
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
20 if ( InputState != r_THEN ) return ERR_RULE;
480 20 InputState = r_ELSE;
481 20 return addAction(r, tok, nToks);
482
483 69 case r_PRIORITY:
484
3/4
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 20 times.
69 if ( InputState != r_THEN && InputState != r_ELSE ) return ERR_RULE;
485 69 InputState = r_PRIORITY;
486
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 69 times.
69 if ( !getDouble(tok[1], &Rules[r].priority) ) return ERR_NUMBER;
487
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 69 times.
69 if ( nToks > 2 ) return ERR_RULE;
488 69 return 0;
489 }
490 return 0;
491 }
492
493 //=============================================================================
494
495 1064980 int controls_evaluate(DateTime currentTime, DateTime elapsedTime, double tStep)
496 //
497 // Input: currentTime = current simulation date/time
498 // elapsedTime = decimal days since start of simulation
499 // tStep = simulation time step (days)
500 // Output: returns number of new actions taken
501 // Purpose: evaluates all control rules at current time of the simulation.
502 //
503 {
504 int r; // control rule index
505 int result; // TRUE if rule premises satisfied
506 struct TPremise* p; // pointer to rule premise clause
507 struct TAction* a; // pointer to rule action clause
508
509 // --- save date and time to shared variables
510 1064980 CurrentDate = floor(currentTime);
511 1064980 CurrentTime = currentTime - floor(currentTime);
512 1064980 ElapsedTime = elapsedTime;
513
514 // --- evaluate each rule
515
2/2
✓ Branch 0 taken 1010750 times.
✓ Branch 1 taken 54230 times.
1064980 if ( RuleCount == 0 ) return 0;
516 54230 clearActionList();
517
2/2
✓ Branch 0 taken 215046 times.
✓ Branch 1 taken 54230 times.
269276 for (r=0; r<RuleCount; r++)
518 {
519 // --- evaluate rule's premises
520 215046 result = TRUE;
521 215046 p = Rules[r].firstPremise;
522
2/2
✓ Branch 0 taken 276611 times.
✓ Branch 1 taken 177260 times.
453871 while (p)
523 {
524
2/2
✓ Branch 0 taken 20102 times.
✓ Branch 1 taken 256509 times.
276611 if ( p->type == r_OR )
525 {
526
2/2
✓ Branch 0 taken 125 times.
✓ Branch 1 taken 19977 times.
20102 if ( result == FALSE )
527 125 result = evaluatePremise(p, tStep);
528 }
529 else
530 {
531
2/2
✓ Branch 0 taken 37786 times.
✓ Branch 1 taken 218723 times.
256509 if ( result == FALSE ) break;
532 218723 result = evaluatePremise(p, tStep);
533 }
534 238825 p = p->next;
535 }
536
537 // --- if premises true, add THEN clauses to action list
538 // else add ELSE clauses to action list
539
2/2
✓ Branch 0 taken 92710 times.
✓ Branch 1 taken 122336 times.
215046 if ( result == TRUE ) a = Rules[r].thenActions;
540 122336 else a = Rules[r].elseActions;
541
2/2
✓ Branch 0 taken 113225 times.
✓ Branch 1 taken 215046 times.
328271 while (a)
542 {
543 113225 updateActionValue(a, currentTime, tStep);
544 113225 updateActionList(a);
545 113225 a = a->next;
546 }
547 }
548
549 // --- execute actions on action list
550
2/2
✓ Branch 0 taken 32629 times.
✓ Branch 1 taken 21601 times.
54230 if ( ActionList ) return executeActionList(currentTime);
551 21601 else return 0;
552 }
553
554 //=============================================================================
555
556 92 int addPremise(int r, int type, char* tok[], int nToks)
557 //
558 // Input: r = control rule index
559 // type = type of premise (IF, AND, OR)
560 // tok = array of string tokens containing premise statement
561 // nToks = number of string tokens
562 // Output: returns an error code
563 // Purpose: adds a new premise to a control rule.
564 //
565 {
566 92 int relation, n, err = 0;
567 92 double value = MISSING;
568 struct TPremise* p;
569 struct TVariable v1;
570 struct TVariable v2;
571 92 int obj, exprIndex, varIndex = -1;
572
573 // --- initialize LHS variable v1
574
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 92 times.
92 if (nToks < 4) return ERR_ITEMS;
575 92 v1.attribute = -1;
576 92 v1.object = -1;
577 92 v1.index = -1;
578 92 n = 1;
579
580 // --- check if 2nd token is a math expression
581 92 exprIndex = getExpressionIndex(tok[1]);
582
583 // --- if not then check if it's a named variable
584
2/2
✓ Branch 0 taken 86 times.
✓ Branch 1 taken 6 times.
92 if (exprIndex < 0)
585 {
586 86 varIndex = getVariableIndex(tok[n]);
587
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 86 times.
86 if (varIndex >= 0)
588 {
589 v1 = NamedVariable[varIndex].variable;
590 }
591
592 // otherwise parse object|index|attribute tokens
593 else
594 {
595 86 err = getPremiseVariable(tok, nToks, &n, &v1);
596
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 86 times.
86 if ( err > 0 ) return err;
597 }
598 }
599
600 // --- get relational operator
601 92 n++;
602
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 92 times.
92 if ( n >= nToks ) return error_setInpError(ERR_ITEMS, "");
603 92 relation = findExactMatch(tok[n], RelOpWords);
604
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 92 times.
92 if ( relation < 0 ) return error_setInpError(ERR_KEYWORD, tok[n]);
605
606 // --- initialize RHS variable v2
607 92 v2.attribute = -1;
608 92 v2.object = -1;
609 92 v2.index = -1;
610 92 n++;
611
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 92 times.
92 if (n >= nToks) return error_setInpError(ERR_ITEMS, "");
612
613 // --- check for named RHS variable
614 92 varIndex = getVariableIndex(tok[n]);
615
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 92 times.
92 if (varIndex >= 0)
616 {
617 v2 = NamedVariable[varIndex].variable;
618 }
619
620 // --- check for object|index|attribute variable
621 else
622 {
623 92 obj = findmatch(tok[n], ObjectWords);
624
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 92 times.
92 if (obj >= 0)
625 {
626 err = getPremiseVariable(tok, nToks, &n, &v2);
627 if ( err > 0 ) return ERR_RULE;
628 if (exprIndex < 0 && v1.attribute != v2.attribute)
629 report_writeWarningMsg(WARN11, Rules[r].ID);
630 }
631
632 // --- check for a single RHS value
633 else
634 {
635 92 err = getPremiseValue(tok[n], v1.attribute, &value);
636
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 92 times.
92 if ( err > 0 ) return err;
637 }
638 }
639
640 // --- make sure another clause is not on same line
641 92 n++;
642
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 92 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
92 if ( n < nToks && findmatch(tok[n], RuleKeyWords) >= 0 ) return ERR_RULE;
643
644 // --- create the premise object
645 92 p = (struct TPremise *) malloc(sizeof(struct TPremise));
646
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 92 times.
92 if ( !p ) return ERR_MEMORY;
647 92 p->type = type;
648 92 p->exprIndex = exprIndex;
649 92 p->lhsVar = v1;
650 92 p->rhsVar = v2;
651 92 p->relation = relation;
652 92 p->value = value;
653 92 p->next = NULL;
654
2/2
✓ Branch 0 taken 76 times.
✓ Branch 1 taken 16 times.
92 if ( Rules[r].firstPremise == NULL )
655 {
656 76 Rules[r].firstPremise = p;
657 }
658 else
659 {
660 16 Rules[r].lastPremise->next = p;
661 }
662 92 Rules[r].lastPremise = p;
663 92 return 0;
664 }
665
666 //=============================================================================
667
668 89 int getPremiseVariable(char* tok[], int nToks, int* k, struct TVariable* v)
669 //
670 // Input: tok = array of string tokens
671 // nToks = number of tokens
672 // k = index of current token
673 // Output: returns an error code; updates k to new current token and
674 // places identity of specified variable in v
675 // Purpose: parses a variable (e.g., Node 123 Depth) used in a control rule.
676 //
677 {
678 89 int n = *k;
679 89 int object = -1;
680 89 int index = -1;
681 int obj, attrib;
682
683 // --- get object type
684 89 obj = findmatch(tok[n], ObjectWords);
685
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 89 times.
89 if ( obj < 0 ) return error_setInpError(ERR_KEYWORD, tok[n]);
686
687 // --- get object index from its name
688 89 n++;
689
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 89 times.
89 if (n >= nToks) return error_setInpError(ERR_ITEMS, "");
690
4/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 53 times.
✓ Branch 2 taken 33 times.
✓ Branch 3 taken 1 time.
89 switch (obj)
691 {
692 2 case r_GAGE:
693 2 index = project_findObject(GAGE, tok[n]);
694
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (index < 0) return error_setInpError(ERR_NAME, tok[n]);
695 2 object = r_GAGE;
696 2 break;
697
698 53 case r_NODE:
699 53 index = project_findObject(NODE, tok[n]);
700
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 53 times.
53 if ( index < 0 ) return error_setInpError(ERR_NAME, tok[n]);
701 53 object = r_NODE;
702 53 break;
703
704 33 case r_LINK:
705 case r_CONDUIT:
706 case r_PUMP:
707 case r_ORIFICE:
708 case r_WEIR:
709 case r_OUTLET:
710 33 index = project_findObject(LINK, tok[n]);
711
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 33 times.
33 if ( index < 0 ) return error_setInpError(ERR_NAME, tok[n]);
712 33 object = r_LINK;
713 33 break;
714 1 default: n--;
715 }
716 89 n++;
717
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 89 times.
89 if (n >= nToks) return error_setInpError(ERR_ITEMS, "");
718
719 // --- get attribute index from its name
720
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 87 times.
89 if (object == r_GAGE)
721 2 attrib = getGageAttrib(tok[n]);
722 else
723 87 attrib = findmatch(tok[n], AttribWords);
724
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 89 times.
89 if ( attrib < 0 ) return error_setInpError(ERR_KEYWORD, tok[n]);
725
726 // --- check that attribute belongs to object type
727
2/2
✓ Branch 0 taken 87 times.
✓ Branch 1 taken 2 times.
89 if (obj == r_GAGE)
728 {
729
730 }
731
732
3/4
✓ Branch 0 taken 53 times.
✓ Branch 1 taken 34 times.
✓ Branch 2 taken 53 times.
✗ Branch 3 not taken.
87 else if ( obj == r_NODE ) switch (attrib)
733 {
734 53 case r_DEPTH:
735 case r_MAXDEPTH:
736 case r_HEAD:
737 case r_VOLUME:
738 53 case r_INFLOW: break;
739 default: return error_setInpError(ERR_KEYWORD, tok[n]);
740 }
741
742 // --- check for link TIMEOPEN & TIMECLOSED attributes
743
5/6
✓ Branch 0 taken 33 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 33 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 32 times.
✓ Branch 5 taken 1 time.
34 else if ( object == r_LINK && index >= 0 &&
744
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 31 times.
32 ( (attrib == r_TIMEOPEN || attrib == r_TIMECLOSED)
745 ))
746 {
747 // nothing to do here
748 }
749
750
5/6
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 21 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 9 times.
✓ Branch 4 taken 23 times.
✗ Branch 5 not taken.
55 else if ( obj == r_LINK || obj == r_CONDUIT ) switch (attrib)
751 {
752 23 case r_STATUS:
753 case r_DEPTH:
754 case r_FULLFLOW:
755 case r_FULLDEPTH:
756 case r_FLOW:
757 case r_LENGTH:
758 case r_SLOPE:
759 23 case r_VELOCITY: break;
760 default: return error_setInpError(ERR_KEYWORD, tok[n]);
761 }
762
3/4
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
9 else if ( obj == r_PUMP ) switch (attrib)
763 {
764 8 case r_FLOW:
765 case r_SETTING:
766 8 case r_STATUS: break;
767 default: return error_setInpError(ERR_KEYWORD, tok[n]);
768 }
769
3/6
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 time.
1 else if ( obj == r_ORIFICE || obj == r_WEIR ||
770 obj == r_OUTLET ) switch (attrib)
771 {
772 case r_FLOW:
773 case r_SETTING: break;
774 default: return error_setInpError(ERR_KEYWORD, tok[n]);
775 }
776
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 else switch (attrib)
777 {
778 1 case r_TIME:
779 case r_DATE:
780 case r_CLOCKTIME:
781 case r_DAY:
782 case r_MONTH:
783 1 case r_DAYOFYEAR: break;
784 default: return error_setInpError(ERR_KEYWORD, tok[n]);
785 }
786
787 // --- populate variable structure
788 89 v->object = object;
789 89 v->index = index;
790 89 v->attribute = attrib;
791 89 *k = n;
792 89 return 0;
793 }
794
795 //=============================================================================
796
797 2 int getGageAttrib(char* token)
798 //
799 // Input: token = a string token
800 // Output: returns an attribute code or -1 if an error occurred
801 // Purpose: determines the atrribute code for a rain gage variable.
802 // Note: a valid token is INTENSITY for current rainfall intensity
803 // (attribute code = 0) or nHR_PRECIP for total rain depth
804 // over past n hours (attribute code = n).
805 //
806 {
807 int attrib;
808
809 // --- check if token is currrent rainfall intensity
810
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
2 if (match(token, IntensityWord))
811 1 return 0;
812
813 // --- token is past rain depth - read number of past hours
814 1 attrib = atoi(token);
815
816 // --- check that number of hours is in allowable range
817
2/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
1 if (attrib < 1 || attrib > MAXPASTRAIN)
818 return -1;
819 1 return attrib;
820 }
821
822 //=============================================================================
823
824 92 int getPremiseValue(char* token, int attrib, double* value)
825 //
826 // Input: token = a string token
827 // attrib = index of a node/link attribute
828 // Output: value = attribute value;
829 // returns an error code;
830 // Purpose: parses the numerical value of a particular node/link attribute
831 // in the premise clause of a control rule.
832 //
833 {
834 char strDate[25];
835
3/7
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 88 times.
92 switch (attrib)
836 {
837 1 case r_STATUS:
838 1 *value = findmatch(token, StatusWords);
839
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 if ( *value < 0.0 ) *value = findmatch(token, ConduitWords);
840
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 if ( *value < 0.0 ) return error_setInpError(ERR_KEYWORD, token);
841 1 break;
842
843 3 case r_TIME:
844 case r_CLOCKTIME:
845 case r_TIMEOPEN:
846 case r_TIMECLOSED:
847
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if ( !datetime_strToTime(token, value) )
848 return error_setInpError(ERR_DATETIME, token);
849 3 break;
850
851 case r_DATE:
852 if ( !datetime_strToDate(token, value) )
853 return error_setInpError(ERR_DATETIME, token);
854 break;
855
856 case r_DAY:
857 if ( !getDouble(token, value) )
858 return error_setInpError(ERR_NUMBER, token);
859 if ( *value < 1.0 || *value > 7.0 )
860 return error_setInpError(ERR_DATETIME, token);
861 break;
862
863 case r_MONTH:
864 if ( !getDouble(token, value) )
865 return error_setInpError(ERR_NUMBER, token);
866 if ( *value < 1.0 || *value > 12.0 )
867 return error_setInpError(ERR_DATETIME, token);
868 break;
869
870 case r_DAYOFYEAR:
871 sstrncpy(strDate, token, 6);
872 sstrcat(strDate, "/1947", 25);
873 if ( datetime_strToDate(strDate, value) )
874 {
875 *value = datetime_dayOfYear(*value);
876 }
877 else if ( !getDouble(token, value) || *value < 1 || *value > 365 )
878 return error_setInpError(ERR_DATETIME, token);
879 break;
880
881
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 88 times.
88 default: if ( !getDouble(token, value) )
882 return error_setInpError(ERR_NUMBER, token);
883 }
884 92 return 0;
885 }
886
887 //=============================================================================
888
889 97 int addAction(int r, char* tok[], int nToks)
890 //
891 // Input: r = control rule index
892 // tok = array of string tokens containing action statement
893 // nToks = number of string tokens
894 // Output: returns an error code
895 // Purpose: adds a new action to a control rule.
896 //
897 {
898 int obj, link, attrib;
899 97 int curve = -1, tseries = -1;
900 int n;
901 int err;
902 97 double values[] = {1.0, 0.0, 0.0};
903
904 struct TAction* a;
905
906 // --- check for proper number of tokens
907
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 97 times.
97 if ( nToks < 6 ) return error_setInpError(ERR_ITEMS, "");
908
909 // --- check for valid object type
910 97 obj = findmatch(tok[1], ObjectWords);
911
7/8
✓ Branch 0 taken 97 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 95 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 93 times.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 7 times.
✓ Branch 7 taken 86 times.
97 if ( obj != r_LINK && obj != r_CONDUIT && obj != r_PUMP &&
912
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
7 obj != r_ORIFICE && obj != r_WEIR && obj != r_OUTLET )
913 return error_setInpError(ERR_KEYWORD, tok[1]);
914
915 // --- check that object name exists and is of correct type
916 97 link = project_findObject(LINK, tok[2]);
917
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 97 times.
97 if ( link < 0 ) return error_setInpError(ERR_NAME, tok[2]);
918
4/6
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 86 times.
✓ Branch 3 taken 7 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
97 switch (obj)
919 {
920 2 case r_CONDUIT:
921
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if ( Link[link].type != CONDUIT )
922 return error_setInpError(ERR_NAME, tok[2]);
923 2 break;
924 2 case r_PUMP:
925
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if ( Link[link].type != PUMP )
926 return error_setInpError(ERR_NAME, tok[2]);
927 2 break;
928 86 case r_ORIFICE:
929
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 86 times.
86 if ( Link[link].type != ORIFICE )
930 return error_setInpError(ERR_NAME, tok[2]);
931 86 break;
932 7 case r_WEIR:
933
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if ( Link[link].type != WEIR )
934 return error_setInpError(ERR_NAME, tok[2]);
935 7 break;
936 case r_OUTLET:
937 if ( Link[link].type != OUTLET )
938 return error_setInpError(ERR_NAME, tok[2]);
939 break;
940 }
941
942 // --- check for valid attribute name
943 97 attrib = findmatch(tok[3], AttribWords);
944
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 97 times.
97 if ( attrib < 0 ) return error_setInpError(ERR_KEYWORD, tok[3]);
945
946 // --- get control action setting
947
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 95 times.
97 if ( obj == r_CONDUIT )
948 {
949
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if ( attrib == r_STATUS )
950 {
951 2 values[0] = findmatch(tok[5], ConduitWords);
952
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if ( values[0] < 0.0 )
953 return error_setInpError(ERR_KEYWORD, tok[5]);
954 }
955 else return error_setInpError(ERR_KEYWORD, tok[3]);
956 }
957
958
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 93 times.
95 else if ( obj == r_PUMP )
959 {
960
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2 if ( attrib == r_STATUS )
961 {
962 1 values[0] = findmatch(tok[5], StatusWords);
963
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 if ( values[0] < 0.0 )
964 return error_setInpError(ERR_KEYWORD, tok[5]);
965 }
966
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 else if ( attrib == r_SETTING )
967 {
968 1 err = setActionSetting(tok, nToks, &curve, &tseries,
969 &attrib, values);
970
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 if ( err > 0 ) return err;
971 }
972 else return error_setInpError(ERR_KEYWORD, tok[3]);
973 }
974
975
3/6
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 86 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 7 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
93 else if ( obj == r_ORIFICE || obj == r_WEIR || obj == r_OUTLET )
976 {
977
1/2
✓ Branch 0 taken 93 times.
✗ Branch 1 not taken.
93 if ( attrib == r_SETTING )
978 {
979 93 err = setActionSetting(tok, nToks, &curve, &tseries,
980 &attrib, values);
981
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 93 times.
93 if ( err > 0 ) return err;
982
2/2
✓ Branch 0 taken 72 times.
✓ Branch 1 taken 21 times.
93 if ( attrib == r_SETTING
983
2/4
✓ Branch 0 taken 72 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 72 times.
72 && (values[0] < 0.0 || values[0] > 1.0) )
984 return error_setInpError(ERR_NUMBER, tok[5]);
985 }
986 else return error_setInpError(ERR_KEYWORD, tok[3]);
987 }
988 else return error_setInpError(ERR_KEYWORD, tok[1]);
989
990 // --- check if another clause is on same line
991 97 n = 6;
992
3/4
✓ Branch 0 taken 96 times.
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✓ Branch 3 taken 96 times.
97 if ( curve >= 0 || tseries >= 0 ) n = 7;
993
2/2
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 76 times.
97 if ( attrib == r_PID ) n = 9;
994
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 97 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
97 if ( n < nToks && findmatch(tok[n], RuleKeyWords) >= 0 ) return ERR_RULE;
995
996 // --- create the action object
997 97 a = (struct TAction *) malloc(sizeof(struct TAction));
998
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 97 times.
97 if ( !a ) return ERR_MEMORY;
999 97 a->rule = r;
1000 97 a->link = link;
1001 97 a->attribute = attrib;
1002 97 a->curve = curve;
1003 97 a->tseries = tseries;
1004 97 a->value = values[0];
1005
2/2
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 76 times.
97 if ( attrib == r_PID )
1006 {
1007 21 a->kp = values[0];
1008 21 a->ki = values[1];
1009 21 a->kd = values[2];
1010 21 a->e1 = 0.0;
1011 21 a->e2 = 0.0;
1012 }
1013
2/2
✓ Branch 0 taken 77 times.
✓ Branch 1 taken 20 times.
97 if ( InputState == r_THEN )
1014 {
1015 77 a->next = Rules[r].thenActions;
1016 77 Rules[r].thenActions = a;
1017 }
1018 else
1019 {
1020 20 a->next = Rules[r].elseActions;
1021 20 Rules[r].elseActions = a;
1022 }
1023 97 return 0;
1024 }
1025
1026 //=============================================================================
1027
1028 94 int setActionSetting(char* tok[], int nToks, int* curve, int* tseries,
1029 int* attrib, double values[])
1030 //
1031 // Input: tok = array of string tokens containing action statement
1032 // nToks = number of string tokens
1033 // Output: curve = index of controller curve
1034 // tseries = index of controller time series
1035 // attrib = r_PID if PID controller used
1036 // values = values of control settings
1037 // returns an error code
1038 // Purpose: identifies how control actions settings are determined.
1039 //
1040 {
1041 int k, m;
1042
1043 // --- see if control action is determined by a Curve or Time Series
1044
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 94 times.
94 if (nToks < 6) return error_setInpError(ERR_ITEMS, "");
1045 94 k = findmatch(tok[5], SettingTypeWords);
1046
3/4
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 72 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 22 times.
94 if ( k >= 0 && nToks < 7 ) return error_setInpError(ERR_ITEMS, "");
1047
3/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 21 times.
✓ Branch 3 taken 72 times.
94 switch (k)
1048 {
1049
1050 // --- control determined by a curve - find curve index
1051 1 case r_CURVE:
1052 1 m = project_findObject(CURVE, tok[6]);
1053
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 if ( m < 0 ) return error_setInpError(ERR_NAME, tok[6]);
1054 1 *curve = m;
1055 1 break;
1056
1057 // --- control determined by a time series - find time series index
1058 case r_TIMESERIES:
1059 m = project_findObject(TSERIES, tok[6]);
1060 if ( m < 0 ) return error_setInpError(ERR_NAME, tok[6]);
1061 *tseries = m;
1062 Tseries[m].refersTo = CONTROL;
1063 break;
1064
1065 // --- control determined by PID controller
1066 21 case r_PID:
1067
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 21 times.
21 if (nToks < 9) return error_setInpError(ERR_ITEMS, "");
1068
2/2
✓ Branch 0 taken 63 times.
✓ Branch 1 taken 21 times.
84 for (m=6; m<=8; m++)
1069 {
1070
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
63 if ( !getDouble(tok[m], &values[m-6]) )
1071 return error_setInpError(ERR_NUMBER, tok[m]);
1072 }
1073 21 *attrib = r_PID;
1074 21 break;
1075
1076 // --- direct numerical control is used
1077 72 default:
1078
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 72 times.
72 if ( !getDouble(tok[5], &values[0]) )
1079 return error_setInpError(ERR_NUMBER, tok[5]);
1080 }
1081 94 return 0;
1082 }
1083
1084 //=============================================================================
1085
1086 113225 void updateActionValue(struct TAction* a, DateTime currentTime, double dt)
1087 //
1088 // Input: a = an action object
1089 // currentTime = current simulation date/time (days)
1090 // dt = time step (days)
1091 // Output: none
1092 // Purpose: updates value of actions found from Curves or Time Series.
1093 //
1094 {
1095
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 113225 times.
113225 if ( a->curve >= 0 )
1096 {
1097 a->value = table_lookup(&Curve[a->curve], ControlValue);
1098 }
1099
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 113225 times.
113225 else if ( a->tseries >= 0 )
1100 {
1101 a->value = table_tseriesLookup(&Tseries[a->tseries], currentTime, TRUE);
1102 }
1103
2/2
✓ Branch 0 taken 39724 times.
✓ Branch 1 taken 73501 times.
113225 else if ( a->attribute == r_PID )
1104 {
1105 39724 a->value = getPIDSetting(a, dt);
1106 }
1107 113225 }
1108
1109 //=============================================================================
1110
1111 39724 double getPIDSetting(struct TAction* a, double dt)
1112 //
1113 // Input: a = an action object
1114 // dt = current time step (days)
1115 // Output: returns a new link setting
1116 // Purpose: computes a new setting for a link subject to a PID controller.
1117 //
1118 // Note: a->kp = gain coefficient,
1119 // a->ki = integral time (minutes)
1120 // a->k2 = derivative time (minutes)
1121 // a->e1 = error from previous time step
1122 // a->e2 = error from two time steps ago
1123 {
1124 double e0, setting;
1125 double p, i, d, update;
1126 39724 double tolerance = 0.0001;
1127
1128 // --- convert time step from days to minutes
1129 39724 dt *= 1440.0;
1130
1131 // --- determine relative error in achieving controller set point
1132 39724 e0 = SetPoint - ControlValue;
1133
1/2
✓ Branch 0 taken 39724 times.
✗ Branch 1 not taken.
39724 if ( fabs(e0) > TINY )
1134 {
1135
1/2
✓ Branch 0 taken 39724 times.
✗ Branch 1 not taken.
39724 if ( SetPoint != 0.0 ) e0 = e0/SetPoint;
1136 else e0 = e0/ControlValue;
1137 }
1138
1139 // --- reset previous errors to 0 if controller gets stuck
1140
2/2
✓ Branch 0 taken 39710 times.
✓ Branch 1 taken 14 times.
39724 if (fabs(e0 - a->e1) < tolerance)
1141 {
1142 39710 a->e2 = 0.0;
1143 39710 a->e1 = 0.0;
1144 }
1145
1146 // --- use the recursive form of the PID controller equation to
1147 // determine the new setting for the controlled link
1148 39724 p = (e0 - a->e1);
1149
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 39724 times.
39724 if ( a->ki == 0.0 ) i = 0.0;
1150 39724 else i = e0 * dt / a->ki;
1151 39724 d = a->kd * (e0 - 2.0*a->e1 + a->e2) / dt;
1152 39724 update = a->kp * (p + i + d);
1153
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 39724 times.
39724 if ( fabs(update) < tolerance ) update = 0.0;
1154 39724 setting = Link[a->link].targetSetting + update;
1155
1156 // --- update previous errors
1157 39724 a->e2 = a->e1;
1158 39724 a->e1 = e0;
1159
1160 // --- check that new setting lies within feasible limits
1161
2/2
✓ Branch 0 taken 19856 times.
✓ Branch 1 taken 19868 times.
39724 if ( setting < 0.0 ) setting = 0.0;
1162
3/4
✓ Branch 0 taken 39724 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 19862 times.
✓ Branch 3 taken 19862 times.
39724 if (Link[a->link].type != PUMP && setting > 1.0 ) setting = 1.0;
1163 39724 return setting;
1164 }
1165
1166 //=============================================================================
1167
1168 113225 void updateActionList(struct TAction* a)
1169 //
1170 // Input: a = an action object
1171 // Output: none
1172 // Purpose: adds a new action to the list of actions to be taken.
1173 //
1174 {
1175 struct TActionList* listItem;
1176 struct TAction* a1;
1177 113225 double priority = Rules[a->rule].priority;
1178
1179 // --- check if link referred to in action is already listed
1180 113225 listItem = ActionList;
1181
2/2
✓ Branch 0 taken 313480 times.
✓ Branch 1 taken 41 times.
313521 while ( listItem )
1182 {
1183 313480 a1 = listItem->action;
1184
2/2
✓ Branch 0 taken 112353 times.
✓ Branch 1 taken 201127 times.
313480 if ( !a1 ) break;
1185
2/2
✓ Branch 0 taken 831 times.
✓ Branch 1 taken 200296 times.
201127 if ( a1->link == a->link )
1186 {
1187 // --- replace old action if new action has higher priority
1188
1/2
✓ Branch 0 taken 831 times.
✗ Branch 1 not taken.
831 if ( priority > Rules[a1->rule].priority ) listItem->action = a;
1189 831 return;
1190 }
1191 200296 listItem = listItem->next;
1192 }
1193
1194 // --- action not listed so add it to ActionList //5.2.1
1195
2/2
✓ Branch 0 taken 41 times.
✓ Branch 1 taken 112353 times.
112394 if ( !listItem )
1196 {
1197 41 listItem = (struct TActionList *) malloc(sizeof(struct TActionList));
1198 41 listItem->next = ActionList;
1199 41 ActionList = listItem;
1200 }
1201 112394 listItem->action = a;
1202 }
1203
1204 //=============================================================================
1205
1206 32629 int executeActionList(DateTime currentTime)
1207 //
1208 // Input: currentTime = current date/time of the simulation
1209 // Output: returns number of new actions taken
1210 // Purpose: executes all actions required by fired control rules.
1211 //
1212 {
1213 struct TActionList* listItem;
1214 struct TActionList* nextItem;
1215 struct TAction* a1;
1216 32629 int count = 0;
1217
1218 32629 listItem = ActionList;
1219
2/2
✓ Branch 0 taken 112557 times.
✓ Branch 1 taken 32466 times.
145023 while ( listItem )
1220 {
1221 112557 a1 = listItem->action;
1222
2/2
✓ Branch 0 taken 163 times.
✓ Branch 1 taken 112394 times.
112557 if ( !a1 ) break;
1223
1/2
✓ Branch 0 taken 112394 times.
✗ Branch 1 not taken.
112394 if ( a1->link >= 0 )
1224 {
1225
2/2
✓ Branch 0 taken 31 times.
✓ Branch 1 taken 112363 times.
112394 if ( Link[a1->link].targetSetting != a1->value )
1226 {
1227 31 Link[a1->link].targetSetting = a1->value;
1228
3/4
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 20 times.
✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
31 if ( RptFlags.controls && a1->curve < 0
1229
2/4
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
11 && a1->tseries < 0 && a1->attribute != r_PID )
1230 11 report_writeControlAction(currentTime, Link[a1->link].ID,
1231 11 a1->value, Rules[a1->rule].ID);
1232 31 count++;
1233 }
1234 }
1235 112394 nextItem = listItem->next;
1236 112394 listItem = nextItem;
1237 }
1238 32629 return count;
1239 }
1240
1241 //=============================================================================
1242
1243 218848 int evaluatePremise(struct TPremise* p, double tStep)
1244 //
1245 // Input: p = a control rule premise condition
1246 // tStep = current time step (days)
1247 // Output: returns TRUE if the condition is true or FALSE otherwise
1248 // Purpose: evaluates the truth of a control rule premise condition.
1249 //
1250 {
1251 double lhsValue, rhsValue;
1252 218848 int result = FALSE;
1253
1254 // --- check if left hand side (lhs) of premise is an expression
1255
2/2
✓ Branch 0 taken 1440 times.
✓ Branch 1 taken 217408 times.
218848 if (p->exprIndex >= 0)
1256 1440 lhsValue = mathexpr_eval(Expression[p->exprIndex].expression,
1257 getNamedVariableValue);
1258
1259 // --- otherwise get value of the lhs variable
1260 else
1261 217408 lhsValue = getVariableValue(p->lhsVar);
1262
1263 // --- if right hand side (rhs) of premise is a variable then get its value
1264
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 218848 times.
218848 if ( p->value == MISSING ) rhsValue = getVariableValue(p->rhsVar);
1265 218848 else rhsValue = p->value;
1266
3/4
✓ Branch 0 taken 218608 times.
✓ Branch 1 taken 240 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 218608 times.
218848 if ( lhsValue == MISSING || rhsValue == MISSING ) return FALSE;
1267
1268 // --- compare the lhs of the premise to the rhs
1269
3/3
✓ Branch 0 taken 240 times.
✓ Branch 1 taken 240 times.
✓ Branch 2 taken 218128 times.
218608 switch (p->lhsVar.attribute)
1270 {
1271 240 case r_TIME:
1272 case r_CLOCKTIME:
1273 240 return compareTimes(lhsValue, p->relation, rhsValue, tStep/2.0);
1274 240 case r_TIMEOPEN:
1275 case r_TIMECLOSED:
1276 240 result = compareTimes(lhsValue, p->relation, rhsValue, tStep/2.0);
1277 240 ControlValue = lhsValue * 24.0; // convert time from days to hours
1278 240 return result;
1279 218128 default:
1280 218128 return compareValues(lhsValue, p->relation, rhsValue);
1281 }
1282 }
1283
1284 //=============================================================================
1285
1286 224368 double getVariableValue(struct TVariable v)
1287 {
1288 224368 int i = -1; // a node index
1289 224368 int j = -1; // a link index
1290
1291
2/2
✓ Branch 0 taken 365 times.
✓ Branch 1 taken 224003 times.
224368 if (v.object == r_GAGE)
1292 365 return getRainValue(v);
1293
2/2
✓ Branch 0 taken 147733 times.
✓ Branch 1 taken 76270 times.
224003 if (v.object == r_NODE) i = v.index;
1294
2/2
✓ Branch 0 taken 76030 times.
✓ Branch 1 taken 147973 times.
224003 if (v.object == r_LINK) j = v.index;
1295
1296
7/18
✓ Branch 0 taken 240 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 3677 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 71873 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 147253 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✓ Branch 14 taken 480 times.
✓ Branch 15 taken 240 times.
✓ Branch 16 taken 240 times.
✗ Branch 17 not taken.
224003 switch ( v.attribute )
1297 {
1298 240 case r_TIME:
1299 240 return ElapsedTime;
1300
1301 case r_DATE:
1302 return CurrentDate;
1303
1304 case r_CLOCKTIME:
1305 return CurrentTime;
1306
1307 case r_DAY:
1308 return datetime_dayOfWeek(CurrentDate);
1309
1310 case r_MONTH:
1311 return datetime_monthOfYear(CurrentDate);
1312
1313 case r_DAYOFYEAR:
1314 return datetime_dayOfYear(CurrentDate);
1315
1316 3677 case r_STATUS:
1317
1/2
✓ Branch 0 taken 3677 times.
✗ Branch 1 not taken.
3677 if ( j < 0 ||
1318
2/4
✓ Branch 0 taken 3677 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3677 times.
3677 (Link[j].type != CONDUIT && Link[j].type != PUMP) ) return MISSING;
1319 3677 else return Link[j].setting;
1320
1321 case r_SETTING:
1322 if ( j < 0 || (Link[j].type != PUMP &&
1323 Link[j].type != ORIFICE &&
1324 Link[j].type != WEIR) )
1325 return MISSING;
1326 else return Link[j].setting;
1327
1328 71873 case r_FLOW:
1329
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 71873 times.
71873 if ( j < 0 ) return MISSING;
1330 71873 else return Link[j].direction*Link[j].newFlow*UCF(FLOW);
1331
1332 case r_FULLFLOW:
1333 case r_FULLDEPTH:
1334 case r_VELOCITY:
1335 case r_LENGTH:
1336 case r_SLOPE:
1337 if ( j < 0 ) return MISSING;
1338 else if (Link[j].type != CONDUIT) return MISSING;
1339 switch (v.attribute)
1340 {
1341 case r_FULLFLOW: return Link[j].qFull * UCF(FLOW);
1342 case r_FULLDEPTH: return Link[j].xsect.yFull * UCF(LENGTH);
1343 case r_VELOCITY:
1344 return link_getVelocity(j, Link[j].newFlow, Link[j].newDepth)
1345 * UCF(LENGTH);
1346 case r_LENGTH: return Conduit[Link[j].subIndex].length * UCF(LENGTH);
1347 case r_SLOPE: return Conduit[Link[j].subIndex].slope;
1348 default: return MISSING;
1349 }
1350 147253 case r_DEPTH:
1351
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 147253 times.
147253 if ( j >= 0 ) return Link[j].newDepth*UCF(LENGTH);
1352
1/2
✓ Branch 0 taken 147253 times.
✗ Branch 1 not taken.
147253 else if ( i >= 0 )
1353 147253 return Node[i].newDepth*UCF(LENGTH);
1354 else return MISSING;
1355
1356 case r_MAXDEPTH:
1357 if (i >= 0) return Node[i].fullDepth*UCF(LENGTH);
1358 else return MISSING;
1359
1360 case r_HEAD:
1361 if ( i < 0 ) return MISSING;
1362 return (Node[i].newDepth + Node[i].invertElev) * UCF(LENGTH);
1363
1364 case r_VOLUME:
1365 if ( i < 0 ) return MISSING;
1366 return (Node[i].newVolume * UCF(VOLUME));
1367
1368 480 case r_INFLOW:
1369
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 480 times.
480 if ( i < 0 ) return MISSING;
1370 480 else return Node[i].newLatFlow*UCF(FLOW);
1371
1372 240 case r_TIMEOPEN:
1373
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 240 times.
240 if ( j < 0 ) return MISSING;
1374
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 240 times.
240 if ( Link[j].setting <= 0.0 ) return MISSING;
1375 240 return CurrentDate + CurrentTime - Link[j].timeLastSet;
1376
1377 240 case r_TIMECLOSED:
1378
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 240 times.
240 if ( j < 0 ) return MISSING;
1379
1/2
✓ Branch 0 taken 240 times.
✗ Branch 1 not taken.
240 if ( Link[j].setting > 0.0 ) return MISSING;
1380 return CurrentDate + CurrentTime - Link[j].timeLastSet;
1381
1382 default: return MISSING;
1383 }
1384 }
1385
1386 //=============================================================================
1387
1388 365 double getRainValue(struct TVariable v)
1389 //
1390 // Input: v = a rule premise variable for a rain gage
1391 // Output: returns current or past rainfall amount
1392 // Purpose: retrieves either the current rainfall intensity or the past
1393 // rainfall total for a rain gage.
1394 //
1395 {
1396
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 365 times.
365 if (v.index < 0) return MISSING;
1397
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 365 times.
365 else if (Gage[v.index].isUsed == FALSE) return 0.0;
1398
2/2
✓ Branch 0 taken 125 times.
✓ Branch 1 taken 240 times.
365 else if (v.attribute == 0)
1399 125 return Gage[v.index].rainfall;
1400 240 else return gage_getPastRain(v.index, v.attribute);
1401 }
1402
1403 //=============================================================================
1404
1405 480 int compareTimes(double lhsValue, int relation, double rhsValue, double halfStep)
1406 //
1407 // Input: lhsValue = date/time value on left hand side of relation
1408 // relation = relational operator code (see RuleRelation enumeration)
1409 // rhsValue = date/time value on right hand side of relation
1410 // halfStep = 1/2 the current time step (days)
1411 // Output: returns TRUE if time relation is satisfied
1412 // Purpose: evaluates the truth of a relation between two date/times.
1413 //
1414 {
1415
2/2
✓ Branch 0 taken 240 times.
✓ Branch 1 taken 240 times.
480 if ( relation == EQ )
1416 {
1417
2/2
✓ Branch 0 taken 180 times.
✓ Branch 1 taken 60 times.
240 if ( lhsValue >= rhsValue - halfStep
1418
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 179 times.
180 && lhsValue < rhsValue + halfStep ) return TRUE;
1419 239 return FALSE;
1420 }
1421
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 240 times.
240 else if ( relation == NE )
1422 {
1423 if ( lhsValue < rhsValue - halfStep
1424 || lhsValue >= rhsValue + halfStep ) return TRUE;
1425 return FALSE;
1426 }
1427 240 else return compareValues(lhsValue, relation, rhsValue);
1428 }
1429
1430 //=============================================================================
1431
1432 218368 int compareValues(double lhsValue, int relation, double rhsValue)
1433 // Input: lhsValue = value on left hand side of relation
1434 // relation = relational operator code (see RuleRelation enumeration)
1435 // rhsValue = value on right hand side of relation
1436 // Output: returns TRUE if relation is satisfied
1437 // Purpose: evaluates the truth of a relation between two values.
1438 {
1439 218368 SetPoint = rhsValue;
1440 218368 ControlValue = lhsValue;
1441
5/7
✓ Branch 0 taken 3677 times.
✓ Branch 1 taken 39724 times.
✓ Branch 2 taken 32149 times.
✓ Branch 3 taken 19862 times.
✓ Branch 4 taken 122956 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
218368 switch (relation)
1442 {
1443
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3677 times.
3677 case EQ: if ( lhsValue == rhsValue ) return TRUE; break;
1444
1/2
✓ Branch 0 taken 39724 times.
✗ Branch 1 not taken.
39724 case NE: if ( lhsValue != rhsValue ) return TRUE; break;
1445
2/2
✓ Branch 0 taken 31986 times.
✓ Branch 1 taken 163 times.
32149 case LT: if ( lhsValue < rhsValue ) return TRUE; break;
1446
1/2
✓ Branch 0 taken 19862 times.
✗ Branch 1 not taken.
19862 case LE: if ( lhsValue <= rhsValue ) return TRUE; break;
1447
2/2
✓ Branch 0 taken 4814 times.
✓ Branch 1 taken 118142 times.
122956 case GT: if ( lhsValue > rhsValue ) return TRUE; break;
1448 case GE: if ( lhsValue >= rhsValue ) return TRUE; break;
1449 }
1450 121982 return FALSE;
1451 }
1452
1453 //=============================================================================
1454
1455 54230 void clearActionList(void)
1456 //
1457 // Input: none
1458 // Output: none
1459 // Purpose: clears the list of actions to be executed.
1460 //
1461 {
1462 struct TActionList* listItem;
1463 54230 listItem = ActionList;
1464
2/2
✓ Branch 0 taken 112516 times.
✓ Branch 1 taken 54230 times.
166746 while ( listItem )
1465 {
1466 112516 listItem->action = NULL;
1467 112516 listItem = listItem->next;
1468 }
1469 54230 }
1470
1471 //=============================================================================
1472
1473 12 void deleteActionList(void)
1474 //
1475 // Input: none
1476 // Output: none
1477 // Purpose: frees the memory used to hold the list of actions to be executed.
1478 //
1479 {
1480 struct TActionList* listItem;
1481 struct TActionList* nextItem;
1482 12 listItem = ActionList;
1483
2/2
✓ Branch 0 taken 41 times.
✓ Branch 1 taken 12 times.
53 while ( listItem )
1484 {
1485 41 nextItem = listItem->next;
1486 41 free(listItem);
1487 41 listItem = nextItem;
1488 }
1489 12 ActionList = NULL;
1490 12 }
1491
1492 //=============================================================================
1493
1494 12 void deleteRules(void)
1495 //
1496 // Input: none
1497 // Output: none
1498 // Purpose: frees the memory used for all of the control rules.
1499 //
1500 {
1501 struct TPremise* p;
1502 struct TPremise* pnext;
1503 struct TAction* a;
1504 struct TAction* anext;
1505 int r;
1506
2/2
✓ Branch 0 taken 76 times.
✓ Branch 1 taken 12 times.
88 for (r=0; r<RuleCount; r++)
1507 {
1508 76 p = Rules[r].firstPremise;
1509
2/2
✓ Branch 0 taken 92 times.
✓ Branch 1 taken 76 times.
168 while ( p )
1510 {
1511 92 pnext = p->next;
1512 92 free(p);
1513 92 p = pnext;
1514 }
1515 76 a = Rules[r].thenActions;
1516
2/2
✓ Branch 0 taken 77 times.
✓ Branch 1 taken 76 times.
153 while (a )
1517 {
1518 77 anext = a->next;
1519 77 free(a);
1520 77 a = anext;
1521 }
1522 76 a = Rules[r].elseActions;
1523
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 76 times.
96 while (a )
1524 {
1525 20 anext = a->next;
1526 20 free(a);
1527 20 a = anext;
1528 }
1529 }
1530
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 FREE(Rules);
1531 12 RuleCount = 0;
1532 12 }
1533
1534 //=============================================================================
1535
1536 95 int findExactMatch(char *s, char *keyword[])
1537 //
1538 // Input: s = character string
1539 // keyword = array of keyword strings
1540 // Output: returns index of keyword which matches s or -1 if no match found
1541 // Purpose: finds exact match between string and array of keyword strings.
1542 //
1543 {
1544 95 int i = 0;
1545
2/2
✓ Branch 0 taken 413 times.
✓ Branch 1 taken 3 times.
416 while (keyword[i] != NULL)
1546 {
1547
2/2
✓ Branch 1 taken 92 times.
✓ Branch 2 taken 321 times.
413 if ( strcomp(s, keyword[i]) ) return(i);
1548 321 i++;
1549 }
1550 3 return(-1);
1551 }
1552
1553 //=============================================================================
1554