epyt-flow 0.13.1__py3-none-any.whl → 0.14.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. epyt_flow/EPANET/EPANET/SRC_engines/AUTHORS +40 -8
  2. epyt_flow/EPANET/EPANET/SRC_engines/LICENSE +3 -3
  3. epyt_flow/EPANET/EPANET/SRC_engines/enumstxt.h +24 -7
  4. epyt_flow/EPANET/EPANET/SRC_engines/epanet.c +726 -374
  5. epyt_flow/EPANET/EPANET/SRC_engines/epanet2.c +128 -32
  6. epyt_flow/EPANET/EPANET/SRC_engines/errors.dat +7 -1
  7. epyt_flow/EPANET/EPANET/SRC_engines/flowbalance.c +186 -0
  8. epyt_flow/EPANET/EPANET/SRC_engines/funcs.h +40 -14
  9. epyt_flow/EPANET/EPANET/SRC_engines/hash.c +177 -177
  10. epyt_flow/EPANET/EPANET/SRC_engines/hash.h +28 -28
  11. epyt_flow/EPANET/EPANET/SRC_engines/hydcoeffs.c +192 -40
  12. epyt_flow/EPANET/EPANET/SRC_engines/hydraul.c +101 -46
  13. epyt_flow/EPANET/EPANET/SRC_engines/hydsolver.c +85 -24
  14. epyt_flow/EPANET/EPANET/SRC_engines/hydstatus.c +29 -63
  15. epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2.h +70 -37
  16. epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2_2.h +408 -234
  17. epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2_enums.h +87 -37
  18. epyt_flow/EPANET/EPANET/SRC_engines/inpfile.c +153 -79
  19. epyt_flow/EPANET/EPANET/SRC_engines/input1.c +59 -94
  20. epyt_flow/EPANET/EPANET/SRC_engines/input2.c +73 -202
  21. epyt_flow/EPANET/EPANET/SRC_engines/input3.c +446 -351
  22. epyt_flow/EPANET/EPANET/SRC_engines/leakage.c +527 -0
  23. epyt_flow/EPANET/EPANET/SRC_engines/mempool.c +8 -4
  24. epyt_flow/EPANET/EPANET/SRC_engines/mempool.h +23 -23
  25. epyt_flow/EPANET/EPANET/SRC_engines/output.c +5 -4
  26. epyt_flow/EPANET/EPANET/SRC_engines/project.c +407 -75
  27. epyt_flow/EPANET/EPANET/SRC_engines/quality.c +12 -2
  28. epyt_flow/EPANET/EPANET/SRC_engines/qualreact.c +70 -13
  29. epyt_flow/EPANET/EPANET/SRC_engines/qualroute.c +7 -5
  30. epyt_flow/EPANET/EPANET/SRC_engines/report.c +88 -20
  31. epyt_flow/EPANET/EPANET/SRC_engines/rules.c +144 -6
  32. epyt_flow/EPANET/EPANET/SRC_engines/smatrix.c +19 -19
  33. epyt_flow/EPANET/EPANET/SRC_engines/text.h +16 -5
  34. epyt_flow/EPANET/EPANET/SRC_engines/types.h +73 -19
  35. epyt_flow/EPANET/EPANET/SRC_engines/util/cstr_helper.c +59 -0
  36. epyt_flow/EPANET/EPANET/SRC_engines/util/cstr_helper.h +38 -0
  37. epyt_flow/EPANET/EPANET/SRC_engines/util/errormanager.c +92 -0
  38. epyt_flow/EPANET/EPANET/SRC_engines/util/errormanager.h +39 -0
  39. epyt_flow/EPANET/EPANET/SRC_engines/util/filemanager.c +212 -0
  40. epyt_flow/EPANET/EPANET/SRC_engines/util/filemanager.h +81 -0
  41. epyt_flow/EPANET/EPANET/SRC_engines/validate.c +408 -0
  42. epyt_flow/EPANET/compile_linux.sh +1 -1
  43. epyt_flow/EPANET/compile_macos.sh +2 -2
  44. epyt_flow/VERSION +1 -1
  45. epyt_flow/__init__.py +1 -1
  46. epyt_flow/gym/scenario_control_env.py +26 -3
  47. epyt_flow/simulation/backend/my_epyt.py +58 -13
  48. epyt_flow/simulation/events/quality_events.py +6 -6
  49. epyt_flow/simulation/events/sensor_faults.py +24 -24
  50. epyt_flow/simulation/events/system_event.py +3 -3
  51. epyt_flow/simulation/scada/scada_data.py +10 -14
  52. epyt_flow/simulation/scenario_simulator.py +100 -20
  53. epyt_flow/topology.py +8 -1
  54. epyt_flow/uncertainty/model_uncertainty.py +292 -150
  55. epyt_flow/uncertainty/uncertainties.py +2 -2
  56. {epyt_flow-0.13.1.dist-info → epyt_flow-0.14.1.dist-info}/METADATA +4 -4
  57. {epyt_flow-0.13.1.dist-info → epyt_flow-0.14.1.dist-info}/RECORD +60 -54
  58. {epyt_flow-0.13.1.dist-info → epyt_flow-0.14.1.dist-info}/WHEEL +1 -1
  59. epyt_flow/EPANET/EPANET/SRC_engines/Readme_SRC_Engines.txt +0 -18
  60. epyt_flow/EPANET/EPANET/SRC_engines/epanet2.def +0 -131
  61. epyt_flow/EPANET/EPANET/SRC_engines/main.c +0 -93
  62. {epyt_flow-0.13.1.dist-info → epyt_flow-0.14.1.dist-info}/licenses/LICENSE +0 -0
  63. {epyt_flow-0.13.1.dist-info → epyt_flow-0.14.1.dist-info}/top_level.txt +0 -0
@@ -1,13 +1,13 @@
1
1
  /*
2
2
  ******************************************************************************
3
3
  Project: OWA EPANET
4
- Version: 2.2
4
+ Version: 2.3
5
5
  Module: quality.c
6
6
  Description: implements EPANET's water quality engine
7
7
  Authors: see AUTHORS
8
8
  Copyright: see AUTHORS
9
9
  License: see LICENSE
10
- Last Updated: 05/15/2019
10
+ Last Updated: 02/14/2025
11
11
  ******************************************************************************
12
12
  */
13
13
 
@@ -63,8 +63,16 @@ int openqual(Project *pr)
63
63
  // Build nodal adjacency lists if they don't already exist
64
64
  if (net->Adjlist == NULL)
65
65
  {
66
+ // Check for too few nodes & no fixed grade nodes
67
+ if (net->Nnodes < 2) return 223;
68
+ if (net->Ntanks == 0) return 224;
69
+
70
+ // Build adjacency lists
66
71
  errcode = buildadjlists(net);
67
72
  if (errcode ) return errcode;
73
+
74
+ // Check for unconnected nodes
75
+ if (errcode = unlinked(pr)) return errcode;
68
76
  }
69
77
 
70
78
  // Create a memory pool for water quality segments
@@ -175,6 +183,7 @@ int initqual(Project *pr)
175
183
  qual->MassBalance.reacted = 0.0;
176
184
  qual->MassBalance.final = 0.0;
177
185
  qual->MassBalance.ratio = 0.0;
186
+ qual->MassBalance.segCount = 0;
178
187
  return errcode;
179
188
  }
180
189
 
@@ -402,6 +411,7 @@ int closequal(Project *pr)
402
411
  FREE(qual->FlowDir);
403
412
  FREE(qual->SortedNodes);
404
413
  }
414
+ freeadjlists(&pr->network);
405
415
  return errcode;
406
416
  }
407
417
 
@@ -1,13 +1,13 @@
1
1
  /*
2
2
  ******************************************************************************
3
3
  Project: OWA EPANET
4
- Version: 2.2
4
+ Version: 2.3
5
5
  Module: qualreact.c
6
6
  Description: computes water quality reactions within pipes and tanks
7
7
  Authors: see AUTHORS
8
8
  Copyright: see AUTHORS
9
9
  License: see LICENSE
10
- Last Updated: 05/15/2019
10
+ Last Updated: 12/16/2024
11
11
  ******************************************************************************
12
12
  */
13
13
 
@@ -232,7 +232,7 @@ double piperate(Project *pr, int k)
232
232
  }
233
233
 
234
234
  // Compute Reynolds No.
235
- // Flow rate made consistent with how its saved to hydraulics file
235
+ // Flow rate made consistent with how it's saved to hydraulics file
236
236
  q = (hyd->LinkStatus[k] <= CLOSED) ? 0.0 : hyd->LinkFlow[k];
237
237
  a = PI * d * d / 4.0; // pipe area
238
238
  u = fabs(q) / a; // flow velocity
@@ -492,6 +492,13 @@ void tankmix1(Project *pr, int i, double vin, double win, double vnet)
492
492
  seg->v += vnet;
493
493
  seg->v = MAX(0.0, seg->v);
494
494
  tank->C = seg->c;
495
+
496
+ // Account for mass lost in tank overflow
497
+ if (seg->v > tank->Vmax)
498
+ {
499
+ qual->MassBalance.outflow += ((seg->v) - tank->Vmax) * tank->C;
500
+ seg->v = tank->Vmax;
501
+ }
495
502
  }
496
503
  }
497
504
 
@@ -513,7 +520,8 @@ void tankmix2(Project *pr, int i, double vin, double win, double vnet)
513
520
 
514
521
  int k;
515
522
  double vt, // Transferred volume
516
- vmz; // Full mixing zone volume
523
+ vmz, // Full mixing zone volume
524
+ vsz; // Full stagnant zone volume
517
525
  Pseg mixzone, // Mixing zone segment
518
526
  stagzone; // Stagnant zone segment
519
527
  Stank *tank = &pr->network.Tank[i];
@@ -525,7 +533,7 @@ void tankmix2(Project *pr, int i, double vin, double win, double vnet)
525
533
  if (mixzone == NULL || stagzone == NULL) return;
526
534
 
527
535
  // Full mixing zone volume
528
- vmz = tank->V1max;
536
+ vmz = tank->V1frac * tank->Vmax;
529
537
 
530
538
  // Tank is filling
531
539
  vt = 0.0;
@@ -558,16 +566,31 @@ void tankmix2(Project *pr, int i, double vin, double win, double vnet)
558
566
  // Update segment volumes
559
567
  if (vt > 0.0)
560
568
  {
561
- mixzone->v = vmz;
562
- if (vnet > 0.0) stagzone->v += vt;
563
- else stagzone->v = MAX(0.0, ((stagzone->v) - vt));
569
+ if (vnet > 0.0)
570
+ {
571
+ mixzone->v = vmz;
572
+ stagzone->v += vt;
573
+
574
+ // Account for mass lost in overflow from stagnant zone
575
+ vsz = (tank->Vmax) - vmz;
576
+ if (stagzone->v > vsz)
577
+ {
578
+ qual->MassBalance.outflow += ((stagzone->v) - vsz) * stagzone->c;
579
+ stagzone->v = vsz;
580
+ }
581
+ }
582
+ else
583
+ {
584
+ stagzone->v = MAX(0.0, ((stagzone->v) - vt));
585
+ mixzone->v = vmz + vt + vnet;
586
+ }
564
587
  }
565
588
  else
566
589
  {
567
590
  mixzone->v += vnet;
568
591
  mixzone->v = MIN(mixzone->v, vmz);
569
592
  mixzone->v = MAX(0.0, mixzone->v);
570
- stagzone->v = 0.0;
593
+ if (vmz - mixzone->v > 0.0) stagzone->v = 0.0;
571
594
  }
572
595
 
573
596
  // Use quality of mixing zone to represent quality of
@@ -612,10 +635,13 @@ void tankmix3(Project *pr, int i, double vin, double win, double vnet)
612
635
  else addseg(pr, k, vin, cin);
613
636
  }
614
637
 
615
- // Withdraw flow from first segment
638
+ // Find volume leaving tank, adjusted so its volume doesn't exceed Vmax
639
+ vout = vin - vnet;
640
+ if (tank->V >= tank->Vmax && vnet > 0.0) vout = vin;
641
+
642
+ // Withdraw outflow from first segment
616
643
  vsum = 0.0;
617
644
  wsum = 0.0;
618
- vout = vin - vnet;
619
645
  while (vout > 0.0)
620
646
  {
621
647
  seg = qual->FirstSeg[k];
@@ -643,6 +669,10 @@ void tankmix3(Project *pr, int i, double vin, double win, double vnet)
643
669
  if (vsum > 0.0) tank->C = wsum / vsum;
644
670
  else if (qual->FirstSeg[k] == NULL) tank->C = 0.0;
645
671
  else tank->C = qual->FirstSeg[k]->c;
672
+
673
+ // Account for mass lost in overflow from 1st segment
674
+ if (tank->V >= tank->Vmax && vnet > 0.0)
675
+ qual->MassBalance.outflow += vnet * tank->C;
646
676
  }
647
677
 
648
678
 
@@ -669,7 +699,7 @@ void tankmix4(Project *pr, int i, double vin, double win, double vnet)
669
699
  k = net->Nlinks + i;
670
700
  if (qual->LastSeg[k] == NULL || qual->FirstSeg[k] == NULL) return;
671
701
 
672
- // Find inflows & outflows
702
+ // Find inflow concentration
673
703
  if (vin > 0.0) cin = win / vin;
674
704
  else cin = 0.0;
675
705
 
@@ -687,6 +717,33 @@ void tankmix4(Project *pr, int i, double vin, double win, double vnet)
687
717
 
688
718
  // Update reported tank quality
689
719
  tank->C = qual->LastSeg[k]->c;
720
+
721
+ // If tank full then remove vnet from leading segments
722
+ if (tank->V >= tank->Vmax)
723
+ {
724
+ wsum = 0.0;
725
+ while (vnet > 0.0)
726
+ {
727
+ seg = qual->FirstSeg[k];
728
+ if (seg == NULL) break;
729
+ vseg = seg->v; // Flow volume from leading seg
730
+ vseg = MIN(vseg, vnet);
731
+ if (seg == qual->LastSeg[k]) vseg = vnet;
732
+ wsum += (seg->c) * vseg;
733
+ vnet -= vseg; // Remaining flow volume
734
+ if (vnet >= 0.0 && vseg >= seg->v) // Seg used up
735
+ {
736
+ if (seg->prev)
737
+ {
738
+ qual->FirstSeg[k] = seg->prev;
739
+ seg->prev = qual->FreeSeg;
740
+ qual->FreeSeg = seg;
741
+ }
742
+ }
743
+ else seg->v -= vseg; // Remaining volume in segment
744
+ }
745
+ qual->MassBalance.outflow += wsum;
746
+ }
690
747
  }
691
748
 
692
749
  // If tank emptying then remove last segments until vnet consumed
@@ -715,7 +772,7 @@ void tankmix4(Project *pr, int i, double vin, double win, double vnet)
715
772
  vsum += vseg;
716
773
  wsum += (seg->c) * vseg;
717
774
 
718
- // ... update remiaing volume to remove
775
+ // ... update remaining volume to remove
719
776
  vnet -= vseg;
720
777
 
721
778
  // ... if no more volume left in current segment
@@ -1,13 +1,13 @@
1
1
  /*
2
2
  ******************************************************************************
3
3
  Project: OWA EPANET
4
- Version: 2.2
4
+ Version: 2.3
5
5
  Module: qualroute.c
6
6
  Description: computes water quality transport over a single time step
7
7
  Authors: see AUTHORS
8
8
  Copyright: see AUTHORS
9
9
  License: see LICENSE
10
- Last Updated: 05/15/2019
10
+ Last Updated: 02/14/2025
11
11
  ******************************************************************************
12
12
  */
13
13
 
@@ -183,6 +183,7 @@ void evalnodeinflow(Project *pr, int k, long tstep, double *volin,
183
183
  // ... recycle the used up segment
184
184
  seg->prev = qual->FreeSeg;
185
185
  qual->FreeSeg = seg;
186
+ qual->MassBalance.segCount--;
186
187
  }
187
188
 
188
189
  // ... otherwise just reduce this segment's volume
@@ -246,7 +247,7 @@ double findnodequal(Project *pr, int n, double volin,
246
247
  return qual->NodeQual[n];
247
248
  }
248
249
 
249
- // Find quality contribued by any external chemical source
250
+ // Find quality contributed by any external chemical source
250
251
  else qual->SourceQual = findsourcequal(pr, n, volout, tstep);
251
252
  if (qual->SourceQual == 0.0) return qual->NodeQual[n];
252
253
 
@@ -609,10 +610,10 @@ void initsegs(Project *pr)
609
610
  addseg(pr, k, v, c);
610
611
 
611
612
  // Create a 2nd segment for the 2-compartment tank model
612
- if (net->Tank[j].MixModel == MIX2)
613
+ if (!qual->OutOfMemory && net->Tank[j].MixModel == MIX2)
613
614
  {
614
615
  // ... mixing zone segment
615
- v1 = MAX(0, v - net->Tank[j].V1max);
616
+ v1 = MAX(0, v - net->Tank[j].V1frac * net->Tank[j].Vmax);
616
617
  qual->FirstSeg[k]->v = v1;
617
618
 
618
619
  // ... stagnant zone segment
@@ -691,4 +692,5 @@ void addseg(Project *pr, int k, double v, double c)
691
692
  if (qual->FirstSeg[k] == NULL) qual->FirstSeg[k] = seg;
692
693
  if (qual->LastSeg[k] != NULL) qual->LastSeg[k]->prev = seg;
693
694
  qual->LastSeg[k] = seg;
695
+ qual->MassBalance.segCount++;
694
696
  }
@@ -1,13 +1,13 @@
1
1
  /*
2
2
  ******************************************************************************
3
3
  Project: OWA EPANET
4
- Version: 2.2
4
+ Version: 2.3
5
5
  Module: report.c
6
6
  Description: procedures for writing formatted text to a report file
7
7
  Authors: see AUTHORS
8
8
  Copyright: see AUTHORS
9
9
  License: see LICENSE
10
- Last Updated: 07/22/2019
10
+ Last Updated: 02/14/2025
11
11
  ******************************************************************************
12
12
  */
13
13
 
@@ -45,7 +45,7 @@ static void writeenergy(Project *);
45
45
  static int writeresults(Project *);
46
46
  static int disconnected(Project *);
47
47
  static void marknodes(Project *, int, int *, char *);
48
- static void getclosedlink(Project *, int, char *);
48
+ static void getclosedlink(Project *, int, char *, int *);
49
49
  static void writelimits(Project *, int, int);
50
50
  static int checklimits(Report *, double *, int, int);
51
51
  static char *fillstr(char *, char, int);
@@ -67,7 +67,7 @@ int clearreport(Project *pr)
67
67
  return 0;
68
68
  }
69
69
 
70
- int copyreport(Project* pr, char *filename)
70
+ int copyreport(Project* pr, const char *filename)
71
71
  /*
72
72
  **------------------------------------------------------
73
73
  ** Input: filename = name of file to copy to
@@ -291,7 +291,7 @@ void writesummary(Project *pr)
291
291
  if (qual->Qualflag == NONE || time->Dur == 0.0) sprintf(s, FMT29);
292
292
  else if (qual->Qualflag == CHEM) sprintf(s, FMT30, qual->ChemName);
293
293
  else if (qual->Qualflag == TRACE) sprintf(s, FMT31, net->Node[qual->TraceNode].ID);
294
- else if (qual->Qualflag == AGE) printf(s, FMT32);
294
+ else if (qual->Qualflag == AGE) sprintf(s, FMT32);
295
295
  writeline(pr, s);
296
296
  if (qual->Qualflag != NONE && time->Dur > 0)
297
297
  {
@@ -422,13 +422,51 @@ void writehydstat(Project *pr, int iter, double relerr)
422
422
  writeline(pr, " ");
423
423
  }
424
424
 
425
+ void writeflowbalance(Project *pr)
426
+ /*
427
+ **-------------------------------------------------------------
428
+ ** Input: none
429
+ ** Output: none
430
+ ** Purpose: writes hydraulic flow balance ratio to report file.
431
+ **-------------------------------------------------------------
432
+ */
433
+ {
434
+ Hydraul *hyd = &pr->hydraul;
435
+ Report *rpt = &pr->report;
436
+ char s1[MAXMSG+1];
437
+ double ucf = pr->Ucf[FLOW];
438
+
439
+ snprintf(s1, MAXMSG, "Hydraulic Flow Balance (%s)", rpt->Field[DEMAND].Units);
440
+ writeline(pr, s1);
441
+ snprintf(s1, MAXMSG, "================================");
442
+ writeline(pr, s1);
443
+ snprintf(s1, MAXMSG, "Total Inflow: %12.3f", hyd->FlowBalance.totalInflow*ucf);
444
+ writeline(pr, s1);
445
+ snprintf(s1, MAXMSG, "Consumer Demand: %12.3f", hyd->FlowBalance.consumerDemand*ucf);
446
+ writeline(pr, s1);
447
+ snprintf(s1, MAXMSG, "Demand Deficit: %12.3f", hyd->FlowBalance.deficitDemand*ucf);
448
+ writeline(pr, s1);
449
+ snprintf(s1, MAXMSG, "Emitter Flow: %12.3f", hyd->FlowBalance.emitterDemand*ucf);
450
+ writeline(pr, s1);
451
+ snprintf(s1, MAXMSG, "Leakage Flow: %12.3f", hyd->FlowBalance.leakageDemand*ucf);
452
+ writeline(pr, s1);
453
+ snprintf(s1, MAXMSG, "Total Outflow: %12.3f", hyd->FlowBalance.totalOutflow*ucf);
454
+ writeline(pr, s1);
455
+ snprintf(s1, MAXMSG, "Storage Flow: %12.3f", hyd->FlowBalance.storageDemand*ucf);
456
+ writeline(pr, s1);
457
+ snprintf(s1, MAXMSG, "Flow Ratio: %12.3f", hyd->FlowBalance.ratio);
458
+ writeline(pr, s1);
459
+ snprintf(s1, MAXMSG, "================================\n");
460
+ writeline(pr, s1);
461
+ }
462
+
425
463
  void writemassbalance(Project *pr)
426
464
  /*
427
465
  **-------------------------------------------------------------
428
466
  ** Input: none
429
467
  ** Output: none
430
468
  ** Purpose: writes water quality mass balance ratio
431
- ** (Outflow + Final Storage) / Inflow + Initial Storage)
469
+ ** (Outflow + Final Storage) / Inflow + Initial Storage
432
470
  ** to report file.
433
471
  **-------------------------------------------------------------
434
472
  */
@@ -463,6 +501,8 @@ void writemassbalance(Project *pr)
463
501
  writeline(pr, s1);
464
502
  snprintf(s1, MAXMSG, "Mass Ratio: %-.5f", qual->MassBalance.ratio);
465
503
  writeline(pr, s1);
504
+ snprintf(s1, MAXMSG, "Total Segments: %d", qual->MassBalance.segCount);
505
+ writeline(pr, s1);
466
506
  snprintf(s1, MAXMSG, "================================\n");
467
507
  writeline(pr, s1);
468
508
  }
@@ -876,7 +916,7 @@ void writeheader(Project *pr, int type, int contin)
876
916
  }
877
917
  }
878
918
 
879
- void writeline(Project *pr, char *s)
919
+ void writeline(Project *pr, const char *s)
880
920
  /*
881
921
  **--------------------------------------------------------------
882
922
  ** Input: *s = text string
@@ -886,6 +926,12 @@ void writeline(Project *pr, char *s)
886
926
  */
887
927
  {
888
928
  Report *rpt = &pr->report;
929
+
930
+ if (pr->report.reportCallback != NULL)
931
+ {
932
+ pr->report.reportCallback(pr->report.reportCallbackUserData, pr, s);
933
+ return;
934
+ }
889
935
 
890
936
  if (rpt->RptFile == NULL) return;
891
937
  if (rpt->Rptflag)
@@ -1281,7 +1327,7 @@ int disconnected(Project *pr)
1281
1327
  clocktime(rpt->Atime, time->Htime));
1282
1328
  writeline(pr, pr->Msg);
1283
1329
  }
1284
- getclosedlink(pr, j, marked);
1330
+ getclosedlink(pr, j, marked, nodelist);
1285
1331
  }
1286
1332
 
1287
1333
  // Free allocated memory
@@ -1344,11 +1390,12 @@ void marknodes(Project *pr, int m, int *nodelist, char *marked)
1344
1390
  }
1345
1391
  }
1346
1392
 
1347
- void getclosedlink(Project *pr, int i, char *marked)
1393
+ void getclosedlink(Project *pr, int i, char *marked, int *stack)
1348
1394
  /*
1349
1395
  **----------------------------------------------------------------
1350
1396
  ** Input: i = junction index
1351
1397
  ** marked[] = marks nodes already examined
1398
+ ** stack[] = stack to hold nodes to examine
1352
1399
  ** Output: None.
1353
1400
  ** Purpose: Determines if a closed link connects to junction i.
1354
1401
  **----------------------------------------------------------------
@@ -1359,20 +1406,41 @@ void getclosedlink(Project *pr, int i, char *marked)
1359
1406
  int j, k;
1360
1407
  Padjlist alink;
1361
1408
 
1409
+ int top = 0;
1410
+
1411
+ // Mark the current junction as examined and push onto stack
1362
1412
  marked[i] = 2;
1363
- for (alink = net->Adjlist[i]; alink != NULL; alink = alink->next)
1364
- {
1365
- k = alink->link;
1366
- j = alink->node;
1367
- if (marked[j] == 2) continue;
1368
- if (marked[j] == 1)
1369
- {
1370
- sprintf(pr->Msg, WARN03c, net->Link[k].ID);
1371
- writeline(pr, pr->Msg);
1372
- return;
1413
+ stack[top] = i;
1414
+
1415
+ while (top >= 0) {
1416
+ i = stack[top--];
1417
+ alink = net->Adjlist[i];
1418
+
1419
+ // Iterate through each link adjacent to the current node
1420
+ while (alink != NULL) {
1421
+ k = alink->link;
1422
+ j = alink->node;
1423
+
1424
+ // Skip nodes that have already been examined
1425
+ if (marked[j] == 2) {
1426
+ alink = alink->next;
1427
+ continue;
1428
+ }
1429
+
1430
+ // If a closed link is found, return and display a warning message
1431
+ if (marked[j] == 1) {
1432
+ sprintf(pr->Msg, WARN03c, net->Link[k].ID);
1433
+ writeline(pr, pr->Msg);
1434
+ return;
1435
+ }
1436
+
1437
+ // Mark the node as examined and push it onto the stack
1438
+ marked[j] = 2;
1439
+ stack[++top] = j;
1440
+ alink = alink->next;
1373
1441
  }
1374
- else getclosedlink(pr, j, marked);
1375
1442
  }
1443
+
1376
1444
  }
1377
1445
 
1378
1446
  void writelimits(Project *pr, int j1, int j2)
@@ -1,13 +1,13 @@
1
1
  /*
2
2
  ******************************************************************************
3
3
  Project: OWA EPANET
4
- Version: 2.2
4
+ Version: 2.3
5
5
  Module: rules.c
6
6
  Description: implements rule-based controls
7
7
  Authors: see AUTHORS
8
8
  Copyright: see AUTHORS
9
9
  License: see LICENSE
10
- Last Updated: 05/15/2019
10
+ Last Updated: 02/11/2025
11
11
  ******************************************************************************
12
12
  */
13
13
 
@@ -32,10 +32,11 @@ enum Rulewords {
32
32
  r_THEN,
33
33
  r_ELSE,
34
34
  r_PRIORITY,
35
+ r_DISABLED,
35
36
  r_ERROR
36
37
  };
37
38
  char *Ruleword[] = {w_RULE, w_IF, w_AND, w_OR,
38
- w_THEN, w_ELSE, w_PRIORITY, NULL};
39
+ w_THEN, w_ELSE, w_PRIORITY, w_DISABLED, NULL};
39
40
 
40
41
  enum Varwords {
41
42
  r_DEMAND,
@@ -273,6 +274,16 @@ int ruledata(Project *pr)
273
274
  err = newpriority(pr);
274
275
  break;
275
276
 
277
+ case r_DISABLED:
278
+ if (rules->RuleState != r_THEN && rules->RuleState != r_ELSE &&
279
+ rules->RuleState != r_PRIORITY)
280
+ {
281
+ err = 221;
282
+ break;
283
+ }
284
+ net->Rule[net->Nrules].isEnabled = FALSE;
285
+ break;
286
+
276
287
  default:
277
288
  err = 201;
278
289
  }
@@ -404,7 +415,7 @@ void adjustrules(Project *pr, int objtype, int index)
404
415
  }
405
416
  }
406
417
 
407
- void adjusttankrules(Project *pr)
418
+ void adjusttankrules(Project *pr, int ndiff)
408
419
  //-----------------------------------------------------------
409
420
  // Adjusts tank indices in rule premises.
410
421
  //-----------------------------------------------------------
@@ -420,7 +431,8 @@ void adjusttankrules(Project *pr)
420
431
  p = net->Rule[i].Premises;
421
432
  while (p != NULL)
422
433
  {
423
- if (p->object == r_NODE && p->index > njuncs) p->index++;
434
+ if (p->object == r_NODE && p->index > njuncs)
435
+ p->index += ndiff;
424
436
  p = p->next;
425
437
  }
426
438
  }
@@ -472,7 +484,7 @@ int writerule(Project *pr, FILE *f, int ruleIndex)
472
484
  Srule *rule = &net->Rule[ruleIndex];
473
485
  Spremise *p;
474
486
  Saction *a;
475
-
487
+
476
488
  // Write each premise clause to the file
477
489
  p = rule->Premises;
478
490
  fprintf(f, "\nIF ");
@@ -527,6 +539,11 @@ int checkrules(Project *pr, long dt)
527
539
  rules->ActionList = NULL;
528
540
  for (i = 1; i <= net->Nrules; i++)
529
541
  {
542
+ // skip if the rule is disabled
543
+ if (!net->Rule[i].isEnabled)
544
+ {
545
+ continue;
546
+ }
530
547
  // If premises true, add THEN clauses to action list
531
548
  if (evalpremises(pr, i) == TRUE)
532
549
  {
@@ -549,6 +566,126 @@ int checkrules(Project *pr, long dt)
549
566
  return actionCount;
550
567
  }
551
568
 
569
+ void updateruleunits(Project *pr, double dcf, double pcf, double hcf, double qcf)
570
+ //-----------------------------------------------------------
571
+ // Updates the units of a rule's premises and actions.
572
+ //-----------------------------------------------------------
573
+ {
574
+ Network *net = &pr->network;
575
+ Slink *Link = net->Link;
576
+
577
+ int i, k;
578
+ double x;
579
+ Spremise *p;
580
+ Saction *a;
581
+
582
+ for (i = 1; i <= net->Nrules; i++)
583
+ {
584
+ p = net->Rule[i].Premises;
585
+ while (p != NULL)
586
+ {
587
+
588
+ switch (p->variable)
589
+ {
590
+ case r_DEMAND:
591
+ p->value *= dcf;
592
+ break;
593
+
594
+ case r_HEAD:
595
+ case r_GRADE:
596
+ p->value *= hcf;
597
+ break;
598
+
599
+ case r_PRESSURE:
600
+ p->value *= pcf;
601
+ break;
602
+
603
+ case r_LEVEL:
604
+ p->value *= hcf;
605
+ break;
606
+
607
+ case r_FLOW:
608
+ p->value *= qcf;
609
+ break;
610
+
611
+ case r_SETTING:
612
+
613
+ switch (Link[p->index].Type)
614
+ {
615
+ case PRV:
616
+ case PSV:
617
+ case PBV:
618
+ p->value *= pcf;
619
+ break;
620
+ case FCV:
621
+ p->value *= qcf;
622
+ break;
623
+ default:
624
+ break;
625
+ }
626
+ break;
627
+
628
+ default:
629
+ break;
630
+
631
+ }
632
+ p = p->next;
633
+ }
634
+
635
+ a = net->Rule[i].ThenActions;
636
+ while (a != NULL)
637
+ {
638
+ k = a->link;
639
+ x = a->setting;
640
+
641
+ // Change link's setting
642
+ if (x != MISSING)
643
+ {
644
+ switch (net->Link[k].Type)
645
+ {
646
+ case PRV:
647
+ case PSV:
648
+ case PBV:
649
+ a->setting *= pcf;
650
+ break;
651
+ case FCV:
652
+ a->setting *= qcf;
653
+ break;
654
+ default:
655
+ break;
656
+ }
657
+ }
658
+ a = a->next;
659
+ }
660
+ a = net->Rule[i].ElseActions;
661
+ while (a != NULL)
662
+ {
663
+ k = a->link;
664
+ x = a->setting;
665
+
666
+ // Change link's setting
667
+ if (x != MISSING)
668
+ {
669
+ switch (net->Link[k].Type)
670
+ {
671
+ case PRV:
672
+ case PSV:
673
+ case PBV:
674
+ a->setting *= pcf;
675
+ break;
676
+ case FCV:
677
+ a->setting *= qcf;
678
+ break;
679
+ default:
680
+ break;
681
+ }
682
+ }
683
+ a = a->next;
684
+ }
685
+ }
686
+ }
687
+
688
+
552
689
  void newrule(Project *pr)
553
690
  //----------------------------------------------------------
554
691
  // Adds a new rule to the project
@@ -564,6 +701,7 @@ void newrule(Project *pr)
564
701
  rule->ThenActions = NULL;
565
702
  rule->ElseActions = NULL;
566
703
  rule->priority = 0.0;
704
+ rule->isEnabled = TRUE;
567
705
  pr->rules.LastPremise = NULL;
568
706
  pr->rules.LastThenAction = NULL;
569
707
  pr->rules.LastElseAction = NULL;