def hydrodynamics_only(boundary_conditions_fn, mesh2d, bathymetry_2d, uv_init, elev_init, average_size, dt, t_end, wetting_and_drying=False, wetting_alpha=0.1, friction='nikuradse', friction_coef=0, fluc_bcs=False, viscosity=10**(-6), sponge_viscosity=th.Constant(1.0)): """ Sets up a simulation with only hydrodynamics until a quasi-steady state when it can be used as an initial condition for the full morphological model. We update the bed friction at each time step. The actual run of the model are done outside the function Inputs: boundary_consditions_fn - function defining boundary conditions for problem mesh2d - define mesh working on bathymetry2d - define bathymetry of problem uv_init - initial velocity of problem elev_init - initial elevation of problem average_size - average sediment size dt - timestep t_end - end time wetting_and_drying - wetting and drying switch wetting_alpha - wetting and drying parameter friction - choice of friction formulation - nikuradse and manning friction_coef - value of friction coefficient used in manning viscosity - viscosity of hydrodynamics. The default value should be 10**(-6) Outputs: solver_obj - solver which we need to run to solve the problem update_forcings_hydrodynamics - function defining the updates to the model performed at each timestep outputdir - directory of outputs """ def update_forcings_hydrodynamics(t_new): # update boundary conditions if have fluctuating conditions if fluc_bcs: in_fn, out_fn = boundary_conditions_fn(bathymetry_2d, t_new=t_new, state='update') for j in range(len(in_fn)): exec('constant_in' + str(j) + '.assign(' + str(in_fn[j]) + ')') for k in range(len(out_fn)): exec('constant_out' + str(k) + '.assign(' + str(out_fn[k]) + ')') # update bed friction if friction == 'nikuradse': uv1, elev1 = solver_obj.fields.solution_2d.split() if wetting_and_drying: wd_bath_displacement = solver_obj.depth.wd_bathymetry_displacement depth.project(elev1 + wd_bath_displacement(elev1) + bathymetry_2d) else: depth.interpolate(elev1 + bathymetry_2d) # calculate skin friction coefficient cfactor.interpolate(2 * (0.4**2) / ((th.ln(11.036 * depth / (ksp)))**2)) # choose directory to output results ts = time.time() st = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S') outputdir = 'outputs' + st # export interval in seconds if t_end < 40: t_export = 0.1 else: t_export = np.round(t_end / 40, 0) th.print_output('Exporting to ' + outputdir) # define function spaces V = th.FunctionSpace(mesh2d, 'CG', 1) P1_2d = th.FunctionSpace(mesh2d, 'DG', 1) # define parameters ksp = th.Constant(3 * average_size) # define depth if wetting_and_drying: H = th.Function(V).project(elev_init + bathymetry_2d) depth = th.Function(V).project( H + (0.5 * (th.sqrt(H**2 + wetting_alpha**2) - H))) else: depth = th.Function(V).project(elev_init + bathymetry_2d) # define bed friction cfactor = th.Function(P1_2d).interpolate( 2 * (0.4**2) / ((th.ln(11.036 * depth / (ksp)))**2)) # set up solver solver_obj = th.solver2d.FlowSolver2d(mesh2d, bathymetry_2d) options = solver_obj.options options.simulation_export_time = t_export options.simulation_end_time = t_end options.output_directory = outputdir options.check_volume_conservation_2d = True options.fields_to_export = ['uv_2d', 'elev_2d'] options.solve_tracer = False options.use_lax_friedrichs_tracer = False if friction == 'nikuradse': options.quadratic_drag_coefficient = cfactor elif friction == 'manning': if friction_coef == 0: friction_coef = 0.02 options.manning_drag_coefficient = th.Constant(friction_coef) else: print('Undefined friction') options.horizontal_viscosity = th.Function(P1_2d).interpolate( sponge_viscosity * th.Constant(viscosity)) # crank-nicholson used to integrate in time system of ODEs resulting from application of galerkin FEM options.timestepper_type = 'CrankNicolson' options.timestepper_options.implicitness_theta = 1.0 options.use_wetting_and_drying = wetting_and_drying options.wetting_and_drying_alpha = th.Function(V).interpolate( th.Constant(wetting_alpha)) options.norm_smoother = th.Constant(1.0) if not hasattr(options.timestepper_options, 'use_automatic_timestep'): options.timestep = dt # set boundary conditions swe_bnd, left_bnd_id, right_bnd_id, in_constant, out_constant, left_string, right_string = boundary_conditions_fn( bathymetry_2d, flag='hydro') for j in range(len(in_constant)): exec( 'constant_in' + str(j) + ' = th.Constant(' + str(in_constant[j]) + ')', globals()) str1 = '{' if len(left_string) > 0: for i in range(len(left_string)): str1 += "'" + str(left_string[i]) + "': constant_in" + str(i) + "," str1 = str1[0:len(str1) - 1] + "}" exec('swe_bnd[left_bnd_id] = ' + str1) for k in range(len(out_constant)): exec( 'constant_out' + str(k) + '= th.Constant(' + str(out_constant[k]) + ')', globals()) str2 = '{' if len(right_string) > 0: for i in range(len(right_string)): str2 += "'" + str( right_string[i]) + "': constant_out" + str(i) + "," str2 = str2[0:len(str2) - 1] + "}" exec('swe_bnd[right_bnd_id] = ' + str2) solver_obj.bnd_functions['shallow_water'] = swe_bnd solver_obj.assign_initial_conditions(uv=uv_init, elev=elev_init) return solver_obj, update_forcings_hydrodynamics, outputdir
def morphological(boundary_conditions_fn, morfac, morfac_transport, suspendedload, convectivevel, bedload, angle_correction, slope_eff, seccurrent, mesh2d, bathymetry_2d, input_dir, viscosity_hydro, ks, average_size, dt, final_time, beta_fn, surbeta2_fn, alpha_secc_fn, friction='nikuradse', friction_coef=0, d90=0, fluc_bcs=False, bed_form='meyer', sus_form='vanrijn', diffusivity=0.15): """ Set up a full morphological model simulation using as an initial condition the results of a hydrodynamic only model. Inputs: boundary_consditions_fn - function defining boundary conditions for problem morfac - morphological scale factor morfac_transport - switch to turn on morphological component suspendedload - switch to turn on suspended sediment transport convectivevel - switch on convective velocity correction factor in sediment concentration equation bedload - switch to turn on bedload transport angle_correction - switch on slope effect angle correction slope_eff - switch on slope effect magnitude correction seccurrent - switch on secondary current for helical flow effect mesh2d - define mesh working on bathymetry2d - define bathymetry of problem input_dir - folder containing results of hydrodynamics model which are used as initial conditions here viscosity_hydro - viscosity value in hydrodynamic equations ks - bottom friction coefficient for quadratic drag coefficient average_size - average sediment size dt - timestep final_time - end time beta_fn - magnitude slope effect parameter surbeta2_fn - angle correction slope effect parameter alpha_secc_fn - secondary current parameter friction - choice of friction formulation - nikuradse and manning friction_coef - value of friction coefficient used in manning d90 - sediment size which 90% of the sediment are below fluc_bcs - if true this allows the user to impose boundary conditions which vary with time bed_form - choice of bedload formula between 'meyer' (meyer-peter-muller) and 'soulsby' (soulsby-van-rijn) sus_form - choice of suspended load formula between 'vanrijn' (van Rijn 1984) and 'soulsby' (soulsby-van-rijn) diffusivity - diffusivity value in suspended sediment concentration equation. The default value (found by callibration) should be 0.15 Outputs: solver_obj - solver which we need to run to solve the problem update_forcings_hydrodynamics - function defining the updates to the model performed at each timestep diff_bathy - bedlevel evolution diff_bathy_file - bedlevel evolution file """ t_list = [] def update_forcings_tracer(t_new): # update bathymetry old_bathymetry_2d.assign(bathymetry_2d) # extract new elevation and velocity and project onto CG space uv1, elev1 = solver_obj.fields.solution_2d.split() uv_cg.project(uv1) elev_cg.project(elev1) depth.project(elev_cg + old_bathymetry_2d) horizontal_velocity.interpolate(uv_cg[0]) vertical_velocity.interpolate(uv_cg[1]) # update boundary conditions if have fluctuating conditions if fluc_bcs: in_fn, out_fn = boundary_conditions_fn(orig_bathymetry, flag='morpho', morfac=morfac, t_new=t_new, state='update') for j in range(len(in_fn)): exec('constant_in' + str(j) + '.assign(' + str(in_fn[j]) + ')') for k in range(len(out_fn)): exec('constant_out' + str(k) + '.assign(' + str(out_fn[k]) + ')') # update bedfriction hc.interpolate(th.conditional(depth > 0.001, depth, 0.001)) aux.assign( th.conditional(11.036 * hc / ks > 1.001, 11.036 * hc / ks, 1.001)) qfc.assign(2 / (th.ln(aux) / 0.4)**2) # calculate skin friction coefficient hclip.interpolate(th.conditional(ksp > depth, ksp, depth)) cfactor.interpolate( th.conditional(depth > ksp, 2 * ((2.5 * th.ln(11.036 * hclip / ksp))**(-2)), th.Constant(0.0))) if morfac_transport: # if include tracer solver then update_forcings is run twice but only want to update bathymetry once t_list.append(t_new) double_factor = False if suspendedload: if len(t_list) > 1: if t_list[len(t_list) - 1] == t_list[len(t_list) - 2]: double_factor = True else: # if have no tracer then update_forcings is only run once so update bathymetry at each step double_factor = True if double_factor: z_n.assign(old_bathymetry_2d) # mu - ratio between skin friction and normal friction mu.assign(th.conditional(qfc > 0, cfactor / qfc, 0)) # bed shear stress unorm.interpolate((horizontal_velocity**2) + (vertical_velocity**2)) TOB.interpolate(1000 * 0.5 * qfc * unorm) # calculate gradient of bed (noting bathymetry is -bed) dzdx.interpolate(old_bathymetry_2d.dx(0)) dzdy.interpolate(old_bathymetry_2d.dx(1)) # initialise exner equation f = 0 if suspendedload: # source term # deposition flux - calculating coefficient to account for stronger conc at bed B.interpolate(th.conditional(a > depth, a / a, a / depth)) ustar.interpolate(th.sqrt(0.5 * qfc * unorm)) exp1.assign( th.conditional( (th.conditional( (settling_velocity / (0.4 * ustar)) - 1 > 0, (settling_velocity / (0.4 * ustar)) - 1, -(settling_velocity / (0.4 * ustar)) + 1)) > 10**(-4), th.conditional( (settling_velocity / (0.4 * ustar)) - 1 > 3, 3, (settling_velocity / (0.4 * ustar)) - 1), 0)) coefftest.assign( th.conditional((th.conditional( (settling_velocity / (0.4 * ustar)) - 1 > 0, (settling_velocity / (0.4 * ustar)) - 1, -(settling_velocity / (0.4 * ustar)) + 1)) > 10**(-4), B * (1 - B**exp1) / exp1, -B * th.ln(B))) coeff.assign( th.conditional(coefftest > 0, 1 / coefftest, 0)) if sus_form == 'vanrijn': # erosion flux - above critical velocity bed is eroded s0.assign( (th.conditional(1000 * 0.5 * qfc * unorm * mu > 0, 1000 * 0.5 * qfc * unorm * mu, 0) - taucr) / taucr) ceq.assign(0.015 * (average_size / a) * ((th.conditional(s0 < 0, 0, s0))**(1.5)) / (dstar**0.3)) elif sus_form == 'soulsby': ucr.interpolate(0.19 * (average_size**0.1) * (th.ln(4 * depth / d90) / th.ln(10))) s0.assign( th.conditional((th.sqrt(unorm) - ucr)**2.4 > 0, (th.sqrt(unorm) - ucr)**2.4, 0)) ceq.interpolate(ass * s0 / depth) else: print( 'Unrecognised suspended sediment transport formula. Please choose "vanrijn" or "soulsby"' ) # calculate depth-averaged source term for sediment concentration equation source.interpolate(-(settling_velocity * coeff * solver_obj.fields.tracer_2d / depth) + (settling_velocity * ceq / depth)) # update sediment rate to ensure equilibrium at inflow sediment_rate.assign(ceq.at([0, 0]) / coeff.at([0, 0])) if convectivevel: # correction factor to advection velocity in sediment concentration equation Bconv.interpolate( th.conditional(depth > 1.1 * ksp, ksp / depth, ksp / (1.1 * ksp))) Aconv.interpolate( th.conditional(depth > 1.1 * a, a / depth, a / (1.1 * a))) # take max of value calculated either by ksp or depth Amax.assign(th.conditional(Aconv > Bconv, Aconv, Bconv)) r1conv.assign(1 - (1 / 0.4) * th.conditional( settling_velocity / ustar < 1, settling_velocity / ustar, 1)) Ione.assign( th.conditional( r1conv > 10**(-8), (1 - Amax**r1conv) / r1conv, th.conditional(r1conv < -10**(-8), (1 - Amax**r1conv) / r1conv, th.ln(Amax)))) Itwo.assign( th.conditional( r1conv > 10**(-8), -(Ione + (th.ln(Amax) * (Amax**r1conv))) / r1conv, th.conditional( r1conv < -10**(-8), -(Ione + (th.ln(Amax) * (Amax**r1conv))) / r1conv, -0.5 * th.ln(Amax)**2))) alpha.assign(-(Itwo - (th.ln(Amax) - th.ln(30)) * Ione) / (Ione * ((th.ln(Amax) - th.ln(30)) + 1))) # final correction factor alphatest2.assign( th.conditional( th.conditional(alpha > 1, 1, alpha) < 0, 0, th.conditional(alpha > 1, 1, alpha))) # multiply correction factor by velocity and insert back into sediment concentration equation corrective_velocity.interpolate(alphatest2 * uv1) else: corrective_velocity.interpolate(uv1) if bedload: # calculate angle of flow calfa.interpolate(horizontal_velocity / th.sqrt(unorm)) salfa.interpolate(vertical_velocity / th.sqrt(unorm)) div_function.interpolate(th.as_vector((calfa, salfa))) if slope_eff: # slope effect magnitude correction due to gravity where beta is a parameter normally set to 1.3 # we use z_n1 and equals so that we can use an implicit method in Exner slopecoef = (1 + beta * (z_n1.dx(0) * calfa + z_n1.dx(1) * salfa)) else: slopecoef = th.Constant(1.0) if angle_correction: # slope effect angle correction due to gravity tt1.interpolate( th.conditional( 1000 * 0.5 * qfc * unorm > 10**(-10), th.sqrt(cparam / (1000 * 0.5 * qfc * unorm)), th.sqrt(cparam / (10**(-10))))) # add on a factor of the bed gradient to the normal aa.assign(salfa + tt1 * dzdy) bb.assign(calfa + tt1 * dzdx) norm.assign( th.conditional( th.sqrt(aa**2 + bb**2) > 10**(-10), th.sqrt(aa**2 + bb**2), 10**(-10))) # we use z_n1 and equals so that we can use an implicit method in Exner calfamod = (calfa + (tt1 * z_n1.dx(0))) / norm salfamod = (salfa + (tt1 * z_n1.dx(1))) / norm if seccurrent: # accounts for helical flow effect in a curver channel # again use z_n1 and equals so can use an implicit method in Exner free_surface_dx = depth.dx(0) - z_n1.dx(0) free_surface_dy = depth.dx(1) - z_n1.dx(1) velocity_slide = (horizontal_velocity * free_surface_dy ) - (vertical_velocity * free_surface_dx) tandelta_factor.interpolate( 7 * 9.81 * 1000 * depth * qfc / (2 * alpha_secc * ((horizontal_velocity**2) + (vertical_velocity**2)))) if angle_correction: # if angle has already been corrected we must alter the corrected angle to obtain the corrected secondary current angle t_1 = (TOB * slopecoef * calfamod) + ( vertical_velocity * tandelta_factor * velocity_slide) t_2 = (TOB * slopecoef * salfamod) - ( horizontal_velocity * tandelta_factor * velocity_slide) else: t_1 = (TOB * slopecoef * calfa) + (vertical_velocity * tandelta_factor * velocity_slide) t_2 = ((TOB * slopecoef * salfa) - (horizontal_velocity * tandelta_factor * velocity_slide)) # calculated to normalise the new angles t4 = th.sqrt((t_1**2) + (t_2**2)) # updated magnitude correction and angle corrections slopecoef = t4 / TOB calfanew = t_1 / t4 salfanew = t_2 / t4 if bed_form == 'meyer': # implement meyer-peter-muller bedload transport formula thetaprime.interpolate( mu * (1000 * 0.5 * qfc * unorm) / ((2650 - 1000) * 9.81 * average_size)) # if velocity above a certain critical value then transport occurs phi.assign( th.conditional(thetaprime < thetacr, 0, 8 * (thetaprime - thetacr)**1.5)) # bedload transport flux with magnitude correction qb_total = slopecoef * phi * th.sqrt( g * (2650 / 1000 - 1) * average_size**3) elif bed_form == 'soulsby': abb.interpolate( th.conditional( depth >= average_size, 0.005 * depth * ((average_size / depth)**1.2) / coeff_soulsby, 0.005 * depth / coeff_soulsby)) ucr_bed.interpolate( th.conditional( depth > d90, 0.19 * (average_size**0.1) * (th.ln(4 * depth / d90)) / (th.ln(10)), 0.19 * (average_size**0.1) * (th.ln(4)) / (th.ln(10)))) s0_bed.interpolate( th.conditional((th.sqrt(unorm) - ucr_bed)**2.4 > 0, (th.sqrt(unorm) - ucr_bed)**2.4, 0)) qb_total = slopecoef * abb * s0_bed * th.sqrt(unorm) else: print( 'Unrecognised bedload transport formula. Please choose "meyer" or "soulsby"' ) # add time derivative to exner equation with a morphological scale factor f += (((1 - porosity) * (z_n1 - z_n) / (dt * morfac)) * v) * fire.dx # formulate bedload transport flux with correct angle depending on corrections implemented if angle_correction and seccurrent is False: qbx = qb_total * calfamod qby = qb_total * salfamod elif seccurrent: qbx = qb_total * calfanew qby = qb_total * salfanew else: qbx = qb_total * calfa qby = qb_total * salfa # add bedload transport to exner equation f += -(v * ( (qbx * n[0]) + (qby * n[1]))) * fire.ds(1) - (v * ( (qbx * n[0]) + (qby * n[1]))) * fire.ds(2) + (qbx * (v.dx(0)) + qby * (v.dx(1))) * fire.dx else: # if no bedload transport component initialise exner equation with time derivative f = (((1 - porosity) * (z_n1 - z_n) / (dt * morfac)) * v) * fire.dx if suspendedload: # add suspended sediment transport to exner equation multiplied by depth as the exner equation is not depth-averaged qbsourcedepth.interpolate(source * depth) f += -(qbsourcedepth * v) * fire.dx # solve exner equation using finite element methods fire.solve(f == 0, z_n1) # update bed bathymetry_2d.assign(z_n1) if round(t_new, 2) % t_export == 0: # calculate difference between original bathymetry and new bathymetry diff_bathy.interpolate(-bathymetry_2d + orig_bathymetry) diff_bathy_file.write(diff_bathy) # choose directory to output results ts = time.time() st = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S') outputdir = 'outputs' + st # final time of simulation t_end = final_time / morfac # export interval in seconds t_export = np.round(t_end / 100, 0) th.print_output('Exporting to ' + outputdir) x, y = th.SpatialCoordinate(mesh2d) # define function spaces P1_2d = th.FunctionSpace(mesh2d, 'DG', 1) vectorP1_2d = th.VectorFunctionSpace(mesh2d, 'DG', 1) V = th.FunctionSpace(mesh2d, 'CG', 1) vector_cg = th.VectorFunctionSpace(mesh2d, 'CG', 1) # define test functions on mesh v = fire.TestFunction(V) n = th.FacetNormal(mesh2d) z_n1 = fire.Function(V, name="z^{n+1}") z_n = fire.Function(V, name="z^{n}") # define original bathymetry before bedlevel changes orig_bathymetry = th.Function(V).interpolate(bathymetry_2d) # calculate bed evolution diff_bathy = th.Function(V).interpolate(-bathymetry_2d + orig_bathymetry) # define output file for bed evolution diff_bathy_file = th.File(outputdir + "/diff_bathy.pvd") diff_bathy_file.write(diff_bathy) # define parameters g = th.Constant(9.81) porosity = th.Constant(0.4) ksp = th.Constant(3 * average_size) a = th.Constant(ks / 2) viscosity = th.Constant(10**(-6)) # magnitude slope effect parameter beta = th.Constant(beta_fn) # angle correction slope effect parameters surbeta2 = th.Constant(surbeta2_fn) cparam = th.Constant((2650 - 1000) * 9.81 * average_size * (surbeta2**2)) # secondary current parameter alpha_secc = th.Constant(alpha_secc_fn) # calculate critical shields parameter thetacr R = th.Constant(2650 / 1000 - 1) dstar = th.Constant(average_size * ((g * R) / (viscosity**2))**(1 / 3)) if max(dstar.dat.data[:] < 1): print('ERROR: dstar value less than 1') elif max(dstar.dat.data[:] < 4): thetacr = th.Constant(0.24 * (dstar**(-1))) elif max(dstar.dat.data[:] < 10): thetacr = th.Constant(0.14 * (dstar**(-0.64))) elif max(dstar.dat.data[:] < 20): thetacr = th.Constant(0.04 * (dstar**(-0.1))) elif max(dstar.dat.data[:] < 150): thetacr = th.Constant(0.013 * (dstar**(0.29))) else: thetacr = th.Constant(0.055) # critical bed shear stress taucr = th.Constant((2650 - 1000) * g * average_size * thetacr) # calculate settling velocity if average_size <= 100 * (10**(-6)): settling_velocity = th.Constant(9.81 * (average_size**2) * ((2650 / 1000) - 1) / (18 * viscosity)) elif average_size <= 1000 * (10**(-6)): settling_velocity = th.Constant( (10 * viscosity / average_size) * (th.sqrt(1 + 0.01 * ((((2650 / 1000) - 1) * 9.81 * (average_size**3)) / (viscosity**2))) - 1)) else: settling_velocity = th.Constant(1.1 * th.sqrt(9.81 * average_size * ((2650 / 1000) - 1))) # initialise velocity, elevation and depth elev_init, uv_init = initialise_fields(mesh2d, input_dir, outputdir) uv_cg = th.Function(vector_cg).interpolate(uv_init) elev_cg = th.Function(V).interpolate(elev_init) depth = th.Function(V).project(elev_cg + bathymetry_2d) old_bathymetry_2d = th.Function(V).interpolate(bathymetry_2d) horizontal_velocity = th.Function(V).interpolate(uv_cg[0]) vertical_velocity = th.Function(V).interpolate(uv_cg[1]) # define bed friction hc = th.Function(P1_2d).interpolate( th.conditional(depth > 0.001, depth, 0.001)) aux = th.Function(P1_2d).interpolate( th.conditional(11.036 * hc / ks > 1.001, 11.036 * hc / ks, 1.001)) qfc = th.Function(P1_2d).interpolate(2 / (th.ln(aux) / 0.4)**2) # skin friction coefficient hclip = th.Function(P1_2d).interpolate( th.conditional(ksp > depth, ksp, depth)) cfactor = th.Function(P1_2d).interpolate( th.conditional(depth > ksp, 2 * ((2.5 * th.ln(11.036 * hclip / ksp))**(-2)), th.Constant(0.0))) # mu - ratio between skin friction and normal friction mu = th.Function(P1_2d).interpolate( th.conditional(qfc > 0, cfactor / qfc, 0)) # calculate bed shear stress unorm = th.Function(P1_2d).interpolate((horizontal_velocity**2) + (vertical_velocity**2)) TOB = th.Function(V).interpolate(1000 * 0.5 * qfc * unorm) # define bed gradient dzdx = th.Function(V).interpolate(old_bathymetry_2d.dx(0)) dzdy = th.Function(V).interpolate(old_bathymetry_2d.dx(1)) if suspendedload: # deposition flux - calculating coefficient to account for stronger conc at bed B = th.Function(P1_2d).interpolate( th.conditional(a > depth, a / a, a / depth)) ustar = th.Function(P1_2d).interpolate(th.sqrt(0.5 * qfc * unorm)) exp1 = th.Function(P1_2d).interpolate( th.conditional( (th.conditional( (settling_velocity / (0.4 * ustar)) - 1 > 0, (settling_velocity / (0.4 * ustar)) - 1, -(settling_velocity / (0.4 * ustar)) + 1)) > 10**(-4), th.conditional((settling_velocity / (0.4 * ustar)) - 1 > 3, 3, (settling_velocity / (0.4 * ustar)) - 1), 0)) coefftest = th.Function(P1_2d).interpolate( th.conditional((th.conditional( (settling_velocity / (0.4 * ustar)) - 1 > 0, (settling_velocity / (0.4 * ustar)) - 1, -(settling_velocity / (0.4 * ustar)) + 1)) > 10**(-4), B * (1 - B**exp1) / exp1, -B * th.ln(B))) coeff = th.Function(P1_2d).interpolate( th.conditional(coefftest > 0, 1 / coefftest, 0)) if sus_form == 'vanrijn': # erosion flux - above critical velocity bed is eroded s0 = th.Function(P1_2d).interpolate( (th.conditional(1000 * 0.5 * qfc * unorm * mu > 0, 1000 * 0.5 * qfc * unorm * mu, 0) - taucr) / taucr) ceq = th.Function(P1_2d).interpolate( 0.015 * (average_size / a) * ((th.conditional(s0 < 0, 0, s0))**(1.5)) / (dstar**0.3)) elif sus_form == 'soulsby': if d90 == 0: # if the value of d90 is unspecified set d90 = d50 d90 = th.Constant(average_size) else: d90 = th.Constant(d90) coeff_soulsby = th.Constant((R * g * average_size)**1.2) ass = th.Constant(0.012 * average_size * (dstar**(-0.6)) / coeff_soulsby) ucr = th.Function(P1_2d).interpolate(0.19 * (average_size**0.1) * (th.ln(4 * depth / d90)) / (th.ln(10))) s0 = th.Function(P1_2d).interpolate( th.conditional((th.sqrt(unorm) - ucr)**2.4 > 0, (th.sqrt(unorm) - ucr)**2.4, 0)) ceq = th.Function(P1_2d).interpolate(ass * s0 / depth) else: print( 'Unrecognised suspended sediment transport formula. Please choose "vanrijn" or "soulsby"' ) # update sediment rate to ensure equilibrium at inflow sediment_rate = th.Constant(ceq.at([0, 0]) / coeff.at([0, 0])) testtracer = th.Function(P1_2d).interpolate(ceq / coeff) # calculate depth-averaged source term for sediment concentration equation source = th.Function(P1_2d).interpolate( -(settling_velocity * coeff * sediment_rate / depth) + (settling_velocity * ceq / depth)) # add suspended sediment transport to exner equation multiplied by depth as the exner equation is not depth-averaged qbsourcedepth = th.Function(V).interpolate(source * depth) if convectivevel: # correction factor to advection velocity in sediment concentration equation Bconv = th.Function(P1_2d).interpolate( th.conditional(depth > 1.1 * ksp, ksp / depth, ksp / (1.1 * ksp))) Aconv = th.Function(P1_2d).interpolate( th.conditional(depth > 1.1 * a, a / depth, a / (1.1 * a))) # take max of value calculated either by ksp or depth Amax = th.Function(P1_2d).interpolate( th.conditional(Aconv > Bconv, Aconv, Bconv)) r1conv = th.Function(P1_2d).interpolate( 1 - (1 / 0.4) * th.conditional(settling_velocity / ustar < 1, settling_velocity / ustar, 1)) Ione = th.Function(P1_2d).interpolate( th.conditional( r1conv > 10**(-8), (1 - Amax**r1conv) / r1conv, th.conditional(r1conv < -10**(-8), (1 - Amax**r1conv) / r1conv, th.ln(Amax)))) Itwo = th.Function(P1_2d).interpolate( th.conditional( r1conv > 10**(-8), -(Ione + (th.ln(Amax) * (Amax**r1conv))) / r1conv, th.conditional( r1conv < -10**(-8), -(Ione + (th.ln(Amax) * (Amax**r1conv))) / r1conv, -0.5 * th.ln(Amax)**2))) alpha = th.Function(P1_2d).interpolate( -(Itwo - (th.ln(Amax) - th.ln(30)) * Ione) / (Ione * ((th.ln(Amax) - th.ln(30)) + 1))) # final correction factor alphatest2 = th.Function(P1_2d).interpolate( th.conditional( th.conditional(alpha > 1, 1, alpha) < 0, 0, th.conditional(alpha > 1, 1, alpha))) # multiply correction factor by velocity and insert back into sediment concentration equation corrective_velocity = th.Function(vectorP1_2d).interpolate( alphatest2 * uv_init) else: corrective_velocity = th.Function(vectorP1_2d).interpolate(uv_init) if bedload: # calculate angle of flow calfa = th.Function(V).interpolate(horizontal_velocity / th.sqrt(unorm)) salfa = th.Function(V).interpolate(vertical_velocity / th.sqrt(unorm)) div_function = th.Function(vector_cg).interpolate( th.as_vector((calfa, salfa))) if slope_eff: # slope effect magnitude correction due to gravity where beta is a parameter normally set to 1.3 slopecoef = th.Function(V).interpolate( 1 + beta * (dzdx * calfa + dzdy * salfa)) else: slopecoef = th.Function(V).interpolate(th.Constant(1.0)) if angle_correction: # slope effect angle correction due to gravity tt1 = th.Function(V).interpolate( th.conditional(1000 * 0.5 * qfc * unorm > 10**(-10), th.sqrt(cparam / (1000 * 0.5 * qfc * unorm)), th.sqrt(cparam / (10**(-10))))) # add on a factor of the bed gradient to the normal aa = th.Function(V).interpolate(salfa + tt1 * dzdy) bb = th.Function(V).interpolate(calfa + tt1 * dzdx) norm = th.Function(V).interpolate( th.conditional( th.sqrt(aa**2 + bb**2) > 10**(-10), th.sqrt(aa**2 + bb**2), 10**(-10))) if seccurrent: # accounts for helical flow effect in a curver channel free_surface_dx = th.Function(V).interpolate(elev_cg.dx(0)) free_surface_dy = th.Function(V).interpolate(elev_cg.dx(1)) velocity_slide = (horizontal_velocity * free_surface_dy) - ( vertical_velocity * free_surface_dx) tandelta_factor = th.Function(V).interpolate( 7 * 9.81 * 1000 * depth * qfc / (2 * alpha_secc * ((horizontal_velocity**2) + (vertical_velocity**2)))) t_1 = (TOB * slopecoef * calfa) + ( vertical_velocity * tandelta_factor * velocity_slide) t_2 = ((TOB * slopecoef * salfa) - (horizontal_velocity * tandelta_factor * velocity_slide)) # calculated to normalise the new angles t4 = th.sqrt((t_1**2) + (t_2**2)) # updated magnitude correction and angle corrections slopecoef = t4 / TOB if bed_form == 'meyer': # implement meyer-peter-muller bedload transport formula thetaprime = th.Function(V).interpolate( mu * (1000 * 0.5 * qfc * unorm) / ((2650 - 1000) * 9.81 * average_size)) # if velocity above a certain critical value then transport occurs phi = th.Function(V).interpolate( th.conditional(thetaprime < thetacr, 0, 8 * (thetaprime - thetacr)**1.5)) elif bed_form == 'soulsby': if d90 == 0: d90 = th.Constant(average_size) coeff_soulsby = th.Constant((R * g * average_size)**1.2) abb = th.Function(P1_2d).interpolate( th.conditional( depth >= average_size, 0.005 * depth * ((average_size / depth)**1.2) / coeff_soulsby, 0.005 * depth / coeff_soulsby)) ucr_bed = th.Function(P1_2d).interpolate( th.conditional( depth > d90, 0.19 * (average_size**0.1) * (th.ln(4 * depth / d90)) / (th.ln(10)), 0.19 * (average_size**0.1) * (th.ln(4)) / (th.ln(10)))) s0_bed = th.Function(P1_2d).interpolate( th.conditional((th.sqrt(unorm) - ucr_bed)**2.4 > 0, (th.sqrt(unorm) - ucr_bed)**2.4, 0)) else: print( 'Unrecognised bedload transport formula. Please choose "meyer" or "soulsby"' ) # set up solver solver_obj = th.solver2d.FlowSolver2d(mesh2d, bathymetry_2d) options = solver_obj.options options.simulation_export_time = t_export options.simulation_end_time = t_end options.output_directory = outputdir options.check_volume_conservation_2d = True if suspendedload: # switch on tracer calculation if using sediment transport component options.solve_tracer = True options.fields_to_export = [ 'uv_2d', 'elev_2d', 'tracer_2d', 'bathymetry_2d' ] options.tracer_advective_velocity = corrective_velocity options.tracer_source_2d = source else: options.solve_tracer = False options.fields_to_export = ['uv_2d', 'elev_2d', 'bathymetry_2d'] options.use_lax_friedrichs_tracer = False # set bed friction if friction == 'nikuradse': options.quadratic_drag_coefficient = cfactor elif friction == 'manning': if friction_coef == 0: friction_coef = 0.02 options.manning_drag_coefficient = th.Constant(friction_coef) else: print('Undefined friction') # set horizontal diffusivity parameter options.horizontal_diffusivity = th.Constant(diffusivity) options.horizontal_viscosity = th.Constant(viscosity_hydro) # crank-nicholson used to integrate in time system of ODEs resulting from application of galerkin FEM options.timestepper_type = 'CrankNicolson' options.timestepper_options.implicitness_theta = 1.0 if not hasattr(options.timestepper_options, 'use_automatic_timestep'): options.timestep = dt # set boundary conditions swe_bnd, left_bnd_id, right_bnd_id, in_constant, out_constant, left_string, right_string = boundary_conditions_fn( orig_bathymetry, flag='morpho') for j in range(len(in_constant)): exec( 'constant_in' + str(j) + ' = th.Constant(' + str(in_constant[j]) + ')', globals()) str1 = '{' for i in range(len(left_string)): str1 += "'" + str(left_string[i]) + "': constant_in" + str(i) + "," str1 = str1[0:len(str1) - 1] + "}" exec('swe_bnd[left_bnd_id] = ' + str1) for k in range(len(out_constant)): exec( 'constant_out' + str(k) + '= th.Constant(' + str(out_constant[k]) + ')', globals()) str2 = '{' for i in range(len(right_string)): str2 += "'" + str(right_string[i]) + "': constant_out" + str(i) + "," str2 = str2[0:len(str2) - 1] + "}" exec('swe_bnd[right_bnd_id] = ' + str2) solver_obj.bnd_functions['shallow_water'] = swe_bnd if suspendedload: solver_obj.bnd_functions['tracer'] = {1: {'value': sediment_rate}} # set initial conditions solver_obj.assign_initial_conditions(uv=uv_init, elev=elev_init, tracer=testtracer) else: # set initial conditions solver_obj.assign_initial_conditions(uv=uv_init, elev=elev_init) return solver_obj, update_forcings_tracer, diff_bathy, diff_bathy_file
def update_forcings_tracer(t_new): # update bathymetry old_bathymetry_2d.assign(bathymetry_2d) # extract new elevation and velocity and project onto CG space uv1, elev1 = solver_obj.fields.solution_2d.split() uv_cg.project(uv1) elev_cg.project(elev1) depth.project(elev_cg + old_bathymetry_2d) horizontal_velocity.interpolate(uv_cg[0]) vertical_velocity.interpolate(uv_cg[1]) # update boundary conditions if have fluctuating conditions if fluc_bcs: in_fn, out_fn = boundary_conditions_fn(orig_bathymetry, flag='morpho', morfac=morfac, t_new=t_new, state='update') for j in range(len(in_fn)): exec('constant_in' + str(j) + '.assign(' + str(in_fn[j]) + ')') for k in range(len(out_fn)): exec('constant_out' + str(k) + '.assign(' + str(out_fn[k]) + ')') # update bedfriction hc.interpolate(th.conditional(depth > 0.001, depth, 0.001)) aux.assign( th.conditional(11.036 * hc / ks > 1.001, 11.036 * hc / ks, 1.001)) qfc.assign(2 / (th.ln(aux) / 0.4)**2) # calculate skin friction coefficient hclip.interpolate(th.conditional(ksp > depth, ksp, depth)) cfactor.interpolate( th.conditional(depth > ksp, 2 * ((2.5 * th.ln(11.036 * hclip / ksp))**(-2)), th.Constant(0.0))) if morfac_transport: # if include tracer solver then update_forcings is run twice but only want to update bathymetry once t_list.append(t_new) double_factor = False if suspendedload: if len(t_list) > 1: if t_list[len(t_list) - 1] == t_list[len(t_list) - 2]: double_factor = True else: # if have no tracer then update_forcings is only run once so update bathymetry at each step double_factor = True if double_factor: z_n.assign(old_bathymetry_2d) # mu - ratio between skin friction and normal friction mu.assign(th.conditional(qfc > 0, cfactor / qfc, 0)) # bed shear stress unorm.interpolate((horizontal_velocity**2) + (vertical_velocity**2)) TOB.interpolate(1000 * 0.5 * qfc * unorm) # calculate gradient of bed (noting bathymetry is -bed) dzdx.interpolate(old_bathymetry_2d.dx(0)) dzdy.interpolate(old_bathymetry_2d.dx(1)) # initialise exner equation f = 0 if suspendedload: # source term # deposition flux - calculating coefficient to account for stronger conc at bed B.interpolate(th.conditional(a > depth, a / a, a / depth)) ustar.interpolate(th.sqrt(0.5 * qfc * unorm)) exp1.assign( th.conditional( (th.conditional( (settling_velocity / (0.4 * ustar)) - 1 > 0, (settling_velocity / (0.4 * ustar)) - 1, -(settling_velocity / (0.4 * ustar)) + 1)) > 10**(-4), th.conditional( (settling_velocity / (0.4 * ustar)) - 1 > 3, 3, (settling_velocity / (0.4 * ustar)) - 1), 0)) coefftest.assign( th.conditional((th.conditional( (settling_velocity / (0.4 * ustar)) - 1 > 0, (settling_velocity / (0.4 * ustar)) - 1, -(settling_velocity / (0.4 * ustar)) + 1)) > 10**(-4), B * (1 - B**exp1) / exp1, -B * th.ln(B))) coeff.assign( th.conditional(coefftest > 0, 1 / coefftest, 0)) if sus_form == 'vanrijn': # erosion flux - above critical velocity bed is eroded s0.assign( (th.conditional(1000 * 0.5 * qfc * unorm * mu > 0, 1000 * 0.5 * qfc * unorm * mu, 0) - taucr) / taucr) ceq.assign(0.015 * (average_size / a) * ((th.conditional(s0 < 0, 0, s0))**(1.5)) / (dstar**0.3)) elif sus_form == 'soulsby': ucr.interpolate(0.19 * (average_size**0.1) * (th.ln(4 * depth / d90) / th.ln(10))) s0.assign( th.conditional((th.sqrt(unorm) - ucr)**2.4 > 0, (th.sqrt(unorm) - ucr)**2.4, 0)) ceq.interpolate(ass * s0 / depth) else: print( 'Unrecognised suspended sediment transport formula. Please choose "vanrijn" or "soulsby"' ) # calculate depth-averaged source term for sediment concentration equation source.interpolate(-(settling_velocity * coeff * solver_obj.fields.tracer_2d / depth) + (settling_velocity * ceq / depth)) # update sediment rate to ensure equilibrium at inflow sediment_rate.assign(ceq.at([0, 0]) / coeff.at([0, 0])) if convectivevel: # correction factor to advection velocity in sediment concentration equation Bconv.interpolate( th.conditional(depth > 1.1 * ksp, ksp / depth, ksp / (1.1 * ksp))) Aconv.interpolate( th.conditional(depth > 1.1 * a, a / depth, a / (1.1 * a))) # take max of value calculated either by ksp or depth Amax.assign(th.conditional(Aconv > Bconv, Aconv, Bconv)) r1conv.assign(1 - (1 / 0.4) * th.conditional( settling_velocity / ustar < 1, settling_velocity / ustar, 1)) Ione.assign( th.conditional( r1conv > 10**(-8), (1 - Amax**r1conv) / r1conv, th.conditional(r1conv < -10**(-8), (1 - Amax**r1conv) / r1conv, th.ln(Amax)))) Itwo.assign( th.conditional( r1conv > 10**(-8), -(Ione + (th.ln(Amax) * (Amax**r1conv))) / r1conv, th.conditional( r1conv < -10**(-8), -(Ione + (th.ln(Amax) * (Amax**r1conv))) / r1conv, -0.5 * th.ln(Amax)**2))) alpha.assign(-(Itwo - (th.ln(Amax) - th.ln(30)) * Ione) / (Ione * ((th.ln(Amax) - th.ln(30)) + 1))) # final correction factor alphatest2.assign( th.conditional( th.conditional(alpha > 1, 1, alpha) < 0, 0, th.conditional(alpha > 1, 1, alpha))) # multiply correction factor by velocity and insert back into sediment concentration equation corrective_velocity.interpolate(alphatest2 * uv1) else: corrective_velocity.interpolate(uv1) if bedload: # calculate angle of flow calfa.interpolate(horizontal_velocity / th.sqrt(unorm)) salfa.interpolate(vertical_velocity / th.sqrt(unorm)) div_function.interpolate(th.as_vector((calfa, salfa))) if slope_eff: # slope effect magnitude correction due to gravity where beta is a parameter normally set to 1.3 # we use z_n1 and equals so that we can use an implicit method in Exner slopecoef = (1 + beta * (z_n1.dx(0) * calfa + z_n1.dx(1) * salfa)) else: slopecoef = th.Constant(1.0) if angle_correction: # slope effect angle correction due to gravity tt1.interpolate( th.conditional( 1000 * 0.5 * qfc * unorm > 10**(-10), th.sqrt(cparam / (1000 * 0.5 * qfc * unorm)), th.sqrt(cparam / (10**(-10))))) # add on a factor of the bed gradient to the normal aa.assign(salfa + tt1 * dzdy) bb.assign(calfa + tt1 * dzdx) norm.assign( th.conditional( th.sqrt(aa**2 + bb**2) > 10**(-10), th.sqrt(aa**2 + bb**2), 10**(-10))) # we use z_n1 and equals so that we can use an implicit method in Exner calfamod = (calfa + (tt1 * z_n1.dx(0))) / norm salfamod = (salfa + (tt1 * z_n1.dx(1))) / norm if seccurrent: # accounts for helical flow effect in a curver channel # again use z_n1 and equals so can use an implicit method in Exner free_surface_dx = depth.dx(0) - z_n1.dx(0) free_surface_dy = depth.dx(1) - z_n1.dx(1) velocity_slide = (horizontal_velocity * free_surface_dy ) - (vertical_velocity * free_surface_dx) tandelta_factor.interpolate( 7 * 9.81 * 1000 * depth * qfc / (2 * alpha_secc * ((horizontal_velocity**2) + (vertical_velocity**2)))) if angle_correction: # if angle has already been corrected we must alter the corrected angle to obtain the corrected secondary current angle t_1 = (TOB * slopecoef * calfamod) + ( vertical_velocity * tandelta_factor * velocity_slide) t_2 = (TOB * slopecoef * salfamod) - ( horizontal_velocity * tandelta_factor * velocity_slide) else: t_1 = (TOB * slopecoef * calfa) + (vertical_velocity * tandelta_factor * velocity_slide) t_2 = ((TOB * slopecoef * salfa) - (horizontal_velocity * tandelta_factor * velocity_slide)) # calculated to normalise the new angles t4 = th.sqrt((t_1**2) + (t_2**2)) # updated magnitude correction and angle corrections slopecoef = t4 / TOB calfanew = t_1 / t4 salfanew = t_2 / t4 if bed_form == 'meyer': # implement meyer-peter-muller bedload transport formula thetaprime.interpolate( mu * (1000 * 0.5 * qfc * unorm) / ((2650 - 1000) * 9.81 * average_size)) # if velocity above a certain critical value then transport occurs phi.assign( th.conditional(thetaprime < thetacr, 0, 8 * (thetaprime - thetacr)**1.5)) # bedload transport flux with magnitude correction qb_total = slopecoef * phi * th.sqrt( g * (2650 / 1000 - 1) * average_size**3) elif bed_form == 'soulsby': abb.interpolate( th.conditional( depth >= average_size, 0.005 * depth * ((average_size / depth)**1.2) / coeff_soulsby, 0.005 * depth / coeff_soulsby)) ucr_bed.interpolate( th.conditional( depth > d90, 0.19 * (average_size**0.1) * (th.ln(4 * depth / d90)) / (th.ln(10)), 0.19 * (average_size**0.1) * (th.ln(4)) / (th.ln(10)))) s0_bed.interpolate( th.conditional((th.sqrt(unorm) - ucr_bed)**2.4 > 0, (th.sqrt(unorm) - ucr_bed)**2.4, 0)) qb_total = slopecoef * abb * s0_bed * th.sqrt(unorm) else: print( 'Unrecognised bedload transport formula. Please choose "meyer" or "soulsby"' ) # add time derivative to exner equation with a morphological scale factor f += (((1 - porosity) * (z_n1 - z_n) / (dt * morfac)) * v) * fire.dx # formulate bedload transport flux with correct angle depending on corrections implemented if angle_correction and seccurrent is False: qbx = qb_total * calfamod qby = qb_total * salfamod elif seccurrent: qbx = qb_total * calfanew qby = qb_total * salfanew else: qbx = qb_total * calfa qby = qb_total * salfa # add bedload transport to exner equation f += -(v * ( (qbx * n[0]) + (qby * n[1]))) * fire.ds(1) - (v * ( (qbx * n[0]) + (qby * n[1]))) * fire.ds(2) + (qbx * (v.dx(0)) + qby * (v.dx(1))) * fire.dx else: # if no bedload transport component initialise exner equation with time derivative f = (((1 - porosity) * (z_n1 - z_n) / (dt * morfac)) * v) * fire.dx if suspendedload: # add suspended sediment transport to exner equation multiplied by depth as the exner equation is not depth-averaged qbsourcedepth.interpolate(source * depth) f += -(qbsourcedepth * v) * fire.dx # solve exner equation using finite element methods fire.solve(f == 0, z_n1) # update bed bathymetry_2d.assign(z_n1) if round(t_new, 2) % t_export == 0: # calculate difference between original bathymetry and new bathymetry diff_bathy.interpolate(-bathymetry_2d + orig_bathymetry) diff_bathy_file.write(diff_bathy)
# define mesh mesh2d = th.Mesh("meander_test_3112_test.msh") x, y = th.SpatialCoordinate(mesh2d) # define function spaces V = th.FunctionSpace(mesh2d, 'CG', 1) P1_2d = th.FunctionSpace(mesh2d, 'DG', 1) vectorP1_2d = th.VectorFunctionSpace(mesh2d, 'DG', 1) # define underlying bathymetry bathymetry_2d = th.Function(V, name='Bathymetry') gradient = th.Constant(0.0035) L_function = th.Function(V).interpolate(th.conditional(x > 5, th.pi*4*((th.pi/2)-th.acos((x-5)/(th.sqrt((x-5)**2+(y-2.5)**2))))/th.pi, th.pi*4*((th.pi/2)-th.acos((-x+5)/(th.sqrt((x-5)**2+(y-2.5)**2))))/th.pi)) bathymetry_2d1 = th.Function(V).interpolate(th.conditional(y > 2.5, th.conditional(x < 5, (L_function*gradient) + 9.97072, -(L_function*gradient) + 9.97072), 9.97072)) init = max(bathymetry_2d1.dat.data[:]) final = min(bathymetry_2d1.dat.data[:]) bathymetry_2d2 = th.Function(V).interpolate(th.conditional(x <= 5, th.conditional(y <= 2.5, -9.97072 + gradient*abs(y - 2.5) + init, 0), th.conditional(y <= 2.5, -9.97072-gradient*abs(y - 2.5) + final, 0))) bathymetry_2d = th.Function(V).interpolate(-bathymetry_2d1 - bathymetry_2d2) input_bathymetry_2d = th.Function(V).interpolate(bathymetry_2d) initial_bathymetry_2d = th.Function(V).interpolate(bathymetry_2d) # If the hydrodynamics simulation has already been run with the required parameters # then hydro can be set to False. This is because we do not need to run the initial # hydrodyamics simulation again as the previous run can be used to initialise # the full simulation