-
Notifications
You must be signed in to change notification settings - Fork 20
Expand file tree
/
Copy pathnsuperlink.py
More file actions
3211 lines (3065 loc) · 146 KB
/
Copy pathnsuperlink.py
File metadata and controls
3211 lines (3065 loc) · 146 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import numpy as np
import pandas as pd
import scipy.linalg
import scipy.optimize
import scipy.integrate
import scipy.sparse
import scipy.sparse.linalg
from numba import njit, prange
from numba.types import float64, int64, uint32, uint16, uint8, boolean, UniTuple, Tuple, List, DictType, void
import pipedream_solver.geometry
import pipedream_solver.ngeometry
import pipedream_solver.storage
from pipedream_solver.superlink import SuperLink
class nSuperLink(SuperLink):
"""
Numba implementation of SUPERLINK hydraulic solver, as described in:
Ji, Z. (1998). General Hydrodynamic Model for Sewer/Channel Network Systems.
Journal of Hydraulic Engineering, 124(3), 307–315.
doi: 10.1061/(asce)0733-9429(1998)124:3(307)
Inputs:
-----------
superlinks: pd.DataFrame
Table containing all superlinks in the network along with their attributes.
The following fields are required:
|------------+-------+------+-----------------------------------------------------------|
| Field | Type | Unit | Description |
|------------+-------+------+-----------------------------------------------------------|
| id | int | | Integer id for the superlink |
| name | str | | Name of the superlink |
| sj_0 | int | | Index of the upstream superjunction |
| sj_1 | int | | Index of the downstream superjunction |
| in_offset | float | m | Offset of superlink invert above upstream superjunction |
| out_offset | float | m | Offset of superlink invert above downstream superjunction |
| C_uk | float | - | Upstream discharge coefficient |
| C_dk | float | - | Downstream discharge coefficient |
|------------+-------+------+-----------------------------------------------------------|
If internal links and junctions are provided (arguments 3 and 4), the following
fields are required:
|-------+------+------+------------------------------------------|
| Field | Type | Unit | Description |
|-------+------+------+------------------------------------------|
| j_0 | int | | Index of first junction inside superlink |
| j_1 | int | | Index of last junction inside superlink |
|-------+------+------+------------------------------------------|
If internal links and junctions are not provided (arguments 3 and 4), the following
fields are required:
|-------+-------+-------+------------------------------------------------------|
| Field | Type | Unit | Description |
|-------+-------+-------+------------------------------------------------------|
| dx | float | m | Length of superlink |
| n | float | - | Manning's roughness coefficient for superlink |
| shape | str | | Cross-sectional geometry type (see geometry module) |
| g1 | float | m | First dimension of cross-sectional geometry |
| g2 | float | m | Second dimension of cross-sectional geometry |
| g3 | float | m | Third dimension of cross-sectional geometry |
| g4 | float | m | Fourth dimension of cross-sectional geometry |
| Q_0 | float | m^3/s | Initial flow in internal links |
| h_0 | float | m | Initial depth in internal junctions |
| A_s | float | m | Surface area of internal junctions |
| ctrl | bool | | Indicates presence of control structure in superlink |
| A_c | float | m^2 | Cross-sectional area of internal control structure |
| C | float | - | Discharge coefficient of internal control structure |
|-------+-------+-------+------------------------------------------------------|
superjunctions: pd.DataFrame
Table containing all superjunctions in the network along with their attributes.
The following fields are required:
|-----------+-------+------+-------------------------------------------------------|
| Field | Type | Unit | Description |
|-----------+-------+------+-------------------------------------------------------|
| id | int | | Integer id for superjunction |
| name | str | | Name of superjunction |
| z_inv | float | m | Elevation of bottom of superjunction |
| h_0 | float | m | Initial depth in superjunction |
| bc | bool | | Indicates boundary condition at superjunction |
| storage | str | | Storage type: `functional` or `tabular` |
| a | float | m | `a` value in function relating surface area and depth |
| b | float | - | `b` value in function relating surface area and depth |
| c | float | m^2 | `c` value in function relating surface area and depth |
| max_depth | float | m | Maximum depth allowed at superjunction |
|-----------+-------+------+-------------------------------------------------------|
links: pd.DataFrame (optional)
Table containing all links in the network along with their attributes.
Note that if links and junction are not supplied, they will automatically be
generated at even intervals within each superlink.
The following fields are required:
|-------+-------+-------+-----------------------------------------------------|
| Field | Type | Unit | Description |
|-------+-------+-------+-----------------------------------------------------|
| j_0 | int | | Index of upstream junction |
| j_1 | int | | Index of downstream junction |
| k | int | | Index of containing superlink |
| dx | float | m | Length of link |
| n | float | - | Manning's roughness coefficient for link |
| shape | str | | Cross-sectional geometry type (see geometry module) |
| g1 | float | m | First dimension of cross-sectional geometry |
| g2 | float | m | Second dimension of cross-sectional geometry |
| g3 | float | m | Third dimension of cross-sectional geometry |
| g4 | float | m | Fourth dimension of cross-sectional geometry |
| Q_0 | float | m^3/s | Initial flow in internal links |
| h_0 | float | m | Initial depth in internal junctions |
| A_s | float | m | Surface area of internal junctions |
| ctrl | bool | | Indicates presence of control structure in link |
| A_c | float | m^2 | Cross-sectional area of internal control structure |
| C | float | - | Discharge coefficient of internal control structure |
|-------+-------+-------+-----------------------------------------------------|
junctions: pd.DataFrame (optional)
Table containing all junctions in the network along with their attributes.
Note that if links and junction are not supplied, they will automatically be
generated at even intervals within each superlink.
The following fields are required:
|-------+-------+------+-------------------------------|
| Field | Type | Unit | Description |
|-------+-------+------+-------------------------------|
| id | int | | Integer id for junction |
| k | int | | Index of containing superlink |
| h_0 | float | m | Initial depth at junction |
| A_s | float | m^2 | Surface area of junction |
| z_inv | float | m | Invert elevation of junction |
|-------+-------+------+-------------------------------|
transects: dict (optional)
Dictionary describing nonfunctional channel cross-sectional geometries.
Takes the following structure:
{
<transect_name> :
{
'x' : <x-coordinates of cross-section (list of floats)>,
'y' : <y-coordinates of cross-section (list of floats)>,
'horiz_points' : <Number of horizontal sampling points (int)>
'vert_points' : <Number of vertical sampling points (int)>
}
...
}
storages: dict (optional)
Dictionary describing tabular storages for superjunctions.
Takes the following structure:
{
<storage_name> :
{
'h' : <Depths (list of floats)>,
'A' : <Surface areas associated with depths (list of floats)>,
}
...
}
orifices: pd.DataFrame (optional)
Table containing orifice control structures, and their attributes.
The following fields are required:
|-------------+-------+------+------------------------------------------------------|
| Field | Type | Unit | Description |
|-------------+-------+------+------------------------------------------------------|
| id | int | | Integer id for the orifice |
| name | str | | Name of the orifice |
| sj_0 | int | | Index of the upstream superjunction |
| sj_1 | int | | Index of the downstream superjunction |
| orientation | str | | Orifice orientation: `bottom` or `side` |
| C | float | - | Discharge coefficient for orifice |
| A | float | m^2 | Full area of orifice |
| y_max | float | m | Full height of orifice |
| z_o | float | m | Offset of bottom above upstream superjunction invert |
|-------------+-------+------+------------------------------------------------------|
weirs: pd.DataFrame (optional)
Table containing weir control structures, and their attributes.
The following fields are required:
|-------+-------+------+------------------------------------------------------|
| Field | Type | Unit | Description |
|-------+-------+------+------------------------------------------------------|
| id | int | | Integer id for the weir |
| name | str | | Name of the weir |
| sj_0 | int | | Index of the upstream superjunction |
| sj_1 | int | | Index of the downstream superjunction |
| z_w | float | m | Offset of bottom above upstream superjunction invert |
| y_max | float | m | Full height of weir |
| C_r | float | - | Discharge coefficient for rectangular portion |
| C_t | float | - | Discharge coefficient for triangular portions |
| L | float | m | Length of rectangular portion of weir |
| s | float | - | Inverse slope of triangular portion of weir |
|-------+-------+------+------------------------------------------------------|
pumps: pd.DataFrame (optional)
Table containing pump control structures and their attributes.
The following fields are required:
|--------+-------+------+------------------------------------------------------|
| Field | Type | Unit | Description |
|--------+-------+------+------------------------------------------------------|
| id | int | | Integer id for the pump |
| name | str | | Name of the pump |
| sj_0 | int | | Index of the upstream superjunction |
| sj_1 | int | | Index of the downstream superjunction |
| z_p | float | m | Offset of bottom above upstream superjunction invert |
| a_q | float | | Vertical coefficient of pump ellipse |
| a_h | float | | Horizontal coefficient of pump ellipse |
| dH_min | float | m | Minimum pump head |
| dH_max | float | m | Maximum pump head |
|--------+-------+------+------------------------------------------------------|
dt: float
Default timestep of model (in seconds).
min_depth: float
Minimum depth allowed at junctions and superjunctions (in meters).
method: str
Method for computing internal states in superlinks. Must be one of the following:
- `b` : Backwards (default)
- `f` : Forwards
- `lsq` : Least-squares
auto_permute: bool
If True, permute the superjunctions to enable use of a banded matrix solver and
increase solver speed. Superjunctions are permuted using the Reverse
Cuthill-McKee algorithm.
internal_links: int
If junctions/links are not provided, this gives the number of internal
links that will be generated inside each superlink.
sparse: bool
(Deprecated)
bc_method: str
(Deprecated)
exit_hydraulics: bool
(Deprecated)
end_length: float
(Deprecated)
bc_method: str
(Deprecated)
end_method: str
(Deprecated)
Methods:
-----------
step : Advance model to next time step, computing hydraulic states
save_state : Save current model state
load_state : Load model state
Attributes:
-----------
t : Current time (s)
H_j : Superjunction heads (m)
h_Ik : Junction depths (m)
Q_ik : Link flows (m^3/s)
Q_uk : Flows into upstream ends of superlinks (m^3/s)
Q_dk : Flows into downstream ends of superlinks (m^3/s)
Q_o : Orifice flows (m^3/s)
Q_w : Weir flows (m^3/s)
Q_p : Pump flows (m^3/s)
A_ik : Cross-sectional area of flow in links (m^2)
Pe_ik : Wetted perimeter in links (m)
R_ik : Hydraulic radius in links (m)
B_ik : Top width of flow in links (m)
A_sj : Superjunction surface areas (m^2)
V_sj : Superjunction stored volumes (m^3)
z_inv_j : Superjunction invert elevation (m)
z_inv_uk : Offset of superlink upstream invert above superjunction (m)
z_inv_dk : Offset of superlink downstream invert above superjunction (m)
"""
def __init__(self, superlinks, superjunctions,
links=None, junctions=None,
transects={}, storages={},
orifices=None, weirs=None, pumps=None,
dt=60, sparse=False, min_depth=1e-5, method='b',
inertial_damping=False, bc_method='z',
exit_hydraulics=False, auto_permute=False,
end_length=None, end_method='b', internal_links=4, mobile_elements=False):
super().__init__(superlinks, superjunctions,
links, junctions, transects, storages,
orifices, weirs, pumps, dt, sparse,
min_depth, method, inertial_damping,
bc_method, exit_hydraulics, auto_permute,
end_length, end_method, internal_links, mobile_elements)
def configure_hydraulic_geometry(self):
"""
Prepare data structures for hydraulic geometry computations.
"""
# Import instance variables
transects = self.transects # Table of transects
_shape_ik = self._shape_ik # Shape of link ik
_shape_o = self._shape_o
_transect_ik = self._transect_ik # Transect associated with link ik
_link_start = self._link_start # Link is first link in superlink k
_link_end = self._link_end # Link is last link in superlink k
_geom_numbers = pipedream_solver.geometry.geom_code
_g1_ik = self._g1_ik
_g2_ik = self._g2_ik
_g3_ik = self._g3_ik
_g4_ik = self._g4_ik
_g5_ik = self._g5_ik
_g6_ik = self._g6_ik
_g7_ik = self._g7_ik
nk = self.nk
n_o = self.n_o
# Handle regular geometries
_geom_codes = np.array([_geom_numbers.setdefault(shape, 0)
for shape in _shape_ik], dtype=np.int64)
_is_irregular = _shape_ik == 'irregular'
_has_irregular = _is_irregular.any()
_uk_has_irregular = (_link_start & _is_irregular).any()
_dk_has_irregular = (_link_end & _is_irregular).any()
# Handle irregular geometries
_transect_inds = np.array([])
_transect_lens = np.array([])
_transect_codes = np.array([])
_transect_As = np.array([])
_transect_Bs = np.array([])
_transect_Pes = np.array([])
_transect_Rs = np.array([])
_transect_zs = np.array([])
if transects:
transect_name_to_ind = {transect : index for index, transect in enumerate(transects)}
_transect_inds = []
_transect_lens = []
_transect_As = []
_transect_Bs = []
_transect_Pes = []
_transect_Rs = []
_transect_zs = []
order = []
ix = 0
for name, transect in transects.items():
x = transect['x']
y = transect['y']
sample_points = transect.setdefault('sample_points', 100)
w = np.linspace(0., y.max(), sample_points)
A = np.array([pipedream_solver.ngeometry.Transect_A_ik(w_i, x, y) for w_i in w])
B = np.array([pipedream_solver.ngeometry.Transect_B_ik(w_i, x, y) for w_i in w])
Pe = np.array([pipedream_solver.ngeometry.Transect_Pe_ik(w_i, x, y) for w_i in w])
R = safe_divide_vec(A, Pe)
_transect_As.append(A)
_transect_Bs.append(B)
_transect_Pes.append(Pe)
_transect_Rs.append(R)
_transect_zs.append(w)
_transect_inds.append(ix)
order.append(transect_name_to_ind[name])
ix += len(w)
_transect_lens.append(len(w))
order = np.argsort(order)
_transect_zs = np.concatenate([_transect_zs[i] for i in order])
_transect_As = np.concatenate([_transect_As[i] for i in order])
_transect_Bs = np.concatenate([_transect_Bs[i] for i in order])
_transect_Pes = np.concatenate([_transect_Pes[i] for i in order])
_transect_Rs = np.concatenate([_transect_Rs[i] for i in order])
_transect_inds = np.asarray(_transect_inds)[order]
_transect_lens = np.asarray(_transect_lens)[order]
_transect_codes = np.array([transect_name_to_ind.setdefault(transect, 0)
for transect in _transect_ik.values], dtype=np.int64)
# TODO: Drop support for ellipse
# NOTE: Handle case for elliptical geometry
_ellipse_ix = np.flatnonzero(_geom_codes ==
pipedream_solver.geometry.geom_code['elliptical'])
# Handle orifices
if n_o:
_geom_codes_o = np.array([_geom_numbers.setdefault(shape, 0)
for shape in _shape_o], dtype=np.int64)
self._geom_codes_o = _geom_codes_o
# Add default Preissman slot for circular
_g2_ik[(_geom_codes == 1) & (_g2_ik == 0.)] = 0.001
# Add default Preissman slot for rect_closed
_g3_ik[(_geom_codes == 2) & (_g3_ik == 0.)] = 0.001
# Add default Preissman slot for force_main
_g2_ik[(_geom_codes == 9) & (_g2_ik == 0.)] = 0.001
# Export instance variables
self._g1_ik = _g1_ik
self._g2_ik = _g2_ik
self._g3_ik = _g3_ik
self._g4_ik = _g4_ik
self._g5_ik = _g5_ik
self._g6_ik = _g6_ik
self._g7_ik = _g7_ik
self._is_irregular = _is_irregular
self._has_irregular = _has_irregular
self._uk_has_irregular = _uk_has_irregular
self._dk_has_irregular = _dk_has_irregular
self._geom_codes = _geom_codes
self._ellipse_ix = _ellipse_ix
self._transect_zs = _transect_zs
self._transect_As = _transect_As
self._transect_Bs = _transect_Bs
self._transect_Pes = _transect_Pes
self._transect_Rs = _transect_Rs
self._transect_inds = _transect_inds
self._transect_lens = _transect_lens
self._transect_codes = _transect_codes
def configure_storages(self):
"""
Prepare data structures for computation of superjunction storage.
"""
# Import instance variables
storages = self.storages # Table of storages
_storage_type = self._storage_type # Type of storage (functional/tabular)
_storage_table = self._storage_table # Tabular storages
_storage_factory = {}
_storage_indices = None
_storage_hs = np.array([])
_storage_As = np.array([])
_storage_Vs = np.array([])
_storage_inds = np.array([])
_storage_lens = np.array([])
_storage_js = np.array([])
_storage_codes = np.array([])
# Separate storages into functional and tabular
_functional = (_storage_type.str.lower() == 'functional').values
_tabular = (_storage_type.str.lower() == 'tabular').values
# All entries must either be function or tabular
assert (_tabular.sum() + _functional.sum()) == _storage_type.shape[0]
# Configure tabular storages
if storages:
_tabular_storages = _storage_table[_tabular]
_storage_indices = pd.Series(_tabular_storages.index, _tabular_storages.values)
unique_storage_names = np.unique(_storage_indices.index.values)
storage_name_to_ind = pd.Series(np.arange(unique_storage_names.size),
index=unique_storage_names)
sj_to_storage_ind = _storage_table.dropna().map(storage_name_to_ind)
_storage_inds = []
_storage_lens = []
_storage_As = []
_storage_Vs = []
_storage_hs = []
order = []
ix = 0
for name, storage in storages.items():
A = storage['A']
h = storage['h']
V = scipy.integrate.cumtrapz(h, A, initial=0.)
_storage_As.append(A)
_storage_Vs.append(V)
_storage_hs.append(h)
_storage_inds.append(ix)
order.append(storage_name_to_ind[name])
ix += len(h)
_storage_lens.append(len(h))
order = np.argsort(order)
_storage_hs = np.concatenate([_storage_hs[i] for i in order])
_storage_As = np.concatenate([_storage_As[i] for i in order])
_storage_Vs = np.concatenate([_storage_Vs[i] for i in order])
_storage_inds = np.asarray(_storage_inds)[order]
_storage_lens = np.asarray(_storage_lens)[order]
_storage_js = sj_to_storage_ind.index.values
_storage_codes = sj_to_storage_ind.values
# Export instance variables
self._storage_indices = _storage_indices
self._storage_factory = _storage_factory
self._storage_hs = _storage_hs
self._storage_As = _storage_As
self._storage_Vs = _storage_Vs
self._storage_inds = _storage_inds
self._storage_lens = _storage_lens
self._storage_js = _storage_js
self._storage_codes = _storage_codes
self._functional = _functional
self._tabular = _tabular
def link_hydraulic_geometry(self):
"""
Compute hydraulic geometry for each link.
"""
# Import instance variables
_ik = self._ik # Link index
_Ik = self._Ik # Junction index
_Ip1k = self._Ip1k # Index of next junction
_h_Ik = self._h_Ik # Depth at junction Ik
_A_ik = self._A_ik # Flow area at link ik
_Pe_ik = self._Pe_ik # Hydraulic perimeter at link ik
_R_ik = self._R_ik # Hydraulic radius at link ik
_B_ik = self._B_ik # Top width at link ik
_dx_ik = self._dx_ik # Length of link ik
_g1_ik = self._g1_ik # Geometry 1 of link ik (vertical)
_g2_ik = self._g2_ik # Geometry 2 of link ik (horizontal)
_g3_ik = self._g3_ik # Geometry 3 of link ik (other)
_g4_ik = self._g4_ik # Geometry 4 of link ik (other)
_g5_ik = self._g5_ik # Geometry 5 of link ik (other)
_g6_ik = self._g6_ik # Geometry 6 of link ik (other)
_g7_ik = self._g7_ik # Geometry 7 of link ik (other)
_geom_codes = self._geom_codes
_ellipse_ix = self._ellipse_ix
_is_irregular = self._is_irregular
_has_irregular = self._has_irregular
_transect_zs = self._transect_zs
_transect_As = self._transect_As
_transect_Bs = self._transect_Bs
_transect_Pes = self._transect_Pes
_transect_Rs = self._transect_Rs
_transect_codes = self._transect_codes
_transect_inds = self._transect_inds
_transect_lens = self._transect_lens
# Compute hydraulic geometry for regular geometries
# NOTE: Handle case for elliptical perimeter first
handle_elliptical_perimeter(_Pe_ik, _ellipse_ix, _Ik, _Ip1k, _h_Ik,
_g1_ik, _g2_ik)
# Compute hydraulic geometries for all other regular geometries
numba_hydraulic_geometry(_A_ik, _Pe_ik, _R_ik, _B_ik, _h_Ik,
_g1_ik, _g2_ik, _g3_ik, _g4_ik, _g5_ik, _g6_ik, _g7_ik,
_geom_codes, _Ik, _ik)
# Compute hydraulic geometry for irregular geometries
if _has_irregular:
numba_transect_geometry(_A_ik, _Pe_ik, _R_ik, _B_ik, _h_Ik, _is_irregular,
_transect_zs, _transect_As, _transect_Bs, _transect_Pes,
_transect_Rs, _transect_codes, _transect_inds,
_transect_lens, _Ik, _ik)
# Export to instance variables
self._A_ik = _A_ik
self._Pe_ik = _Pe_ik
self._R_ik = _R_ik
self._B_ik = _B_ik
def upstream_hydraulic_geometry(self, area='avg'):
"""
Compute hydraulic geometry of upstream ends of superlinks.
"""
# Import instance variables
_ik = self._ik # Link index
_Ik = self._Ik # Junction index
_ki = self._ki # Superlink index containing link ik
_h_Ik = self._h_Ik # Depth at junction Ik
_A_uk = self._A_uk # Flow area at upstream end of superlink k
_B_uk = self._B_uk # Top width at upstream end of superlink k
_Pe_uk = self._Pe_uk
_R_uk = self._R_uk
_dx_ik = self._dx_ik # Length of link ik
_g1_ik = self._g1_ik # Geometry 1 of link ik (vertical)
_g2_ik = self._g2_ik # Geometry 2 of link ik (horizontal)
_g3_ik = self._g3_ik # Geometry 3 of link ik (other)
_g4_ik = self._g4_ik # Geometry 4 of link ik (other)
_g5_ik = self._g5_ik # Geometry 5 of link ik (other)
_g6_ik = self._g6_ik # Geometry 6 of link ik (other)
_g7_ik = self._g7_ik # Geometry 7 of link ik (other)
_z_inv_uk = self._z_inv_uk # Invert offset of upstream end of superlink k
_J_uk = self._J_uk # Index of junction upstream of superlink k
H_j = self.H_j # Head at superjunction j
_theta_uk = self._theta_uk
_is_irregular = self._is_irregular
_uk_has_irregular = self._uk_has_irregular
_i_1k = self._i_1k
_I_1k = self._I_1k
_geom_codes = self._geom_codes
_transect_zs = self._transect_zs
_transect_As = self._transect_As
_transect_Bs = self._transect_Bs
_transect_Pes = self._transect_Pes
_transect_Rs = self._transect_Rs
_transect_codes = self._transect_codes
_transect_inds = self._transect_inds
_transect_lens = self._transect_lens
# Compute hydraulic geometry for regular geometries
numba_boundary_geometry(_A_uk, _Pe_uk, _R_uk, _B_uk, _h_Ik, H_j, _z_inv_uk, _theta_uk,
_g1_ik, _g2_ik, _g3_ik, _g4_ik, _g5_ik, _g6_ik, _g7_ik,
_geom_codes, _i_1k, _I_1k, _J_uk)
# Compute hydraulic geometry for irregular geometries
if _uk_has_irregular:
numba_boundary_transect(_A_uk, _Pe_uk, _R_uk, _B_uk, _h_Ik, H_j, _z_inv_uk, _theta_uk,
_is_irregular, _transect_zs, _transect_As, _transect_Bs,
_transect_Pes, _transect_Rs, _transect_codes, _transect_inds,
_transect_lens, _I_1k, _i_1k, _J_uk)
# TODO: Export rest of instance variables here?
# Export to instance variables
self._A_uk = _A_uk
def downstream_hydraulic_geometry(self, area='avg'):
"""
Compute hydraulic geometry of downstream ends of superlinks.
"""
# Import instance variables
_ik = self._ik # Link index
_Ip1k = self._Ip1k # Next junction index
_ki = self._ki # Superlink index containing link ik
_h_Ik = self._h_Ik # Depth at junction Ik
_A_dk = self._A_dk # Flow area at downstream end of superlink k
_B_dk = self._B_dk # Top width at downstream end of superlink k
_Pe_dk = self._Pe_dk
_R_dk = self._R_dk
_dx_ik = self._dx_ik # Length of link ik
_g1_ik = self._g1_ik # Geometry 1 of link ik (vertical)
_g2_ik = self._g2_ik # Geometry 2 of link ik (horizontal)
_g3_ik = self._g3_ik # Geometry 3 of link ik (other)
_g4_ik = self._g4_ik # Geometry 4 of link ik (other)
_g5_ik = self._g5_ik # Geometry 5 of link ik (other)
_g6_ik = self._g6_ik # Geometry 6 of link ik (other)
_g7_ik = self._g7_ik # Geometry 7 of link ik (other)
_z_inv_dk = self._z_inv_dk # Invert offset of downstream end of superlink k
_J_dk = self._J_dk # Index of junction downstream of superlink k
H_j = self.H_j # Head at superjunction j
_theta_dk = self._theta_dk
_is_irregular = self._is_irregular
_dk_has_irregular = self._dk_has_irregular
_i_nk = self._i_nk
_I_Np1k = self._I_Np1k
_geom_codes = self._geom_codes
_transect_zs = self._transect_zs
_transect_As = self._transect_As
_transect_Bs = self._transect_Bs
_transect_Pes = self._transect_Pes
_transect_Rs = self._transect_Rs
_transect_codes = self._transect_codes
_transect_inds = self._transect_inds
_transect_lens = self._transect_lens
# Compute hydraulic geometry for regular geometries
numba_boundary_geometry(_A_dk, _Pe_dk, _R_dk, _B_dk, _h_Ik, H_j, _z_inv_dk, _theta_dk,
_g1_ik, _g2_ik, _g3_ik, _g4_ik, _g5_ik, _g6_ik, _g7_ik,
_geom_codes, _i_nk, _I_Np1k, _J_dk)
# Compute hydraulic geometry for irregular geometries
if _dk_has_irregular:
numba_boundary_transect(_A_dk, _Pe_dk, _R_dk, _B_dk, _h_Ik, H_j, _z_inv_dk, _theta_dk,
_is_irregular, _transect_zs, _transect_As, _transect_Bs,
_transect_Pes, _transect_Rs, _transect_codes, _transect_inds,
_transect_lens, _I_Np1k, _i_nk, _J_dk)
# Export to instance variables
self._A_dk = _A_dk
def orifice_hydraulic_geometry(self, u=None):
"""
Compute hydraulic geometry for each link.
"""
# Import instance variables
_Ao = self._Ao # Flow area at link ik
_g1_o = self._g1_o # Geometry 1 of link ik (vertical)
_g2_o = self._g2_o # Geometry 2 of link ik (horizontal)
_g3_o = self._g3_o # Geometry 3 of link ik (other)
_geom_codes_o = self._geom_codes_o
n_o = self.n_o
_z_o = self._z_o
_J_uo = self._J_uo
_J_do = self._J_do
H_j = self.H_j
_z_inv_j = self._z_inv_j
# Compute effective head
H_uo = H_j[_J_uo]
H_do = H_j[_J_do]
_z_inv_uo = _z_inv_j[_J_uo]
h_e = np.maximum(H_uo - _z_inv_uo - _z_o, H_do - _z_inv_uo - _z_o)
if u is None:
u = np.zeros(n_o, dtype=np.float64)
# Compute orifice geometries
numba_orifice_geometry(_Ao, h_e, u, _g1_o, _g2_o, _g3_o, _geom_codes_o, n_o)
# Export to instance variables
self._Ao = _Ao
def compute_storage_areas(self):
"""
Compute surface area of superjunctions at current time step.
"""
# Import instance variables
_functional = self._functional # Superlinks with functional area curves
_tabular = self._tabular # Superlinks with tabular area curves
_storage_factory = self._storage_factory # Dictionary of storage curves
_storage_indices = self._storage_indices # Indices of storage curves
_storage_a = self._storage_a # Coefficient of functional storage curve
_storage_b = self._storage_b # Exponent of functional storage curve
_storage_c = self._storage_c # Constant of functional storage curve
H_j = self.H_j # Head at superjunction j
_z_inv_j = self._z_inv_j # Invert elevation at superjunction j
min_depth = self.min_depth # Minimum depth allowed at superjunctions/nodes
_A_sj = self._A_sj # Surface area at superjunction j
_storage_hs = self._storage_hs
_storage_As = self._storage_As
_storage_inds = self._storage_inds
_storage_lens = self._storage_lens
_storage_js = self._storage_js
_storage_codes = self._storage_codes
# Compute storage areas
_h_j = np.maximum(H_j - _z_inv_j, min_depth)
numba_compute_functional_storage_areas(_h_j, _A_sj, _storage_a, _storage_b,
_storage_c, _functional)
if _tabular.any():
numba_compute_tabular_storage_areas(_h_j, _A_sj, _storage_hs, _storage_As,
_storage_js, _storage_codes,
_storage_inds, _storage_lens)
# Export instance variables
self._A_sj = _A_sj
def node_velocities(self):
"""
Compute velocity of flow at each link and junction.
"""
# Import instance variables
_Ip1k = self._Ip1k # Next junction index
_A_ik = self._A_ik # Flow area at link ik
_Q_ik = self._Q_ik # Flow rate at link ik
_u_ik = self._u_ik
_u_Ik = self._u_Ik # Flow velocity at junction Ik
_u_Ip1k = self._u_Ip1k # Flow velocity at junction I + 1k
_dx_ik = self._dx_ik # Length of link ik
_link_start = self._link_start
_link_end = self._link_end
# Determine start and end nodes
# Compute link velocities
numba_u_ik(_Q_ik, _A_ik, _u_ik)
# Compute velocities for start nodes (1 -> Nk)
numba_u_Ik(_dx_ik, _u_ik, _link_start, _u_Ik)
# Compute velocities for end nodes (2 -> Nk+1)
numba_u_Ip1k(_dx_ik, _u_ik, _link_end, _u_Ip1k)
# Export to instance variables
self._u_ik = _u_ik
self._u_Ik = _u_Ik
self._u_Ip1k = _u_Ip1k
def link_coeffs(self, _dt=None, first_iter=True):
"""
Compute link momentum coefficients: a_ik, b_ik, c_ik and P_ik.
"""
# Import instance variables
_u_Ik = self._u_Ik # Flow velocity at junction Ik
_u_Ip1k = self._u_Ip1k # Flow velocity at junction I + 1k
_dx_ik = self._dx_ik # Length of link ik
_Sf_method_ik = self._Sf_method_ik
_n_ik = self._n_ik # Manning's roughness of link ik
_Q_ik_prev = np.copy(self.states['Q_ik'])
_Q_ik_next = self._Q_ik # Flow rate at link ik
_A_ik = self._A_ik # Flow area at link ik
_R_ik = self._R_ik # Hydraulic radius at link ik
_S_o_ik = self._S_o_ik # Channel bottom slope at link ik
_A_c_ik = self._A_c_ik # Area of control structure at link ik
_C_ik = self._C_ik # Discharge coefficient of control structure at link ik
_ctrl = self._ctrl # Control structure exists at link ik (y/n)
inertial_damping = self.inertial_damping # Use inertial damping (y/n)
_sigma_ik = self._sigma_ik # Inertial damping coefficient
g = 9.81
# If time step not specified, use instance time
if _dt is None:
_dt = self._dt
# Compute link coefficients
_a_ik = numba_a_ik(_u_Ik, _sigma_ik)
_c_ik = numba_c_ik(_u_Ip1k, _sigma_ik)
_b_ik = numba_b_ik(_dx_ik, _dt, _n_ik, _Q_ik_next, _A_ik, _R_ik, _A_c_ik,
_C_ik, _a_ik, _c_ik, _ctrl, _sigma_ik, _Sf_method_ik, g)
_P_ik = numba_P_ik(_Q_ik_prev, _dx_ik, _dt, _A_ik, _S_o_ik,
_sigma_ik, g)
# Export to instance variables
self._a_ik = _a_ik
self._b_ik = _b_ik
self._c_ik = _c_ik
self._P_ik = _P_ik
def node_coeffs(self, _Q_0Ik=None, _dt=None, first_iter=True):
"""
Compute nodal continuity coefficients: D_Ik and E_Ik.
"""
# Import instance variables
forward_I_i = self.forward_I_i # Index of link after junction Ik
backward_I_i = self.backward_I_i # Index of link before junction Ik
_is_start = self._is_start
_is_end = self._is_end
_B_ik = self._B_ik # Top width of link ik
_dx_ik = self._dx_ik # Length of link ik
_A_SIk = self._A_SIk # Surface area of junction Ik
_h_Ik_prev = np.copy(self.states['h_Ik']) # Depth at junction Ik
_E_Ik = self._E_Ik # Continuity coefficient E_Ik
_D_Ik = self._D_Ik # Continuity coefficient D_Ik
_B_uk = self._B_uk
_B_dk = self._B_dk
_dx_uk = self._dx_uk
_dx_dk = self._dx_dk
_kI = self._kI
# If no time step specified, use instance time step
if _dt is None:
_dt = self._dt
# If no nodal input specified, use zero input
if _Q_0Ik is None:
_Q_0Ik = np.zeros(_h_Ik_prev.size)
# Compute E_Ik and D_Ik
numba_node_coeffs(_D_Ik, _E_Ik, _Q_0Ik, _B_ik, _h_Ik_prev, _dx_ik, _A_SIk,
_B_uk, _B_dk, _dx_uk, _dx_dk, _kI,
_dt, forward_I_i, backward_I_i, _is_start, _is_end)
# Export instance variables
self._E_Ik = _E_Ik
self._D_Ik = _D_Ik
def forward_recurrence(self):
"""
Compute forward recurrence coefficients: T_ik, U_Ik, V_Ik, and W_Ik.
"""
# Import instance variables
_I_1k = self._I_1k # Index of first junction in each superlink
_i_1k = self._i_1k # Index of first link in each superlink
_A_ik = self._A_ik # Flow area in link ik
_E_Ik = self._E_Ik # Continuity coefficient E_Ik
_D_Ik = self._D_Ik # Continuity coefficient D_Ik
_a_ik = self._a_ik # Momentum coefficient a_ik
_b_ik = self._b_ik # Momentum coefficient b_ik
_c_ik = self._c_ik # Momentum coefficient c_ik
_P_ik = self._P_ik # Momentum coefficient P_ik
_T_ik = self._T_ik # Recurrence coefficient T_ik
_U_Ik = self._U_Ik # Recurrence coefficient U_Ik
_V_Ik = self._V_Ik # Recurrence coefficient V_Ik
_W_Ik = self._W_Ik # Recurrence coefficient W_Ik
NK = self.NK
nk = self.nk
numba_forward_recurrence(_T_ik, _U_Ik, _V_Ik, _W_Ik, _a_ik, _b_ik, _c_ik,
_P_ik, _A_ik, _E_Ik, _D_Ik, NK, nk, _I_1k, _i_1k)
# Export instance variables
self._T_ik = _T_ik
self._U_Ik = _U_Ik
self._V_Ik = _V_Ik
self._W_Ik = _W_Ik
def backward_recurrence(self):
"""
Compute backward recurrence coefficients: O_ik, X_Ik, Y_Ik, and Z_Ik.
"""
_I_Nk = self._I_Nk # Index of penultimate junction in each superlink
_i_nk = self._i_nk # Index of last link in each superlink
_A_ik = self._A_ik # Flow area in link ik
_E_Ik = self._E_Ik # Continuity coefficient E_Ik
_D_Ik = self._D_Ik # Continuity coefficient D_Ik
_a_ik = self._a_ik # Momentum coefficient a_ik
_b_ik = self._b_ik # Momentum coefficient b_ik
_c_ik = self._c_ik # Momentum coefficient c_ik
_P_ik = self._P_ik # Momentum coefficient P_ik
_O_ik = self._O_ik # Recurrence coefficient O_ik
_X_Ik = self._X_Ik # Recurrence coefficient X_Ik
_Y_Ik = self._Y_Ik # Recurrence coefficient Y_Ik
_Z_Ik = self._Z_Ik # Recurrence coefficient Z_Ik
NK = self.NK
nk = self.nk
numba_backward_recurrence(_O_ik, _X_Ik, _Y_Ik, _Z_Ik, _a_ik, _b_ik, _c_ik,
_P_ik, _A_ik, _E_Ik, _D_Ik, NK, nk, _I_Nk, _i_nk)
# Export instance variables
self._O_ik = _O_ik
self._X_Ik = _X_Ik
self._Y_Ik = _Y_Ik
self._Z_Ik = _Z_Ik
def superlink_upstream_head_coefficients(self, _dt=None):
"""
Compute upstream head coefficients for superlinks: kappa_uk, lambda_uk, and mu_uk.
"""
# Import instance variables
_I_1k = self._I_1k # Index of first junction in superlink k
_i_1k = self._i_1k # Index of first link in superlink k
_h_Ik = self._h_Ik # Depth at junction Ik
_J_uk = self._J_uk # Superjunction upstream of superlink k
_z_inv_uk = self._z_inv_uk # Invert offset of upstream end of superlink k
_A_ik = self._A_ik # Flow area of link ik
_B_ik = self._B_ik # Top width of link ik
_Q_ik = self._Q_ik # Flow rate of link ik
_bc_method = self._bc_method # Method for computing superlink boundary condition (j/z)
H_j = self.H_j # Head at superjunction j
_A_uk = self._A_uk # Flow area at upstream end of superlink k
_B_uk = self._B_uk # Top width at upstream end of superlink k
_R_uk = self._R_uk
_dx_uk = self._dx_uk
_S_o_uk = self._S_o_uk
_theta_uk = self._theta_uk
# Placeholder discharge coefficient
_C_uk = self._C_uk
# Current upstream flows
_Q_uk_next = self._Q_uk
_Q_uk_prev = np.copy(self.states['Q_uk'])
# Friction parameters
_n_uk = self._n_uk
_Sf_method_uk = self._Sf_method_uk
g = 9.81
# If time step not specified, use instance time
if _dt is None:
_dt = self._dt
# Compute theta indicator variables
_H_juk = H_j[_J_uk]
upstream_depth_above_invert = _H_juk >= _z_inv_uk
_theta_uk.fill(0.)
_theta_uk[upstream_depth_above_invert] = 1.
if _bc_method == 'z':
# Compute superlink upstream coefficients (Zahner)
_gamma_uk = gamma_uk(_Q_uk_next, _C_uk, _A_uk, g)
self._kappa_uk = _gamma_uk
self._lambda_uk = _theta_uk
self._mu_uk = - _theta_uk * _z_inv_uk
elif _bc_method == 'b':
# Compute superlink upstream coefficients (momentum)
self._kappa_uk = kappa_uk(_Q_uk_next, _dx_uk, _A_uk, _C_uk,
_R_uk, _n_uk, _Sf_method_uk, _dt, g)
self._lambda_uk = _theta_uk
self._mu_uk = mu_uk(_Q_uk_prev, _dx_uk, _A_uk, _theta_uk, _z_inv_uk,
_S_o_uk, _dt, g)
else:
raise ValueError('Invalid BC method {}.'.format(_bc_method))
self._theta_uk = _theta_uk
def superlink_downstream_head_coefficients(self, _dt=None):
"""
Compute downstream head coefficients for superlinks: kappa_dk, lambda_dk, and mu_dk.
"""
# Import instance variables
_I_Np1k = self._I_Np1k # Index of last junction in superlink k
_i_nk = self._i_nk # Index of last link in superlink k
_h_Ik = self._h_Ik # Depth at junction Ik
_J_dk = self._J_dk # Superjunction downstream of superlink k
_z_inv_dk = self._z_inv_dk # Invert offset of downstream end of superlink k
_A_ik = self._A_ik # Flow area of link ik
_B_ik = self._B_ik # Top width of link ik
_Q_ik = self._Q_ik # Flow rate of link ik
_bc_method = self._bc_method # Method for computing superlink boundary condition (j/z)
H_j = self.H_j # Head at superjunction j
_A_dk = self._A_dk # Flow area at downstream end of superlink k
_B_dk = self._B_dk # Top width at downstream end of superlink k
_R_dk = self._R_dk
_dx_dk = self._dx_dk
_S_o_dk = self._S_o_dk
_theta_dk = self._theta_dk
# Placeholder discharge coefficient
_C_dk = self._C_dk
# Current downstream flows
_Q_dk_next = self._Q_dk
_Q_dk_prev = np.copy(self.states['Q_dk'])
# Friction parameters
_n_dk = self._n_dk
_Sf_method_dk = self._Sf_method_dk
g = 9.81
if _dt is None:
_dt = self._dt
# Compute theta indicator variables
_H_jdk = H_j[_J_dk]
downstream_depth_above_invert = _H_jdk >= _z_inv_dk
_theta_dk.fill(0.)
_theta_dk[downstream_depth_above_invert] = 1.
if _bc_method == 'z':
# Compute superlink downstream coefficients (Zahner)
_gamma_dk = gamma_dk(_Q_dk_next, _C_dk, _A_dk, g)
self._kappa_dk = _gamma_dk
self._lambda_dk = _theta_dk
self._mu_dk = - _theta_dk * _z_inv_dk
elif _bc_method == 'b':
# Compute superlink upstream coefficients (momentum)
self._kappa_dk = kappa_dk(_Q_dk_next, _dx_dk, _A_dk, _C_dk,
_R_dk, _n_dk, _Sf_method_dk, _dt, g)
self._lambda_dk = _theta_dk
self._mu_dk = mu_dk(_Q_dk_prev, _dx_dk, _A_dk, _theta_dk, _z_inv_dk, _S_o_dk, _dt, g)
else:
raise ValueError('Invalid BC method {}.'.format(_bc_method))
self._theta_dk = _theta_dk
def superlink_flow_coefficients(self):
"""
Compute superlink flow coefficients: alpha_uk, beta_uk, chi_uk,
alpha_dk, beta_dk, chi_dk.
"""
# Import instance variables
_I_1k = self._I_1k # Index of first junction in superlink k
_I_Nk = self._I_Nk # Index of penultimate junction in superlink k
_I_Np1k = self._I_Np1k # Index of last junction in superlink k
_D_Ik = self._D_Ik # Continuity coefficient
_E_Ik = self._E_Ik # Continuity coefficient
_X_Ik = self._X_Ik # Backward recurrence coefficient X_Ik
_Y_Ik = self._Y_Ik # Backward recurrence coefficient Y_Ik
_Z_Ik = self._Z_Ik # Backward recurrence coefficient Z_Ik
_U_Ik = self._U_Ik # Forward recurrence coefficient U_Ik
_V_Ik = self._V_Ik # Forward recurrence coefficient V_Ik
_W_Ik = self._W_Ik # Forward recurrence coefficient W_Ik
_kappa_uk = self._kappa_uk # Upstream superlink head coefficient kappa_uk
_kappa_dk = self._kappa_dk # Downstream superlink head coefficient kappa_dk
_lambda_uk = self._lambda_uk # Upstream superlink head coefficient lambda_uk
_lambda_dk = self._lambda_dk # Downstream superlink head coefficient lambda_dk
_mu_uk = self._mu_uk # Upstream superlink head coefficient mu_uk
_mu_dk = self._mu_dk # Downstream superlink head coefficient mu_dk
_J_uk = self._J_uk # Superjunction upstream of superlink k
_J_dk = self._J_dk # Superjunction downstream of superlink k
H_j = self.H_j # Head at superjunction j
_z_inv_uk = self._z_inv_uk # Invert offset of upstream end of superlink k
_z_inv_dk = self._z_inv_dk # Invert offset of downstream end of superlink k
_z_inv_j = self._z_inv_j # Invert elevation at superjunction j
_end_method = self._end_method # Method for computing flow at pipe ends
_theta_uk = self._theta_uk # Upstream indicator variable
_theta_dk = self._theta_dk # Downstream indicator variable
if _end_method == 'o':
_X_1k = _X_Ik[_I_1k]
_Y_1k = _Y_Ik[_I_1k]
_Z_1k = _Z_Ik[_I_1k]
_U_Nk = _U_Ik[_I_Nk]
_V_Nk = _V_Ik[_I_Nk]
_W_Nk = _W_Ik[_I_Nk]
else:
_X_1k = _X_Ik[_I_1k] + _E_Ik[_I_1k]
_Y_1k = _Y_Ik[_I_1k] - _D_Ik[_I_1k]