import warp as wp import numpy as np from warp.fem.types import ElementIndex, Coords from warp.fem.polynomial import Polynomial, is_closed from warp.fem.geometry import Grid2D from warp.fem import cache from .topology import SpaceTopology, DiscontinuousSpaceTopologyMixin, forward_base_topology from .basis_space import ShapeBasisSpace, TraceBasisSpace from .shape import ShapeFunction, ConstantShapeFunction from .shape import ( SquareBipolynomialShapeFunctions, SquareSerendipityShapeFunctions, SquareNonConformingPolynomialShapeFunctions, ) class Grid2DSpaceTopology(SpaceTopology): def __init__(self, grid: Grid2D, shape: ShapeFunction): super().__init__(grid, shape.NODES_PER_ELEMENT) self._shape = shape @wp.func def _vertex_coords(vidx_in_cell: int): x = vidx_in_cell // 2 y = vidx_in_cell - 2 * x return wp.vec2i(x, y) @wp.func def _vertex_index(cell_arg: Grid2D.CellArg, cell_index: ElementIndex, vidx_in_cell: int): res = cell_arg.res x_stride = res[1] + 1 corner = Grid2D.get_cell(res, cell_index) + Grid2DSpaceTopology._vertex_coords(vidx_in_cell) return Grid2D._from_2d_index(x_stride, corner) class Grid2DDiscontinuousSpaceTopology( DiscontinuousSpaceTopologyMixin, Grid2DSpaceTopology, ): pass class Grid2DBasisSpace(ShapeBasisSpace): def __init__(self, topology: Grid2DSpaceTopology, shape: ShapeFunction): super().__init__(topology, shape) self._grid: Grid2D = topology.geometry class GridPiecewiseConstantBasis(Grid2DBasisSpace): def __init__(self, grid: Grid2D): shape = ConstantShapeFunction(grid.reference_cell(), space_dimension=2) topology = Grid2DDiscontinuousSpaceTopology(grid, shape) super().__init__(shape=shape, topology=topology) if isinstance(grid, Grid2D): self.node_grid = self._node_grid def _node_grid(self): res = self._grid.res X = (np.arange(0, res[0], dtype=float) + 0.5) * self._grid.cell_size[0] + self._grid.origin[0] Y = (np.arange(0, res[1], dtype=float) + 0.5) * self._grid.cell_size[1] + self._grid.origin[1] return np.meshgrid(X, Y, indexing="ij") class Trace(TraceBasisSpace): @wp.func def _node_coords_in_element( side_arg: Grid2D.SideArg, basis_arg: Grid2DBasisSpace.BasisArg, element_index: ElementIndex, node_index_in_element: int, ): return Coords(0.5, 0.0, 0.0) def make_node_coords_in_element(self): return self._node_coords_in_element def trace(self): return GridPiecewiseConstantBasis.Trace(self) class GridBipolynomialSpaceTopology(Grid2DSpaceTopology): def __init__(self, grid: Grid2D, shape: SquareBipolynomialShapeFunctions): super().__init__(grid, shape) self.element_node_index = self._make_element_node_index() def node_count(self) -> int: return (self.geometry.res[0] * self._shape.ORDER + 1) * (self.geometry.res[1] * self._shape.ORDER + 1) def _make_element_node_index(self): ORDER = self._shape.ORDER @cache.dynamic_func(suffix=self.name) def element_node_index( cell_arg: Grid2D.CellArg, topo_arg: Grid2DSpaceTopology.TopologyArg, element_index: ElementIndex, node_index_in_elt: int, ): res = cell_arg.res cell = Grid2D.get_cell(res, element_index) node_i = node_index_in_elt // (ORDER + 1) node_j = node_index_in_elt - (ORDER + 1) * node_i node_x = ORDER * cell[0] + node_i node_y = ORDER * cell[1] + node_j node_pitch = (res[1] * ORDER) + 1 node_index = node_pitch * node_x + node_y return node_index return element_node_index class GridBipolynomialBasisSpace(Grid2DBasisSpace): def __init__( self, grid: Grid2D, degree: int, family: Polynomial, ): if family is None: family = Polynomial.LOBATTO_GAUSS_LEGENDRE if not is_closed(family): raise ValueError("A closed polynomial family is required to define a continuous function space") shape = SquareBipolynomialShapeFunctions(degree, family=family) topology = forward_base_topology(GridBipolynomialSpaceTopology, grid, shape) super().__init__(topology, shape) if isinstance(grid, Grid2D): self.node_grid = self._node_grid def _node_grid(self): res = self._grid.res cell_coords = np.array(self._shape.LOBATTO_COORDS)[:-1] grid_coords_x = np.repeat(np.arange(0, res[0], dtype=float), len(cell_coords)) + np.tile( cell_coords, reps=res[0] ) grid_coords_x = np.append(grid_coords_x, res[0]) X = grid_coords_x * self._grid.cell_size[0] + self._grid.origin[0] grid_coords_y = np.repeat(np.arange(0, res[1], dtype=float), len(cell_coords)) + np.tile( cell_coords, reps=res[1] ) grid_coords_y = np.append(grid_coords_y, res[1]) Y = grid_coords_y * self._grid.cell_size[1] + self._grid.origin[1] return np.meshgrid(X, Y, indexing="ij") class GridDGBipolynomialBasisSpace(Grid2DBasisSpace): def __init__( self, grid: Grid2D, degree: int, family: Polynomial, ): if family is None: family = Polynomial.LOBATTO_GAUSS_LEGENDRE shape = SquareBipolynomialShapeFunctions(degree, family=family) topology = Grid2DDiscontinuousSpaceTopology(grid, shape) super().__init__(shape=shape, topology=topology) class GridSerendipitySpaceTopology(Grid2DSpaceTopology): def __init__(self, grid: Grid2D, shape: SquareSerendipityShapeFunctions): super().__init__(grid, shape) self.element_node_index = self._make_element_node_index() TopologyArg = Grid2D.SideArg def topo_arg_value(self, device): return self.geometry.side_arg_value(device) def node_count(self) -> int: return self.geometry.vertex_count() + (self._shape.ORDER - 1) * self.geometry.side_count() def _make_element_node_index(self): ORDER = self._shape.ORDER @cache.dynamic_func(suffix=self.name) def element_node_index( cell_arg: Grid2D.CellArg, topo_arg: Grid2D.SideArg, element_index: ElementIndex, node_index_in_elt: int, ): node_type, type_index = self._shape.node_type_and_type_index(node_index_in_elt) if node_type == SquareSerendipityShapeFunctions.VERTEX: return Grid2DSpaceTopology._vertex_index(cell_arg, element_index, type_index) side_offset, index_in_side = SquareSerendipityShapeFunctions.side_offset_and_index(type_index) axis = 1 - (node_type - SquareSerendipityShapeFunctions.EDGE_X) cell = Grid2D.get_cell(cell_arg.res, element_index) origin = wp.vec2i(cell[Grid2D.ROTATION[axis, 0]] + side_offset, cell[Grid2D.ROTATION[axis, 1]]) side = Grid2D.Side(axis, origin) side_index = Grid2D.side_index(topo_arg, side) res = cell_arg.res vertex_count = (res[0] + 1) * (res[1] + 1) return vertex_count + (ORDER - 1) * side_index + index_in_side return element_node_index class GridSerendipityBasisSpace(Grid2DBasisSpace): def __init__( self, grid: Grid2D, degree: int, family: Polynomial, ): if family is None: family = Polynomial.LOBATTO_GAUSS_LEGENDRE shape = SquareSerendipityShapeFunctions(degree, family=family) topology = forward_base_topology(GridSerendipitySpaceTopology, grid, shape=shape) super().__init__(topology=topology, shape=shape) class GridDGSerendipityBasisSpace(Grid2DBasisSpace): def __init__( self, grid: Grid2D, degree: int, family: Polynomial, ): if family is None: family = Polynomial.LOBATTO_GAUSS_LEGENDRE shape = SquareSerendipityShapeFunctions(degree, family=family) topology = Grid2DDiscontinuousSpaceTopology(grid, shape=shape) super().__init__(topology=topology, shape=shape) class GridDGPolynomialBasisSpace(Grid2DBasisSpace): def __init__( self, grid: Grid2D, degree: int, ): shape = SquareNonConformingPolynomialShapeFunctions(degree) topology = Grid2DDiscontinuousSpaceTopology(grid, shape=shape) super().__init__(topology=topology, shape=shape)