def build_hdiv_space(self, family, degree): if self.extruded_mesh: if not self._initialised_base_spaces: self.build_base_spaces(family, degree) Vh_elt = HDiv(TensorProductElement(self.S1, self.T1)) Vt_elt = TensorProductElement(self.S2, self.T0) Vv_elt = HDiv(Vt_elt) V_elt = Vh_elt + Vv_elt else: cell = self.mesh.ufl_cell().cellname() V_elt = FiniteElement(family, cell, degree + 1) return FunctionSpace(self.mesh, V_elt, name='HDiv')
def test_physics_recovery_kernels(boundary): m = IntervalMesh(3, 3) mesh = ExtrudedMesh(m, layers=3, layer_height=1.0) cell = m.ufl_cell().cellname() hori_elt = FiniteElement("DG", cell, 0) vert_elt = FiniteElement("CG", interval, 1) theta_elt = TensorProductElement(hori_elt, vert_elt) Vt = FunctionSpace(mesh, theta_elt) Vt_brok = FunctionSpace(mesh, BrokenElement(theta_elt)) initial_field = Function(Vt) true_field = Function(Vt_brok) new_field = Function(Vt_brok) initial_field, true_field = setup_values(boundary, initial_field, true_field) kernel = kernels.PhysicsRecoveryTop( ) if boundary == "top" else kernels.PhysicsRecoveryBottom() kernel.apply(new_field, initial_field) tolerance = 1e-12 index = 11 if boundary == "top" else 6 assert abs(true_field.dat.data[index] - new_field.dat.data[index]) < tolerance, \ "Value at %s from physics recovery is not correct" % boundary
def build_theta_space(self, degree): assert self.extruded_mesh if not self._initialised_base_spaces: cell = self.mesh._base_mesh.ufl_cell().cellname() self.S2 = FiniteElement("DG", cell, degree) self.T0 = FiniteElement("CG", interval, degree + 1) V_elt = TensorProductElement(self.S2, self.T0) return FunctionSpace(self.mesh, V_elt, name='Vtheta')
def _build_spaces(self, mesh, vertical_degree, horizontal_degree, family): """ Build: velocity space self.V2, pressure space self.V3, temperature space self.Vt, mixed function space self.W = (V2,V3,Vt) """ self.spaces = SpaceCreator() if vertical_degree is not None: # horizontal base spaces cell = mesh._base_mesh.ufl_cell().cellname() S1 = FiniteElement(family, cell, horizontal_degree+1) S2 = FiniteElement("DG", cell, horizontal_degree) # vertical base spaces T0 = FiniteElement("CG", interval, vertical_degree+1) T1 = FiniteElement("DG", interval, vertical_degree) # build spaces V2, V3, Vt V2h_elt = HDiv(TensorProductElement(S1, T1)) V2t_elt = TensorProductElement(S2, T0) V3_elt = TensorProductElement(S2, T1) V2v_elt = HDiv(V2t_elt) V2_elt = V2h_elt + V2v_elt V0 = self.spaces("HDiv", mesh, V2_elt) V1 = self.spaces("DG", mesh, V3_elt) V2 = self.spaces("HDiv_v", mesh, V2t_elt) self.Vv = self.spaces("Vv", mesh, V2v_elt) self.W = MixedFunctionSpace((V0, V1, V2)) else: cell = mesh.ufl_cell().cellname() V1_elt = FiniteElement(family, cell, horizontal_degree+1) V0 = self.spaces("HDiv", mesh, V1_elt) V1 = self.spaces("DG", mesh, "DG", horizontal_degree) self.W = MixedFunctionSpace((V0, V1))
def __init__(self, space): """ Initialise limiter :arg space: the space in which the transported variables lies. It should be a form of the DG1xCG2 space. """ if not space.extruded: raise ValueError( 'The Theta Limiter can only be used on an extruded mesh') # check that horizontal degree is 1 and vertical degree is 2 sub_elements = space.ufl_element().sub_elements() if (sub_elements[0].family() not in ['Discontinuous Lagrange', 'DQ'] or sub_elements[1].family() != 'Lagrange' or space.ufl_element().degree() != (1, 2)): raise ValueError( 'Theta Limiter should only be used with the DG1xCG2 space') # Transport will happen in broken form of Vtheta mesh = space.mesh() self.Vt_brok = FunctionSpace(mesh, BrokenElement(space.ufl_element())) # Create equispaced DG1 space needed for limiting 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") CG2_vert_elt = FiniteElement("CG", interval, 2) DG1_element = TensorProductElement(DG1_hori_elt, DG1_vert_elt) Vt_element = TensorProductElement(DG1_hori_elt, CG2_vert_elt) DG1_equispaced = FunctionSpace(mesh, DG1_element) Vt_equispaced = FunctionSpace(mesh, Vt_element) Vt_brok_equispaced = FunctionSpace( mesh, BrokenElement(Vt_equispaced.ufl_element())) self.vertex_limiter = VertexBasedLimiter(DG1_equispaced) self.field_hat = Function(Vt_brok_equispaced) self.field_old = Function(Vt_brok_equispaced) self.field_DG1 = Function(DG1_equispaced) self._limit_midpoints_kernel = LimitMidpoints(Vt_brok_equispaced)
def get_functionspace(mesh, h_family, h_degree, v_family=None, v_degree=None, vector=False, hdiv=False, variant=None, v_variant=None, **kwargs): cell_dim = mesh.cell_dimension() print(cell_dim) assert cell_dim in [2, (2, 1), (1, 1)], 'Unsupported cell dimension' hdiv_families = [ 'RT', 'RTF', 'RTCF', 'RAVIART-THOMAS', 'BDM', 'BDMF', 'BDMCF', 'BREZZI-DOUGLAS-MARINI', ] if variant is None: if h_family.upper() in hdiv_families: if h_family in ['RTCF', 'BDMCF']: variant = 'equispaced' else: variant = 'integral' else: print("var = equi") variant = 'equispaced' if v_variant is None: v_variant = 'equispaced' if cell_dim == (2, 1) or (1, 1): if v_family is None: v_family = h_family if v_degree is None: v_degree = h_degree h_cell, v_cell = mesh.ufl_cell().sub_cells() h_elt = FiniteElement(h_family, h_cell, h_degree, variant=variant) v_elt = FiniteElement(v_family, v_cell, v_degree, variant=v_variant) elt = TensorProductElement(h_elt, v_elt) if hdiv: elt = ufl.HDiv(elt) else: elt = FiniteElement(h_family, mesh.ufl_cell(), h_degree, variant=variant) constructor = VectorFunctionSpace if vector else FunctionSpace return constructor(mesh, elt, **kwargs)
def build_dg_space(self, degree, variant=None): if self.extruded_mesh: if not self._initialised_base_spaces or self.T1.degree( ) != degree or self.T1.variant() != variant: cell = self.mesh._base_mesh.ufl_cell().cellname() S2 = FiniteElement("DG", cell, degree, variant=variant) T1 = FiniteElement("DG", interval, degree, variant=variant) else: S2 = self.S2 T1 = self.T1 V_elt = TensorProductElement(S2, T1) else: cell = self.mesh.ufl_cell().cellname() V_elt = FiniteElement("DG", cell, degree, variant=variant) name = f'DG{degree}_equispaced' if variant == 'equispaced' else f'DG{degree}' return FunctionSpace(self.mesh, V_elt, name=name)
def correct_eff_coords(eff_coords): """ Correct the effective coordinates calculated by simply averaging which will not be correct at periodic boundaries. :arg eff_coords: the effective coordinates in vec_DG1 space. """ mesh = eff_coords.function_space().mesh() vec_CG1 = VectorFunctionSpace(mesh, "CG", 1) if vec_CG1.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") vec_DG1 = VectorFunctionSpace(mesh, DG1_element) x = SpatialCoordinate(mesh) if eff_coords.function_space() != vec_DG1: raise ValueError('eff_coords needs to be in the vector DG1 space') # obtain different coords in DG1 DG1_coords = Function(vec_DG1).interpolate(x) CG1_coords_from_DG1 = Function(vec_CG1) averager = Averager(DG1_coords, CG1_coords_from_DG1) averager.project() DG1_coords_from_averaged_CG1 = Function(vec_DG1).interpolate( CG1_coords_from_DG1) DG1_coords_diff = Function(vec_DG1).interpolate( DG1_coords - DG1_coords_from_averaged_CG1) # interpolate coordinates, adjusting those different coordinates adjusted_coords = Function(vec_DG1) adjusted_coords.interpolate(eff_coords + DG1_coords_diff) return adjusted_coords
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()
def __init__(self, v_CG1, v_DG1, method=Boundary_Method.physics, eff_coords=None): self.v_DG1 = v_DG1 self.v_CG1 = v_CG1 self.v_DG1_old = Function(v_DG1.function_space()) self.eff_coords = eff_coords self.method = method mesh = v_CG1.function_space().mesh() DG0 = FunctionSpace(mesh, "DG", 0) CG1 = FunctionSpace(mesh, "CG", 1) if DG0.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") DG1 = FunctionSpace(mesh, DG1_element) self.num_ext = find_domain_boundaries(mesh) # check function spaces of functions if self.method == Boundary_Method.dynamics: if v_CG1.function_space() != CG1: raise NotImplementedError( "This boundary recovery method requires v1 to be in CG1.") if v_DG1.function_space() != DG1: raise NotImplementedError( "This boundary recovery method requires v_out to be in DG1." ) if eff_coords is None: raise ValueError( 'Need eff_coords field for dynamics boundary methods') elif self.method == Boundary_Method.physics: # check that mesh is valid -- must be an extruded mesh if not DG0.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.") vec_DG1 = VectorFunctionSpace(DG0.mesh(), DG1_element) x = SpatialCoordinate(DG0.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(vec_DG1).project( x) # actual coordinates self.eff_coords = eff_coords # effective coordinates self.output = Function(DG1) self.on_exterior = find_domain_boundaries(mesh) self.gaussian_elimination_kernel = kernels.GaussianElimination(DG1) elif self.method == Boundary_Method.physics: self.bottom_kernel = kernels.PhysicsRecoveryBottom() self.top_kernel = kernels.PhysicsRecoveryTop()
def get_latlon_mesh(mesh): coords_orig = mesh.coordinates coords_fs = coords_orig.function_space() if coords_fs.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_elt = TensorProductElement(DG1_hori_elt, DG1_vert_elt) else: cell = mesh.ufl_cell().cellname() DG1_elt = FiniteElement("DG", cell, 1, variant="equispaced") vec_DG1 = VectorFunctionSpace(mesh, DG1_elt) coords_dg = Function(vec_DG1).interpolate(coords_orig) coords_latlon = Function(vec_DG1) shapes = {"nDOFs": vec_DG1.finat_element.space_dimension(), 'dim': 3} radius = np.min( np.sqrt(coords_dg.dat.data[:, 0]**2 + coords_dg.dat.data[:, 1]**2 + coords_dg.dat.data[:, 2]**2)) # lat-lon 'x' = atan2(y, x) coords_latlon.dat.data[:, 0] = np.arctan2(coords_dg.dat.data[:, 1], coords_dg.dat.data[:, 0]) # lat-lon 'y' = asin(z/sqrt(x^2 + y^2 + z^2)) coords_latlon.dat.data[:, 1] = np.arcsin( coords_dg.dat.data[:, 2] / np.sqrt(coords_dg.dat.data[:, 0]**2 + coords_dg.dat.data[:, 1]**2 + coords_dg.dat.data[:, 2]**2)) # our vertical coordinate is radius - the minimum radius coords_latlon.dat.data[:, 2] = np.sqrt(coords_dg.dat.data[:, 0]**2 + coords_dg.dat.data[:, 1]**2 + coords_dg.dat.data[:, 2]**2) - radius # We need to ensure that all points in a cell are on the same side of the branch cut in longitude coords # This kernel amends the longitude coords so that all longitudes in one cell are close together kernel = op2.Kernel( """ #define PI 3.141592653589793 #define TWO_PI 6.283185307179586 void splat_coords(double *coords) {{ double max_diff = 0.0; double diff = 0.0; for (int i=0; i<{nDOFs}; i++) {{ for (int j=0; j<{nDOFs}; j++) {{ diff = coords[i*{dim}] - coords[j*{dim}]; if (fabs(diff) > max_diff) {{ max_diff = diff; }} }} }} if (max_diff > PI) {{ for (int i=0; i<{nDOFs}; i++) {{ if (coords[i*{dim}] < 0) {{ coords[i*{dim}] += TWO_PI; }} }} }} }} """.format(**shapes), "splat_coords") op2.par_loop(kernel, coords_latlon.cell_set, coords_latlon.dat(op2.RW, coords_latlon.cell_node_map())) return Mesh(coords_latlon)
def find_eff_coords(V0): """ Takes a function in a field V0 and returns the effective coordinates, in a vector DG1 space, of a recovery into a CG1 field. This is for use with the Boundary_Recoverer, as it facilitates the Gaussian elimination used to get second-order recovery at boundaries. If V0 is a vector function space, this returns an array of coordinates for each component. :arg V0: the original function space. """ mesh = V0.mesh() if V0.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") vec_CG1 = VectorFunctionSpace(mesh, "CG", 1) vec_DG1 = VectorFunctionSpace(mesh, DG1_element) x = SpatialCoordinate(mesh) if V0.ufl_element().value_size() > 1: eff_coords_list = [] V0_coords_list = [] # treat this separately for each component for i in range(V0.ufl_element().value_size()): # fill an d-dimensional list with i-th coordinate x_list = [x[i] for j in range(V0.ufl_element().value_size())] # the i-th element in V0_coords_list is a vector with all components the i-th coord ith_V0_coords = Function(V0).project(as_vector(x_list)) V0_coords_list.append(ith_V0_coords) for i in range(V0.ufl_element().value_size()): # slice through V0_coords_list to obtain the coords of the DOFs for that component x_list = [V0_coords[i] for V0_coords in V0_coords_list] # average these to find effective coords in CG1 V0_coords_in_DG1 = Function(vec_DG1).interpolate(as_vector(x_list)) eff_coords_in_CG1 = Function(vec_CG1) eff_coords_averager = Averager(V0_coords_in_DG1, eff_coords_in_CG1) eff_coords_averager.project() # obtain these in DG1 eff_coords_in_DG1 = Function(vec_DG1).interpolate( eff_coords_in_CG1) eff_coords_list.append(correct_eff_coords(eff_coords_in_DG1)) return eff_coords_list else: # find the coordinates at DOFs in V0 vec_V0 = VectorFunctionSpace(mesh, V0.ufl_element()) V0_coords = Function(vec_V0).project(x) # average these to find effective coords in CG1 V0_coords_in_DG1 = Function(vec_DG1).interpolate(V0_coords) eff_coords_in_CG1 = Function(vec_CG1) eff_coords_averager = Averager(V0_coords_in_DG1, eff_coords_in_CG1) eff_coords_averager.project() # obtain these in DG1 eff_coords_in_DG1 = Function(vec_DG1).interpolate(eff_coords_in_CG1) return correct_eff_coords(eff_coords_in_DG1)
def test_3D_cartesian_recovery(geometry, element, mesh, expr): family = "RTCF" if element == "quadrilateral" else "BDM" # horizontal base spaces cell = mesh._base_mesh.ufl_cell().cellname() u_hori = FiniteElement(family, cell, 1) w_hori = FiniteElement("DG", cell, 0) # vertical base spaces u_vert = FiniteElement("DG", interval, 0) w_vert = FiniteElement("CG", interval, 1) # build elements u_element = HDiv(TensorProductElement(u_hori, u_vert)) w_element = HDiv(TensorProductElement(w_hori, w_vert)) theta_element = TensorProductElement(w_hori, w_vert) v_element = u_element + w_element # DG1 DG1_hori = FiniteElement("DG", cell, 1, variant="equispaced") DG1_vert = FiniteElement("DG", interval, 1, variant="equispaced") DG1_elt = TensorProductElement(DG1_hori, DG1_vert) DG1 = FunctionSpace(mesh, DG1_elt) vec_DG1 = VectorFunctionSpace(mesh, DG1_elt) # spaces DG0 = FunctionSpace(mesh, "DG", 0) CG1 = FunctionSpace(mesh, "CG", 1) Vt = FunctionSpace(mesh, theta_element) Vt_brok = FunctionSpace(mesh, BrokenElement(theta_element)) Vu = FunctionSpace(mesh, v_element) vec_CG1 = VectorFunctionSpace(mesh, "CG", 1) # our actual theta and rho and v rho_CG1_true = Function(CG1).interpolate(expr) theta_CG1_true = Function(CG1).interpolate(expr) v_CG1_true = Function(vec_CG1).interpolate(as_vector([expr, expr, expr])) rho_Vt_true = Function(Vt).interpolate(expr) # make the initial fields by projecting expressions into the lowest order spaces rho_DG0 = Function(DG0).interpolate(expr) rho_CG1 = Function(CG1) theta_Vt = Function(Vt).interpolate(expr) theta_CG1 = Function(CG1) v_Vu = Function(Vu).project(as_vector([expr, expr, expr])) v_CG1 = Function(vec_CG1) rho_Vt = Function(Vt) # make the recoverers and do the recovery rho_recoverer = Recoverer(rho_DG0, rho_CG1, VDG=DG1, boundary_method=Boundary_Method.dynamics) theta_recoverer = Recoverer(theta_Vt, theta_CG1, VDG=DG1, boundary_method=Boundary_Method.dynamics) v_recoverer = Recoverer(v_Vu, v_CG1, VDG=vec_DG1, boundary_method=Boundary_Method.dynamics) rho_Vt_recoverer = Recoverer(rho_DG0, rho_Vt, VDG=Vt_brok, boundary_method=Boundary_Method.physics) rho_recoverer.project() theta_recoverer.project() v_recoverer.project() rho_Vt_recoverer.project() rho_diff = errornorm(rho_CG1, rho_CG1_true) / norm(rho_CG1_true) theta_diff = errornorm(theta_CG1, theta_CG1_true) / norm(theta_CG1_true) v_diff = errornorm(v_CG1, v_CG1_true) / norm(v_CG1_true) rho_Vt_diff = errornorm(rho_Vt, rho_Vt_true) / norm(rho_Vt_true) tolerance = 1e-7 error_message = (""" Incorrect recovery for {variable} with {boundary} boundary method on {geometry} 3D Cartesian domain with {element} elements """) assert rho_diff < tolerance, error_message.format(variable='rho', boundary='dynamics', geometry=geometry, element=element) assert v_diff < tolerance, error_message.format(variable='v', boundary='dynamics', geometry=geometry, element=element) assert theta_diff < tolerance, error_message.format(variable='rho', boundary='dynamics', geometry=geometry, element=element) assert rho_Vt_diff < tolerance, error_message.format(variable='rho', boundary='physics', geometry=geometry, element=element)
def setup_3d_recovery(dirname): L = 100. H = 10. W = 1. deltax = L / 5. deltay = W / 5. deltaz = H / 5. nlayers = int(H / deltaz) ncolumnsx = int(L / deltax) ncolumnsy = int(W / deltay) m = RectangleMesh(ncolumnsx, ncolumnsy, L, W, quadrilateral=True) mesh = ExtrudedMesh(m, layers=nlayers, layer_height=H / nlayers) x, y, z = SpatialCoordinate(mesh) # horizontal base spaces cell = mesh._base_mesh.ufl_cell().cellname() u_hori = FiniteElement("RTCF", cell, 1) w_hori = FiniteElement("DG", cell, 0) # vertical base spaces u_vert = FiniteElement("DG", interval, 0) w_vert = FiniteElement("CG", interval, 1) # build elements u_element = HDiv(TensorProductElement(u_hori, u_vert)) w_element = HDiv(TensorProductElement(w_hori, w_vert)) theta_element = TensorProductElement(w_hori, w_vert) v_element = u_element + w_element # spaces VDG0 = FunctionSpace(mesh, "DG", 0) VCG1 = FunctionSpace(mesh, "CG", 1) VDG1 = FunctionSpace(mesh, "DG", 1) Vt = FunctionSpace(mesh, theta_element) Vt_brok = FunctionSpace(mesh, BrokenElement(theta_element)) Vu = FunctionSpace(mesh, v_element) VuCG1 = VectorFunctionSpace(mesh, "CG", 1) VuDG1 = VectorFunctionSpace(mesh, "DG", 1) # set up initial conditions np.random.seed(0) expr = np.random.randn( ) + np.random.randn() * x + np.random.randn() * y + np.random.randn( ) * z + np.random.randn() * x * y + np.random.randn( ) * x * z + np.random.randn() * y * z + np.random.randn() * x * y * z # our actual theta and rho and v rho_CG1_true = Function(VCG1).interpolate(expr) theta_CG1_true = Function(VCG1).interpolate(expr) v_CG1_true = Function(VuCG1).interpolate(as_vector([expr, expr, expr])) rho_Vt_true = Function(Vt).interpolate(expr) # make the initial fields by projecting expressions into the lowest order spaces rho_DG0 = Function(VDG0).interpolate(expr) rho_CG1 = Function(VCG1) theta_Vt = Function(Vt).interpolate(expr) theta_CG1 = Function(VCG1) v_Vu = Function(Vu).project(as_vector([expr, expr, expr])) v_CG1 = Function(VuCG1) rho_Vt = Function(Vt) # make the recoverers and do the recovery rho_recoverer = Recoverer(rho_DG0, rho_CG1, VDG=VDG1, boundary_method=Boundary_Method.dynamics) theta_recoverer = Recoverer(theta_Vt, theta_CG1, VDG=VDG1, boundary_method=Boundary_Method.dynamics) v_recoverer = Recoverer(v_Vu, v_CG1, VDG=VuDG1, boundary_method=Boundary_Method.dynamics) rho_Vt_recoverer = Recoverer(rho_DG0, rho_Vt, VDG=Vt_brok, boundary_method=Boundary_Method.physics) rho_recoverer.project() theta_recoverer.project() v_recoverer.project() rho_Vt_recoverer.project() rho_diff = errornorm(rho_CG1, rho_CG1_true) / norm(rho_CG1_true) theta_diff = errornorm(theta_CG1, theta_CG1_true) / norm(theta_CG1_true) v_diff = errornorm(v_CG1, v_CG1_true) / norm(v_CG1_true) rho_Vt_diff = errornorm(rho_Vt, rho_Vt_true) / norm(rho_Vt_true) return (rho_diff, theta_diff, v_diff, rho_Vt_diff)
def find_coords_to_adjust(V0, DG1): """ This function finds the coordinates that need to be adjusted for the recovery at the boundary. These are assigned by a 1, while all coordinates to be left unchanged are assigned a 0. This field is returned as a DG1 field. Fields can be scalar or vector. :arg V0: the space of the original field (before recovery). :arg DG1: a DG1 space, in which the boundary recovery will happen. """ # check that spaces are correct mesh = DG1.mesh() if DG1.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") scalar_DG1 = FunctionSpace(mesh, DG1_element) vector_DG1 = VectorFunctionSpace(mesh, DG1_element) # check DG1 field is correct if type(DG1.ufl_element()) == VectorElement: if DG1 != vector_DG1: raise ValueError( 'The function space entered as vector DG1 is not vector DG1.') elif DG1 != scalar_DG1: raise ValueError('The function space entered as DG1 is not DG1.') # STRATEGY # We need to pass the boundary recoverer a field denoting the location # of nodes on the boundary, which denotes the coordinates to adjust to be new effective # coords. This field will be 1 for these coords and 0 otherwise. # How do we do this? # 1. Obtain a DG1 field which is 1 at all exterior DOFs by applying Dirichlet # boundary conditions. i.e. for cells in the bottom right corner of a domain: # ------- 0 ------- 0 ------- 1 # | | || # | | || # | | || # ======= 1 ======= 1 ======= 1 # 2. Obtain a field in DG1 that is 1 at exterior DOFs adjacent to the exterior # DOFs of V0 (i.e. the original space). For V0=DG0 there will be no exterior # DOFs, but could be if velocity is in RT or if there is a temperature space. # This is done by applying topological boundary conditions to a field in V0, # before interpolating these into DG1. # For instance, marking V0 DOFs with x, for rho and theta spaces this would give # ------- 0 ------- 0 ------- 0 ---x--- 0 ---x--- 0 ---x--- 0 # | | || | | || # x | x | x || | | || # | | || | | || # ======= 0 ======= 0 ======= 0 ===x=== 1 ===x=== 1 ===x=== 1 # 3. Obtain a field that is 1 at corners in 2D or along edges in 3D. # We do this by using that corners in 2D and edges in 3D are intersections # of edges/faces respectively. In 2D, this means that if a field which is 1 on a # horizontal edge is summed with a field that is 1 on a vertical edge, the # corner value will be 2. Subtracting the exterior DG1 field from step 1 leaves # a field that is 1 in the corner. This is generalised to 3D. # ------- 0 ------- 0 ------- 0 ------- 1 ------- 0 ------- 1 ------- 0 ------- 0 # | || | || | || | || # | || + | || - | || = | || # | || | || | || | || # ======= 1 ======= 1 ======= 0 ======= 1 ======= 1 ======= 1 ======= 0 ======= 1 # 4. The field of coords to be adjusted is then found by the following formula: # f1 + f3 - f2 # where f1, f2 and f3 are the DG1 fields obtained from steps 1, 2 and 3. # make DG1 field with 1 at all exterior coords all_ext_in_DG1 = Function(DG1) bcs = [DirichletBC(DG1, Constant(1.0), "on_boundary", method="geometric")] if DG1.extruded: bcs.append(DirichletBC(DG1, Constant(1.0), "top", method="geometric")) bcs.append( DirichletBC(DG1, Constant(1.0), "bottom", method="geometric")) for bc in bcs: bc.apply(all_ext_in_DG1) # make DG1 field with 1 at coords surrounding exterior coords of V0 # first do topological BCs to get V0 function which is 1 at DOFs on edges all_ext_in_V0 = Function(V0) bcs = [DirichletBC(V0, Constant(1.0), "on_boundary", method="topological")] if V0.extruded: bcs.append(DirichletBC(V0, Constant(1.0), "top", method="topological")) bcs.append( DirichletBC(V0, Constant(1.0), "bottom", method="topological")) for bc in bcs: bc.apply(all_ext_in_V0) if DG1.value_size > 1: # for vector valued functions, DOFs aren't pointwise evaluation. We break into components and use a conditional interpolation to get values of 1 V0_ext_in_DG1_components = [] for i in range(DG1.value_size): V0_ext_in_DG1_components.append( Function(scalar_DG1).interpolate( conditional(abs(all_ext_in_V0[i]) > 0.0, 1.0, 0.0))) V0_ext_in_DG1 = Function(DG1).project( as_vector(V0_ext_in_DG1_components)) else: # for scalar functions (where DOFs are pointwise evaluation) we can simply interpolate to get these values V0_ext_in_DG1 = Function(DG1).interpolate(all_ext_in_V0) corners_in_DG1 = Function(DG1) if DG1.mesh().topological_dimension() == 2: if DG1.extruded: DG1_ext_hori = Function(DG1) DG1_ext_vert = Function(DG1) hori_bcs = [ DirichletBC(DG1, Constant(1.0), "top", method="geometric"), DirichletBC(DG1, Constant(1.0), "bottom", method="geometric") ] vert_bc = DirichletBC(DG1, Constant(1.0), "on_boundary", method="geometric") for bc in hori_bcs: bc.apply(DG1_ext_hori) vert_bc.apply(DG1_ext_vert) corners_in_DG1.assign(DG1_ext_hori + DG1_ext_vert - all_ext_in_DG1) else: # we don't know whether its periodic or in how many directions DG1_ext_x = Function(DG1) DG1_ext_y = Function(DG1) x_bcs = [ DirichletBC(DG1, Constant(1.0), 1, method="geometric"), DirichletBC(DG1, Constant(1.0), 2, method="geometric") ] y_bcs = [ DirichletBC(DG1, Constant(1.0), 3, method="geometric"), DirichletBC(DG1, Constant(1.0), 4, method="geometric") ] # there is no easy way to know if the mesh is periodic or in which # directions, so we must use a try here # LookupError is the error for asking for a boundary number that doesn't exist try: for bc in x_bcs: bc.apply(DG1_ext_x) except LookupError: pass try: for bc in y_bcs: bc.apply(DG1_ext_y) except LookupError: pass corners_in_DG1.assign(DG1_ext_x + DG1_ext_y - all_ext_in_DG1) elif DG1.mesh().topological_dimension() == 3: DG1_vert_x = Function(DG1) DG1_vert_y = Function(DG1) DG1_hori = Function(DG1) x_bcs = [ DirichletBC(DG1, Constant(1.0), 1, method="geometric"), DirichletBC(DG1, Constant(1.0), 2, method="geometric") ] y_bcs = [ DirichletBC(DG1, Constant(1.0), 3, method="geometric"), DirichletBC(DG1, Constant(1.0), 4, method="geometric") ] hori_bcs = [ DirichletBC(DG1, Constant(1.0), "top", method="geometric"), DirichletBC(DG1, Constant(1.0), "bottom", method="geometric") ] # there is no easy way to know if the mesh is periodic or in which # directions, so we must use a try here # LookupError is the error for asking for a boundary number that doesn't exist try: for bc in x_bcs: bc.apply(DG1_vert_x) except LookupError: pass try: for bc in y_bcs: bc.apply(DG1_vert_y) except LookupError: pass for bc in hori_bcs: bc.apply(DG1_hori) corners_in_DG1.assign(DG1_vert_x + DG1_vert_y + DG1_hori - all_ext_in_DG1) # we now combine the different functions. We use max_value to avoid getting 2s or 3s at corners/edges # we do this component-wise because max_value only works component-wise if DG1.value_size > 1: coords_to_correct_components = [] for i in range(DG1.value_size): coords_to_correct_components.append( Function(scalar_DG1).interpolate( max_value(corners_in_DG1[i], all_ext_in_DG1[i] - V0_ext_in_DG1[i]))) coords_to_correct = Function(DG1).project( as_vector(coords_to_correct_components)) else: coords_to_correct = Function(DG1).interpolate( max_value(corners_in_DG1, all_ext_in_DG1 - V0_ext_in_DG1)) return coords_to_correct
def setup_hori_limiters(dirname): # declare grid shape L = 400. H = L ncolumns = int(L / 10.) nlayers = ncolumns # make mesh m = PeriodicIntervalMesh(ncolumns, L) mesh = ExtrudedMesh(m, layers=nlayers, layer_height=(H / nlayers)) x, z = SpatialCoordinate(mesh) fieldlist = ['u'] timestepping = TimesteppingParameters(dt=1.0, maxk=4, maxi=1) output = OutputParameters(dirname=dirname + "/limiting_hori", dumpfreq=5, dumplist=['u'], perturbation_fields=['theta0', 'theta1']) parameters = CompressibleParameters() state = State(mesh, vertical_degree=1, horizontal_degree=1, family="CG", timestepping=timestepping, output=output, parameters=parameters, fieldlist=fieldlist) # make elements # v is continuous in vertical, h is horizontal cell = mesh._base_mesh.ufl_cell().cellname() DG0_element = FiniteElement("DG", cell, 0) CG1_element = FiniteElement("CG", cell, 1) DG1_element = FiniteElement("DG", cell, 1) CG2_element = FiniteElement("CG", cell, 2) V0_element = TensorProductElement(DG0_element, CG1_element) V1_element = TensorProductElement(DG1_element, CG2_element) # spaces Vpsi = FunctionSpace(mesh, "CG", 2) VDG1 = FunctionSpace(mesh, "DG", 1) VCG1 = FunctionSpace(mesh, "CG", 1) V0 = FunctionSpace(mesh, V0_element) V1 = FunctionSpace(mesh, V1_element) V0_brok = FunctionSpace(mesh, BrokenElement(V0.ufl_element())) V0_spaces = (VDG1, VCG1, V0_brok) # declare initial fields u0 = state.fields("u") theta0 = state.fields("theta0", V0) theta1 = state.fields("theta1", V1) # make a gradperp gradperp = lambda u: as_vector([-u.dx(1), u.dx(0)]) # Isentropic background state Tsurf = 300. thetab = Constant(Tsurf) theta_b1 = Function(V1).interpolate(thetab) theta_b0 = Function(V0).interpolate(thetab) # set up bubble xc = 200. zc = 200. rc = 100. theta_expr = conditional( sqrt((x - xc)**2.0) < rc, conditional(sqrt((z - zc)**2.0) < rc, Constant(2.0), Constant(0.0)), Constant(0.0)) theta_pert1 = Function(V1).interpolate(theta_expr) theta_pert0 = Function(V0).interpolate(theta_expr) # set up velocity field u_max = Constant(10.0) psi_expr = -u_max * z psi0 = Function(Vpsi).interpolate(psi_expr) u0.project(gradperp(psi0)) theta0.interpolate(theta_b0 + theta_pert0) theta1.interpolate(theta_b1 + theta_pert1) state.initialise([('u', u0), ('theta1', theta1), ('theta0', theta0)]) state.set_reference_profiles([('theta1', theta_b1), ('theta0', theta_b0)]) # set up advection schemes thetaeqn1 = EmbeddedDGAdvection(state, V1, equation_form="advective") thetaeqn0 = EmbeddedDGAdvection(state, V0, equation_form="advective", recovered_spaces=V0_spaces) # build advection dictionary advected_fields = [] advected_fields.append(('u', NoAdvection(state, u0, None))) advected_fields.append(('theta1', SSPRK3(state, theta1, thetaeqn1, limiter=ThetaLimiter(thetaeqn1)))) advected_fields.append(('theta0', SSPRK3(state, theta0, thetaeqn0, limiter=VertexBasedLimiter(VDG1)))) # build time stepper stepper = AdvectionDiffusion(state, advected_fields) return stepper, 40.0
def setup_limiters(dirname, direction, grid_params, ic_params): # declare grid shape L, H, ncolumns, nlayers = grid_params # make mesh m = PeriodicIntervalMesh(ncolumns, L) mesh = ExtrudedMesh(m, layers=nlayers, layer_height=(H / nlayers)) x, z = SpatialCoordinate(mesh) fieldlist = ['u'] timestepping = TimesteppingParameters(dt=1.0) output = OutputParameters(dirname=dirname, dumpfreq=5, dumplist=['u'], perturbation_fields=['theta0', 'theta1']) parameters = CompressibleParameters() state = State(mesh, vertical_degree=1, horizontal_degree=1, family="CG", timestepping=timestepping, output=output, parameters=parameters, fieldlist=fieldlist) # make elements # v is continuous in vertical, h is horizontal cell = mesh._base_mesh.ufl_cell().cellname() DG0_element = FiniteElement("DG", cell, 0) CG1_element = FiniteElement("CG", cell, 1) DG1_element = FiniteElement("DG", cell, 1) CG2_element = FiniteElement("CG", cell, 2) V0_element = TensorProductElement(DG0_element, CG1_element) V1_element = TensorProductElement(DG1_element, CG2_element) # spaces VDG1 = FunctionSpace(mesh, "DG", 1) VCG1 = FunctionSpace(mesh, "CG", 1) V0 = FunctionSpace(mesh, V0_element) V1 = FunctionSpace(mesh, V1_element) V0_brok = FunctionSpace(mesh, BrokenElement(V0.ufl_element())) # declare initial fields u0 = state.fields("u") theta0 = state.fields("theta0", V0) theta1 = state.fields("theta1", V1) Tsurf, xc, zc, rc, u_max = ic_params # Isentropic background state thetab = Constant(Tsurf) theta_b1 = Function(V1).interpolate(thetab) theta_b0 = Function(V0).interpolate(thetab) # set up bubble theta_expr = conditional( sqrt((x - xc)**2.0) < rc, conditional(sqrt((z - zc)**2.0) < rc, Constant(2.0), Constant(0.0)), Constant(0.0)) theta_pert1 = Function(V1).interpolate(theta_expr) theta_pert0 = Function(V0).interpolate(theta_expr) if direction == "horizontal": Vpsi = FunctionSpace(mesh, "CG", 2) psi_expr = -u_max * z psi0 = Function(Vpsi).interpolate(psi_expr) gradperp = lambda u: as_vector([-u.dx(1), u.dx(0)]) u0.project(gradperp(psi0)) elif direction == "vertical": u0.project(as_vector([0, -u_max])) theta0.interpolate(theta_b0 + theta_pert0) theta1.interpolate(theta_b1 + theta_pert1) state.initialise([('u', u0), ('theta1', theta1), ('theta0', theta0)]) state.set_reference_profiles([('theta1', theta_b1), ('theta0', theta_b0)]) # set up advection schemes dg_opts = EmbeddedDGOptions() recovered_opts = RecoveredOptions(embedding_space=VDG1, recovered_space=VCG1, broken_space=V0_brok) thetaeqn1 = EmbeddedDGAdvection(state, V1, equation_form="advective", options=dg_opts) thetaeqn0 = EmbeddedDGAdvection(state, V0, equation_form="advective", options=recovered_opts) # build advection dictionary advected_fields = [] advected_fields.append( ('theta1', SSPRK3(state, theta1, thetaeqn1, limiter=ThetaLimiter(V1)))) advected_fields.append(('theta0', SSPRK3(state, theta0, thetaeqn0, limiter=VertexBasedLimiter(VDG1)))) # build time stepper stepper = AdvectionDiffusion(state, advected_fields) return stepper, 40.0
def setup_limiters(dirname): dt = 0.01 Ld = 1. tmax = 0.2 rotations = 0.1 m = PeriodicIntervalMesh(20, Ld) mesh = ExtrudedMesh(m, layers=20, layer_height=(Ld / 20)) output = OutputParameters( dirname=dirname, dumpfreq=1, dumplist=['u', 'chemical', 'moisture_higher', 'moisture_lower']) parameters = CompressibleParameters() timestepping = TimesteppingParameters(dt=dt, maxk=4, maxi=1) fieldlist = [ 'u', 'rho', 'theta', 'chemical', 'moisture_higher', 'moisture_lower' ] diagnostic_fields = [] state = State(mesh, vertical_degree=1, horizontal_degree=1, family="CG", timestepping=timestepping, output=output, parameters=parameters, fieldlist=fieldlist, diagnostic_fields=diagnostic_fields) x, z = SpatialCoordinate(mesh) Vr = state.spaces("DG") Vt = state.spaces("HDiv_v") Vpsi = FunctionSpace(mesh, "CG", 2) cell = mesh._base_mesh.ufl_cell().cellname() DG0_element = FiniteElement("DG", cell, 0) CG1_element = FiniteElement("CG", interval, 1) Vt0_element = TensorProductElement(DG0_element, CG1_element) Vt0 = FunctionSpace(mesh, Vt0_element) Vt0_brok = FunctionSpace(mesh, BrokenElement(Vt0_element)) VCG1 = FunctionSpace(mesh, "CG", 1) u = state.fields("u", dump=True) chemical = state.fields("chemical", Vr, dump=True) moisture_higher = state.fields("moisture_higher", Vt, dump=True) moisture_lower = state.fields("moisture_lower", Vt0, dump=True) x_lower = 2 * Ld / 5 x_upper = 3 * Ld / 5 z_lower = 6 * Ld / 10 z_upper = 8 * Ld / 10 bubble_expr_1 = conditional( x > x_lower, conditional( x < x_upper, conditional(z > z_lower, conditional(z < z_upper, 1.0, 0.0), 0.0), 0.0), 0.0) bubble_expr_2 = conditional( x > z_lower, conditional( x < z_upper, conditional(z > x_lower, conditional(z < x_upper, 1.0, 0.0), 0.0), 0.0), 0.0) chemical.assign(1.0) moisture_higher.assign(280.) chem_pert_1 = Function(Vr).interpolate(bubble_expr_1) chem_pert_2 = Function(Vr).interpolate(bubble_expr_2) moist_h_pert_1 = Function(Vt).interpolate(bubble_expr_1) moist_h_pert_2 = Function(Vt).interpolate(bubble_expr_2) moist_l_pert_1 = Function(Vt0).interpolate(bubble_expr_1) moist_l_pert_2 = Function(Vt0).interpolate(bubble_expr_2) chemical.assign(chemical + chem_pert_1 + chem_pert_2) moisture_higher.assign(moisture_higher + moist_h_pert_1 + moist_h_pert_2) moisture_lower.assign(moisture_lower + moist_l_pert_1 + moist_l_pert_2) # set up solid body rotation for advection # we do this slightly complicated stream function to make the velocity 0 at edges # thus we avoid odd effects at boundaries xc = Ld / 2 zc = Ld / 2 r = sqrt((x - xc)**2 + (z - zc)**2) omega = rotations * 2 * pi / tmax r_out = 9 * Ld / 20 r_in = 2 * Ld / 5 A = omega * r_in / (2 * (r_in - r_out)) B = -omega * r_in * r_out / (r_in - r_out) C = omega * r_in**2 * r_out / (r_in - r_out) / 2 psi_expr = conditional( r < r_in, omega * r**2 / 2, conditional(r < r_out, A * r**2 + B * r + C, A * r_out**2 + B * r_out + C)) psi = Function(Vpsi).interpolate(psi_expr) gradperp = lambda v: as_vector([-v.dx(1), v.dx(0)]) u.project(gradperp(psi)) state.initialise([('u', u), ('chemical', chemical), ('moisture_higher', moisture_higher), ('moisture_lower', moisture_lower)]) # set up advection schemes dg_opts = EmbeddedDGOptions() recovered_opts = RecoveredOptions(embedding_space=Vr, recovered_space=VCG1, broken_space=Vt0_brok, boundary_method=Boundary_Method.dynamics) chemeqn = AdvectionEquation(state, Vr, equation_form="advective") moisteqn_higher = EmbeddedDGAdvection(state, Vt, equation_form="advective", options=dg_opts) moisteqn_lower = EmbeddedDGAdvection(state, Vt0, equation_form="advective", options=recovered_opts) # build advection dictionary advected_fields = [] advected_fields.append(('chemical', SSPRK3(state, chemical, chemeqn, limiter=VertexBasedLimiter(Vr)))) advected_fields.append(('moisture_higher', SSPRK3(state, moisture_higher, moisteqn_higher, limiter=ThetaLimiter(Vt)))) advected_fields.append(('moisture_lower', SSPRK3(state, moisture_lower, moisteqn_lower, limiter=VertexBasedLimiter(Vr)))) # build time stepper stepper = AdvectionDiffusion(state, advected_fields) return stepper, tmax
def initialize(self, pc): """ Set up the problem context. Takes the original mixed problem and transforms it into the equivalent hybrid-mixed system. A KSP object is created for the Lagrange multipliers on the top/bottom faces of the mesh cells. """ from firedrake import (FunctionSpace, Function, Constant, FiniteElement, TensorProductElement, TrialFunction, TrialFunctions, TestFunction, DirichletBC, interval, MixedElement, BrokenElement) from firedrake.assemble import (allocate_matrix, create_assembly_callable) from firedrake.formmanipulation import split_form from ufl.algorithms.replace import replace from ufl.cell import TensorProductCell # Extract PC context prefix = pc.getOptionsPrefix() + "vert_hybridization_" _, P = pc.getOperators() self.ctx = P.getPythonContext() if not isinstance(self.ctx, ImplicitMatrixContext): raise ValueError( "The python context must be an ImplicitMatrixContext") test, trial = self.ctx.a.arguments() V = test.function_space() mesh = V.mesh() # Magically determine which spaces are vector and scalar valued for i, Vi in enumerate(V): # Vector-valued spaces will have a non-empty value_shape if Vi.ufl_element().value_shape(): self.vidx = i else: self.pidx = i Vv = V[self.vidx] Vp = V[self.pidx] # Create the space of approximate traces in the vertical. # NOTE: Technically a hack since the resulting space is technically # defined in cell interiors, however the degrees of freedom will only # be geometrically defined on edges. Arguments will only be used in # surface integrals deg, _ = Vv.ufl_element().degree() # Assumes a tensor product cell (quads, triangular-prisms, cubes) if not isinstance(Vp.ufl_element().cell(), TensorProductCell): raise NotImplementedError( "Currently only implemented for tensor product discretizations" ) # Only want the horizontal cell cell, _ = Vp.ufl_element().cell()._cells DG = FiniteElement("DG", cell, deg) CG = FiniteElement("CG", interval, 1) Vv_tr_element = TensorProductElement(DG, CG) Vv_tr = FunctionSpace(mesh, Vv_tr_element) # Break the spaces broken_elements = MixedElement( [BrokenElement(Vi.ufl_element()) for Vi in V]) V_d = FunctionSpace(mesh, broken_elements) # Set up relevant functions self.broken_solution = Function(V_d) self.broken_residual = Function(V_d) self.trace_solution = Function(Vv_tr) self.unbroken_solution = Function(V) self.unbroken_residual = Function(V) # Set up transfer kernels to and from the broken velocity space # NOTE: Since this snippet of code is used in a couple places in # in Gusto, might be worth creating a utility function that is # is importable and just called where needed. shapes = { "i": Vv.finat_element.space_dimension(), "j": np.prod(Vv.shape, dtype=int) } weight_kernel = """ for (int i=0; i<{i}; ++i) for (int j=0; j<{j}; ++j) w[i*{j} + j] += 1.0; """.format(**shapes) self.weight = Function(Vv) par_loop(weight_kernel, dx, {"w": (self.weight, INC)}) # Averaging kernel self.average_kernel = """ for (int i=0; i<{i}; ++i) for (int j=0; j<{j}; ++j) vec_out[i*{j} + j] += vec_in[i*{j} + j]/w[i*{j} + j]; """.format(**shapes) # Original mixed operator replaced with "broken" arguments arg_map = {test: TestFunction(V_d), trial: TrialFunction(V_d)} Atilde = Tensor(replace(self.ctx.a, arg_map)) gammar = TestFunction(Vv_tr) n = FacetNormal(mesh) sigma = TrialFunctions(V_d)[self.vidx] # Again, assumes tensor product structure. Why use this if you # don't have some form of vertical extrusion? Kform = gammar('+') * jump(sigma, n=n) * dS_h # Here we deal with boundary conditions if self.ctx.row_bcs: # Find all the subdomains with neumann BCS # These are Dirichlet BCs on the vidx space neumann_subdomains = set() for bc in self.ctx.row_bcs: if bc.function_space().index == self.pidx: raise NotImplementedError( "Dirichlet conditions for scalar variable not supported. Use a weak bc." ) if bc.function_space().index != self.vidx: raise NotImplementedError( "Dirichlet bc set on unsupported space.") # append the set of sub domains subdom = bc.sub_domain if isinstance(subdom, str): neumann_subdomains |= set([subdom]) else: neumann_subdomains |= set(as_tuple(subdom, int)) # separate out the top and bottom bcs extruded_neumann_subdomains = neumann_subdomains & { "top", "bottom" } neumann_subdomains = neumann_subdomains - extruded_neumann_subdomains integrand = gammar * dot(sigma, n) measures = [] trace_subdomains = [] for subdomain in sorted(extruded_neumann_subdomains): measures.append({"top": ds_t, "bottom": ds_b}[subdomain]) trace_subdomains.extend( sorted({"top", "bottom"} - extruded_neumann_subdomains)) measures.extend((ds(sd) for sd in sorted(neumann_subdomains))) markers = [int(x) for x in mesh.exterior_facets.unique_markers] dirichlet_subdomains = set(markers) - neumann_subdomains trace_subdomains.extend(sorted(dirichlet_subdomains)) for measure in measures: Kform += integrand * measure else: trace_subdomains = ["top", "bottom"] trace_bcs = [ DirichletBC(Vv_tr, Constant(0.0), subdomain) for subdomain in trace_subdomains ] # Make a SLATE tensor from Kform K = Tensor(Kform) # Assemble the Schur complement operator and right-hand side self.schur_rhs = Function(Vv_tr) self._assemble_Srhs = create_assembly_callable( K * Atilde.inv * AssembledVector(self.broken_residual), tensor=self.schur_rhs, form_compiler_parameters=self.ctx.fc_params) mat_type = PETSc.Options().getString(prefix + "mat_type", "aij") schur_comp = K * Atilde.inv * K.T self.S = allocate_matrix(schur_comp, bcs=trace_bcs, form_compiler_parameters=self.ctx.fc_params, mat_type=mat_type, options_prefix=prefix) self._assemble_S = create_assembly_callable( schur_comp, tensor=self.S, bcs=trace_bcs, form_compiler_parameters=self.ctx.fc_params, mat_type=mat_type) self._assemble_S() self.S.force_evaluation() Smat = self.S.petscmat nullspace = self.ctx.appctx.get("vert_trace_nullspace", None) if nullspace is not None: nsp = nullspace(Vv_tr) Smat.setNullSpace(nsp.nullspace(comm=pc.comm)) # Set up the KSP for the system of Lagrange multipliers trace_ksp = PETSc.KSP().create(comm=pc.comm) trace_ksp.setOptionsPrefix(prefix) trace_ksp.setOperators(Smat) trace_ksp.setUp() trace_ksp.setFromOptions() self.trace_ksp = trace_ksp split_mixed_op = dict(split_form(Atilde.form)) split_trace_op = dict(split_form(K.form)) # Generate reconstruction calls self._reconstruction_calls(split_mixed_op, split_trace_op)
def test_limit_midpoints(profile): # ------------------------------------------------------------------------ # # Set up meshes and spaces # ------------------------------------------------------------------------ # m = IntervalMesh(3, 3) mesh = ExtrudedMesh(m, layers=1, layer_height=3.0) cell = m.ufl_cell().cellname() DG_hori_elt = FiniteElement("DG", cell, 1, variant='equispaced') DG_vert_elt = FiniteElement("DG", interval, 1, variant='equispaced') Vt_vert_elt = FiniteElement("CG", interval, 2) DG_elt = TensorProductElement(DG_hori_elt, DG_vert_elt) theta_elt = TensorProductElement(DG_hori_elt, Vt_vert_elt) Vt_brok = FunctionSpace(mesh, BrokenElement(theta_elt)) DG1 = FunctionSpace(mesh, DG_elt) new_field = Function(Vt_brok) init_field = Function(Vt_brok) DG1_field = Function(DG1) # ------------------------------------------------------------------------ # # Initial conditions # ------------------------------------------------------------------------ # _, z = SpatialCoordinate(mesh) if profile == 'undershoot': # A quadratic whose midpoint is lower than the top and bottom values init_expr = (80. / 9.) * z**2 - 20. * z + 300. elif profile == 'overshoot': # A quadratic whose midpoint is higher than the top and bottom values init_expr = (-80. / 9.) * z**2 + (100. / 3) * z + 300. elif profile == 'linear': # Linear profile which must be unchanged init_expr = (20. / 3.) * z + 300. else: raise NotImplementedError # Linear DG field has the same values at top and bottom as quadratic DG_expr = (20. / 3.) * z + 300. init_field.interpolate(init_expr) DG1_field.interpolate(DG_expr) # ------------------------------------------------------------------------ # # Apply kernel # ------------------------------------------------------------------------ # kernel = kernels.LimitMidpoints(Vt_brok) kernel.apply(new_field, DG1_field, init_field) # ------------------------------------------------------------------------ # # Check values # ------------------------------------------------------------------------ # tol = 1e-12 assert np.max(new_field.dat.data) <= np.max(init_field.dat.data) + tol, \ 'LimitMidpoints kernel is giving an overshoot' assert np.min(new_field.dat.data) >= np.min(init_field.dat.data) - tol, \ 'LimitMidpoints kernel is giving an undershoot' if profile == 'linear': assert np.allclose(init_field.dat.data, new_field.dat.data), \ 'For a profile with no maxima or minima, the LimitMidpoints ' + \ 'kernel should leave the field unchanged'
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)
def __init__(self, v_in, v_out, VDG=None, boundary_method=None): # check if v_in is valid if isinstance(v_in, expression.Expression) or not isinstance( v_in, (ufl.core.expr.Expr, function.Function)): raise ValueError( "Can only recover UFL expression or Functions not '%s'" % type(v_in)) self.v_in = v_in self.v_out = v_out self.V = v_out.function_space() if VDG is not None: self.v = Function(VDG) self.interpolator = Interpolator(v_in, self.v) else: self.v = v_in self.interpolator = None self.VDG = VDG self.boundary_method = boundary_method self.averager = Averager(self.v, self.v_out) # check boundary method options are valid if boundary_method is not None: if boundary_method != Boundary_Method.dynamics and boundary_method != Boundary_Method.physics: raise ValueError( "Boundary method must be a Boundary_Method Enum object.") if VDG is None: raise ValueError( "If boundary_method is specified, VDG also needs specifying." ) # now specify things that we'll need if we are doing boundary recovery if boundary_method == Boundary_Method.physics: # check dimensions if self.V.value_size != 1: raise ValueError( 'This method only works for scalar functions.') self.boundary_recoverer = Boundary_Recoverer( self.v_out, self.v, method=Boundary_Method.physics) else: mesh = self.V.mesh() # this ensures we get the pure function space, not an indexed function space V0 = FunctionSpace(mesh, self.v_in.function_space().ufl_element()) VCG1 = FunctionSpace(mesh, "CG", 1) if V0.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) if self.V.value_size == 1: coords_to_adjust = find_coords_to_adjust(V0, VDG1) self.boundary_recoverer = Boundary_Recoverer( self.v_out, self.v, coords_to_adjust=coords_to_adjust, method=Boundary_Method.dynamics) else: VuDG1 = VectorFunctionSpace(mesh, DG1_element) coords_to_adjust = find_coords_to_adjust(V0, VuDG1) # now, break the problem down into components v_scalars = [] v_out_scalars = [] self.boundary_recoverers = [] self.project_to_scalars_CG = [] self.extra_averagers = [] coords_to_adjust_list = [] for i in range(self.V.value_size): v_scalars.append(Function(VDG1)) v_out_scalars.append(Function(VCG1)) coords_to_adjust_list.append( Function(VDG1).project(coords_to_adjust[i])) self.project_to_scalars_CG.append( Projector(self.v_out[i], v_out_scalars[i])) self.boundary_recoverers.append( Boundary_Recoverer( v_out_scalars[i], v_scalars[i], method=Boundary_Method.dynamics, coords_to_adjust=coords_to_adjust_list[i])) # need an extra averager that works on the scalar fields rather than the vector one self.extra_averagers.append( Averager(v_scalars[i], v_out_scalars[i])) # the boundary recoverer needs to be done on a scalar fields # so need to extract component and restore it after the boundary recovery is done self.interpolate_to_vector = Interpolator( as_vector(v_out_scalars), self.v_out)
def __init__(self, v_CG1, v_DG1, method=Boundary_Method.physics, eff_coords=None): self.v_DG1 = v_DG1 self.v_CG1 = v_CG1 self.v_DG1_old = Function(v_DG1.function_space()) self.eff_coords = eff_coords self.method = method mesh = v_CG1.function_space().mesh() DG0 = FunctionSpace(mesh, "DG", 0) CG1 = FunctionSpace(mesh, "CG", 1) if DG0.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") DG1 = FunctionSpace(mesh, DG1_element) self.num_ext = find_domain_boundaries(mesh) # check function spaces of functions if self.method == Boundary_Method.dynamics: if v_CG1.function_space() != CG1: raise NotImplementedError("This boundary recovery method requires v1 to be in CG1.") if v_DG1.function_space() != DG1: raise NotImplementedError("This boundary recovery method requires v_out to be in DG1.") if eff_coords is None: raise ValueError('Need eff_coords field for dynamics boundary methods') elif self.method == Boundary_Method.physics: # check that mesh is valid -- must be an extruded mesh if not DG0.extruded: raise NotImplementedError('The physics boundary method only works on extruded meshes') # check that function spaces are valid sub_elements = v_CG1.function_space().ufl_element().sub_elements() if (sub_elements[0].family() not in ['Discontinuous Lagrange', 'DQ'] or sub_elements[1].family() != 'Lagrange' or v_CG1.function_space().ufl_element().degree() != (0, 1)): raise ValueError("This boundary recovery method requires v_CG1 to be in DG0xCG1 TensorProductSpace.") brok_elt = v_DG1.function_space().ufl_element() if (brok_elt.degree() != (0, 1) or (type(brok_elt) is not BrokenElement and (brok_elt.sub_elements[0].family() not in ['Discontinuous Lagrange', 'DQ'] or brok_elt.sub_elements[1].family() != 'Discontinuous Lagrange'))): 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.") vec_DG1 = VectorFunctionSpace(DG0.mesh(), DG1_element) x = SpatialCoordinate(DG0.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(vec_DG1).project(x) # actual coordinates self.eff_coords = eff_coords # effective coordinates self.output = Function(DG1) self.on_exterior = find_domain_boundaries(mesh) self.gaussian_elimination_kernel = kernels.GaussianElimination(DG1) elif self.method == Boundary_Method.physics: self.bottom_kernel = kernels.PhysicsRecoveryBottom() self.top_kernel = kernels.PhysicsRecoveryTop()