tbegin implementing Wilcock's two-fraction sediment transport model - granular-channel-hydro - subglacial hydrology model for sedimentary channels
git clone git://src.adamsgaard.dk/granular-channel-hydro
Log
Files
Refs
README
LICENSE
---
commit cc1ced89147308322393b4220845eabe5970e39a
parent 97a5cad4554cc24b354750d75d98d7e661bab5ca
Author: Anders Damsgaard 
Date:   Wed,  1 Mar 2017 20:43:10 -0800

begin implementing Wilcock's two-fraction sediment transport model

Diffstat:
  M 1d-channel.py                       |     167 +++++++++++++++++++++++--------

1 file changed, 125 insertions(+), 42 deletions(-)
---
diff --git a/1d-channel.py b/1d-channel.py
t@@ -22,13 +22,16 @@ import sys
 
 # # Model parameters
 Ns = 25                # Number of nodes [-]
-Ls = 100e3             # Model length [m]
-t_end = 24.*60.*60.*30.  # Total simulation time [s]
+#Ls = 100e3             # Model length [m]
+Ls = 1e3             # Model length [m]
+total_days = 60.       # Total simulation time [d]
+t_end = 24.*60.*60.*total_days  # Total simulation time [s]
 tol_Q = 1e-3       # Tolerance criteria for the normalized max. residual for Q
 tol_P_c = 1e-3     # Tolerance criteria for the normalized max residual for P_c
 max_iter = 1e2*Ns  # Maximum number of solver iterations before failure
 output_convergence = False  # Display convergence statistics during run
 safety = 0.1     # Safety factor ]0;1] for adaptive timestepping
+plot_interval = 20  # Time steps between plots
 
 # Physical parameters
 rho_w = 1000.  # Water density [kg/m^3]
t@@ -36,18 +39,23 @@ rho_i = 910.   # Ice density [kg/m^3]
 rho_s = 2600.  # Sediment density [kg/m^3]
 g = 9.8        # Gravitational acceleration [m/s^2]
 theta = 30.    # Angle of internal friction in sediment [deg]
+sand_fraction = 0.5  # Initial volumetric fraction of sand relative to gravel
+D_g = 1.       # Mean grain size in gravel fraction (> 2 mm)
+D_s = 0.01     # Mean grain size in sand fraction (<= 2 mm)
 
 # Water source term [m/s]
-# m_dot = 7.93e-11
-m_dot = 4.5e-7
-# m_dot = 4.5e-6
-# m_dot = 5.79e-5
+m_dot = 7.93e-11
+#m_dot = 1.0e-7
+#m_dot = 2.0e-6
+#m_dot = 4.5e-7
+#m_dot = 5.79e-5
+#m_dot = 5.0e-6
 # m_dot = 1.8/(1000.*365.*24.*60.*60.)  # Whillan's melt rate from Joughin 2004
 
 # Walder and Fowler 1994 sediment transport parameters
 K_e = 6.0   # Erosion constant [-], disabled when 0.0
 # K_d = 6.0   # Deposition constant [-], disabled when 0.0
-K_d = K_e   # Deposition constant [-], disabled when 0.0
+K_d = 0.01*K_e   # Deposition constant [-], disabled when 0.0
 alpha = 1e5  # Geometric correction factor (Carter et al 2017)
 # D50 = 1e-3 # Median grain size [m]
 # tau_c = 0.5*g*(rho_s - rho_i)*D50  # Critical shear stress for transport
t@@ -67,7 +75,9 @@ c_1 = -0.118  # [m/kPa]
 c_2 = 4.60    # [m]
 
 # Minimum channel size [m^2], must be bigger than 0
-S_min = 1e-1
+#S_min = 1e-1
+#S_min = 1e-2
+S_min = 1.
 
 
 # # Initialize model arrays
t@@ -76,9 +86,9 @@ s = numpy.linspace(0., Ls, Ns)
 ds = s[1:] - s[:-1]
 
 # Ice thickness and bed topography
-# H = 6.*(numpy.sqrt(Ls - s + 5e3) - numpy.sqrt(5e3)) + 1.0  # glacier
-slope = 0.1  # Surface slope [%]
-H = 1000. + -slope/100.*s
+H = 6.*(numpy.sqrt(Ls - s + 5e3) - numpy.sqrt(5e3)) + 1.0  # glacier
+#slope = 0.1  # Surface slope [%]
+#H = 1000. + -slope/100.*s
 
 b = numpy.zeros_like(H)
 
t@@ -101,13 +111,17 @@ c_bar = numpy.zeros_like(S)  # Vertically integrated sediment concentration [-]
 tau = numpy.zeros_like(S)    # Avg. shear stress from current [Pa]
 porosity = numpy.ones_like(S)*0.3  # Sediment porosity [-]
 res = numpy.zeros_like(S)   # Solution residual during solver iterations
+Q_t = numpy.zeros_like(S)   # Sediment flux where D <= 2 mm [m3/s]
+Q_s = numpy.zeros_like(S)   # Sediment flux where D <= 2 mm [m3/s]
+Q_g = numpy.zeros_like(S)   # Sediment flux where D > 2 mm [m3/s]
+f_s = numpy.ones_like(S)*sand_fraction # Initial sediment fraction of sand [-]
 
 
 # # Helper functions
 def gradient(arr, arr_x):
     # Central difference gradient of an array ``arr`` with node positions at
     # ``arr_x``.
-    return (arr[:-1] - arr[1:])/(arr_x[:-1] - arr_x[1:])
+    return (arr[1:] - arr[:-1])/(arr_x[1:] - arr_x[:-1])
 
 
 def avg_midpoint(arr):
t@@ -129,27 +143,21 @@ def channel_shear_stress(Q, S):
 def channel_erosion_rate(tau):
     # Parker 1979, Walder and Fowler 1994
     # return K_e*v_s*(tau - tau_c).clip(min=0.)/(g*(rho_s - rho_w)*d15)**(3./2)
+
     # Carter et al 2017
-    return K_e*v_s/alpha*(tau - tau_c).clip(min=0.) / \
-        (g*(rho_s - rho_w)*d15)**(3./2.)
+    #return K_e*v_s/alpha*(tau - tau_c).clip(min=0.) / \
+        #(g*(rho_s - rho_w)*d15)**(3./2.)
+
+    # Ng 2000
+    return 0.092*(tau/(2.*(rho_s - rho_w)*g*d15))**(3./2.)
 
 
 def channel_deposition_rate_kernel(tau, c_bar, ix):
     # Parker 1979, Walder and Fowler 1994
-    # result = K_d*v_s*c_bar[ix]*(g*(rho_s - rho_w)*d15/tau[ix])**0.5
+    # return K_d*v_s*c_bar[ix]*(g*(rho_s - rho_w)*d15/tau[ix])**0.5
 
     # Carter et al. 2017
-    result = K_d*v_s/alpha*c_bar[ix]*(g*(rho_s - rho_w)*d15/tau[ix])**0.5
-
-    '''
-    print('tau[{}] = {}'.format(ix, tau[ix]))
-    print('c_bar[{}] = {}'.format(ix, c_bar[ix]))
-    print('e_dot[{}] = {}'.format(ix, e_dot[ix]))
-    print('d_dot[{}] = {}'.format(ix, result))
-    print('')
-    '''
-
-    return result
+    return K_d*v_s/alpha*c_bar[ix]*(g*(rho_s - rho_w)*d15/tau[ix])**0.5
 
 
 def channel_deposition_rate_kernel_ng(c_bar, ix):
t@@ -196,22 +204,81 @@ def channel_deposition_rate(tau, c_bar, d_dot, Ns):
     return d_dot, c_bar
 
 
-def channel_growth_rate(e_dot, d_dot, porosity, W):
+def channel_sediment_flux_sand(tau, W, f_s, D_s):
+    # Parker 1979, Wilcock 1997, 2001, Egholm 2013
+    # tau: Shear stress by water flow
+    # W: Channel width
+    # f_s: Sand volume fraction
+    # D_s: Mean sand fraction grain size
+
+    # Piecewise linear functions for nondimensional critical shear stresses
+    # dependent on sand fraction from Gasparini et al 1999 of Wilcock 1997
+    # data.
+    ref_shear_stress = numpy.ones_like(f_s)*0.04
+    ref_shear_stress[numpy.nonzero(f_s <= 0.1)] = 0.88
+    I = numpy.nonzero((0.1 < f_s) & (f_s <= 0.4))
+    ref_shear_stress[I] = 0.88 - 2.8*(f_s[I] - 0.1)
+
+    shields_stress = tau/((rho_s - rho_w)*g*D_s)
+
+    #import ipdb; ipdb.set_trace()
+    return 11.2*f_s*W/((rho_s - rho_w)/rho_w*g) \
+        * (tau/rho_w)**1.5 \
+        * (1.0 - 0.846*numpy.sqrt(ref_shear_stress/shields_stress))**4.5
+
+def channel_sediment_flux_gravel(tau, W, f_g, D_g):
+    # Parker 1979, Wilcock 1997, 2001, Egholm 2013
+    # tau: Shear stress by water flow
+    # W: Channel width
+    # f_g: Gravel volume fraction
+    # D_g: Mean gravel fraction grain size
+
+    # Piecewise linear functions for nondimensional critical shear stresses
+    # dependent on sand fraction from Gasparini et al 1999 of Wilcock 1997
+    # data.
+    ref_shear_stress = numpy.ones_like(f_g)*0.01
+    ref_shear_stress[numpy.nonzero(f_g <= 0.1)] = 0.04
+    I = numpy.nonzero((0.1 < f_g) & (f_g <= 0.4))
+    ref_shear_stress[I] = 0.04 - 0.1*(f_g[I] - 0.1)
+
+    shields_stress = tau/((rho_s - rho_w)*g*D_g)
+
+    # From Wilcock 2001, eq. 3
+    Q_g = 11.2*f_g*W/((rho_s - rho_w)/rho_w*g) \
+            * (tau/rho_w)**1.5 \
+            * (1.0 - 0.846*ref_shear_stress/shields_stress)**4.5
+
+    # From Wilcock 2001, eq. 4
+    I = numpy.nonzero(ref_shear_stress/shields_stress < 1.)
+    Q_g[I] = f_g[I]*W[I]/((rho_s - rho_w)/rho_w*g) \
+            * (tau[I]/rho_w)**1.5 \
+            * 0.0025*(shields_stress[I]/ref_shear_stress[I])**14.2
+
+    return Q_g
+
+
+def channel_growth_rate(e_dot, d_dot, W):
     # Damsgaard et al, in prep
     return (e_dot - d_dot)*W
 
 
-def update_channel_size_with_limit(S, S_old, dSdt, dt, N):
+def channel_growth_rate_sedflux(Q_t, porosity, s_c):
+    # Damsgaard et al, in prep
+    return 1./porosity[1:] * gradient(Q_t, s_c)
+
+
+def update_channel_size_with_limit(S, S_old, dSdt, dt, N_c):
     # Damsgaard et al, in prep
-    S_max = numpy.maximum((c_1*numpy.maximum(N, 0.)/1000. + c_2)**2./4. *
-                          numpy.tan(numpy.deg2rad(theta)), S_min)
+    S_max = numpy.maximum(
+        numpy.maximum((c_1*numpy.maximum(N_c, 0.)/1000. + c_2), 0.)**2./4. *
+        numpy.tan(numpy.deg2rad(theta)), S_min)
     S = numpy.maximum(numpy.minimum(S_old + dSdt*dt, S_max), S_min)
     W = S/numpy.tan(numpy.deg2rad(theta))  # Assume no channel floor wedge
     return S, W, S_max
 
 
 def flux_solver(m_dot, ds):
-    # Iteratively find new fluxes
+    # Iteratively find new water fluxes
     it = 0
     max_res = 1e9  # arbitrary large value
 
t@@ -286,16 +353,17 @@ def plot_state(step, time, S_, S_max_, title=True):
     ax_Pa = plt.subplot(2, 1, 1)  # axis with Pascals as y-axis unit
     #ax_Pa.plot(s_c/1000., P_c/1000., '--r', label='$P_c$')
     #ax_Pa.plot(s/1000., N/1000., '--r', label='$N$')
-    ax_Pa.plot(s/1000., H*rho_i*g/1e6, '--r', label='$P_i$')
-    ax_Pa.plot(s_c/1000., P_c/1e6, ':b', label='$P_c$')
+    ax_Pa.plot(s_c/1000., N_c/1e6, '-k', label='$N$')
+    ax_Pa.plot(s_c/1000., H_c*rho_i*g/1e6, '--r', label='$P_i$')
+    ax_Pa.plot(s_c/1000., P_c/1e6, ':y', label='$P_c$')
 
     ax_m3s = ax_Pa.twinx()  # axis with m3/s as y-axis unit
-    ax_m3s.plot(s_c/1000., Q, '-k', label='$Q$')
+    ax_m3s.plot(s_c/1000., Q, '.-b', label='$Q$')
 
     if title:
         plt.title('Day: {:.3}'.format(time/(60.*60.*24.)))
     ax_Pa.legend(loc=2)
-    ax_m3s.legend(loc=3)
+    ax_m3s.legend(loc=4)
     #ax_Pa.set_ylabel('[kPa]')
     ax_Pa.set_ylabel('[MPa]')
     ax_m3s.set_ylabel('[m$^3$/s]')
t@@ -307,8 +375,12 @@ def plot_state(step, time, S_, S_max_, title=True):
     # ax_m.semilogy(s_c/1000., S_max, '--k', label='$S_{max}$')
 
     ax_ms = ax_m2.twinx()
-    ax_ms.plot(s_c/1000., e_dot, '--r', label='$\dot{e}$')
-    ax_ms.plot(s_c/1000., d_dot, ':b', label='$\dot{d}$')
+    #ax_ms.plot(s_c/1000., e_dot, '--r', label='$\dot{e}$')
+    #ax_ms.plot(s_c/1000., d_dot, ':b', label='$\dot{d}$')
+    ax_ms.plot(s_c/1000., Q_t, label='$Q_t$')
+    ax_ms.plot(s_c/1000., Q_g, label='$Q_g$')
+    ax_ms.plot(s_c/1000., Q_s, label='$Q_s$')
+    # TODO: check units on sediment fluxes: m2/s or m3/s ?
 
     ax_m2.legend(loc=2)
     ax_ms.legend(loc=3)
t@@ -351,7 +423,7 @@ hydro_pot_grad = gradient(hydro_pot, s)
 
 # Find field values at the middle of channel segments
 N_c = avg_midpoint(N)
-H_c = avg_midpoint(N)
+H_c = avg_midpoint(H)
 
 # Find fluxes in channel segments [m^3/s]
 Q = channel_water_flux(S, hydro_pot_grad)
t@@ -389,11 +461,21 @@ while time <= t_end:
         tau = channel_shear_stress(Q, S)
 
         # Find sediment erosion and deposition rates for each channel segment
-        e_dot = channel_erosion_rate(tau)
-        d_dot, c_bar = channel_deposition_rate(tau, c_bar, d_dot, Ns)
+        # e_dot = channel_erosion_rate(tau)
+        # d_dot, c_bar = channel_deposition_rate(tau, c_bar, d_dot, Ns)
+
+        f_g = 1./f_s  # gravel volume fraction is reciprocal to sand
+        Q_s = channel_sediment_flux_sand(tau, W, f_s, D_s)
+        Q_g = channel_sediment_flux_gravel(tau, W, f_g, D_g)
+        Q_t = Q_s + Q_g
+        break
+
+        # TODO: Update f_s from fluxes
 
         # Determine change in channel size for each channel segment
-        dSdt = channel_growth_rate(e_dot, d_dot, porosity, W)
+        #dSdt = channel_growth_rate(e_dot, d_dot, W)
+        # Use backward differences and assume dS/dt=0 in first segment
+        dSdt[1:] = channel_growth_rate_sedflux(Q_t, porosity, s_c)
 
         # Update channel cross-sectional area and width according to growth rate
         # and size limit for each channel segment
t@@ -424,7 +506,8 @@ while time <= t_end:
                             'Iterative solution not found for Q')
         it += 1
 
-    if step % 10 == 0:
+    # Generate an output figure for every n time steps
+    if step % plot_interval == 0:
         plot_state(step, time, S, S_max)
 
     # import ipdb; ipdb.set_trace()