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