def __init__(self, mesh_hierarchy, family, degree, dim=None, name=None, vfamily=None, vdegree=None): """ :arg mesh_hierarchy: a :class:`~.MeshHierarchy` to build the function spaces on. :arg family: the function space family :arg degree: the degree of the function space See :class:`~.VectorFunctionSpace` for more details on the form of the remaining parameters. """ fses = [ functionspace.VectorFunctionSpace(m, family, degree, dim=dim, name=name, vfamily=vfamily, vdegree=vdegree) for m in mesh_hierarchy ] self.dim = fses[0].dim super(VectorFunctionSpaceHierarchy, self).__init__(mesh_hierarchy, fses)
def sanitise_input(v, V): if isinstance(v, expression.Expression): shape = v.value_shape() # Build a function space that supports PointEvaluation so that # we can interpolate into it. deg = max(as_tuple(V.ufl_element().degree())) if v.rank() == 0: fs = functionspace.FunctionSpace(V.mesh(), 'DG', deg+1) elif v.rank() == 1: fs = functionspace.VectorFunctionSpace(V.mesh(), 'DG', deg+1, dim=shape[0]) else: fs = functionspace.TensorFunctionSpace(V.mesh(), 'DG', deg+1, shape=shape) f = function.Function(fs) f.interpolate(v) return f elif isinstance(v, function.Function): return v elif isinstance(v, ufl.classes.Expr): return v else: raise ValueError("Can't project from source object %r" % v)
def CubedSphereMesh(radius, refinement_level=0, degree=1, reorder=None, comm=COMM_WORLD): """Generate an cubed approximation to the surface of the sphere. :arg radius: The radius of the sphere to approximate. :kwarg refinement_level: optional number of refinements (0 is a cube). :kwarg degree: polynomial degree of coordinate space (defaults to 1: bilinear quads) :kwarg reorder: (optional), should the mesh be reordered? """ if refinement_level < 0 or refinement_level % 1: raise RuntimeError("Number of refinements must be a non-negative integer") if degree < 1: raise ValueError("Mesh coordinate degree must be at least 1") cells, coords = _cubedsphere_cells_and_coords(radius, refinement_level) plex = mesh._from_cell_list(2, cells, coords, comm) m = mesh.Mesh(plex, dim=3, reorder=reorder) if degree > 1: new_coords = function.Function(functionspace.VectorFunctionSpace(m, "Q", degree)) new_coords.interpolate(ufl.SpatialCoordinate(m)) # "push out" to sphere new_coords.dat.data[:] *= (radius / np.linalg.norm(new_coords.dat.data, axis=1)).reshape(-1, 1) m = mesh.Mesh(new_coords) m._radius = radius return m
def IcosahedralSphereMesh(radius, refinement_level=0, degree=1, reorder=None): """Generate an icosahedral approximation to the surface of the sphere. :arg radius: The radius of the sphere to approximate. For a radius R the edge length of the underlying icosahedron will be. .. math:: a = \\frac{R}{\\sin(2 \\pi / 5)} :kwarg refinement_level: optional number of refinements (0 is an icosahedron). :kwarg degree: polynomial degree of coordinate space (defaults to 1: flat triangles) :kwarg reorder: (optional), should the mesh be reordered? """ if degree < 1: raise ValueError("Mesh coordinate degree must be at least 1") from math import sqrt phi = (1 + sqrt(5)) / 2 # vertices of an icosahedron with an edge length of 2 vertices = np.array([[-1, phi, 0], [1, phi, 0], [-1, -phi, 0], [1, -phi, 0], [0, -1, phi], [0, 1, phi], [0, -1, -phi], [0, 1, -phi], [phi, 0, -1], [phi, 0, 1], [-phi, 0, -1], [-phi, 0, 1]]) # faces of the base icosahedron faces = np.array( [[0, 11, 5], [0, 5, 1], [0, 1, 7], [0, 7, 10], [0, 10, 11], [1, 5, 9], [5, 11, 4], [11, 10, 2], [10, 7, 6], [7, 1, 8], [3, 9, 4], [3, 4, 2], [3, 2, 6], [3, 6, 8], [3, 8, 9], [4, 9, 5], [2, 4, 11], [6, 2, 10], [8, 6, 7], [9, 8, 1]], dtype=np.int32) plex = mesh._from_cell_list(2, faces, vertices) plex.setRefinementUniform(True) for i in range(refinement_level): plex = plex.refine() coords = plex.getCoordinatesLocal().array.reshape(-1, 3) scale = (radius / np.linalg.norm(coords, axis=1)).reshape(-1, 1) coords *= scale m = mesh.Mesh(plex, dim=3, reorder=reorder) if degree > 1: new_coords = function.Function( functionspace.VectorFunctionSpace(m, "CG", degree)) new_coords.interpolate(expression.Expression(("x[0]", "x[1]", "x[2]"))) # "push out" to sphere new_coords.dat.data[:] *= ( radius / np.linalg.norm(new_coords.dat.data, axis=1)).reshape( -1, 1) m = mesh.Mesh(new_coords) m._icosahedral_sphere = radius return m
def errornorm(u, uh, norm_type="L2", degree_rise=3, mesh=None): """Compute the error :math:`e = u - u_h` in the specified norm. :arg u: a :class:`.Function` containing an "exact" solution :arg uh: a :class:`.Function` containing the approximate solution :arg norm_type: the type of norm to compute, see :func:`.norm` for details of supported norm types. :arg degree_rise: increase in polynomial degree to use as the approximation space for computing the error. :arg mesh: an optional mesh on which to compute the error norm (currently ignored). This function works by :func:`.project`\ing ``u`` and ``uh`` into a space of degree ``degree_rise`` higher than the degree of ``uh`` and computing the error there. """ urank = len(u.ufl_shape) uhrank = len(uh.ufl_shape) rank = urank if urank != uhrank: raise RuntimeError("Mismatching rank between u and uh") degree = uh.function_space().ufl_element().degree() if isinstance(degree, tuple): degree = max(degree) + degree_rise else: degree += degree_rise # The exact solution might be an expression, in which case this test is irrelevant. if isinstance(u, function.Function): degree_u = u.function_space().ufl_element().degree() if degree > degree_u: warning("Degree of exact solution less than approximation degree") mesh = uh.function_space().mesh() if rank == 0: V = functionspace.FunctionSpace(mesh, 'DG', degree) elif rank == 1: V = functionspace.VectorFunctionSpace(mesh, 'DG', degree, dim=u.ufl_shape[0]) else: raise RuntimeError( "Don't know how to compute error norm for tensor valued functions") u_ = projection.project(u, V) uh_ = projection.project(uh, V) uh_ -= u_ return norm(uh_, norm_type=norm_type, mesh=mesh)
def spatial_index(self): """Spatial index to quickly find which cell contains a given point.""" from firedrake import function, functionspace from firedrake.parloops import par_loop, READ, RW gdim = self.ufl_cell().geometric_dimension() if gdim <= 1: info_red("libspatialindex does not support 1-dimension, falling back on brute force.") return None # Calculate the bounding boxes for all cells by running a kernel V = functionspace.VectorFunctionSpace(self, "DG", 0, dim=gdim) coords_min = function.Function(V) coords_max = function.Function(V) coords_min.dat.data.fill(np.inf) coords_max.dat.data.fill(-np.inf) kernel = """ for (int d = 0; d < gdim; d++) { for (int i = 0; i < nodes_per_cell; i++) { f_min[0][d] = fmin(f_min[0][d], f[i][d]); f_max[0][d] = fmax(f_max[0][d], f[i][d]); } } """ cell_node_list = self.coordinates.function_space().cell_node_list nodes_per_cell = len(cell_node_list[0]) kernel = kernel.replace("gdim", str(gdim)) kernel = kernel.replace("nodes_per_cell", str(nodes_per_cell)) par_loop(kernel, ufl.dx, {'f': (self.coordinates, READ), 'f_min': (coords_min, RW), 'f_max': (coords_max, RW)}) # Reorder bounding boxes according to the cell indices we use column_list = V.cell_node_list.reshape(-1) coords_min = self._order_data_by_cell_index(column_list, coords_min.dat.data_ro_with_halos) coords_max = self._order_data_by_cell_index(column_list, coords_max.dat.data_ro_with_halos) # Build spatial index return spatialindex.from_regions(coords_min, coords_max)
def callback(self): """Finish initialisation.""" del self._callback # Finish the initialisation of mesh topology self.topology.init() with timed_region("Mesh: coordinate field"): coordinates_fs = functionspace.VectorFunctionSpace(self.topology, "Lagrange", 1, dim=geometric_dim) coordinates_data = dmplex.reordered_coords(plex, coordinates_fs._global_numbering, (self.num_vertices(), geometric_dim)) coordinates = function.CoordinatelessFunction(coordinates_fs, val=coordinates_data, name="Coordinates") self.__init__(coordinates)
def ExtrudedMesh(mesh, layers, layer_height=None, extrusion_type='uniform', kernel=None, gdim=None): """Build an extruded mesh from an input mesh :arg mesh: the unstructured base mesh :arg layers: number of extruded cell layers in the "vertical" direction. :arg layer_height: the layer height, assuming all layers are evenly spaced. If this is omitted, the value defaults to 1/layers (i.e. the extruded mesh has total height 1.0) unless a custom kernel is used. :arg extrusion_type: the algorithm to employ to calculate the extruded coordinates. One of "uniform", "radial", "radial_hedgehog" or "custom". See below. :arg kernel: a :class:`pyop2.Kernel` to produce coordinates for the extruded mesh. See :func:`~.make_extruded_coords` for more details. :arg gdim: number of spatial dimensions of the resulting mesh (this is only used if a custom kernel is provided) The various values of ``extrusion_type`` have the following meanings: ``"uniform"`` the extruded mesh has an extra spatial dimension compared to the base mesh. The layers exist in this dimension only. ``"radial"`` the extruded mesh has the same number of spatial dimensions as the base mesh; the cells are radially extruded outwards from the origin. This requires the base mesh to have topological dimension strictly smaller than geometric dimension. ``"radial_hedgehog"`` similar to `radial`, but the cells are extruded in the direction of the outward-pointing cell normal (this produces a P1dgxP1 coordinate field). In this case, a radially extruded coordinate field (generated with ``extrusion_type="radial"``) is available in the :attr:`radial_coordinates` attribute. ``"custom"`` use a custom kernel to generate the extruded coordinates For more details see the :doc:`manual section on extruded meshes <extruded-meshes>`. """ import firedrake.functionspace as functionspace import firedrake.function as function mesh.init() topology = ExtrudedMeshTopology(mesh.topology, layers) if extrusion_type == "uniform": pass elif extrusion_type in ("radial", "radial_hedgehog"): # do not allow radial extrusion if tdim = gdim if mesh.ufl_cell().geometric_dimension() == mesh.ufl_cell().topological_dimension(): raise RuntimeError("Cannot radially-extrude a mesh with equal geometric and topological dimension") else: # check for kernel if kernel is None: raise RuntimeError("If the custom extrusion_type is used, a kernel must be provided") # otherwise, use the gdim that was passed in if gdim is None: raise RuntimeError("The geometric dimension of the mesh must be specified if a custom extrusion kernel is used") # Compute Coordinates of the extruded mesh if layer_height is None: # Default to unit layer_height = 1.0 / layers if extrusion_type == 'radial_hedgehog': hfamily = "DG" else: hfamily = mesh._coordinates.ufl_element().family() hdegree = mesh._coordinates.ufl_element().degree() if gdim is None: gdim = mesh.ufl_cell().geometric_dimension() + (extrusion_type == "uniform") coordinates_fs = functionspace.VectorFunctionSpace(topology, hfamily, hdegree, dim=gdim, vfamily="Lagrange", vdegree=1) coordinates = function.CoordinatelessFunction(coordinates_fs, name="Coordinates") eutils.make_extruded_coords(topology, mesh._coordinates, coordinates, layer_height, extrusion_type=extrusion_type, kernel=kernel) self = make_mesh_from_coordinates(coordinates) self._base_mesh = mesh if extrusion_type == "radial_hedgehog": fs = functionspace.VectorFunctionSpace(self, "CG", hdegree, dim=gdim, vfamily="CG", vdegree=1) self.radial_coordinates = function.Function(fs) eutils.make_extruded_coords(topology, mesh._coordinates, self.radial_coordinates, layer_height, extrusion_type="radial", kernel=kernel) return self
def __lshift__(self, data): """It allows file << function syntax for writing data out to disk. In the case of parallel, it would also accept (function, timestep) tuple as an argument. If only function is given, then the timestep will be automatically generated.""" # If parallel, it needs to keep track of its timestep. if MPI.parallel: # if statements to keep the consistency of how to update the # timestep. if isinstance(data, tuple): if self._time_step == -1 or not self._generate_time: function = data[0] self._time_step = data[1] else: raise TypeError("Expected function, got tuple.") else: if self._time_step != -1 and not self._generate_time: raise TypeError("Expected tuple, got function.") function = data self._time_step += 1 self._generate_time = True else: function = data def is_family1(e, family): import ufl.finiteelement.hdivcurl as hc if isinstance(e, (hc.HDivElement, hc.HCurlElement)): return False if e.family() == 'OuterProductElement': if e.degree() == (1, 1): if e._A.family() == family \ and e._B.family() == family: return True elif e.family() == family and e.degree() == 1: return True return False def is_cgN(e): import ufl.finiteelement.hdivcurl as hc if isinstance(e, (hc.HDivElement, hc.HCurlElement)): return False if e.family() == 'OuterProductElement': if e._A.family() in ('Lagrange', 'Q') \ and e._B.family() == 'Lagrange': return True elif e.family() in ('Lagrange', 'Q'): return True return False mesh = function.function_space().mesh() e = function.function_space().ufl_element() if len(e.value_shape()) > 1: raise RuntimeError("Can't output tensor valued functions") ce = mesh.coordinates.function_space().ufl_element() coords_p1 = is_family1(ce, 'Lagrange') or is_family1(ce, 'Q') coords_p1dg = is_family1(ce, 'Discontinuous Lagrange') or is_family1(ce, 'DQ') coords_cgN = is_cgN(ce) function_p1 = is_family1(e, 'Lagrange') or is_family1(e, 'Q') function_p1dg = is_family1(e, 'Discontinuous Lagrange') or is_family1(e, 'DQ') function_cgN = is_cgN(e) project_coords = False project_function = False discontinuous = False # We either output in P1 or P1dg. if coords_cgN and function_cgN: family = 'CG' project_coords = not coords_p1 project_function = not function_p1 else: family = 'DG' project_coords = not coords_p1dg project_function = not function_p1dg discontinuous = True if project_function: if len(e.value_shape()) == 0: Vo = fs.FunctionSpace(mesh, family, 1) elif len(e.value_shape()) == 1: Vo = fs.VectorFunctionSpace(mesh, family, 1, dim=e.value_shape()[0]) else: # Never reached Vo = None if not self._warnings[0]: warning(RED % "*** Projecting output function to %s1", family) self._warnings[0] = True output = projection.project(function, Vo, name=function.name()) else: output = function Vo = output.function_space() if project_coords: Vc = fs.VectorFunctionSpace(mesh, family, 1, dim=mesh._coordinate_fs.dim) if not self._warnings[1]: warning(RED % "*** Projecting coordinates to %s1", family) self._warnings[1] = True coordinates = projection.project(mesh.coordinates, Vc, name=mesh.coordinates.name()) else: coordinates = mesh.coordinates Vc = coordinates.function_space() num_points = Vo.node_count layers = mesh.layers - 1 if isinstance(e.cell(), OuterProductCell) else 1 num_cells = mesh.num_cells() * layers if not isinstance(e.cell(), OuterProductCell) and e.cell().cellname() != "quadrilateral": connectivity = Vc.cell_node_map().values_with_halo.flatten() else: # Connectivity of bottom cell in extruded mesh base = Vc.cell_node_map().values_with_halo if _cells[mesh.ufl_cell()] == hl.VtkQuad: # Quad is # # 1--3 # | | # 0--2 # # needs to be # # 3--2 # | | # 0--1 base = base[:, [0, 2, 3, 1]] points_per_cell = 4 elif _cells[mesh.ufl_cell()] == hl.VtkWedge: # Wedge is # # 5 # /|\ # / | \ # 1----3 # | 4 | # | /\ | # |/ \| # 0----2 # # needs to be # # 5 # /|\ # / | \ # 3----4 # | 2 | # | /\ | # |/ \| # 0----1 # base = base[:, [0, 2, 4, 1, 3, 5]] points_per_cell = 6 elif _cells[mesh.ufl_cell()] == hl.VtkHexahedron: # Hexahedron is # # 5----7 # /| /| # 4----6 | # | 1--|-3 # |/ |/ # 0----2 # # needs to be # # 7----6 # /| /| # 4----5 | # | 3--|-2 # |/ |/ # 0----1 # base = base[:, [0, 2, 3, 1, 4, 6, 7, 5]] points_per_cell = 8 # Repeat up the column connectivity_temp = np.repeat(base, layers, axis=0) if discontinuous: scale = points_per_cell else: scale = 1 offsets = np.arange(layers) * scale # Add offsets going up the column connectivity_temp += np.tile(offsets.reshape(-1, 1), (mesh.num_cells(), 1)) connectivity = connectivity_temp.flatten() if isinstance(output.function_space(), fs.VectorFunctionSpace): tmp = output.dat.data_ro_with_halos vdata = [None]*3 if output.dat.dim[0] == 1: vdata[0] = tmp.flatten() else: for i in range(output.dat.dim[0]): vdata[i] = tmp[:, i].flatten() for i in range(output.dat.dim[0], 3): vdata[i] = np.zeros_like(vdata[0]) data = tuple(vdata) # only for checking large file size flat_data = {function.name(): tmp.flatten()} else: data = output.dat.data_ro_with_halos.flatten() flat_data = {function.name(): data} coordinates = self._fd_to_evtk_coord(coordinates.dat.data_ro_with_halos) cell_types = np.empty(num_cells, dtype="uint8") # Assume that all cells are of same shape. cell_types[:] = _cells[mesh.ufl_cell()].tid p_c = _points_per_cell[mesh.ufl_cell()] # This tells which are the last nodes of each cell. offsets = np.arange(start=p_c, stop=p_c * (num_cells + 1), step=p_c, dtype='int32') large_file_flag = _requiresLargeVTKFileSize("VtkUnstructuredGrid", numPoints=num_points, numCells=num_cells, pointData=flat_data, cellData=None) new_name = self._filename # When vtu file makes part of a parallel process, aggregated by a # pvtu file, the output is : filename_timestep_rank.vtu if MPI.parallel: new_name += "_" + str(self._time_step) + "_" + str(MPI.comm.rank) self._writer = hl.VtkFile( new_name, hl.VtkUnstructuredGrid, large_file_flag) self._writer.openGrid() self._writer.openPiece(ncells=num_cells, npoints=num_points) # openElement allows the stuff in side of the tag <arg></arg> # to be editted. self._writer.openElement("Points") # addData adds the DataArray in the tag <arg1> self._writer.addData("Points", coordinates) self._writer.closeElement("Points") self._writer.openElement("Cells") self._writer.addData("connectivity", connectivity) self._writer.addData("offsets", offsets) self._writer.addData("types", cell_types) self._writer.closeElement("Cells") self._writer.openData("Point", scalars=function.name()) self._writer.addData(function.name(), data) self._writer.closeData("Point") self._writer.closePiece() self._writer.closeGrid() # Create the AppendedData self._writer.appendData(coordinates) self._writer.appendData(connectivity) self._writer.appendData(offsets) self._writer.appendData(cell_types) self._writer.appendData(data) self._writer.save()
def project(v, V, bcs=None, mesh=None, solver_parameters=None, form_compiler_parameters=None, name=None): """Project an :class:`.Expression` or :class:`.Function` into a :class:`.FunctionSpace` :arg v: the :class:`.Expression`, :class:`ufl.Expr` or :class:`.Function` to project :arg V: the :class:`.FunctionSpace` or :class:`.Function` to project into :arg bcs: boundary conditions to apply in the projection :arg mesh: the mesh to project into :arg solver_parameters: parameters to pass to the solver used when projecting. :arg form_compiler_parameters: parameters to the form compiler :arg name: name of the resulting :class:`.Function` If ``V`` is a :class:`.Function` then ``v`` is projected into ``V`` and ``V`` is returned. If `V` is a :class:`.FunctionSpace` then ``v`` is projected into a new :class:`.Function` and that :class:`.Function` is returned. The ``bcs``, ``mesh`` and ``form_compiler_parameters`` are currently ignored.""" from firedrake import function if isinstance(V, functionspace.FunctionSpaceBase): ret = function.Function(V, name=name) elif isinstance(V, function.Function): ret = V V = V.function_space() else: raise RuntimeError( 'Can only project into functions and function spaces, not %r' % type(V)) if isinstance(v, expression.Expression): shape = v.value_shape() # Build a function space that supports PointEvaluation so that # we can interpolate into it. if isinstance(V.ufl_element().degree(), tuple): deg = max(V.ufl_element().degree()) else: deg = V.ufl_element().degree() if v.rank() == 0: fs = functionspace.FunctionSpace(V.mesh(), 'DG', deg + 1) elif v.rank() == 1: fs = functionspace.VectorFunctionSpace(V.mesh(), 'DG', deg + 1, dim=shape[0]) else: fs = functionspace.TensorFunctionSpace(V.mesh(), 'DG', deg + 1, shape=shape) f = function.Function(fs) f.interpolate(v) v = f elif isinstance(v, function.Function): if v.function_space().mesh() != ret.function_space().mesh(): raise RuntimeError("Can't project between mismatching meshes") elif not isinstance(v, ufl.core.expr.Expr): raise RuntimeError( "Can't only project from expressions and functions, not %r" % type(v)) if v.ufl_shape != ret.ufl_shape: raise RuntimeError( 'Shape mismatch between source %s and target function spaces %s in project' % (v.ufl_shape, ret.ufl_shape)) p = ufl_expr.TestFunction(V) q = ufl_expr.TrialFunction(V) a = ufl.inner(p, q) * ufl.dx(domain=V.mesh()) L = ufl.inner(p, v) * ufl.dx(domain=V.mesh()) # Default to 1e-8 relative tolerance if solver_parameters is None: solver_parameters = {'ksp_type': 'cg', 'ksp_rtol': 1e-8} else: solver_parameters.setdefault('ksp_type', 'cg') solver_parameters.setdefault('ksp_rtol', 1e-8) _solve(a == L, ret, bcs=bcs, solver_parameters=solver_parameters, form_compiler_parameters=form_compiler_parameters) return ret
def __init__(self, mesh, layers, layer_height=None, extrusion_type='uniform', kernel=None, gdim=None): # A cache of function spaces that have been built on this mesh import firedrake.function as function import firedrake.functionspace as functionspace self._cache = {} mesh.init() self._old_mesh = mesh if layers < 1: raise RuntimeError( "Must have at least one layer of extruded cells (not %d)" % layers) # All internal logic works with layers of base mesh (not layers of cells) self._layers = layers + 1 self.parent = mesh.parent self.uid = mesh.uid self.name = mesh.name self._plex = mesh._plex self._plex_renumbering = mesh._plex_renumbering self._cell_numbering = mesh._cell_numbering self._entity_classes = mesh._entity_classes interior_f = self._old_mesh.interior_facets self._interior_facets = _Facets(self, interior_f.classes, "interior", interior_f.facet_cell, interior_f.local_facet_number) exterior_f = self._old_mesh.exterior_facets self._exterior_facets = _Facets( self, exterior_f.classes, "exterior", exterior_f.facet_cell, exterior_f.local_facet_number, exterior_f.markers, unique_markers=exterior_f.unique_markers) self.ufl_cell_element = ufl.FiniteElement("Lagrange", domain=mesh.ufl_cell(), degree=1) self.ufl_interval_element = ufl.FiniteElement("Lagrange", domain=ufl.Cell( "interval", 1), degree=1) self.fiat_base_element = fiat_utils.fiat_from_ufl_element( self.ufl_cell_element) self.fiat_vert_element = fiat_utils.fiat_from_ufl_element( self.ufl_interval_element) fiat_element = FIAT.tensor_finite_element.TensorFiniteElement( self.fiat_base_element, self.fiat_vert_element) if extrusion_type == "uniform": # *must* add a new dimension self._ufl_cell = ufl.OuterProductCell( mesh.ufl_cell(), ufl.Cell("interval", 1), gdim=mesh.ufl_cell().geometric_dimension() + 1) elif extrusion_type in ("radial", "radial_hedgehog"): # do not allow radial extrusion if tdim = gdim if mesh.ufl_cell().geometric_dimension() == mesh.ufl_cell( ).topological_dimension(): raise RuntimeError( "Cannot radially-extrude a mesh with equal geometric and topological dimension" ) # otherwise, all is fine, so make cell self._ufl_cell = ufl.OuterProductCell(mesh.ufl_cell(), ufl.Cell("interval", 1)) else: # check for kernel if kernel is None: raise RuntimeError( "If the custom extrusion_type is used, a kernel must be provided" ) # otherwise, use the gdim that was passed in if gdim is None: raise RuntimeError( "The geometric dimension of the mesh must be specified if a custom extrusion kernel is used" ) self._ufl_cell = ufl.OuterProductCell(mesh.ufl_cell(), ufl.Cell("interval", 1), gdim=gdim) self._ufl_domain = ufl.Domain(self.ufl_cell(), data=self) flat_temp = fiat_utils.FlattenedElement(fiat_element) # Calculated dofs_per_column from flattened_element and layers. # The mirrored elements have to be counted only once. # Then multiply by layers and layers - 1 accordingly. self.dofs_per_column = eutils.compute_extruded_dofs( fiat_element, flat_temp.entity_dofs(), layers) # Compute Coordinates of the extruded mesh if layer_height is None: # Default to unit layer_height = 1.0 / layers if extrusion_type == 'radial_hedgehog': hfamily = "DG" else: hfamily = mesh.coordinates.element().family() hdegree = mesh.coordinates.element().degree() self._coordinate_fs = functionspace.VectorFunctionSpace(self, hfamily, hdegree, vfamily="CG", vdegree=1) self.coordinates = function.Function(self._coordinate_fs) self._ufl_domain = ufl.Domain(self.coordinates) eutils.make_extruded_coords(self, layer_height, extrusion_type=extrusion_type, kernel=kernel) if extrusion_type == "radial_hedgehog": fs = functionspace.VectorFunctionSpace(self, "CG", hdegree, vfamily="CG", vdegree=1) self.radial_coordinates = function.Function(fs) eutils.make_extruded_coords(self, layer_height, extrusion_type="radial", output_coords=self.radial_coordinates) # Build a new ufl element for this function space with the # correct domain. This is necessary since this function space # is in the cache and will be picked up by later # VectorFunctionSpace construction. self._coordinate_fs._ufl_element = self._coordinate_fs.ufl_element( ).reconstruct(domain=self.ufl_domain()) # HACK alert! # Replace coordinate Function by one that has a real domain on it (but don't copy values) self.coordinates = function.Function(self._coordinate_fs, val=self.coordinates.dat) # Add subdomain_data to the measure objects we store with # the mesh. These are weakrefs for consistency with the # "global" measure objects self._dx = ufl.Measure('cell', subdomain_data=weakref.ref(self.coordinates)) self._ds = ufl.Measure('exterior_facet', subdomain_data=weakref.ref(self.coordinates)) self._dS = ufl.Measure('interior_facet', subdomain_data=weakref.ref(self.coordinates)) self._ds_t = ufl.Measure('exterior_facet_top', subdomain_data=weakref.ref(self.coordinates)) self._ds_b = ufl.Measure('exterior_facet_bottom', subdomain_data=weakref.ref(self.coordinates)) self._ds_v = ufl.Measure('exterior_facet_vert', subdomain_data=weakref.ref(self.coordinates)) self._dS_h = ufl.Measure('interior_facet_horiz', subdomain_data=weakref.ref(self.coordinates)) self._dS_v = ufl.Measure('interior_facet_vert', subdomain_data=weakref.ref(self.coordinates)) # Set the subdomain_data on all the default measures to this # coordinate field. We don't set the domain on the measure # since this causes an uncollectable reference in the global # space (dx is global). Furthermore, it's never used anyway. for measure in [ ufl.ds, ufl.dS, ufl.dx, ufl.ds_t, ufl.ds_b, ufl.ds_v, ufl.dS_h, ufl.dS_v ]: measure._subdomain_data = weakref.ref(self.coordinates)
def callback(self): import firedrake.function as function import firedrake.functionspace as functionspace del self._callback if op2.MPI.comm.size > 1: self._plex.distributeOverlap(1) self._grown_halos = True if reorder: with timed_region("Mesh: reorder"): old_to_new = self._plex.getOrdering( PETSc.Mat.OrderingType.RCM).indices reordering = np.empty_like(old_to_new) reordering[old_to_new] = np.arange(old_to_new.size, dtype=old_to_new.dtype) else: # No reordering reordering = None # Mark OP2 entities and derive the resulting Plex renumbering with timed_region("Mesh: renumbering"): dmplex.mark_entity_classes(self._plex) self._entity_classes = dmplex.get_entity_classes(self._plex) self._plex_renumbering = dmplex.plex_renumbering( self._plex, self._entity_classes, reordering) with timed_region("Mesh: cell numbering"): # Derive a cell numbering from the Plex renumbering entity_dofs = np.zeros(topological_dim + 1, dtype=np.int32) entity_dofs[-1] = 1 self._cell_numbering = self._plex.createSection( [1], entity_dofs, perm=self._plex_renumbering) entity_dofs[:] = 0 entity_dofs[0] = 1 self._vertex_numbering = self._plex.createSection( [1], entity_dofs, perm=self._plex_renumbering) # Note that for bendy elements, this needs to change. with timed_region("Mesh: coordinate field"): if periodic_coords is not None: if self.ufl_cell().geometric_dimension() != 1: raise NotImplementedError( "Periodic coordinates in more than 1D are unsupported" ) # We've been passed a periodic coordinate field, so use that. self._coordinate_fs = functionspace.VectorFunctionSpace( self, "DG", 1) self.coordinates = function.Function(self._coordinate_fs, val=periodic_coords, name="Coordinates") else: self._coordinate_fs = functionspace.VectorFunctionSpace( self, "Lagrange", 1) coordinates = dmplex.reordered_coords( self._plex, self._coordinate_fs._global_numbering, (self.num_vertices(), geometric_dim)) self.coordinates = function.Function(self._coordinate_fs, val=coordinates, name="Coordinates") self._ufl_domain = ufl.Domain(self.coordinates) # Build a new ufl element for this function space with the # correct domain. This is necessary since this function space # is in the cache and will be picked up by later # VectorFunctionSpace construction. self._coordinate_fs._ufl_element = self._coordinate_fs.ufl_element( ).reconstruct(domain=self.ufl_domain()) # HACK alert! # Replace coordinate Function by one that has a real domain on it (but don't copy values) self.coordinates = function.Function(self._coordinate_fs, val=self.coordinates.dat) # Add subdomain_data to the measure objects we store with # the mesh. These are weakrefs for consistency with the # "global" measure objects self._dx = ufl.Measure('cell', subdomain_data=weakref.ref( self.coordinates)) self._ds = ufl.Measure('exterior_facet', subdomain_data=weakref.ref( self.coordinates)) self._dS = ufl.Measure('interior_facet', subdomain_data=weakref.ref( self.coordinates)) # Set the subdomain_data on all the default measures to this # coordinate field. # We don't set the domain on the measure since this causes # an uncollectable reference in the global space (dx is # global). Furthermore, it's never used anyway. for measure in [ufl.dx, ufl.ds, ufl.dS]: measure._subdomain_data = weakref.ref(self.coordinates)
def CubedSphereMesh(radius, refinement_level=0, degree=1, reorder=None, use_dmplex_refinement=False): """Generate an cubed approximation to the surface of the sphere. :arg radius: The radius of the sphere to approximate. :kwarg refinement_level: optional number of refinements (0 is a cube). :kwarg degree: polynomial degree of coordinate space (defaults to 1: bilinear quads) :kwarg reorder: (optional), should the mesh be reordered? :kwarg use_dmplex_refinement: (optional), use dmplex to apply the refinement. """ if degree < 1: raise ValueError("Mesh coordinate degree must be at least 1") if use_dmplex_refinement: # vertices of a cube with an edge length of 2 vertices = np.array([[-1., -1., -1.], [1., -1., -1.], [-1., 1., -1.], [1., 1., -1.], [-1., -1., 1.], [1., -1., 1.], [-1., 1., 1.], [1., 1., 1.]]) # faces of the base cube # bottom face viewed from above # 2 3 # 0 1 # top face viewed from above # 6 7 # 4 5 faces = np.array([[0, 1, 3, 2], # bottom [4, 5, 7, 6], # top [0, 1, 5, 4], [2, 3, 7, 6], [0, 2, 6, 4], [1, 3, 7, 5]], dtype=np.int32) plex = mesh._from_cell_list(2, faces, vertices) plex.setRefinementUniform(True) for i in range(refinement_level): plex = plex.refine() # rescale points to the sphere # this is not the same as the gnonomic transformation coords = plex.getCoordinatesLocal().array.reshape(-1, 3) scale = (radius / np.linalg.norm(coords, axis=1)).reshape(-1, 1) coords *= scale else: cells, coords = _cubedsphere_cells_and_coords(radius, refinement_level) plex = mesh._from_cell_list(2, cells, coords) m = mesh.Mesh(plex, dim=3, reorder=reorder) if degree > 1: new_coords = function.Function(functionspace.VectorFunctionSpace(m, "Q", degree)) new_coords.interpolate(expression.Expression(("x[0]", "x[1]", "x[2]"))) # "push out" to sphere new_coords.dat.data[:] *= (radius / np.linalg.norm(new_coords.dat.data, axis=1)).reshape(-1, 1) m = mesh.Mesh(new_coords) return m