diff --git a/Makefile b/Makefile index fb10480..ee09515 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,9 @@ endif docs: @(cd docs/ && (make html SPHINXOPTS="-W --keep-going -n")) +clean: + @(cd docs/ && make clean) + lint: @echo " Linting FUSE codebase" @python3 -m flake8 $(FLAKE8_FORMAT) fuse @@ -37,9 +40,7 @@ test_cells: @firedrake-clean @python3 -m pytest -rPx --run-cleared test/test_cells.py::test_ref_els[expect1] -clean: - @(cd docs/ && make clean) prepush: lint tests @rm .coverage.* - make clean docs \ No newline at end of file + make clean docs diff --git a/fuse/dof.py b/fuse/dof.py index 81c5b6d..00cd259 100644 --- a/fuse/dof.py +++ b/fuse/dof.py @@ -1,6 +1,6 @@ from FIAT.quadrature_schemes import create_quadrature from FIAT.quadrature import FacetQuadratureRule -from FIAT.functional import PointEvaluation, FrobeniusIntegralMoment +from FIAT.functional import PointEvaluation, FrobeniusIntegralMoment, Functional from fuse.utils import sympy_to_numpy import numpy as np import sympy as sp @@ -38,6 +38,10 @@ def convert_to_fiat(self, ref_el, dof, interpolant_deg): pt = dof.eval(FuseFunction(lambda *x: x)) return PointEvaluation(ref_el, pt) + def get_pts(self, ref_el, total_degree): + entity = ref_el.construct_subelement(self.entity.dim()) + return [(0,) * entity.get_spatial_dimension()], [1], 1 + def add_entity(self, entity): res = DeltaPairing() res.entity = entity @@ -90,6 +94,18 @@ def add_entity(self, entity): res.entity = entity return res + def get_pts(self, ref_el, total_degree): + entity = ref_el.construct_subelement(self.entity.dim()) + Q_ref = create_quadrature(entity, total_degree) + + ent_id = self.entity.id - ref_el.fe_cell.get_starter_ids()[self.entity.dim()] + Q = FacetQuadratureRule(ref_el, self.entity.dim(), ent_id, Q_ref) + Jdet = Q.jacobian_determinant() + # TODO work out how to get J from attachment + + qpts, qwts = Q_ref.get_points(), Q_ref.get_weights() + return qpts, qwts, Jdet + def convert_to_fiat(self, ref_el, dof, interpolant_degree): total_deg = interpolant_degree + dof.kernel.degree() ent_id = self.entity.id - ref_el.fe_cell.get_starter_ids()[self.entity.dim()] @@ -98,11 +114,7 @@ def convert_to_fiat(self, ref_el, dof, interpolant_degree): Q = FacetQuadratureRule(ref_el, self.entity.dim(), ent_id, Q_ref) Jdet = Q.jacobian_determinant() qpts, _ = Q.get_points(), Q.get_weights() - print(qpts) - print(dof.tabulate(qpts)) f_at_qpts = dof.tabulate(qpts).T / Jdet - print(len(Q.pts)) - print(f_at_qpts.shape) functional = FrobeniusIntegralMoment(ref_el, Q, f_at_qpts) return functional @@ -154,8 +166,11 @@ def permute(self, g): def __call__(self, *args): return self.pt - def tabulate(self, Qpts): - return np.array([self.pt for _ in Qpts]).astype(np.float64) + def tabulate(self, Qpts, attachment=None): + if attachment is None: + return np.array([self.pt for _ in Qpts]).astype(np.float64) + else: + return np.array([attachment(*self.pt) for _ in Qpts]).astype(np.float64) def _to_dict(self): o_dict = {"pt": self.pt} @@ -195,8 +210,10 @@ def __call__(self, *args): return [res] return res - def tabulate(self, Qpts): - return np.array([self(*pt) for pt in Qpts]).astype(np.float64) + def tabulate(self, Qpts, attachment=None): + if attachment is None: + return np.array([self(*pt) for pt in Qpts]).astype(np.float64) + return np.array([self(*attachment(*pt)) for pt in Qpts]).astype(np.float64) def _to_dict(self): o_dict = {"fn": self.fn} @@ -255,8 +272,20 @@ def add_context(self, dof_gen, cell, space, g, overall_id=None, generator_id=Non self.sub_id = generator_id def convert_to_fiat(self, ref_el, interpolant_degree): - return self.pairing.convert_to_fiat(ref_el, self, interpolant_degree) - raise NotImplementedError("Fiat conversion only implemented for Point eval") + total_degree = self.kernel.degree() + interpolant_degree + pts, wts, jdet = self.pairing.get_pts(ref_el, total_degree) + f_pts = self.kernel.tabulate(pts).T / jdet + # TODO need to work out how i can discover the shape in a better way + if isinstance(self.pairing, DeltaPairing): + shp = tuple() + pt_dict = {tuple(p): [(w, tuple())] for (p, w) in zip(f_pts.T, wts)} + else: + shp = tuple(f_pts.shape[:-1]) + weights = np.transpose(np.multiply(f_pts, wts), (-1,) + tuple(range(len(shp)))) + alphas = list(np.ndindex(shp)) + pt_dict = {tuple(pt): [(wt[alpha], alpha) for alpha in alphas] for pt, wt in zip(pts, weights)} + + return [Functional(ref_el, shp, pt_dict, {}, self.__repr__())] def __repr__(self, fn="v"): return str(self.pairing).format(fn=fn, kernel=self.kernel) @@ -297,6 +326,24 @@ def tabulate(self, Qpts): res = self.kernel.tabulate(Qpts) return immersion*res + def convert_to_fiat(self, ref_el, interpolant_degree): + total_degree = self.kernel.degree() + interpolant_degree + pts, wts, jdet = self.pairing.get_pts(ref_el, total_degree) + f_pts = self.kernel.tabulate(pts, self.attachment) + attached_pts = [self.attachment(*p) for p in pts] + immersion = self.target_space.tabulate(f_pts, self.trace_entity, self.g) + + f_pts = (f_pts * immersion).T / jdet + dict_list = self.target_space.convert_to_fiat(attached_pts, f_pts, wts) + + # breakpoint() + # TODO need to work out how i can discover the shape in a better way + if isinstance(self.pairing, DeltaPairing): + shp = tuple() + else: + shp = tuple(f_pts.shape[:-1]) + return [Functional(ref_el, shp, pt_dict, deriv_dict, self.__repr__()) for (pt_dict, deriv_dict) in dict_list] + def __call__(self, g): permuted = self.cell.permute_entities(g, self.trace_entity.dim()) index_trace = self.cell.d_entities_ids(self.trace_entity.dim()).index(self.trace_entity.id) diff --git a/fuse/traces.py b/fuse/traces.py index 2f77606..81fa713 100644 --- a/fuse/traces.py +++ b/fuse/traces.py @@ -57,6 +57,10 @@ def to_tikz(self, coord, trace_entity, g, scale, color="black"): def tabulate(self, Qpts, trace_entity, g): return np.ones_like(Qpts) + def convert_to_fiat(self, qpts, pts, wts): + pt_dict = {tuple(p): [(w, tuple())] for (p, w) in zip(pts.T, wts)} + return [(pt_dict, {})] + def __repr__(self): return "H1" @@ -80,6 +84,14 @@ def plot(self, ax, coord, trace_entity, g, **kwargs): vec = self.tabulate([], trace_entity, g).squeeze() ax.quiver(*coord, *vec, **kwargs) + def convert_to_fiat(self, qpts, pts, wts): + f_at_qpts = pts + shp = tuple(f_at_qpts.shape[:-1]) + weights = np.transpose(np.multiply(f_at_qpts, wts), (-1,) + tuple(range(len(shp)))) + alphas = list(np.ndindex(shp)) + pt_dict = {tuple(pt): [(wt[alpha], alpha) for alpha in alphas] for pt, wt in zip(qpts, weights)} + return [(pt_dict, {})] + def tabulate(self, Qpts, trace_entity, g): entityBasis = np.array(trace_entity.basis_vectors()) cellEntityBasis = np.array(self.domain.basis_vectors(entity=trace_entity)) @@ -123,6 +135,14 @@ def tabulate(self, Qpts, trace_entity, g): result = np.matmul(tangent, subEntityBasis) return result + def convert_to_fiat(self, qpts, pts, wts): + f_at_qpts = pts + shp = tuple(f_at_qpts.shape[:-1]) + weights = np.transpose(np.multiply(f_at_qpts, wts), (-1,) + tuple(range(len(shp)))) + alphas = list(np.ndindex(shp)) + pt_dict = {tuple(pt): [(wt[alpha], alpha) for alpha in alphas] for pt, wt in zip(qpts, weights)} + return [(pt_dict, {})] + def plot(self, ax, coord, trace_entity, g, **kwargs): vec = self.tabulate([], trace_entity, g).squeeze() ax.quiver(*coord, *vec, **kwargs) @@ -159,10 +179,28 @@ def apply(*x): return tuple(result) return apply + def convert_to_fiat(self, qpts, pts, wts): + shp = (self.domain.get_spatial_dimension(),) + alphas = [] + for i in range(pts.shape[0]): + new = np.zeros(shp, dtype=int) + new[i] = 1 + alphas += [tuple(new)] + deriv_dicts = [] + for alpha in alphas: + deriv_dicts += [{tuple(p): [(1.0, tuple(alpha), tuple())] for p in pts.T}] + + # self.alpha = tuple(alpha) + # self.order = sum(self.alpha) + return [({}, d) for d in deriv_dicts] + def plot(self, ax, coord, trace_entity, g, **kwargs): circle1 = plt.Circle(coord, 0.075, fill=False, **kwargs) ax.add_patch(circle1) + def tabulate(self, Qpts, trace_entity, g): + return np.ones_like(Qpts) + def to_tikz(self, coord, trace_entity, g, scale, color="black"): return f"\\draw[{color}] {numpy_to_str_tuple(coord, scale)} circle (4pt) node[anchor = south] {{}};" diff --git a/fuse/triples.py b/fuse/triples.py index e047a72..2de1bcb 100644 --- a/fuse/triples.py +++ b/fuse/triples.py @@ -120,21 +120,27 @@ def to_fiat(self): dim = entity[0] for i in range(len(dofs)): if entity[1] == dofs[i].trace_entity.id - min_ids[dim]: - entity_ids[dim][dofs[i].trace_entity.id - min_ids[dim]].append(counter) - nodes.append(dofs[i].convert_to_fiat(ref_el, degree)) - counter += 1 + fiat_dofs = dofs[i].convert_to_fiat(ref_el, degree) + nodes.extend(fiat_dofs) + entity_ids[dim][dofs[i].trace_entity.id - min_ids[dim]].extend([counter + i for i in range(len(fiat_dofs))]) + counter += len(fiat_dofs) + print("nodes", nodes) entity_perms, pure_perm = self.make_dof_perms(ref_el, entity_ids, nodes, poly_set) - self.matrices = self.make_overall_dense_matrices(ref_el, entity_ids, nodes, poly_set) + # self.matrices = self.make_overall_dense_matrices(ref_el, entity_ids, nodes, poly_set) form_degree = 1 if self.spaces[0].set_shape else 0 - print("my", [n.pt_dict for n in nodes]) + print("pts", [n.pt_dict for n in nodes]) + print("derivs", [n.deriv_dict for n in nodes]) print(entity_perms) print(entity_ids) - print(ref_el.vertices) + print(nodes) + print(len(nodes)) print() # TODO: Change this when Dense case in Firedrake if pure_perm: + self.matrices = self.make_overall_dense_matrices(ref_el, entity_ids, nodes, poly_set) dual = DualSet(nodes, ref_el, entity_ids, entity_perms) else: + self.matrices = None dual = DualSet(nodes, ref_el, entity_ids) return CiarletElement(poly_set, dual, degree, form_degree) @@ -227,7 +233,7 @@ def compute_dense_matrix(self, ref_el, entity_ids, nodes, poly_set): A = dualmat.reshape((shp[0], -1)) B = old_coeffs.reshape((shp[0], -1)) V = np.dot(A, np.transpose(B)) - + print(V) with warnings.catch_warnings(): warnings.filterwarnings("error") try: @@ -249,7 +255,7 @@ def make_overall_dense_matrices(self, ref_el, entity_ids, nodes, poly_set): if g.perm.is_Identity: res_dict[dim][e_id][val] = np.eye(len(nodes)) else: - new_nodes = [d(g).convert_to_fiat(ref_el, degree) for d in self.generate()] + new_nodes = sum([d(g).convert_to_fiat(ref_el, degree) for d in self.generate()], []) transformed_V, transformed_basis = self.compute_dense_matrix(ref_el, entity_ids, new_nodes, poly_set) res_dict[dim][e_id][val] = np.matmul(transformed_basis, original_V.T) return res_dict @@ -286,7 +292,8 @@ def make_dof_perms(self, ref_el, entity_ids, nodes, poly_set): if pure_perm is False: # TODO think about where this call goes - return self.make_overall_dense_matrices(ref_el, entity_ids, nodes, poly_set), pure_perm + return None, False + # return self.make_overall_dense_matrices(ref_el, entity_ids, nodes, poly_set), pure_perm dof_id_mat = np.eye(len(dofs)) oriented_mats_by_entity = {} diff --git a/test/bfs.py b/test/bfs.py new file mode 100644 index 0000000..1152ec5 --- /dev/null +++ b/test/bfs.py @@ -0,0 +1,25 @@ +from firedrake import * +from fuse import * +from test_convert_to_fiat import create_dg1 +from test_tensor_prod import mass_solve + + +def her_int(): + deg = 3 + cell = Point(1, [Point(0), Point(0)], vertex_num=2) + vert_dg = create_dg1(cell.vertices()[0]) + xs = [immerse(cell, vert_dg, TrH1), immerse(cell, vert_dg, TrGrad)] + + Pk = PolynomialSpace(deg) + # TODO fix faking out permutations + her = ElementTriple(cell, (Pk, CellL2, C0), DOFGenerator(xs, S2, S2)) + return her + + +r = 3 +her1 = her_int() +her2 = her_int() +bfs = tensor_product(her1, her2).flatten() +mesh = UnitSquareMesh(2 ** r, 2 ** r, quadrilateral=quadrilateral) +U = FunctionSpace(mesh, bfs.to_ufl()) +mass_solve(U) \ No newline at end of file diff --git a/test/test_convert_to_fiat.py b/test/test_convert_to_fiat.py index 4c00cf7..2cd112d 100644 --- a/test/test_convert_to_fiat.py +++ b/test/test_convert_to_fiat.py @@ -155,6 +155,27 @@ def create_cg2_tri(cell): return cg +def create_hermite(tri): + vert = tri.vertices()[0] + + xs = [DOF(DeltaPairing(), PointKernel(()))] + dg0 = ElementTriple(vert, (P0, CellL2, C0), DOFGenerator(xs, S1, S1)) + + v_xs = [immerse(tri, dg0, TrH1)] + v_dofs = DOFGenerator(v_xs, S3/S2, S1) + + v_derv_xs = [immerse(tri, dg0, TrGrad)] + # TODO fix perms so this can be S1 + v_derv_dofs = DOFGenerator(v_derv_xs, S3/S2, S3) + + i_xs = [DOF(DeltaPairing(), PointKernel((0, 0)))] + i_dofs = DOFGenerator(i_xs, S1, S1) + + her = ElementTriple(tri, (P3, CellH2, C0), + [v_dofs, v_derv_dofs, i_dofs]) + return her + + def test_create_fiat_nd(): cell = polygon(3) nd = construct_nd(cell) @@ -485,6 +506,7 @@ def test_non_tensor_quad(): (lambda cell: CR_n(cell, 3), "CR", 1), (create_cf, "CR", 1), # Don't think Crouzeix Falk in in Firedrake (construct_cg3, "CG", 3), + (create_hermite, "HER", 3), pytest.param(construct_nd, "N1curl", 1, marks=pytest.mark.xfail(reason='Dense Matrices needed')), pytest.param(construct_rt, "RT", 1, marks=pytest.mark.xfail(reason='Dense Matrices needed'))]) def test_project(elem_gen, elem_code, deg): @@ -557,6 +579,35 @@ def test_project_3d(elem_gen, elem_code, deg): assert np.allclose(out.dat.data, f.dat.data, rtol=1e-5) +# @pytest.mark.xfail(reason='Handling generation of multiple fiat nodes from one in permutations') +def test_create_hermite(): + deg = 3 + cell = polygon(3) + elem = create_hermite(cell) + ref_el = cell.to_fiat() + sd = ref_el.get_spatial_dimension() + + from FIAT.hermite import CubicHermite + fiat_elem = CubicHermite(ref_el, deg) + + my_elem = elem.to_fiat() + + Q = create_quadrature(ref_el, 2*(deg+1)) + Qpts, _ = Q.get_points(), Q.get_weights() + + fiat_vals = fiat_elem.tabulate(0, Qpts) + my_vals = my_elem.tabulate(0, Qpts) + + fiat_vals = flatten(fiat_vals[(0,) * sd]) + my_vals = flatten(my_vals[(0,) * sd]) + + (x, res, _, _) = np.linalg.lstsq(fiat_vals.T, my_vals.T) + x1 = np.linalg.inv(x) + assert np.allclose(np.linalg.norm(my_vals.T - fiat_vals.T @ x), 0) + assert np.allclose(np.linalg.norm(fiat_vals.T - my_vals.T @ x1), 0) + assert np.allclose(res, 0) + + def test_investigate_dpc(): mesh = UnitSquareMesh(2, 2, quadrilateral=True) diff --git a/test/test_dofs.py b/test/test_dofs.py index dc2c737..3b964a7 100644 --- a/test/test_dofs.py +++ b/test/test_dofs.py @@ -1,5 +1,5 @@ from fuse import * -from test_convert_to_fiat import create_cg1, create_dg1, construct_cg3, construct_rt, construct_nd +from test_convert_to_fiat import create_cg1, create_dg1, construct_cg3, construct_rt, construct_nd, create_hermite import sympy as sp import numpy as np @@ -106,3 +106,33 @@ def test_permute_nd(): # print(g) # print(nd.cell.permute_entities(g, 0)) # print(nd.cell.permute_entities(g, 1)) + + +def test_convert_dofs(): + cell = polygon(3) + + cg3 = create_hermite(cell) + + # for dof in cg3.generate(): + # print(dof) + # # print("old") + # # # old = dof.convert_to_fiat(cell.to_fiat(), 5).pt_dict + # # print(old) + # # print("new") + # new = dof.convert_to_fiat(cell.to_fiat(), 5)[0].pt_dict + # print(new) + # new = dof.convert_to_fiat(cell.to_fiat(), 5)[0].deriv_dict + # print(new) + # print(len(cg3.generate())) + fuse_elem = cg3.to_fiat() + for n in fuse_elem.dual.nodes: + print(n.pt_dict) + print(n.deriv_dict) + + from FIAT.hermite import CubicHermite + fiat_elem = CubicHermite(cell.to_fiat(), 3) + + print(len([n.pt_dict for n in fiat_elem.dual.nodes])) + for n in fiat_elem.dual.nodes: + print(n.pt_dict) + print(n.deriv_dict) \ No newline at end of file