Exemple #1
0
    def __init__(self, equation):
        """
        Initialise limiter

        :param space : equation, as we need the broken space attached to it
        """

        self.Vt = equation.space
        # check this is the right space, only currently working for 2D extruded mesh
        if self.Vt.extruded and self.Vt.mesh().topological_dimension() == 2:
            # check that horizontal degree is 1 and vertical degree is 2
            if self.Vt.ufl_element().degree()[0] is not 1 or \
               self.Vt.ufl_element().degree()[1] is not 2:
                raise ValueError('This is not the right limiter for this space.')
            # check that continuity of the spaces is correct
            # this will fail if the space does not use broken elements
            if self.Vt.ufl_element()._element.sobolev_space()[0].name is not 'L2' or \
               self.Vt.ufl_element()._element.sobolev_space()[1].name is not 'H1':
                raise ValueError('This is not the right limiter for this space.')
        else:
            logger.warning('This limiter may not work for the space you are using.')

        self.Q1DG = FunctionSpace(self.Vt.mesh(), 'DG', 1)  # space with only vertex DOFs
        self.vertex_limiter = VertexBasedLimiter(self.Q1DG)
        self.theta_hat = Function(self.Q1DG)  # theta function with only vertex DOFs
        self.w = Function(self.Vt)
        self.result = Function(self.Vt)
        par_loop(_weight_kernel, dx, {"weight": (self.w, INC)})
Exemple #2
0
    def __init__(self,
                 state,
                 equations,
                 alpha=0.5,
                 quadrature_degree=None,
                 solver_parameters=None,
                 overwrite_solver_parameters=False,
                 moisture=None):

        self.moisture = moisture

        if quadrature_degree is not None:
            self.quadrature_degree = quadrature_degree
        else:
            dgspace = state.spaces("DG")
            if any(deg > 2 for deg in dgspace.ufl_element().degree()):
                logger.warning(
                    "default quadrature degree most likely not sufficient for this degree element"
                )
            self.quadrature_degree = (5, 5)

        if logger.isEnabledFor(DEBUG):
            # Set outer solver to FGMRES and turn on KSP monitor for the outer system
            self.solver_parameters["ksp_type"] = "fgmres"
            self.solver_parameters["mat_type"] = "aij"
            self.solver_parameters["pmat_type"] = "matfree"
            self.solver_parameters["ksp_monitor_true_residual"] = None

            # Turn monitor on for the trace system
            self.solver_parameters["condensed_field"][
                "ksp_monitor_true_residual"] = None

        super().__init__(state, equations, alpha, solver_parameters,
                         overwrite_solver_parameters)
    def __init__(self, state, quadrature_degree=None, solver_parameters=None,
                 overwrite_solver_parameters=False, moisture=None):

        self.moisture = moisture

        if quadrature_degree is not None:
            self.quadrature_degree = quadrature_degree
        else:
            dgspace = state.spaces("DG")
            if any(deg > 2 for deg in dgspace.ufl_element().degree()):
                logger.warning("default quadrature degree most likely not sufficient for this degree element")
            self.quadrature_degree = (5, 5)

        super().__init__(state, solver_parameters, overwrite_solver_parameters)
Exemple #4
0
    def __init__(self,
                 state,
                 euler_poincare=True,
                 linear=False,
                 extra_terms=None,
                 moisture=None):
        self.state = state
        if linear:
            self.euler_poincare = False
            logger.warning(
                'Setting euler_poincare to False because you have set linear=True'
            )
        else:
            self.euler_poincare = euler_poincare

        # set up functions
        self.Vu = state.spaces("HDiv")
        # this is the function that the forcing term is applied to
        self.x0 = Function(state.W)
        self.test = TestFunction(self.Vu)
        self.trial = TrialFunction(self.Vu)
        # this is the function that contains the result of solving
        # <test, trial> = <test, F(x0)>, where F is the forcing term
        self.uF = Function(self.Vu)

        # find out which terms we need
        self.extruded = self.Vu.extruded
        self.coriolis = state.Omega is not None or hasattr(
            state.fields, "coriolis")
        self.sponge = state.mu is not None
        self.hydrostatic = state.hydrostatic
        self.topography = hasattr(state.fields, "topography")
        self.extra_terms = extra_terms
        self.moisture = moisture

        # some constants to use for scaling terms
        self.scaling = Constant(1.)
        self.impl = Constant(1.)

        self._build_forcing_solvers()
Exemple #5
0
def unsaturated_hydrostatic_balance(state,
                                    theta_d,
                                    H,
                                    exner0=None,
                                    top=False,
                                    exner_boundary=Constant(1.0),
                                    max_outer_solve_count=40,
                                    max_inner_solve_count=20):
    """
    Given vertical profiles for dry potential temperature
    and relative humidity compute hydrostatically balanced
    virtual potential temperature, dry density and water vapour profiles.

    The general strategy is to split up the solving into two steps:
    1) finding rho to balance the theta profile
    2) finding theta_v and r_v to get back theta_d and H
    We iteratively solve these steps until we (hopefully)
    converge to a solution.

    :arg state: The :class:`State` object.
    :arg theta_d: The initial dry potential temperature profile.
    :arg H: The relative humidity profile.
    :arg exner0: Optional function to put exner pressure into.
    :arg top: If True, set a boundary condition at the top, otherwise
              it will be at the bottom.
    :arg exner_boundary: The value of exner on the specified boundary.
    :arg max_outer_solve_count: Max number of iterations for outer loop of balance solver.
    :arg max_inner_solve_count: Max number of iterations for inner loop of balanace solver.
    """

    theta0 = state.fields('theta')
    rho0 = state.fields('rho')
    mr_v0 = state.fields('vapour_mixing_ratio')

    # Calculate hydrostatic exner pressure
    Vt = theta0.function_space()
    Vr = rho0.function_space()
    R_d = state.parameters.R_d
    R_v = state.parameters.R_v
    epsilon = R_d / R_v

    VDG = state.spaces("DG")
    if any(deg > 2 for deg in VDG.ufl_element().degree()):
        logger.warning(
            "default quadrature degree most likely not sufficient for this degree element"
        )

    # apply first guesses
    theta0.assign(theta_d * 1.01)
    mr_v0.assign(0.01)

    v_deg = Vr.ufl_element().degree()[1]
    if v_deg == 0:
        method = Boundary_Method.physics
    else:
        method = None
    rho_h = Function(Vr)
    rho_averaged = Function(Vt)
    Vt_broken = FunctionSpace(state.mesh, BrokenElement(Vt.ufl_element()))
    rho_recoverer = Recoverer(rho0,
                              rho_averaged,
                              VDG=Vt_broken,
                              boundary_method=method)
    w_h = Function(Vt)
    delta = 1.0

    # make expressions for determining mr_v0
    exner = thermodynamics.exner_pressure(state.parameters, rho_averaged,
                                          theta0)
    p = thermodynamics.p(state.parameters, exner)
    T = thermodynamics.T(state.parameters, theta0, exner, mr_v0)
    r_v_expr = thermodynamics.r_v(state.parameters, H, T, p)

    # make expressions to evaluate residual
    exner_ev = thermodynamics.exner_pressure(state.parameters, rho_averaged,
                                             theta0)
    p_ev = thermodynamics.p(state.parameters, exner_ev)
    T_ev = thermodynamics.T(state.parameters, theta0, exner_ev, mr_v0)
    RH_ev = thermodynamics.RH(state.parameters, mr_v0, T_ev, p_ev)
    RH = Function(Vt)

    for i in range(max_outer_solve_count):
        # solve for rho with theta_vd and w_v guesses
        compressible_hydrostatic_balance(state,
                                         theta0,
                                         rho_h,
                                         top=top,
                                         exner_boundary=exner_boundary,
                                         mr_t=mr_v0,
                                         solve_for_rho=True)

        # damp solution
        rho0.assign(rho0 * (1 - delta) + delta * rho_h)

        # calculate averaged rho
        rho_recoverer.project()

        RH.assign(RH_ev)
        if errornorm(RH, H) < 1e-10:
            break

        # now solve for r_v
        for j in range(max_inner_solve_count):
            w_h.interpolate(r_v_expr)
            mr_v0.assign(mr_v0 * (1 - delta) + delta * w_h)

            # compute theta_vd
            theta0.assign(theta_d * (1 + mr_v0 / epsilon))

            # test quality of solution by re-evaluating expression
            RH.assign(RH_ev)
            if errornorm(RH, H) < 1e-10:
                break

        if i == max_outer_solve_count:
            raise RuntimeError(
                'Hydrostatic balance solve has not converged within %i' % i,
                'iterations')

    if exner0 is not None:
        exner = thermodynamics.exner_pressure(state.parameters, rho0, theta0)
        exner0.interpolate(exner)

    # do one extra solve for rho
    compressible_hydrostatic_balance(state,
                                     theta0,
                                     rho0,
                                     top=top,
                                     exner_boundary=exner_boundary,
                                     mr_t=mr_v0,
                                     solve_for_rho=True)
Exemple #6
0
def saturated_hydrostatic_balance(state,
                                  theta_e,
                                  mr_t,
                                  exner0=None,
                                  top=False,
                                  exner_boundary=Constant(1.0),
                                  max_outer_solve_count=40,
                                  max_theta_solve_count=5,
                                  max_inner_solve_count=3):
    """
    Given a wet equivalent potential temperature, theta_e, and the total moisture
    content, mr_t, compute a hydrostatically balance virtual potential temperature,
    dry density and water vapour profile.

    The general strategy is to split up the solving into two steps:
    1) finding rho to balance the theta profile
    2) finding theta_v and r_v to get back theta_e and saturation
    We iteratively solve these steps until we (hopefully)
    converge to a solution.

    :arg state: The :class:`State` object.
    :arg theta_e: The initial wet equivalent potential temperature profile.
    :arg mr_t: The total water pseudo-mixing ratio profile.
    :arg exner0: Optional function to put exner pressure into.
    :arg top: If True, set a boundary condition at the top, otherwise
              it will be at the bottom.
    :arg exner_boundary: The value of exner on the specified boundary.
    :arg max_outer_solve_count: Max number of outer iterations for balance solver.
    :arg max_theta_solve_count: Max number of iterations for theta solver (middle part of solve).
    :arg max_inner_solve_count: Max number of iterations on the inner most
                                loop for the water vapour solver.
    """

    theta0 = state.fields('theta')
    rho0 = state.fields('rho')
    mr_v0 = state.fields('vapour_mixing_ratio')

    # Calculate hydrostatic exner pressure
    Vt = theta0.function_space()
    Vr = rho0.function_space()

    VDG = state.spaces("DG")
    if any(deg > 2 for deg in VDG.ufl_element().degree()):
        logger.warning(
            "default quadrature degree most likely not sufficient for this degree element"
        )

    theta0.interpolate(theta_e)
    mr_v0.interpolate(mr_t)

    v_deg = Vr.ufl_element().degree()[1]
    if v_deg == 0:
        boundary_method = Boundary_Method.physics
    else:
        boundary_method = None
    rho_h = Function(Vr)
    Vt_broken = FunctionSpace(state.mesh, BrokenElement(Vt.ufl_element()))
    rho_averaged = Function(Vt)
    rho_recoverer = Recoverer(rho0,
                              rho_averaged,
                              VDG=Vt_broken,
                              boundary_method=boundary_method)
    w_h = Function(Vt)
    theta_h = Function(Vt)
    theta_e_test = Function(Vt)
    delta = 0.8

    # expressions for finding theta0 and mr_v0 from theta_e and mr_t
    exner = thermodynamics.exner_pressure(state.parameters, rho_averaged,
                                          theta0)
    p = thermodynamics.p(state.parameters, exner)
    T = thermodynamics.T(state.parameters, theta0, exner, mr_v0)
    r_v_expr = thermodynamics.r_sat(state.parameters, T, p)
    theta_e_expr = thermodynamics.theta_e(state.parameters, T, p, mr_v0, mr_t)

    for i in range(max_outer_solve_count):
        # solve for rho with theta_vd and w_v guesses
        compressible_hydrostatic_balance(state,
                                         theta0,
                                         rho_h,
                                         top=top,
                                         exner_boundary=exner_boundary,
                                         mr_t=mr_t,
                                         solve_for_rho=True)

        # damp solution
        rho0.assign(rho0 * (1 - delta) + delta * rho_h)

        theta_e_test.assign(theta_e_expr)
        if errornorm(theta_e_test, theta_e) < 1e-8:
            break

        # calculate averaged rho
        rho_recoverer.project()

        # now solve for r_v
        for j in range(max_theta_solve_count):
            theta_h.interpolate(theta_e / theta_e_expr * theta0)
            theta0.assign(theta0 * (1 - delta) + delta * theta_h)

            # break when close enough
            if errornorm(theta_e_test, theta_e) < 1e-6:
                break
            for k in range(max_inner_solve_count):
                w_h.interpolate(r_v_expr)
                mr_v0.assign(mr_v0 * (1 - delta) + delta * w_h)

                # break when close enough
                theta_e_test.assign(theta_e_expr)
                if errornorm(theta_e_test, theta_e) < 1e-6:
                    break

        if i == max_outer_solve_count:
            raise RuntimeError(
                'Hydrostatic balance solve has not converged within %i' % i,
                'iterations')

    if exner0 is not None:
        exner = thermodynamics.exner(state.parameters, rho0, theta0)
        exner0.interpolate(exner)

    # do one extra solve for rho
    compressible_hydrostatic_balance(state,
                                     theta0,
                                     rho0,
                                     top=top,
                                     exner_boundary=exner_boundary,
                                     mr_t=mr_t,
                                     solve_for_rho=True)
Exemple #7
0
    def __init__(self,
                 v_CG1,
                 v_DG1,
                 method=Boundary_Method.physics,
                 coords_to_adjust=None):

        self.v_DG1 = v_DG1
        self.v_CG1 = v_CG1
        self.v_DG1_old = Function(v_DG1.function_space())
        self.coords_to_adjust = coords_to_adjust

        self.method = method
        mesh = v_CG1.function_space().mesh()
        VDG0 = FunctionSpace(mesh, "DG", 0)
        VCG1 = FunctionSpace(mesh, "CG", 1)

        if VDG0.extruded:
            cell = mesh._base_mesh.ufl_cell().cellname()
            DG1_hori_elt = FiniteElement("DG", cell, 1, variant="equispaced")
            DG1_vert_elt = FiniteElement("DG",
                                         interval,
                                         1,
                                         variant="equispaced")
            DG1_element = TensorProductElement(DG1_hori_elt, DG1_vert_elt)
        else:
            cell = mesh.ufl_cell().cellname()
            DG1_element = FiniteElement("DG", cell, 1, variant="equispaced")
        VDG1 = FunctionSpace(mesh, DG1_element)

        self.num_ext = Function(VDG0)

        # check function spaces of functions
        if self.method == Boundary_Method.dynamics:
            if v_CG1.function_space() != VCG1:
                raise NotImplementedError(
                    "This boundary recovery method requires v1 to be in CG1.")
            if v_DG1.function_space() != VDG1:
                raise NotImplementedError(
                    "This boundary recovery method requires v_out to be in DG1."
                )
            # check whether mesh is valid
            if mesh.topological_dimension() == 2:
                # if mesh is extruded then we're fine, but if not needs to be quads
                if not VDG0.extruded and mesh.ufl_cell().cellname(
                ) != 'quadrilateral':
                    raise NotImplementedError(
                        'For 2D meshes this recovery method requires that elements are quadrilaterals'
                    )
            elif mesh.topological_dimension() == 3:
                # assume that 3D mesh is extruded
                if mesh._base_mesh.ufl_cell().cellname() != 'quadrilateral':
                    raise NotImplementedError(
                        'For 3D extruded meshes this recovery method requires a base mesh with quadrilateral elements'
                    )
            elif mesh.topological_dimension() != 1:
                raise NotImplementedError(
                    'This boundary recovery is implemented only on certain classes of mesh.'
                )
            if coords_to_adjust is None:
                raise ValueError(
                    'Need coords_to_adjust field for dynamics boundary methods'
                )

        elif self.method == Boundary_Method.physics:
            # check that mesh is valid -- must be an extruded mesh
            if not VDG0.extruded:
                raise NotImplementedError(
                    'The physics boundary method only works on extruded meshes'
                )
            # base spaces
            cell = mesh._base_mesh.ufl_cell().cellname()
            w_hori = FiniteElement("DG", cell, 0, variant="equispaced")
            w_vert = FiniteElement("CG", interval, 1, variant="equispaced")
            # build element
            theta_element = TensorProductElement(w_hori, w_vert)
            # spaces
            Vtheta = FunctionSpace(mesh, theta_element)
            Vtheta_broken = FunctionSpace(mesh, BrokenElement(theta_element))
            if v_CG1.function_space() != Vtheta:
                raise ValueError(
                    "This boundary recovery method requires v_CG1 to be in DG0xCG1 TensorProductSpace."
                )
            if v_DG1.function_space() != Vtheta_broken:
                raise ValueError(
                    "This boundary recovery method requires v_DG1 to be in the broken DG0xCG1 TensorProductSpace."
                )
        else:
            raise ValueError(
                "Boundary method should be a Boundary Method Enum object.")

        VuDG1 = VectorFunctionSpace(VDG0.mesh(), DG1_element)
        x = SpatialCoordinate(VDG0.mesh())
        self.interpolator = Interpolator(self.v_CG1, self.v_DG1)

        if self.method == Boundary_Method.dynamics:

            # STRATEGY
            # obtain a coordinate field for all the nodes
            self.act_coords = Function(VuDG1).project(x)  # actual coordinates
            self.eff_coords = Function(VuDG1).project(
                x)  # effective coordinates
            self.output = Function(VDG1)

            shapes = {
                "nDOFs":
                self.v_DG1.function_space().finat_element.space_dimension(),
                "dim":
                np.prod(VuDG1.shape, dtype=int)
            }

            num_ext_domain = ("{{[i]: 0 <= i < {nDOFs}}}").format(**shapes)
            num_ext_instructions = ("""
            <float64> SUM_EXT = 0
            for i
                SUM_EXT = SUM_EXT + EXT_V1[i]
            end

            NUM_EXT[0] = SUM_EXT
            """)

            coords_domain = ("{{[i, j, k, ii, jj, kk, ll, mm, iii, kkk]: "
                             "0 <= i < {nDOFs} and "
                             "0 <= j < {nDOFs} and 0 <= k < {dim} and "
                             "0 <= ii < {nDOFs} and 0 <= jj < {nDOFs} and "
                             "0 <= kk < {dim} and 0 <= ll < {dim} and "
                             "0 <= mm < {dim} and 0 <= iii < {nDOFs} and "
                             "0 <= kkk < {dim}}}").format(**shapes)
            coords_insts = (
                """
                            <float64> sum_V1_ext = 0
                            <int> index = 100
                            <float64> dist = 0.0
                            <float64> max_dist = 0.0
                            <float64> min_dist = 0.0
                            """

                # only do adjustment in cells with at least one DOF to adjust
                """
                            if NUM_EXT[0] > 0
                            """

                # find the maximum distance between DOFs in this cell, to serve as starting point for finding min distances
                """
                                for i
                                    for j
                                        dist = 0.0
                                        for k
                                            dist = dist + pow(ACT_COORDS[i,k] - ACT_COORDS[j,k], 2.0)
                                        end
                                        dist = pow(dist, 0.5) {{id=sqrt_max_dist, dep=*}}
                                        max_dist = fmax(dist, max_dist) {{id=max_dist, dep=sqrt_max_dist}}
                                    end
                                end
                            """

                # loop through cells and find which ones to adjust
                """
                                for ii
                                    if EXT_V1[ii] > 0.5
                            """

                # find closest interior node
                """
                                        min_dist = max_dist
                                        index = 100
                                        for jj
                                            if EXT_V1[jj] < 0.5
                                                dist = 0.0
                                                for kk
                                                    dist = dist + pow(ACT_COORDS[ii,kk] - ACT_COORDS[jj,kk], 2)
                                                end
                                                dist = pow(dist, 0.5)
                                                if dist <= min_dist
                                                    index = jj
                                                end
                                                min_dist = fmin(min_dist, dist)
                                                for ll
                                                    EFF_COORDS[ii,ll] = 0.5 * (ACT_COORDS[ii,ll] + ACT_COORDS[index,ll])
                                                end
                                            end
                                        end
                                    else
                            """

                # for DOFs that aren't exterior, use the original coordinates
                """
                                        for mm
                                            EFF_COORDS[ii, mm] = ACT_COORDS[ii, mm]
                                        end
                                    end
                                end
                            else
                            """

                # for interior elements, just use the original coordinates
                """
                                for iii
                                    for kkk
                                        EFF_COORDS[iii, kkk] = ACT_COORDS[iii, kkk]
                                    end
                                end
                            end
                            """).format(**shapes)

            _num_ext_kernel = (num_ext_domain, num_ext_instructions)
            _eff_coords_kernel = (coords_domain, coords_insts)
            self.gaussian_elimination_kernel = kernels.GaussianElimination(
                VDG1)

            # find number of external DOFs per cell
            par_loop(_num_ext_kernel,
                     dx, {
                         "NUM_EXT": (self.num_ext, WRITE),
                         "EXT_V1": (self.coords_to_adjust, READ)
                     },
                     is_loopy_kernel=True)

            # find effective coordinates
            logger.warning(
                'Finding effective coordinates for boundary recovery. This could give unexpected results for deformed meshes over very steep topography.'
            )
            par_loop(_eff_coords_kernel,
                     dx, {
                         "EFF_COORDS": (self.eff_coords, WRITE),
                         "ACT_COORDS": (self.act_coords, READ),
                         "NUM_EXT": (self.num_ext, READ),
                         "EXT_V1": (self.coords_to_adjust, READ)
                     },
                     is_loopy_kernel=True)

        elif self.method == Boundary_Method.physics:

            self.bottom_kernel = kernels.PhysicsRecoveryBottom()
            self.top_kernel = kernels.PhysicsRecoveryTop()
Exemple #8
0
    def __init__(self, state, moments=AdvectedMoments.M3, limit=True):
        super().__init__(state)

        # function spaces
        Vt = state.fields('rain').function_space()
        Vu = state.fields('u').function_space()

        # declare properties of class
        self.state = state
        self.moments = moments
        self.rain = state.fields('rain')
        self.v = state.fields('rainfall_velocity', Vu)
        self.limit = limit

        if moments == AdvectedMoments.M0:
            # all rain falls at terminal velocity
            terminal_velocity = Constant(5)  # in m/s
            if state.mesh.geometric_dimension() == 2:
                self.v.project(as_vector([0, -terminal_velocity]))
            elif state.mesh.geometric_dimension() == 3:
                self.v.project(as_vector([0, 0, -terminal_velocity]))
        elif moments == AdvectedMoments.M3:
            # this advects the third moment M3 of the raindrop
            # distribution, which corresponds to the mean mass
            rho = state.fields('rho')
            rho_w = Constant(1000.0)  # density of liquid water
            # assume n(D) = n_0 * D^mu * exp(-Lambda*D)
            # n_0 = N_r * Lambda^(1+mu) / gamma(1 + mu)
            N_r = Constant(10**5)  # number of rain droplets per m^3
            mu = 0.0  # shape constant of droplet gamma distribution
            # assume V(D) = a * D^b * exp(-f*D) * (rho_0 / rho)^g
            # take f = 0
            a = Constant(362.)  # intercept for velocity distr. in log space
            b = 0.65  # inverse scale parameter for velocity distr.
            rho0 = Constant(1.22)  # reference density in kg/m^3
            g = Constant(0.5)  # scaling of density correction
            # we keep mu in the expressions even though mu = 0
            threshold = Constant(10**-10)  # only do rainfall for r > threshold
            Lambda = (N_r * pi * rho_w * gamma(4 + mu) /
                      (6 * gamma(1 + mu) * rho * self.rain))**(1. / 3)
            Lambda0 = (N_r * pi * rho_w * gamma(4 + mu) /
                       (6 * gamma(1 + mu) * rho * threshold))**(1. / 3)
            v_expression = conditional(
                self.rain > threshold,
                (a * gamma(4 + b + mu) / (gamma(4 + mu) * Lambda**b) *
                 (rho0 / rho)**g),
                (a * gamma(4 + b + mu) / (gamma(4 + mu) * Lambda0**b) *
                 (rho0 / rho)**g))
        else:
            raise NotImplementedError(
                'Currently we only have implementations for zero and one moment schemes for rainfall. Valid options are AdvectedMoments.M0 and AdvectedMoments.M3'
            )

        if moments != AdvectedMoments.M0:
            if state.mesh.geometric_dimension() == 2:
                self.determine_v = Projector(as_vector([0, -v_expression]),
                                             self.v)
            elif state.mesh.geometric_dimension() == 3:
                self.determine_v = Projector(as_vector([0, 0, -v_expression]),
                                             self.v)

        # determine whether to do recovered space advection scheme
        # if horizontal and vertical degrees are 0 do recovered space
        if state.horizontal_degree == 0 and state.vertical_degree == 0:
            VDG1 = FunctionSpace(Vt.mesh(), "DG", 1)
            VCG1 = FunctionSpace(Vt.mesh(), "CG", 1)
            Vbrok = FunctionSpace(Vt.mesh(), BrokenElement(Vt.ufl_element()))
            boundary_method = Boundary_Method.dynamics
            advect_options = RecoveredOptions(embedding_space=VDG1,
                                              recovered_space=VCG1,
                                              broken_space=Vbrok,
                                              boundary_method=boundary_method)
        else:
            advect_options = EmbeddedDGOptions()

        # need to define advection equation before limiter (as it is needed for the ThetaLimiter)
        advection_equation = EmbeddedDGAdvection(state,
                                                 Vt,
                                                 equation_form="advective",
                                                 outflow=True,
                                                 options=advect_options)

        # decide which limiter to use
        if self.limit:
            if state.horizontal_degree == 0 and state.vertical_degree == 0:
                limiter = VertexBasedLimiter(VDG1)
            elif state.horizontal_degree == 1 and state.vertical_degree == 1:
                limiter = ThetaLimiter(Vt)
            else:
                logger.warning(
                    "There is no limiter yet implemented for the spaces used. NoLimiter() is being used for the rainfall in this case."
                )
                limiter = NoLimiter()
        else:
            limiter = None

        # sedimentation will happen using a full advection method
        self.advection_method = SSPRK3(state,
                                       self.rain,
                                       advection_equation,
                                       limiter=limiter)
Exemple #9
0
    def __init__(self,
                 v_CG1,
                 v_DG1,
                 method=Boundary_Method.physics,
                 coords_to_adjust=None):

        self.v_DG1 = v_DG1
        self.v_CG1 = v_CG1
        self.v_DG1_old = Function(v_DG1.function_space())
        self.coords_to_adjust = coords_to_adjust

        self.method = method
        mesh = v_CG1.function_space().mesh()
        VDG0 = FunctionSpace(mesh, "DG", 0)
        VCG1 = FunctionSpace(mesh, "CG", 1)
        VDG1 = FunctionSpace(mesh, "DG", 1)

        self.num_ext = Function(VDG0)

        # check function spaces of functions
        if self.method == Boundary_Method.dynamics:
            if v_CG1.function_space() != VCG1:
                raise NotImplementedError(
                    "This boundary recovery method requires v1 to be in CG1.")
            if v_DG1.function_space() != VDG1:
                raise NotImplementedError(
                    "This boundary recovery method requires v_out to be in DG1."
                )
            # check whether mesh is valid
            if mesh.topological_dimension() == 2:
                # if mesh is extruded then we're fine, but if not needs to be quads
                if not VDG0.extruded and mesh.ufl_cell().cellname(
                ) != 'quadrilateral':
                    raise NotImplementedError(
                        'For 2D meshes this recovery method requires that elements are quadrilaterals'
                    )
            elif mesh.topological_dimension() == 3:
                # assume that 3D mesh is extruded
                if mesh._base_mesh.ufl_cell().cellname() != 'quadrilateral':
                    raise NotImplementedError(
                        'For 3D extruded meshes this recovery method requires a base mesh with quadrilateral elements'
                    )
            elif mesh.topological_dimension() != 1:
                raise NotImplementedError(
                    'This boundary recovery is implemented only on certain classes of mesh.'
                )
            if coords_to_adjust is None:
                raise ValueError(
                    'Need coords_to_adjust field for dynamics boundary methods'
                )

        elif self.method == Boundary_Method.physics:
            # check that mesh is valid -- must be an extruded mesh
            if not VDG0.extruded:
                raise NotImplementedError(
                    'The physics boundary method only works on extruded meshes'
                )
            # base spaces
            cell = mesh._base_mesh.ufl_cell().cellname()
            w_hori = FiniteElement("DG", cell, 0)
            w_vert = FiniteElement("CG", interval, 1)
            # build element
            theta_element = TensorProductElement(w_hori, w_vert)
            # spaces
            Vtheta = FunctionSpace(mesh, theta_element)
            Vtheta_broken = FunctionSpace(mesh, BrokenElement(theta_element))
            if v_CG1.function_space() != Vtheta:
                raise ValueError(
                    "This boundary recovery method requires v_CG1 to be in DG0xCG1 TensorProductSpace."
                )
            if v_DG1.function_space() != Vtheta_broken:
                raise ValueError(
                    "This boundary recovery method requires v_DG1 to be in the broken DG0xCG1 TensorProductSpace."
                )
        else:
            raise ValueError(
                "Boundary method should be a Boundary Method Enum object.")

        VuDG1 = VectorFunctionSpace(VDG0.mesh(), "DG", 1)
        x = SpatialCoordinate(VDG0.mesh())
        self.interpolator = Interpolator(self.v_CG1, self.v_DG1)

        if self.method == Boundary_Method.dynamics:

            # STRATEGY
            # obtain a coordinate field for all the nodes
            VuDG1 = VectorFunctionSpace(mesh, "DG", 1)
            self.act_coords = Function(VuDG1).project(x)  # actual coordinates
            self.eff_coords = Function(VuDG1).project(
                x)  # effective coordinates

            shapes = {
                "nDOFs":
                self.v_DG1.function_space().finat_element.space_dimension(),
                "dim":
                np.prod(VuDG1.shape, dtype=int)
            }

            num_ext_domain = ("{{[i]: 0 <= i < {nDOFs}}}").format(**shapes)
            num_ext_instructions = ("""
            <float64> SUM_EXT = 0
            for i
                SUM_EXT = SUM_EXT + EXT_V1[i]
            end

            NUM_EXT[0] = SUM_EXT
            """)

            coords_domain = ("{{[i, j, k, ii, jj, kk, ll, mm, iii, kkk]: "
                             "0 <= i < {nDOFs} and "
                             "0 <= j < {nDOFs} and 0 <= k < {dim} and "
                             "0 <= ii < {nDOFs} and 0 <= jj < {nDOFs} and "
                             "0 <= kk < {dim} and 0 <= ll < {dim} and "
                             "0 <= mm < {dim} and 0 <= iii < {nDOFs} and "
                             "0 <= kkk < {dim}}}").format(**shapes)
            coords_insts = (
                """
                            <float64> sum_V1_ext = 0
                            <int> index = 100
                            <float64> dist = 0.0
                            <float64> max_dist = 0.0
                            <float64> min_dist = 0.0
                            """

                # only do adjustment in cells with at least one DOF to adjust
                """
                            if NUM_EXT[0] > 0
                            """

                # find the maximum distance between DOFs in this cell, to serve as starting point for finding min distances
                """
                                for i
                                    for j
                                        dist = 0.0
                                        for k
                                            dist = dist + pow(ACT_COORDS[i,k] - ACT_COORDS[j,k], 2.0)
                                        end
                                        dist = pow(dist, 0.5) {{id=sqrt_max_dist, dep=*}}
                                        max_dist = fmax(dist, max_dist) {{id=max_dist, dep=sqrt_max_dist}}
                                    end
                                end
                            """

                # loop through cells and find which ones to adjust
                """
                                for ii
                                    if EXT_V1[ii] > 0.5
                            """

                # find closest interior node
                """
                                        min_dist = max_dist
                                        index = 100
                                        for jj
                                            if EXT_V1[jj] < 0.5
                                                dist = 0.0
                                                for kk
                                                    dist = dist + pow(ACT_COORDS[ii,kk] - ACT_COORDS[jj,kk], 2)
                                                end
                                                dist = pow(dist, 0.5)
                                                if dist <= min_dist
                                                    index = jj
                                                end
                                                min_dist = fmin(min_dist, dist)
                                                for ll
                                                    EFF_COORDS[ii,ll] = 0.5 * (ACT_COORDS[ii,ll] + ACT_COORDS[index,ll])
                                                end
                                            end
                                        end
                                    else
                            """

                # for DOFs that aren't exterior, use the original coordinates
                """
                                        for mm
                                            EFF_COORDS[ii, mm] = ACT_COORDS[ii, mm]
                                        end
                                    end
                                end
                            else
                            """

                # for interior elements, just use the original coordinates
                """
                                for iii
                                    for kkk
                                        EFF_COORDS[iii, kkk] = ACT_COORDS[iii, kkk]
                                    end
                                end
                            end
                            """).format(**shapes)

            elimin_domain = (
                "{{[i, ii_loop, jj_loop, kk, ll_loop, mm, iii_loop, kkk_loop, iiii, iiiii]: "
                "0 <= i < {nDOFs} and 0 <= ii_loop < {nDOFs} and "
                "ii_loop + 1 <= jj_loop < {nDOFs} and ii_loop <= kk < {nDOFs} and "
                "ii_loop + 1 <= ll_loop < {nDOFs} and ii_loop <= mm < {nDOFs} + 1 and "
                "0 <= iii_loop < {nDOFs} and {nDOFs} - iii_loop <= kkk_loop < {nDOFs} + 1 and "
                "0 <= iiii < {nDOFs} and 0 <= iiiii < {nDOFs}}}").format(
                    **shapes)
            elimin_insts = (
                """
                            <int> ii = 0
                            <int> jj = 0
                            <int> ll = 0
                            <int> iii = 0
                            <int> jjj = 0
                            <int> i_max = 0
                            <float64> A_max = 0.0
                            <float64> temp_f = 0.0
                            <float64> temp_A = 0.0
                            <float64> c = 0.0
                            <float64> f[{nDOFs}] = 0.0
                            <float64> a[{nDOFs}] = 0.0
                            <float64> A[{nDOFs},{nDOFs}] = 0.0
                            """

                # We are aiming to find the vector a that solves A*a = f, for matrix A and vector f.
                # This is done by performing row operations (swapping and scaling) to obtain A in upper diagonal form.
                # N.B. several for loops must be executed in numerical order (loopy does not necessarily do this).
                # For these loops we must manually iterate the index.
                """
                            if NUM_EXT[0] > 0.0
                            """

                # only do Gaussian elimination for elements with effective coordinates
                """
                                for i
                            """

                # fill f with the original field values and A with the effective coordinate values
                """
                                    f[i] = DG1_OLD[i]
                                    A[i,0] = 1.0
                                    A[i,1] = EFF_COORDS[i,0]
                                    if {nDOFs} > 3
                                        A[i,2] = EFF_COORDS[i,1]
                                        A[i,3] = EFF_COORDS[i,0]*EFF_COORDS[i,1]
                                        if {nDOFs} > 7
                                            A[i,4] = EFF_COORDS[i,{dim}-1]
                                            A[i,5] = EFF_COORDS[i,0]*EFF_COORDS[i,{dim}-1]
                                            A[i,6] = EFF_COORDS[i,1]*EFF_COORDS[i,{dim}-1]
                                            A[i,7] = EFF_COORDS[i,0]*EFF_COORDS[i,1]*EFF_COORDS[i,{dim}-1]
                                        end
                                    end
                                end
                            """

                # now loop through rows/columns of A
                """
                                for ii_loop
                                    A_max = fabs(A[ii,ii])
                                    i_max = ii
                            """

                # loop to find the largest value in the ith column
                # set i_max as the index of the row with this largest value.
                """
                                    jj = ii + 1
                                    for jj_loop
                                        if fabs(A[jj,ii]) > A_max
                                            i_max = jj
                                        end
                                        A_max = fmax(A_max, fabs(A[jj,ii]))
                                        jj = jj + 1
                                    end
                            """

                # if the max value in the ith column isn't in the ith row, we must swap the rows
                """
                                    if i_max != ii
                            """

                # swap the elements of f
                """
                                        temp_f = f[ii]  {{id=set_temp_f, dep=*}}
                                        f[ii] = f[i_max]  {{id=set_f_imax, dep=set_temp_f}}
                                        f[i_max] = temp_f  {{id=set_f_ii, dep=set_f_imax}}
                            """

                # swap the elements of A
                # N.B. kk runs from ii to (nDOFs-1) as elements below diagonal should be 0
                """
                                        for kk
                                            temp_A = A[ii,kk]  {{id=set_temp_A, dep=*}}
                                            A[ii, kk] = A[i_max, kk]  {{id=set_A_ii, dep=set_temp_A}}
                                            A[i_max, kk] = temp_A  {{id=set_A_imax, dep=set_A_ii}}
                                        end
                                    end
                            """

                # scale the rows below the ith row
                """
                                    ll = ii + 1
                                    for ll_loop
                                        if ll > ii
                            """

                # find scaling factor
                """
                                            c = - A[ll,ii] / A[ii,ii]
                            """

                # N.B. mm runs from ii to (nDOFs-1) as elements below diagonal should be 0
                """
                                            for mm
                                                A[ll, mm] = A[ll, mm] + c * A[ii,mm]
                                            end
                                            f[ll] = f[ll] + c * f[ii]
                                        end
                                        ll = ll + 1
                                    end
                                    ii = ii + 1
                                end
                            """

                # do back substitution of upper diagonal A to obtain a
                """
                                iii = 0
                                for iii_loop
                            """

                # jjj starts at the bottom row and works upwards
                """
                                    jjj = {nDOFs} - iii - 1  {{id=assign_jjj, dep=*}}
                                    a[jjj] = f[jjj]   {{id=set_a, dep=assign_jjj}}
                                    for kkk_loop
                                        a[jjj] = a[jjj] - A[jjj,kkk_loop] * a[kkk_loop]
                                    end
                                    a[jjj] = a[jjj] / A[jjj,jjj]
                                    iii = iii + 1
                                end
                            """

                # Having found a, this gives us the coefficients for the Taylor expansion with the actual coordinates.
                """
                                for iiii
                                    if {nDOFs} == 2
                                        DG1[iiii] = a[0] + a[1]*ACT_COORDS[iiii,0]
                                    elif {nDOFs} == 4
                                        DG1[iiii] = a[0] + a[1]*ACT_COORDS[iiii,0] + a[2]*ACT_COORDS[iiii,1] + a[3]*ACT_COORDS[iiii,0]*ACT_COORDS[iiii,1]
                                    elif {nDOFs} == 8
                                        DG1[iiii] = a[0] + a[1]*ACT_COORDS[iiii,0] + a[2]*ACT_COORDS[iiii,1] + a[3]*ACT_COORDS[iiii,0]*ACT_COORDS[iiii,1] + a[4]*ACT_COORDS[iiii,{dim}-1] + a[5]*ACT_COORDS[iiii,0]*ACT_COORDS[iiii,{dim}-1] + a[6]*ACT_COORDS[iiii,1]*ACT_COORDS[iiii,{dim}-1] + a[7]*ACT_COORDS[iiii,0]*ACT_COORDS[iiii,1]*ACT_COORDS[iiii,{dim}-1]
                                    end
                                end
                            """

                # if element is not external, just use old field values.
                """
                            else
                                for iiiii
                                    DG1[iiiii] = DG1_OLD[iiiii]
                                end
                            end
                            """).format(**shapes)

            _num_ext_kernel = (num_ext_domain, num_ext_instructions)
            _eff_coords_kernel = (coords_domain, coords_insts)
            self._gaussian_elimination_kernel = (elimin_domain, elimin_insts)

            # find number of external DOFs per cell
            par_loop(_num_ext_kernel,
                     dx, {
                         "NUM_EXT": (self.num_ext, WRITE),
                         "EXT_V1": (self.coords_to_adjust, READ)
                     },
                     is_loopy_kernel=True)

            # find effective coordinates
            logger.warning(
                'Finding effective coordinates for boundary recovery. This could give unexpected results for deformed meshes over very steep topography.'
            )
            par_loop(_eff_coords_kernel,
                     dx, {
                         "EFF_COORDS": (self.eff_coords, WRITE),
                         "ACT_COORDS": (self.act_coords, READ),
                         "NUM_EXT": (self.num_ext, READ),
                         "EXT_V1": (self.coords_to_adjust, READ)
                     },
                     is_loopy_kernel=True)

        elif self.method == Boundary_Method.physics:
            top_bottom_domain = ("{[i]: 0 <= i < 1}")
            bottom_instructions = ("""
                                   DG1[0] = 2 * CG1[0] - CG1[1]
                                   DG1[1] = CG1[1]
                                   """)
            top_instructions = ("""
                                DG1[0] = CG1[0]
                                DG1[1] = -CG1[0] + 2 * CG1[1]
                                """)

            self._bottom_kernel = (top_bottom_domain, bottom_instructions)
            self._top_kernel = (top_bottom_domain, top_instructions)