def test_bc_elements(elements_to_test, cells_to_test, n_tri, element_type): if elements_to_test != "ALL" and element_type not in elements_to_test: pytest.skip() if cells_to_test != "ALL" and "dual polygon" not in cells_to_test: pytest.skip() create_element(f"dual polygon({n_tri})", element_type, 1)
def plot_single_element(matches): if "variant=" in matches[1]: a, b = matches[1].split(" variant=") e = symfem.create_element(a, matches[2], int(matches[3]), b) else: e = symfem.create_element(matches[1], matches[2], int(matches[3])) return f"<center>{plotting.plot_function(e, int(matches[4]))}</center>"
def plot_element(matches): if "variant=" in matches[1]: a, b = matches[1].split(" variant=") e = symfem.create_element(a, matches[2], int(matches[3]), b) else: e = symfem.create_element(matches[1], matches[2], int(matches[3])) return ("<center>" f"{''.join([plotting.plot_function(e, i) for i in range(e.space_dim)])}" "</center>")
def test_orthogonal(cell): if cell == "pyramid": pytest.xfail("Legendre polynomials not implemented for pyramids yet.") if cell == "interval": e = create_element(cell, "Lagrange", 5) else: e = create_element(cell, "Lagrange", 2) basis = legendre.get_legendre_basis(e._basis, e.reference) for i, f in enumerate(basis): for g in basis[:i]: assert e.reference.integral(subs(f * g, x, t)) == 0
def test_dual_elements(elements_to_test, cells_to_test, n_tri, order): if elements_to_test != "ALL" and "dual" not in elements_to_test: pytest.skip() if cells_to_test != "ALL" and "dual polygon" not in cells_to_test: pytest.skip() space = create_element(f"dual polygon({n_tri})", "dual", order) sub_e = create_element("triangle", space.fine_space, space.order) for f, coeff_list in zip(space.get_basis_functions(), space.dual_coefficients): for piece, coeffs in zip(f.pieces, coeff_list): map = sub_e.reference.get_map_to(piece[0]) for dof, value in zip(sub_e.dofs, coeffs): point = subs(map, x, dof.point) assert symequal(value, subs(piece[1], x, point))
def test_guzman_neilan_triangle(order): e = symfem.create_element("triangle", "Guzman-Neilan", order) for p in e._basis[-3:]: for piece in p.pieces: print(div(piece[1]).expand()) float(div(piece[1]).expand())
def get_tensor_factorisation(self): """Get the representation of the element as a tensor product.""" from symfem import create_element interval_q = create_element("interval", "Lagrange", self.order) if self.order == 0: perm = [0] elif self.reference.name == "quadrilateral": n = self.order - 1 perm = [0, 2] + [4 + n + i for i in range(n)] perm += [1, 3] + [4 + 2 * n + i for i in range(n)] for i in range(n): perm += [4 + i, 4 + 3 * n + i] + [4 + i + (4 + j) * n for j in range(n)] elif self.reference.name == "hexahedron": n = self.order - 1 perm = [0, 4] + [8 + 2 * n + i for i in range(n)] perm += [2, 6] + [8 + 6 * n + i for i in range(n)] for i in range(n): perm += [8 + n + i, 8 + 9 * n + i] perm += [8 + 12 * n + 2 * n ** 2 + i + n * j for j in range(n)] perm += [1, 5] + [8 + 4 * n + i for i in range(n)] perm += [3, 7] + [8 + 7 * n + i for i in range(n)] for i in range(n): perm += [8 + 3 * n + i, 8 + 10 * n + i] perm += [8 + 12 * n + 3 * n ** 2 + i + n * j for j in range(n)] for i in range(n): perm += [8 + i, 8 + 8 * n + i] perm += [8 + 12 * n + n ** 2 + i + n * j for j in range(n)] perm += [8 + 5 * n + i, 8 + 11 * n + i] perm += [8 + 12 * n + 4 * n ** 2 + i + n * j for j in range(n)] for j in range(n): perm += [8 + 12 * n + i + n * j, 8 + 12 * n + 5 * n ** 2 + i + n * j] perm += [8 + 12 * n + 6 * n ** 2 + i + n * j + n ** 2 * k for k in range(n)] return [("scalar", [interval_q for i in range(self.reference.tdim)], perm)]
def test_basis_continuity_triangle(order): N = 5 e = symfem.create_element("triangle", "Guzman-Neilan", order) third = sympy.Rational(1, 3) one = sympy.Integer(1) for pt in [(0, 0), (1, 0), (0, 1), (third, third)]: for f in e.get_polynomial_basis(): value = None for p in f.pieces: if pt in p[0]: if value is None: value = symfem.symbolic.subs(p[1], symfem.symbolic.x, pt) assert value == symfem.symbolic.subs( p[1], symfem.symbolic.x, pt) for pts in combinations([(0, 0), (1, 0), (1, 0), (third, third)], 2): for i in range(N + 1): pt = tuple(a + (b - a) * i * one / N for a, b in zip(*pts)) for f in e.get_polynomial_basis(): value = None for p in f.pieces: if pts[0] in p[0] and pts[1] in p[0]: if value is None: value = symfem.symbolic.subs( p[1], symfem.symbolic.x, pt) assert value == symfem.symbolic.subs( p[1], symfem.symbolic.x, pt)
def get_basis_functions(self, reshape=True, symbolic=True, use_tensor_factorisation=False): """Get the basis functions of the element.""" assert not use_tensor_factorisation if self._basis_functions is None: from symfem import create_element self._basis_functions = [] for coeff_list in self.dual_coefficients: v0 = self.reference.origin pieces = [] for coeffs, v1, v2 in zip( coeff_list, self.reference.vertices, self.reference.vertices[1:] + self.reference.vertices[:1] ): sub_e = create_element("triangle", self.fine_space, self.order) sub_basis = sub_e.map_to_cell([v0, v1, v2]) if self.range_dim == 1: sub_fun = sym_sum(a * b for a, b in zip(coeffs, sub_basis)) else: sub_fun = tuple( sym_sum(a * b[i] for a, b in zip(coeffs, sub_basis)) for i in range(self.range_dim)) pieces.append(((v0, v1, v2), sub_fun)) self._basis_functions.append(PiecewiseFunction(pieces)) return self._basis_functions
def test_element(elements_to_test, cells_to_test, cell_type, element_type, order, kwargs, speed): """Run tests for each element.""" if elements_to_test != "ALL" and element_type not in elements_to_test: pytest.skip() if cells_to_test != "ALL" and cell_type not in cells_to_test: pytest.skip() if speed == "fast": if order > 2: pytest.skip() if order == 2 and cell_type in [ "tetrahedron", "hexahedron", "prism", "pyramid" ]: pytest.skip() element = create_element(cell_type, element_type, order, **kwargs) try: factorised_basis = element._get_basis_functions_tensor() except symfem.finite_element.NoTensorProduct: pytest.skip( "This element does not have a tensor product representation.") basis = element.get_basis_functions() for i, j in zip(basis, factorised_basis): assert symequal(i, j)
def test_nedelec_3d(): space = create_element("tetrahedron", "Nedelec", 1) k = sympy.Symbol("k") tdim = 3 for i, edge in enumerate([((0, 1, 0), (0, 0, 1)), ((1, 0, 0), (0, 0, 1)), ((1, 0, 0), (0, 1, 0)), ((0, 0, 0), (0, 0, 1)), ((0, 0, 0), (0, 1, 0)), ((0, 0, 0), (1, 0, 0))]): for j, f in enumerate(space.get_basis_functions()): norm = sympy.sqrt( sum((edge[0][i] - edge[1][i])**2 for i in range(tdim))) tangent = tuple( (edge[1][i] - edge[0][i]) / norm for i in range(tdim)) integrand = sum(a * b for a, b in zip(f, tangent)) for d in range(tdim): integrand = integrand.subs(x[d], (1 - k) * edge[0][d] + k * edge[1][d]) integrand *= norm result = sympy.integrate(integrand, (k, 0, 1)) if i == j: assert result == 1 else: assert result == 0
def test_hct(): e = symfem.create_element("triangle", "HCT", 3) for f in e.get_polynomial_basis(): # edge from (1,0) to (1/3,1/3) f1 = f.get_piece((half, 0)) f2 = f.get_piece((half, half)) line = ((1 - 2 * t[0], t[0])) f1 = subs(f1, x[:2], line) f2 = subs(f2, x[:2], line) assert symequal(f1, f2) assert symequal(grad(f1, 2), grad(f2, 2)) # edge from (0,1) to (1/3,1/3) f1 = f.get_piece((half, half)) f2 = f.get_piece((0, half)) line = ((t[0], 1 - 2 * t[0])) f1 = subs(f1, x[:2], line) f2 = subs(f2, x[:2], line) assert symequal(f1, f2) assert symequal(grad(f1, 2), grad(f2, 2)) # edge from (0,0) to (1/3,1/3) f1 = f.get_piece((0, half)) f2 = f.get_piece((half, 0)) line = ((t[0], t[0])) f1 = subs(f1, x[:2], line) f2 = subs(f2, x[:2], line) assert symequal(f1, f2) assert symequal(grad(f1, 2), grad(f2, 2))
def test_guzman_neilan_tetrahedron(order): e = symfem.create_element("tetrahedron", "Guzman-Neilan", order) mid = tuple( sympy.Rational(sum(i), len(i)) for i in zip(*e.reference.vertices)) for p in e._basis[-4:]: for piece in p.pieces: print(div(piece[1]).expand()) float(div(piece[1]).expand()) assert subs(p, x, mid) == (0, 0, 0)
def test_xxyyzz(): element = symfem.create_element("triangle", "RT", 1) points = [(0, 1), (1, 0), (0, 0), (half, half)] r1 = element.tabulate_basis(points, "xyzxyz") r2 = element.tabulate_basis(points, "xxyyzz") for i, j in zip(r1, r2): assert i[0] == j[0] assert i[1] == j[3] assert i[2] == j[1] assert i[3] == j[4] assert i[4] == j[2] assert i[5] == j[5]
def test_MTW_space(reference): e = create_element(reference, "MTW", 3) polynomials = e.get_polynomial_basis() for p in polynomials: assert div(p).is_real for vs in e.reference.sub_entities(1): sub_ref = create_reference(e.reference.sub_entity_types[1], [e.reference.vertices[i] for i in vs]) p_edge = subs(p, x, [ i + t[0] * j for i, j in zip(sub_ref.origin, sub_ref.axes[0]) ]) poly = vdot(p_edge, sub_ref.normal()).expand().simplify() assert poly.is_real or sympy.Poly(poly).degree() <= 1
def test_legendre(cell, order): if cell == "pyramid": pytest.xfail("Legendre polynomials not implemented for pyramids yet.") e = create_element(cell, "Lagrange", order) points = make_lattice(cell) basis = legendre.get_legendre_basis(e._basis, e.reference) values = legendre.evaluate_legendre_basis(points, e._basis, e.reference) for b in basis: assert b != 0 values2 = np.array([[to_float(subs(b, x, p)) for b in basis] for p in points]) assert np.allclose(values, values2)
def test_element(elements_to_test, cells_to_test, cell_type, element_type, order, kwargs, speed): """Run tests for each element.""" if elements_to_test != "ALL" and element_type not in elements_to_test: pytest.skip() if cells_to_test != "ALL" and cell_type not in cells_to_test: pytest.skip() if speed == "fast": if order > 2: pytest.skip() if order == 2 and cell_type in [ "tetrahedron", "hexahedron", "prism", "pyramid" ]: pytest.skip() space = create_element(cell_type, element_type, order, **kwargs) space.test()
def test_basis_continuity_tetrahedron(order): N = 5 e = symfem.create_element("tetrahedron", "Guzman-Neilan", order) quarter = sympy.Rational(1, 4) one = sympy.Integer(1) for pt in [(0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1), (quarter, quarter, quarter)]: for f in e.get_polynomial_basis(): value = None for p in f.pieces: if pt in p[0]: if value is None: value = symfem.symbolic.subs(p[1], symfem.symbolic.x, pt) assert value == symfem.symbolic.subs( p[1], symfem.symbolic.x, pt) for pts in combinations([(0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1), (quarter, quarter, quarter)], 2): for i in range(N + 1): pt = tuple(a + (b - a) * i * one / N for a, b in zip(*pts)) for f in e.get_polynomial_basis(): value = None for p in f.pieces: if pts[0] in p[0] and pts[1] in p[0]: if value is None: value = symfem.symbolic.subs( p[1], symfem.symbolic.x, pt) assert value == symfem.symbolic.subs( p[1], symfem.symbolic.x, pt) for pts in combinations([(0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1), (quarter, quarter, quarter)], 3): for i in range(N + 1): for j in range(N + 1 - i): pt = tuple(a + (b - a) * i * one / N + (c - a) * j * one / N for a, b, c in zip(*pts)) for f in e.get_polynomial_basis(): value = None for p in f.pieces: if pts[0] in p[0] and pts[1] in p[0] and pts[2] in p[0]: if value is None: value = symfem.symbolic.subs( p[1], symfem.symbolic.x, pt) assert value == symfem.symbolic.subs( p[1], symfem.symbolic.x, pt)
def test_BDFM_space(reference, order): e = create_element(reference, "BDFM", order) polynomials = e.get_polynomial_basis() tdim = e.reference.tdim for p in polynomials: for vs in e.reference.sub_entities(tdim - 1): sub_ref = create_reference(e.reference.sub_entity_types[tdim - 1], [e.reference.vertices[i] for i in vs]) if tdim == 2: p_edge = subs(p, x, [ i + t[0] * j for i, j in zip(sub_ref.origin, sub_ref.axes[0]) ]) else: p_edge = subs(p, x, [ i + t[0] * j + t[1] * k for i, j, k in zip( sub_ref.origin, sub_ref.axes[0], sub_ref.axes[1]) ]) poly = vdot(p_edge, sub_ref.normal()).expand().simplify() assert poly.is_real or sympy.Poly(poly).degree() <= order - 1
def test_nedelec_2d(): space = create_element("triangle", "Nedelec", 1) k = sympy.Symbol("k") tdim = 2 for i, edge in enumerate([((1, 0), (0, 1)), ((0, 0), (0, 1)), ((0, 0), (1, 0))]): for j, f in enumerate(space.get_basis_functions()): norm = sympy.sqrt( sum((edge[0][i] - edge[1][i])**2 for i in range(tdim))) tangent = tuple( (edge[1][i] - edge[0][i]) / norm for i in range(tdim)) line = sympy.Curve([(1 - k) * edge[0][i] + k * edge[1][i] for i in range(tdim)], (k, 0, 1)) result = sympy.line_integrate(vdot(f, tangent), line, x[:tdim]) if i == j: assert result == 1 else: assert result == 0
def test_against_basix(has_basix, elements_to_test, cells_to_test, cell, symfem_type, basix_type, order, args, speed): if elements_to_test != "ALL" and symfem_type not in elements_to_test: pytest.skip() if cells_to_test != "ALL" and cell not in cells_to_test: pytest.skip() if speed == "fast" and order > 2: pytest.skip() if has_basix: import basix else: try: import basix except ImportError: pytest.skip("Basix must be installed to run this test.") points = make_lattice(cell, 2) parsed_args = [] for a in args: if a[0] == "LagrangeVariant": parsed_args.append(basix.variants.string_to_lagrange_variant(a[1])) elif a[0] == "DPCVariant": parsed_args.append(basix.variants.string_to_dpc_variant(a[1])) elif a[0] == "bool": parsed_args.append(a[1]) else: raise ValueError(f"Unknown arg type: {a[0]}") space = basix.create_element( basix.finite_element.string_to_family(basix_type, cell), basix.cell.string_to_type(cell), order, *parsed_args) result = space.tabulate(0, points)[0] element = create_element(cell, symfem_type, order) sym_result = element.tabulate_basis(points, "xyz,xyz", symbolic=False, use_legendre=True) if len(result.shape) != len(sym_result.shape): sym_result = sym_result.reshape(result.shape) assert np.allclose(result, sym_result)
def test_rhct(): e = symfem.create_element("triangle", "rHCT", 3) for f in e.get_polynomial_basis(): # edge from (1,0) to (1/3,1/3) f1 = f.get_piece((half, 0)) f2 = f.get_piece((half, half)) line = ((1 - 2 * t[0], t[0])) f1 = subs(f1, x[:2], line) f2 = subs(f2, x[:2], line) assert symequal(f1, f2) assert symequal(grad(f1, 2), grad(f2, 2)) # edge from (0,1) to (1/3,1/3) f1 = f.get_piece((half, half)) f2 = f.get_piece((0, half)) line = ((t[0], 1 - 2 * t[0])) f1 = subs(f1, x[:2], line) f2 = subs(f2, x[:2], line) assert symequal(f1, f2) assert symequal(grad(f1, 2), grad(f2, 2)) # edge from (0,0) to (1/3,1/3) f1 = f.get_piece((0, half)) f2 = f.get_piece((half, 0)) line = ((t[0], t[0])) f1 = subs(f1, x[:2], line) f2 = subs(f2, x[:2], line) assert symequal(f1, f2) assert symequal(grad(f1, 2), grad(f2, 2)) # Check that normal derivatives are linear f1 = diff(f.get_piece((half, 0)), x[1]).subs(x[1], 0) f2 = f.get_piece((half, half)) f2 = (diff(f2, x[0]) + diff(f2, x[1])).subs(x[1], 1 - x[0]) f3 = diff(f.get_piece((0, half)), x[0]).subs(x[0], 0) assert diff(f1, x[0], x[0]) == 0 assert diff(f2, x[0], x[0]) == 0 assert diff(f3, x[1], x[1]) == 0
def test_stiffness_matrix(): vertices = [(0, 0), (1, 0), (0, 1), (1, 1)] triangles = [[0, 1, 2], [1, 3, 2]] matrix = [[0 for i in vertices] for j in vertices] element = symfem.create_element("triangle", "Lagrange", 1) for triangle in triangles: vs = [vertices[i] for i in triangle] ref = symfem.create_reference("triangle", vertices=vs) basis = element.map_to_cell(vs) for test_i, test_f in zip(triangle, basis): for trial_i, trial_f in zip(triangle, basis): integrand = vdot(grad(test_f, 2), grad(trial_f, 2)) matrix[test_i][trial_i] += ref.integral(integrand) half = sympy.Rational(1, 2) actual_matrix = [[1, -half, -half, 0], [-half, 1, 0, -half], [-half, 0, 1, -half], [0, -half, -half, 1]] for row1, row2 in zip(matrix, actual_matrix): for i, j in zip(row1, row2): assert i == j
def test_Q(): space = create_element("quadrilateral", "Q", 1) assert symequal( space.tabulate_basis([[0, 0], [1, 0], [0, 1], [1, 1]]), ((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1)), )
def test_rt(): space = create_element("triangle", "Raviart-Thomas", 1) assert symequal( space.tabulate_basis([[0, 0], [1, 0], [0, 1]], "xxyyzz"), ((0, -1, 0, 0, 0, 1), (-1, 0, -1, 0, 0, 1), (0, -1, 0, -1, 1, 0)), )
def test_nedelec(): space = create_element("triangle", "Nedelec", 1) assert symequal( space.tabulate_basis([[0, 0], [1, 0], [0, 1]], "xxyyzz"), ((0, 0, 1, 0, 1, 0), (0, 0, 1, 1, 0, 1), (-1, 1, 0, 0, 1, 0)), )