@@ -23,6 +23,7 @@ def __init__(self):
23
23
self .dx_2cell = None
24
24
self .Q_s_0 = None
25
25
self .z_bl = None
26
+ self .ssd = 0. # distributed sources or sinks
26
27
self .sinuosity = 1.
27
28
self .intermittency = 1.
28
29
self .t = 0
@@ -44,7 +45,7 @@ def set_upstream_segment_IDs(self, upstream_segment_IDs):
44
45
Requires list or None input
45
46
"""
46
47
self .upstream_segment_IDs = upstream_segment_IDs
47
-
48
+
48
49
def set_downstream_segment_IDs (self , downstream_segment_IDs ):
49
50
"""
50
51
Set a list of ID numbers assigned to downstream river segments
@@ -54,7 +55,7 @@ def set_downstream_segment_IDs(self, downstream_segment_IDs):
54
55
55
56
#def set_downstream_dx(self, downstream_dx)
56
57
# """
57
- # Downstream dx, if applicable, for linking together segments in a
58
+ # Downstream dx, if applicable, for linking together segments in a
58
59
# network. This could be part of x_ext
59
60
# """
60
61
# self.downstream_dx = downstream_dx
@@ -67,15 +68,15 @@ def basic_constants(self):
67
68
self .epsilon = 0.2 # Parker channel criterion
68
69
self .tau_star_c = 0.0495
69
70
self .phi = 3.97 # coefficient for Wong and Parker MPM
70
-
71
+
71
72
def bedload_lumped_constants (self ):
72
73
self .k_qs = self .phi * ((self .rho_s - self .rho )/ self .rho )** .5 \
73
74
* self .g ** .5 * self .epsilon ** 1.5 * self .tau_star_c ** 1.5
74
75
self .k_b = 0.17 * self .g ** (- .5 ) \
75
76
* ((self .rho_s - self .rho )/ self .rho )** (- 5 / 3. ) \
76
77
* (1 + self .epsilon )** (- 5 / 3. ) * self .tau_star_c ** (- 5 / 3. )
77
78
self .k_Qs = self .k_b * self .k_qs
78
-
79
+
79
80
def set_hydrologic_constants (self , P_xA = 7 / 4. , P_AQ = 0.7 , P_xQ = None ):
80
81
self .P_xA = P_xA # inverse Hack exponent
81
82
self .P_AQ = P_AQ # drainage area -- discharge exponent
@@ -87,7 +88,7 @@ def set_hydrologic_constants(self, P_xA=7/4., P_AQ=0.7, P_xQ=None):
87
88
88
89
def set_intermittency (self , I ):
89
90
self .intermittency = I
90
-
91
+
91
92
def set_x (self , x = None , x_ext = None , dx = None , nx = None , x0 = None ):
92
93
"""
93
94
Set x directly or calculate it.
@@ -121,7 +122,7 @@ def set_x(self, x=None, x_ext=None, dx=None, nx=None, x0=None):
121
122
self .nx = len (self .x )
122
123
if (nx is not None ) and (nx != self .nx ):
123
124
warnings .warn ("Choosing x length instead of supplied nx" )
124
-
125
+
125
126
def set_z (self , z = None , z_ext = None , S0 = None , z1 = 0 ):
126
127
"""
127
128
Set z directly or calculate it
@@ -143,7 +144,7 @@ def set_z(self, z=None, z_ext=None, S0=None, z1=0):
143
144
else :
144
145
sys .exit ("Error defining variable" )
145
146
#self.dz = self.z_ext[2:] - self.z_ext[:-2] # dz over 2*dx!
146
-
147
+
147
148
def set_A (self , A = None , A_ext = None , k_xA = None , P_xA = None ):
148
149
"""
149
150
Set A directly or calculate it
@@ -183,7 +184,7 @@ def set_Q(self, Q=None, Q_ext=None, q_R=None, A_R=None, P_AQ=None,
183
184
self .Q = Q * np .ones (self .x .shape )
184
185
# Have to be able to pass Q_ext, created with adjacencies
185
186
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
186
- Q_ext = np .hstack ( (2 * self .Q [0 ]- self .Q [1 ],
187
+ Q_ext = np .hstack ( (2 * self .Q [0 ]- self .Q [1 ],
187
188
self .Q ,
188
189
2 * self .Q [- 1 ]- self .Q [- 2 ]) )
189
190
elif Q_ext is not None :
@@ -230,20 +231,20 @@ def set_B(self, B=None, B_ext=None, k_xB=None, P_xB=None):
230
231
B_ext = k_xB * self .x_ext ** P_xB
231
232
self .k_xB = k_xB
232
233
self .P_xB = P_xB
233
- # dB over 2*dx!
234
- # This, like the dQ, is intentional.
235
- self .dB = B_ext [2 :] - B_ext [:- 2 ]
236
-
234
+
237
235
def set_uplift_rate (self , U ):
238
236
"""
239
237
Uplift rate if positive -- or equivalently, rate of base-level fall
240
238
Subsidence (or base-level rise) accomplished by negative uplift
241
239
"""
242
240
self .U = - U # not sure this is the best -- flipping the sign
243
241
242
+ def set_source_sink_distributed (self , U ):
243
+ self .ssd = - U * self .dt
244
+
244
245
def set_niter (self , niter = 3 ):
245
246
self .niter = niter
246
-
247
+
247
248
def set_Qs_input_upstream (self , Q_s_0 ):
248
249
"""
249
250
S0, the boundary-condition slope, is set as a function of Q_s_0.
@@ -254,11 +255,11 @@ def set_Qs_input_upstream(self, Q_s_0):
254
255
"""
255
256
self .Q_s_0 = Q_s_0
256
257
# Q[0] is centerpoint of S?
257
- self .S0 = - self .sinuosity * ( Q_s_0 / ( self .k_Qs * self .intermittency
258
+ self .S0 = - self .sinuosity * ( Q_s_0 / ( self .k_Qs * self .intermittency
258
259
* self .Q [0 ]) )** (6 / 7. )
259
260
# Give upstream cell the same width as the first cell in domain
260
261
self .z_ext [0 ] = self .z [0 ] - self .S0 * self .dx_ext [0 ]
261
-
262
+
262
263
def update_z_ext_0 (self ):
263
264
# Give upstream cell the same width as the first cell in domain
264
265
self .z_ext [0 ] = self .z [0 ] - self .S0 * self .dx_ext [0 ]
@@ -298,18 +299,17 @@ def set_z_bl(self, z_bl):
298
299
def set_bcr_Dirichlet (self ):
299
300
self .bcr = self .z_bl * ( self .C1 [- 1 ] * 7 / 3. \
300
301
* (1 / self .dx_ext [- 2 ] + 1 / self .dx_ext [- 1 ])/ 2. \
301
- + self .dQ [- 1 ]/ self .Q [- 1 ] \
302
- - self .dB [- 1 ]/ self .B [- 1 ] )
303
-
302
+ + self .dQ [- 1 ]/ self .Q [- 1 ] )
303
+
304
304
def set_bcl_Neumann_RHS (self ):
305
305
"""
306
- Boundary condition on the left (conventionally upstream) side of the
306
+ Boundary condition on the left (conventionally upstream) side of the
307
307
domain.
308
-
309
- This is for the RHS of the equation as a result of the ghost-node
310
- approach for the Neumann upstream boundary condition with a prescribed
308
+
309
+ This is for the RHS of the equation as a result of the ghost-node
310
+ approach for the Neumann upstream boundary condition with a prescribed
311
311
transport slope.
312
-
312
+
313
313
This equals 2*dx * S_0 * left_coefficients
314
314
(2*dx is replaced with the x_ext{i+1} - x_ext{i-1} for the irregular
315
315
grid case)
@@ -318,24 +318,23 @@ def set_bcl_Neumann_RHS(self):
318
318
# 2*dx * S_0 * left_coefficients
319
319
self .bcl = self .dx_ext_2cell [0 ] * self .S0 * \
320
320
- self .C1 [0 ] * ( 7 / 3. / self .dx_ext [0 ]
321
- - self .dQ [0 ]/ self .Q [0 ]/ self .dx_ext_2cell [0 ]
322
- + self .dB [0 ]/ self .B [0 ]/ self .dx_ext_2cell [0 ] )
321
+ - self .dQ [0 ]/ self .Q [0 ]/ self .dx_ext_2cell [0 ] )
323
322
324
323
def set_bcl_Neumann_LHS (self ):
325
324
"""
326
- Boundary condition on the left (conventionally upstream) side of the
325
+ Boundary condition on the left (conventionally upstream) side of the
327
326
domain.
328
-
329
- This changes the right diagonal on the LHS of the equation using a
330
- ghost-node approach by defining a boundary slope that is calculated
327
+
328
+ This changes the right diagonal on the LHS of the equation using a
329
+ ghost-node approach by defining a boundary slope that is calculated
331
330
as a function of input water-to-sediment supply ratio.
332
331
333
332
LHS = coeff_right at 0 + coeff_left at 0, with appropriate dx
334
333
for boundary (already supplied)
335
334
"""
336
335
self .right [0 ] = - self .C1 [0 ] * 7 / 3. \
337
336
* (1 / self .dx_ext [0 ] + 1 / self .dx_ext [1 ])
338
-
337
+
339
338
def evolve_threshold_width_river (self , nt = 1 , dt = 3.15E7 ):
340
339
"""
341
340
Solve the triadiagonal matrix through time, with a given
@@ -362,14 +361,14 @@ def evolve_threshold_width_river(self, nt=1, dt=3.15E7):
362
361
* self .B + self .Q_s_0
363
362
if self .S0 is not None :
364
363
self .update_z_ext_0 ()
365
-
364
+
366
365
def build_LHS_coeff_C0 (self , dt = 3.15E7 ):
367
366
"""
368
367
Build the LHS coefficient for the tridiagonal matrix.
369
- This is the "C0" coefficient, which is likely to be constant and
368
+ This is the "C0" coefficient, which is likely to be constant and
370
369
uniform unless there are dynamic changes in width (epsilon_0 in
371
370
k_Qs), sinuosity, or intermittency, in space and/or through time
372
-
371
+
373
372
See eq. D3. "1/4" subsumed into "build matrices".
374
373
For C1 (other function), Q/B included as well.
375
374
"""
@@ -384,15 +383,13 @@ def build_matrices(self):
384
383
"""
385
384
self .compute_coefficient_time_varying ()
386
385
self .left = - self .C1 * ( (7 / 3. )/ self .dx_ext [:- 1 ]
387
- - self .dQ / self .Q / self .dx_ext_2cell \
388
- + self .dB / self .B / self .dx_ext_2cell )
386
+ - self .dQ / self .Q / self .dx_ext_2cell )
389
387
self .center = - self .C1 * ( (7 / 3. ) \
390
388
* (- 1 / self .dx_ext [:- 1 ] \
391
389
- 1 / self .dx_ext [1 :]) ) \
392
390
+ 1.
393
391
self .right = - self .C1 * ( (7 / 3. )/ self .dx_ext [1 :] # REALLY?
394
- + self .dQ / self .Q / self .dx_ext_2cell \
395
- - self .dB / self .B / self .dx_ext_2cell )
392
+ + self .dQ / self .Q / self .dx_ext_2cell )
396
393
# Apply boundary conditions if the segment is at the edges of the
397
394
# network (both if there is only one segment!)
398
395
if len (self .upstream_segment_IDs ) == 0 :
@@ -409,12 +406,12 @@ def build_matrices(self):
409
406
self .right = np .roll (self .right , 1 )
410
407
self .diagonals = np .vstack ((self .left , self .center , self .right ))
411
408
self .offsets = np .array ([- 1 , 0 , 1 ])
412
- self .LHSmatrix = spdiags (self .diagonals , self .offsets , len (self .z ),
409
+ self .LHSmatrix = spdiags (self .diagonals , self .offsets , len (self .z ),
413
410
len (self .z ), format = 'csr' )
414
- self .RHS = np .hstack ((self .bcl + self .z [0 ], self .z [1 :- 1 ],
415
- self .bcr + self .z [- 1 ]))
416
-
417
- def analytical_threshold_width (self , P_xB = None , P_xQ = None , x0 = None , x1 = None ,
411
+ self .RHS = np .hstack ((self .bcl + self .z [0 ], self .z [1 :- 1 ],
412
+ self .bcr + self .z [- 1 ])) + self . ssd
413
+
414
+ def analytical_threshold_width (self , P_xB = None , P_xQ = None , x0 = None , x1 = None ,
418
415
z0 = None , z1 = None ):
419
416
"""
420
417
Analytical: no uplift
@@ -440,8 +437,8 @@ def analytical_threshold_width(self, P_xB=None, P_xQ=None, x0=None, x1=None,
440
437
self .c_a = z0 - x0 ** self .P_a / (x1 ** self .P_a - x0 ** self .P_a ) * (z1 - z0 ) # gamma
441
438
self .zanalytical = self .k_a * self .x ** self .P_a + self .c_a
442
439
return self .zanalytical
443
-
444
- def analytical_threshold_width_perturbation (self , P_xB = None , P_xQ = None , x0 = None , x1 = None ,
440
+
441
+ def analytical_threshold_width_perturbation (self , P_xB = None , P_xQ = None , x0 = None , x1 = None ,
445
442
z0 = None , z1 = None , U = None ):
446
443
if x0 is None :
447
444
x0 = self .x [0 ]
@@ -480,12 +477,12 @@ def analytical_threshold_width_perturbation(self, P_xB=None, P_xQ=None, x0=None,
480
477
self .zanalytical = c1 * self .x ** self .P_a / self .P_a \
481
478
- self .U * self .x ** (2 - P ) / (K * (P - 2 ) * (self .P_a + P - 2 )) \
482
479
+ c2
483
-
480
+
484
481
#def analytical_threshold_width_perturbation_2(self):
485
482
# self.analytical_threshold_width()
486
-
483
+
487
484
def compute_Q_s (self ):
488
- self .S = np .abs ( (self .z_ext [2 :] - self .z_ext [:- 2 ]) /
485
+ self .S = np .abs ( (self .z_ext [2 :] - self .z_ext [:- 2 ]) /
489
486
(self .dx_ext_2cell ) ) / self .sinuosity
490
487
self .Q_s = - np .sign ( self .z_ext [2 :] - self .z_ext [:- 2 ] ) \
491
488
* self .k_Qs * self .intermittency * self .Q * self .S ** (7 / 6. )
@@ -503,7 +500,7 @@ def slope_area(self, verbose=False):
503
500
print ("Concavity = " , self .theta )
504
501
print ("k_s = " , self .ks )
505
502
print ("R2 = " , out .rvalue ** 2. )
506
-
503
+
507
504
class Network (object ):
508
505
"""
509
506
Gravel-bed river long-profile solution builder and solver
@@ -517,14 +514,14 @@ def __init__(self, list_of_LongProfile_objects):
517
514
"""
518
515
self .list_of_LongProfile_objects = list_of_LongProfile_objects
519
516
self .t = 0
520
-
517
+
521
518
def build_ID_list (self ):
522
519
self .IDs = []
523
520
for lp in self .list_of_LongProfile_objects :
524
521
# IDs
525
522
self .IDs .append (lp .ID )
526
523
self .IDs = np .array (self .IDs )
527
-
524
+
528
525
def build_block_diagonal_matrix_core (self ):
529
526
self .block_start_absolute = []
530
527
self .block_end_absolute = []
@@ -548,7 +545,7 @@ def build_block_diagonal_matrix_core(self):
548
545
block_diag (self .sparse_matrices ) )
549
546
self .block_start_absolute = np .array (self .block_start_absolute )
550
547
self .block_end_absolute = np .array (self .block_end_absolute ) - 1
551
-
548
+
552
549
def add_block_diagonal_matrix_upstream_boundary_conditions (self ):
553
550
for lp in self .list_of_LongProfile_objects :
554
551
for ID in lp .upstream_segment_IDs :
@@ -561,12 +558,12 @@ def add_block_diagonal_matrix_upstream_boundary_conditions(self):
561
558
/ ((1 - upseg .lambda_p ) * upseg .sinuosity ** (7 / 6. )) \
562
559
* self .dt / (2 * lp .dx_ext [0 ])
563
560
#C0 = upseg.C0[-1] # Should be consistent
564
- dzdx_0_16 = ( np .abs (lp .z_ext [1 ] - lp .z_ext [0 ])
561
+ dzdx_0_16 = ( np .abs (lp .z_ext [1 ] - lp .z_ext [0 ])
565
562
/ (lp .dx_ext [0 ]))** (1 / 6. )
566
563
C1 = C0 * dzdx_0_16 * upseg .Q [- 1 ] / lp .B [0 ]
567
564
left_new = - C1 * 7 / 6. * 2 / lp .dx_ext [0 ]
568
565
self .LHSblock_matrix [row , col ] = left_new
569
-
566
+
570
567
def add_block_diagonal_matrix_downstream_boundary_conditions (self ):
571
568
for lp in self .list_of_LongProfile_objects :
572
569
for ID in lp .downstream_segment_IDs :
@@ -578,7 +575,7 @@ def add_block_diagonal_matrix_downstream_boundary_conditions(self):
578
575
C0 = downseg .k_Qs * downseg .intermittency \
579
576
/ ((1 - downseg .lambda_p ) * downseg .sinuosity ** (7 / 6. )) \
580
577
* self .dt / (2 * lp .dx_ext [- 1 ])
581
- dzdx_0_16 = ( np .abs (lp .z_ext [- 2 ] - lp .z_ext [- 1 ])
578
+ dzdx_0_16 = ( np .abs (lp .z_ext [- 2 ] - lp .z_ext [- 1 ])
582
579
/ (lp .dx_ext [0 ]))** (1 / 6. )
583
580
C1 = C0 * dzdx_0_16 * lp .Q [- 1 ] / downseg .B [0 ]
584
581
right_new = - C1 * 7 / 6. * 2 / lp .dx_ext [- 1 ]
@@ -677,6 +674,8 @@ def evolve_threshold_width_river_network(self, nt=1, dt=3.15E7):
677
674
self .update_zext ()
678
675
self .t += self .dt # Update each lp z? Should make a global class
679
676
# that these both inherit from
677
+ for lp in self .list_of_LongProfile_objects :
678
+ lp .t = self .t
680
679
i = 0
681
680
idx = 0
682
681
for lp in self .list_of_LongProfile_objects :
@@ -686,4 +685,3 @@ def evolve_threshold_width_river_network(self, nt=1, dt=3.15E7):
686
685
# + lp.Q_s_0
687
686
if lp .S0 is not None :
688
687
lp .update_z_ext_0 ()
689
-
0 commit comments