Skip to content

Commit 039e498

Browse files
committed
Remove impact of valley-width variations
I had erroneously included valley width within the derivative in the Exner equation. I have fixed this in the code and am preparing corrections to the ESurf article.
1 parent 34f1214 commit 039e498

File tree

1 file changed

+53
-55
lines changed

1 file changed

+53
-55
lines changed

grlp/grlp.py

Lines changed: 53 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ def __init__(self):
2323
self.dx_2cell = None
2424
self.Q_s_0 = None
2525
self.z_bl = None
26+
self.ssd = 0. # distributed sources or sinks
2627
self.sinuosity = 1.
2728
self.intermittency = 1.
2829
self.t = 0
@@ -44,7 +45,7 @@ def set_upstream_segment_IDs(self, upstream_segment_IDs):
4445
Requires list or None input
4546
"""
4647
self.upstream_segment_IDs = upstream_segment_IDs
47-
48+
4849
def set_downstream_segment_IDs(self, downstream_segment_IDs):
4950
"""
5051
Set a list of ID numbers assigned to downstream river segments
@@ -54,7 +55,7 @@ def set_downstream_segment_IDs(self, downstream_segment_IDs):
5455

5556
#def set_downstream_dx(self, downstream_dx)
5657
# """
57-
# Downstream dx, if applicable, for linking together segments in a
58+
# Downstream dx, if applicable, for linking together segments in a
5859
# network. This could be part of x_ext
5960
# """
6061
# self.downstream_dx = downstream_dx
@@ -67,15 +68,15 @@ def basic_constants(self):
6768
self.epsilon = 0.2 # Parker channel criterion
6869
self.tau_star_c = 0.0495
6970
self.phi = 3.97 # coefficient for Wong and Parker MPM
70-
71+
7172
def bedload_lumped_constants(self):
7273
self.k_qs = self.phi * ((self.rho_s - self.rho)/self.rho)**.5 \
7374
* self.g**.5 * self.epsilon**1.5 * self.tau_star_c**1.5
7475
self.k_b = 0.17 * self.g**(-.5) \
7576
* ((self.rho_s - self.rho)/self.rho)**(-5/3.) \
7677
* (1+self.epsilon)**(-5/3.) * self.tau_star_c**(-5/3.)
7778
self.k_Qs = self.k_b * self.k_qs
78-
79+
7980
def set_hydrologic_constants(self, P_xA=7/4., P_AQ=0.7, P_xQ=None):
8081
self.P_xA = P_xA # inverse Hack exponent
8182
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):
8788

8889
def set_intermittency(self, I):
8990
self.intermittency = I
90-
91+
9192
def set_x(self, x=None, x_ext=None, dx=None, nx=None, x0=None):
9293
"""
9394
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):
121122
self.nx = len(self.x)
122123
if (nx is not None) and (nx != self.nx):
123124
warnings.warn("Choosing x length instead of supplied nx")
124-
125+
125126
def set_z(self, z=None, z_ext=None, S0=None, z1=0):
126127
"""
127128
Set z directly or calculate it
@@ -143,7 +144,7 @@ def set_z(self, z=None, z_ext=None, S0=None, z1=0):
143144
else:
144145
sys.exit("Error defining variable")
145146
#self.dz = self.z_ext[2:] - self.z_ext[:-2] # dz over 2*dx!
146-
147+
147148
def set_A(self, A=None, A_ext=None, k_xA=None, P_xA=None):
148149
"""
149150
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,
183184
self.Q = Q * np.ones(self.x.shape)
184185
# Have to be able to pass Q_ext, created with adjacencies
185186
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
186-
Q_ext = np.hstack( (2*self.Q[0]-self.Q[1],
187+
Q_ext = np.hstack( (2*self.Q[0]-self.Q[1],
187188
self.Q,
188189
2*self.Q[-1]-self.Q[-2]) )
189190
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):
230231
B_ext = k_xB * self.x_ext**P_xB
231232
self.k_xB = k_xB
232233
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+
237235
def set_uplift_rate(self, U):
238236
"""
239237
Uplift rate if positive -- or equivalently, rate of base-level fall
240238
Subsidence (or base-level rise) accomplished by negative uplift
241239
"""
242240
self.U = -U # not sure this is the best -- flipping the sign
243241

242+
def set_source_sink_distributed(self, U):
243+
self.ssd = -U * self.dt
244+
244245
def set_niter(self, niter=3):
245246
self.niter = niter
246-
247+
247248
def set_Qs_input_upstream(self, Q_s_0):
248249
"""
249250
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):
254255
"""
255256
self.Q_s_0 = Q_s_0
256257
# 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
258259
* self.Q[0]) )**(6/7.)
259260
# Give upstream cell the same width as the first cell in domain
260261
self.z_ext[0] = self.z[0] - self.S0 * self.dx_ext[0]
261-
262+
262263
def update_z_ext_0(self):
263264
# Give upstream cell the same width as the first cell in domain
264265
self.z_ext[0] = self.z[0] - self.S0 * self.dx_ext[0]
@@ -298,18 +299,17 @@ def set_z_bl(self, z_bl):
298299
def set_bcr_Dirichlet(self):
299300
self.bcr = self.z_bl * ( self.C1[-1] * 7/3. \
300301
* (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+
304304
def set_bcl_Neumann_RHS(self):
305305
"""
306-
Boundary condition on the left (conventionally upstream) side of the
306+
Boundary condition on the left (conventionally upstream) side of the
307307
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
311311
transport slope.
312-
312+
313313
This equals 2*dx * S_0 * left_coefficients
314314
(2*dx is replaced with the x_ext{i+1} - x_ext{i-1} for the irregular
315315
grid case)
@@ -318,24 +318,23 @@ def set_bcl_Neumann_RHS(self):
318318
# 2*dx * S_0 * left_coefficients
319319
self.bcl = self.dx_ext_2cell[0] * self.S0 * \
320320
- 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] )
323322

324323
def set_bcl_Neumann_LHS(self):
325324
"""
326-
Boundary condition on the left (conventionally upstream) side of the
325+
Boundary condition on the left (conventionally upstream) side of the
327326
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
331330
as a function of input water-to-sediment supply ratio.
332331
333332
LHS = coeff_right at 0 + coeff_left at 0, with appropriate dx
334333
for boundary (already supplied)
335334
"""
336335
self.right[0] = -self.C1[0] * 7/3. \
337336
* (1/self.dx_ext[0] + 1/self.dx_ext[1])
338-
337+
339338
def evolve_threshold_width_river(self, nt=1, dt=3.15E7):
340339
"""
341340
Solve the triadiagonal matrix through time, with a given
@@ -362,14 +361,14 @@ def evolve_threshold_width_river(self, nt=1, dt=3.15E7):
362361
* self.B + self.Q_s_0
363362
if self.S0 is not None:
364363
self.update_z_ext_0()
365-
364+
366365
def build_LHS_coeff_C0(self, dt=3.15E7):
367366
"""
368367
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
370369
uniform unless there are dynamic changes in width (epsilon_0 in
371370
k_Qs), sinuosity, or intermittency, in space and/or through time
372-
371+
373372
See eq. D3. "1/4" subsumed into "build matrices".
374373
For C1 (other function), Q/B included as well.
375374
"""
@@ -384,15 +383,13 @@ def build_matrices(self):
384383
"""
385384
self.compute_coefficient_time_varying()
386385
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 )
389387
self.center = -self.C1 * ( (7/3.) \
390388
* (-1/self.dx_ext[:-1] \
391389
-1/self.dx_ext[1:]) ) \
392390
+ 1.
393391
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 )
396393
# Apply boundary conditions if the segment is at the edges of the
397394
# network (both if there is only one segment!)
398395
if len(self.upstream_segment_IDs) == 0:
@@ -409,12 +406,12 @@ def build_matrices(self):
409406
self.right = np.roll(self.right, 1)
410407
self.diagonals = np.vstack((self.left, self.center, self.right))
411408
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),
413410
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,
418415
z0=None, z1=None):
419416
"""
420417
Analytical: no uplift
@@ -440,8 +437,8 @@ def analytical_threshold_width(self, P_xB=None, P_xQ=None, x0=None, x1=None,
440437
self.c_a = z0 - x0**self.P_a/(x1**self.P_a - x0**self.P_a) * (z1 - z0) # gamma
441438
self.zanalytical = self.k_a * self.x**self.P_a + self.c_a
442439
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,
445442
z0=None, z1=None, U=None):
446443
if x0 is None:
447444
x0 = self.x[0]
@@ -480,12 +477,12 @@ def analytical_threshold_width_perturbation(self, P_xB=None, P_xQ=None, x0=None,
480477
self.zanalytical = c1 * self.x**self.P_a / self.P_a \
481478
- self.U * self.x**(2-P) / (K * (P-2) * (self.P_a + P - 2)) \
482479
+ c2
483-
480+
484481
#def analytical_threshold_width_perturbation_2(self):
485482
# self.analytical_threshold_width()
486-
483+
487484
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]) /
489486
(self.dx_ext_2cell) ) / self.sinuosity
490487
self.Q_s = -np.sign( self.z_ext[2:] - self.z_ext[:-2] ) \
491488
* self.k_Qs * self.intermittency * self.Q * self.S**(7/6.)
@@ -503,7 +500,7 @@ def slope_area(self, verbose=False):
503500
print("Concavity = ", self.theta)
504501
print("k_s = ", self.ks)
505502
print("R2 = ", out.rvalue**2.)
506-
503+
507504
class Network(object):
508505
"""
509506
Gravel-bed river long-profile solution builder and solver
@@ -517,14 +514,14 @@ def __init__(self, list_of_LongProfile_objects):
517514
"""
518515
self.list_of_LongProfile_objects = list_of_LongProfile_objects
519516
self.t = 0
520-
517+
521518
def build_ID_list(self):
522519
self.IDs = []
523520
for lp in self.list_of_LongProfile_objects:
524521
# IDs
525522
self.IDs.append(lp.ID)
526523
self.IDs = np.array(self.IDs)
527-
524+
528525
def build_block_diagonal_matrix_core(self):
529526
self.block_start_absolute = []
530527
self.block_end_absolute = []
@@ -548,7 +545,7 @@ def build_block_diagonal_matrix_core(self):
548545
block_diag(self.sparse_matrices) )
549546
self.block_start_absolute = np.array(self.block_start_absolute)
550547
self.block_end_absolute = np.array(self.block_end_absolute) - 1
551-
548+
552549
def add_block_diagonal_matrix_upstream_boundary_conditions(self):
553550
for lp in self.list_of_LongProfile_objects:
554551
for ID in lp.upstream_segment_IDs:
@@ -561,12 +558,12 @@ def add_block_diagonal_matrix_upstream_boundary_conditions(self):
561558
/ ((1-upseg.lambda_p) * upseg.sinuosity**(7/6.)) \
562559
* self.dt / (2 * lp.dx_ext[0])
563560
#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])
565562
/ (lp.dx_ext[0]))**(1/6.)
566563
C1 = C0 * dzdx_0_16 * upseg.Q[-1] / lp.B[0]
567564
left_new = -C1 * 7/6. * 2 / lp.dx_ext[0]
568565
self.LHSblock_matrix[row, col] = left_new
569-
566+
570567
def add_block_diagonal_matrix_downstream_boundary_conditions(self):
571568
for lp in self.list_of_LongProfile_objects:
572569
for ID in lp.downstream_segment_IDs:
@@ -578,7 +575,7 @@ def add_block_diagonal_matrix_downstream_boundary_conditions(self):
578575
C0 = downseg.k_Qs * downseg.intermittency \
579576
/ ((1-downseg.lambda_p) * downseg.sinuosity**(7/6.)) \
580577
* 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])
582579
/ (lp.dx_ext[0]))**(1/6.)
583580
C1 = C0 * dzdx_0_16 * lp.Q[-1] / downseg.B[0]
584581
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):
677674
self.update_zext()
678675
self.t += self.dt # Update each lp z? Should make a global class
679676
# that these both inherit from
677+
for lp in self.list_of_LongProfile_objects:
678+
lp.t = self.t
680679
i = 0
681680
idx = 0
682681
for lp in self.list_of_LongProfile_objects:
@@ -686,4 +685,3 @@ def evolve_threshold_width_river_network(self, nt=1, dt=3.15E7):
686685
# + lp.Q_s_0
687686
if lp.S0 is not None:
688687
lp.update_z_ext_0()
689-

0 commit comments

Comments
 (0)