class VertexBasedLimiter(Limiter):
    """
    A vertex based limiter for P1DG fields.

    This limiter implements the vertex-based limiting scheme described in
    Dmitri Kuzmin, "A vertex-based hierarchical slope limiter for p-adaptive
    discontinuous Galerkin methods". J. Comp. Appl. Maths (2010)
    http://dx.doi.org/10.1016/j.cam.2009.05.028
    """

    def __init__(self, space):
        """
        Initialise limiter

        :param space : FunctionSpace instance
        """

        self.P1DG = space
        self.P1CG = FunctionSpace(self.P1DG.mesh(), 'CG', 1)  # for min/max limits
        self.P0 = FunctionSpace(self.P1DG.mesh(), 'DG', 0)  # for centroids

        # Storage containers for cell means, max and mins
        self.centroids = Function(self.P0)
        self.centroids_rhs = Function(self.P0)
        self.max_field = Function(self.P1CG)
        self.min_field = Function(self.P1CG)

        self.centroid_solver = self._construct_centroid_solver()

        # Update min and max loop
        domain = "{[i]: 0 <= i < maxq.dofs}"
        instructions = """
        for i
            maxq[i] = fmax(maxq[i], q[0])
            minq[i] = fmin(minq[i], q[0])
        end
        """
        self._min_max_loop = (domain, instructions)

        # Perform limiting loop
        domain = "{[i, ii]: 0 <= i < q.dofs and 0 <= ii < q.dofs}"
        instructions = """
        <float64> alpha = 1
        <float64> qavg = qbar[0, 0]
        for i
            <float64> _alpha1 = fmin(alpha, fmin(1, (qmax[i] - qavg)/(q[i] - qavg)))
            <float64> _alpha2 = fmin(alpha, fmin(1, (qavg - qmin[i])/(qavg - q[i])))
            alpha = if(q[i] > qavg, _alpha1, if(q[i] < qavg, _alpha2, alpha))
        end
        for ii
            q[ii] = qavg + alpha * (q[ii] - qavg)
        end
        """
        self._limit_kernel = (domain, instructions)

    def _construct_centroid_solver(self):
        """
        Constructs a linear problem for computing the centroids

        :return: LinearSolver instance
        """
        u = TrialFunction(self.P0)
        v = TestFunction(self.P0)
        a = assemble(u * v * dx)
        return LinearSolver(a, solver_parameters={'ksp_type': 'preonly',
                                                  'pc_type': 'bjacobi',
                                                  'sub_pc_type': 'ilu'})

    def _update_centroids(self, field):
        """
        Update centroid values
        """
        assemble(TestFunction(self.P0) * field * dx, tensor=self.centroids_rhs)
        self.centroid_solver.solve(self.centroids, self.centroids_rhs)

    def compute_bounds(self, field):
        """
        Only computes min and max bounds of neighbouring cells
        """
        self._update_centroids(field)
        self.max_field.assign(-1.0e10)  # small number
        self.min_field.assign(1.0e10)  # big number

        par_loop(self._min_max_loop,
                 dx,
                 {"maxq": (self.max_field, MAX),
                  "minq": (self.min_field, MIN),
                  "q": (self.centroids, READ)},
                 is_loopy_kernel=True)

    def apply_limiter(self, field):
        """
        Only applies limiting loop on the given field
        """
        par_loop(self._limit_kernel, dx,
                 {"qbar": (self.centroids, READ),
                  "q": (field, RW),
                  "qmax": (self.max_field, READ),
                  "qmin": (self.min_field, READ)},
                 is_loopy_kernel=True)

    def apply(self, field):
        """
        Re-computes centroids and applies limiter to given field
        """
        assert field.function_space() == self.P1DG, \
            'Given field does not belong to this objects function space'

        self.compute_bounds(field)
        self.apply_limiter(field)
class VertexBasedLimiter(Limiter):
    """
    A vertex based limiter for P1DG fields.

    This limiter implements the vertex-based limiting scheme described in
    Dmitri Kuzmin, "A vertex-based hierarchical slope limiter for p-adaptive
    discontinuous Galerkin methods". J. Comp. Appl. Maths (2010)
    http://dx.doi.org/10.1016/j.cam.2009.05.028
    """
    def __init__(self, space):
        """
        Initialise limiter

        :param space : FunctionSpace instance
        """

        self.P1DG = space
        self.P1CG = FunctionSpace(self.P1DG.mesh(), 'CG',
                                  1)  # for min/max limits
        self.P0 = FunctionSpace(self.P1DG.mesh(), 'DG', 0)  # for centroids

        # Storage containers for cell means, max and mins
        self.centroids = Function(self.P0)
        self.centroids_rhs = Function(self.P0)
        self.max_field = Function(self.P1CG)
        self.min_field = Function(self.P1CG)

        self.centroid_solver = self._construct_centroid_solver()

        # Update min and max loop
        self._min_max_loop = """
for(int i = 0; i < maxq.dofs; i++) {
    maxq[i][0] = fmax(maxq[i][0],q[0][0]);
    minq[i][0] = fmin(minq[i][0],q[0][0]);
}
                             """
        # Perform limiting loop
        self._limit_kernel = """
double alpha = 1.0;
double qavg = qbar[0][0];
for (int i=0; i < q.dofs; i++) {
    if (q[i][0] > qavg)
        alpha = fmin(alpha, fmin(1, (qmax[i][0] - qavg)/(q[i][0] - qavg)));
    else if (q[i][0] < qavg)
        alpha = fmin(alpha, fmin(1, (qavg - qmin[i][0])/(qavg - q[i][0])));
}
for (int i=0; i<q.dofs; i++) {
    q[i][0] = qavg + alpha*(q[i][0] - qavg);
}
                             """

    def _construct_centroid_solver(self):
        """
        Constructs a linear problem for computing the centroids

        :return: LinearSolver instance
        """
        u = TrialFunction(self.P0)
        v = TestFunction(self.P0)
        a = assemble(u * v * dx)
        return LinearSolver(a,
                            solver_parameters={
                                'ksp_type': 'preonly',
                                'pc_type': 'bjacobi',
                                'sub_pc_type': 'ilu'
                            })

    def _update_centroids(self, field):
        """
        Update centroid values
        """
        assemble(TestFunction(self.P0) * field * dx, tensor=self.centroids_rhs)
        self.centroid_solver.solve(self.centroids, self.centroids_rhs)

    def compute_bounds(self, field):
        """
        Only computes min and max bounds of neighbouring cells
        """
        self._update_centroids(field)
        self.max_field.assign(-1.0e10)  # small number
        self.min_field.assign(1.0e10)  # big number

        par_loop(
            self._min_max_loop, dx, {
                "maxq": (self.max_field, MAX),
                "minq": (self.min_field, MIN),
                "q": (self.centroids, READ)
            })

    def apply_limiter(self, field):
        """
        Only applies limiting loop on the given field
        """
        par_loop(
            self._limit_kernel, dx, {
                "qbar": (self.centroids, READ),
                "q": (field, RW),
                "qmax": (self.max_field, READ),
                "qmin": (self.min_field, READ)
            })

    def apply(self, field):
        """
        Re-computes centroids and applies limiter to given field
        """
        assert field.function_space() == self.P1DG, \
            'Given field does not belong to this objects function space'

        self.compute_bounds(field)
        self.apply_limiter(field)
Esempio n. 3
0
class ThetaLimiter(object):
    """
    A vertex based limiter for fields in the DG1xCG2 space,
    i.e. temperature variables. This acts like the vertex-based
    limiter implemented in Firedrake, but in addition corrects
    the central nodes to prevent new maxima or minima forming.
    """

    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)})

    def copy_vertex_values(self, field):
        """
        Copies the vertex values from temperature space to
        Q1DG space which only has vertices.
        """
        par_loop(_copy_into_Q1DG_loop, dx,
                 {"theta": (field, READ),
                  "theta_hat": (self.theta_hat, RW)})

    def copy_vertex_values_back(self, field):
        """
        Copies the vertex values back from the Q1DG space to
        the original temperature space.
        """
        par_loop(_copy_from_Q1DG_loop, dx,
                 {"theta": (field, RW),
                  "theta_hat": (self.theta_hat, READ)})

    def check_midpoint_values(self, field):
        """
        Checks the midpoint field values are less than the maximum
        and more than the minimum values. Amends them to the average
        if they are not.
        """
        par_loop(_check_midpoint_values_loop, dx,
                 {"theta": (field, RW)})

    def remap_to_embedded_space(self, field):
        """
        Remap from DG space to embedded DG space.
        """

        self.result.assign(0.)
        par_loop(_average_kernel, dx, {"vrec": (self.result, INC),
                                       "v_b": (field, READ),
                                       "weight": (self.w, READ)})
        field.assign(self.result)

    def apply(self, field):
        """
        The application of the limiter to the theta-space field.
        """
        assert field.function_space() == self.Vt, \
            'Given field does not belong to this objects function space'

        self.copy_vertex_values(field)
        self.vertex_limiter.apply(self.theta_hat)
        self.copy_vertex_values_back(field)
        self.check_midpoint_values(field)
        self.remap_to_embedded_space(field)
Esempio n. 4
0
class VertexBasedLimiter(Limiter):
    """
    A vertex based limiter for P1DG fields.

    This limiter implements the vertex-based limiting scheme described in
    Dmitri Kuzmin, "A vertex-based hierarchical slope limiter for p-adaptive
    discontinuous Galerkin methods". J. Comp. Appl. Maths (2010)
    http://dx.doi.org/10.1016/j.cam.2009.05.028
    """
    def __init__(self, space):
        """
        Initialise limiter

        :param space : FunctionSpace instance
        """

        if utils.complex_mode:
            raise ValueError(
                "We haven't decided what limiting complex valued fields means. Please get in touch if you have need."
            )

        self.P1DG = space
        self.P1CG = FunctionSpace(self.P1DG.mesh(), 'CG',
                                  1)  # for min/max limits
        self.P0 = FunctionSpace(self.P1DG.mesh(), 'DG', 0)  # for centroids

        # Storage containers for cell means, max and mins
        self.centroids = Function(self.P0)
        self.centroids_rhs = Function(self.P0)
        self.max_field = Function(self.P1CG)
        self.min_field = Function(self.P1CG)

        self.centroid_solver = self._construct_centroid_solver()

        # Update min and max loop
        domain = "{[i]: 0 <= i < maxq.dofs}"
        instructions = """
        for i
            maxq[i] = fmax(maxq[i], q[0])
            minq[i] = fmin(minq[i], q[0])
        end
        """
        self._min_max_loop = (domain, instructions)

        # Perform limiting loop
        domain = "{[i, ii]: 0 <= i < q.dofs and 0 <= ii < q.dofs}"
        instructions = """
        <float64> alpha = 1
        <float64> qavg = qbar[0, 0]
        for i
            <float64> _alpha1 = fmin(alpha, fmin(1, (qmax[i] - qavg)/(q[i] - qavg)))
            <float64> _alpha2 = fmin(alpha, fmin(1, (qavg - qmin[i])/(qavg - q[i])))
            alpha = _alpha1 if q[i] > qavg else (_alpha2 if q[i] < qavg else  alpha)
        end
        for ii
            q[ii] = qavg + alpha * (q[ii] - qavg)
        end
        """
        self._limit_kernel = (domain, instructions)

    def _construct_centroid_solver(self):
        """
        Constructs a linear problem for computing the centroids

        :return: LinearSolver instance
        """
        u = TrialFunction(self.P0)
        v = TestFunction(self.P0)
        a = assemble(inner(u, v) * dx)
        return LinearSolver(a,
                            solver_parameters={
                                'ksp_type': 'preonly',
                                'pc_type': 'bjacobi',
                                'sub_pc_type': 'ilu'
                            })

    def _update_centroids(self, field):
        """
        Update centroid values
        """
        assemble(inner(field, TestFunction(self.P0)) * dx,
                 tensor=self.centroids_rhs)
        self.centroid_solver.solve(self.centroids, self.centroids_rhs)

    def compute_bounds(self, field):
        """
        Only computes min and max bounds of neighbouring cells
        """
        self._update_centroids(field)
        self.max_field.assign(-1.0e10)  # small number
        self.min_field.assign(1.0e10)  # big number

        par_loop(self._min_max_loop,
                 dx, {
                     "maxq": (self.max_field, MAX),
                     "minq": (self.min_field, MIN),
                     "q": (self.centroids, READ)
                 },
                 is_loopy_kernel=True)

    def apply_limiter(self, field):
        """
        Only applies limiting loop on the given field
        """
        par_loop(self._limit_kernel,
                 dx, {
                     "qbar": (self.centroids, READ),
                     "q": (field, RW),
                     "qmax": (self.max_field, READ),
                     "qmin": (self.min_field, READ)
                 },
                 is_loopy_kernel=True)

    def apply(self, field):
        """
        Re-computes centroids and applies limiter to given field
        """
        assert field.function_space() == self.P1DG, \
            'Given field does not belong to this objects function space'

        self.compute_bounds(field)
        self.apply_limiter(field)