예제 #1
0
fac_y = 1

# define mesh
lx = 220
ly = 10
nx = np.int(lx * fac_x)
ny = np.int(10 * fac_y)
mesh2d = th.RectangleMesh(nx, ny, lx, ly)

# define function spaces
V = th.FunctionSpace(mesh2d, 'CG', 1)
P1_2d = th.FunctionSpace(mesh2d, 'CG', 1)

# define underlying bathymetry
bathymetry_2d = th.Function(V, name='Bathymetry')
x, y = th.SpatialCoordinate(mesh2d)

beach_profile = (-180 / 40 + x / 40)

bathymetry_2d.interpolate(-beach_profile)

# define initial elevation
elev_init = th.Function(P1_2d).interpolate(th.Constant(0.0))

uv_init = th.Constant((10**(-7), 0.))

value = 1 / 40

sponge_fn = th.Function(V).interpolate(
    th.conditional(x >= 100, -399 + 4 * x, th.Constant(1.0)))
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