From 069efc9e6a65cf423115a4b1a8843218046693b5 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Sat, 2 Dec 2023 16:52:06 +0000 Subject: [PATCH 01/93] Simplex Legendre element --- FIAT/hierarchical.py | 55 +++++++++++++----------- test/unit/test_gauss_legendre.py | 2 +- test/unit/test_gauss_lobatto_legendre.py | 2 +- test/unit/test_hierarchical.py | 23 +++++----- 4 files changed, 43 insertions(+), 39 deletions(-) diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index f42f52737..f1c1bcbd5 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -8,41 +8,44 @@ import numpy -from FIAT import (finite_element, reference_element, - dual_set, functional, quadrature, - jacobi, barycentric_interpolation) +from FIAT import (finite_element, dual_set, functional, reference_element, + jacobi, polynomial_set) +from FIAT.reference_element import POINT, LINE, TRIANGLE, TETRAHEDRON, make_affine_mapping from FIAT.orientation_utils import make_entity_permutations_simplex -from FIAT.barycentric_interpolation import LagrangePolynomialSet +from FIAT.barycentric_interpolation import LagrangePolynomialSet, make_dmat +from FIAT.quadrature_schemes import create_quadrature class LegendreDual(dual_set.DualSet): """The dual basis for Legendre elements.""" - def __init__(self, ref_el, degree, rule): - v1 = ref_el.get_vertices() - A, b = reference_element.make_affine_mapping(v1, [(-1.0,), (1.0,)]) - mapping = lambda x: numpy.dot(A, x) + b - xhat = numpy.array([mapping(pt) for pt in rule.pts]) - - basis = jacobi.eval_jacobi_batch(0, 0, degree, xhat) - nodes = [functional.IntegralMoment(ref_el, rule, f) for f in basis] - - entity_ids = {0: {0: [], 1: []}, - 1: {0: list(range(0, degree+1))}} + def __init__(self, ref_el, degree, poly_set): + entity_ids = {} entity_permutations = {} - entity_permutations[0] = {0: {0: []}, 1: {0: []}} - entity_permutations[1] = {0: make_entity_permutations_simplex(1, degree + 1)} + top = ref_el.get_topology() + for dim in sorted(top): + entity_ids[dim] = {} + entity_permutations[dim] = {} + perms = make_entity_permutations_simplex(dim, degree + 1 if dim == len(top)-1 else -1) + for entity in sorted(top[dim]): + entity_ids[dim][entity] = [] + entity_permutations[dim][entity] = perms + + dim = ref_el.get_spatial_dimension() + Q = create_quadrature(ref_el, 2 * degree) + phis = poly_set.tabulate(Q.get_points())[(0,) * dim] + nodes = [functional.IntegralMoment(ref_el, Q, phi) for phi in phis] + super(LegendreDual, self).__init__(nodes, ref_el, entity_ids, entity_permutations) class Legendre(finite_element.CiarletElement): - """1D discontinuous element with Legendre polynomials.""" + """Simplicial discontinuous element with Legendre polynomials.""" def __init__(self, ref_el, degree): - if ref_el.shape != reference_element.LINE: - raise ValueError("%s is only defined in one dimension." % type(self)) - rule = quadrature.GaussLegendreQuadratureLineRule(ref_el, degree+1) - poly_set = LagrangePolynomialSet(ref_el, rule.get_points()) - dual = LegendreDual(ref_el, degree, rule) + if ref_el.shape not in {POINT, LINE, TRIANGLE, TETRAHEDRON}: + raise ValueError("%s is only defined on simplices." % type(self)) + poly_set = polynomial_set.ONPolynomialSet(ref_el, degree) + dual = LegendreDual(ref_el, degree, poly_set) formdegree = ref_el.get_spatial_dimension() # n-form super(Legendre, self).__init__(poly_set, dual, degree, formdegree) @@ -51,12 +54,12 @@ class IntegratedLegendreDual(dual_set.DualSet): """The dual basis for integrated Legendre elements.""" def __init__(self, ref_el, degree, rule): v1 = ref_el.get_vertices() - A, b = reference_element.make_affine_mapping(v1, [(-1.0,), (1.0,)]) + A, b = make_affine_mapping(v1, [(-1.0,), (1.0,)]) mapping = lambda x: numpy.dot(A, x) + b xhat = numpy.array([mapping(pt) for pt in rule.pts]) W = rule.get_weights() - D, _ = barycentric_interpolation.make_dmat(numpy.array(rule.pts).flatten()) + D, _ = make_dmat(numpy.array(rule.pts).flatten()) P = jacobi.eval_jacobi_batch(0, 0, degree-1, xhat) basis = numpy.dot(numpy.multiply(P, W), numpy.multiply(D.T, 1.0/W)) @@ -78,7 +81,7 @@ class IntegratedLegendre(finite_element.CiarletElement): def __init__(self, ref_el, degree): if ref_el.shape != reference_element.LINE: raise ValueError("%s is only defined in one dimension." % type(self)) - rule = quadrature.GaussLegendreQuadratureLineRule(ref_el, degree+1) + rule = create_quadrature(ref_el, 2 * degree) poly_set = LagrangePolynomialSet(ref_el, rule.get_points()) dual = IntegratedLegendreDual(ref_el, degree, rule) formdegree = 0 # 0-form diff --git a/test/unit/test_gauss_legendre.py b/test/unit/test_gauss_legendre.py index 0acc9bb4d..22babd157 100644 --- a/test/unit/test_gauss_legendre.py +++ b/test/unit/test_gauss_legendre.py @@ -52,7 +52,7 @@ def test_gl_basis_values(dim, degree): v = lambda x: sum(x)**test_degree coefs = [n(v) for n in fe.dual.nodes] integral = np.dot(coefs, np.dot(tab, q.wts)) - reference = np.dot([v(x) for x in q.pts], q.wts) + reference = q.integrate(v) assert np.allclose(integral, reference, rtol=1e-14) diff --git a/test/unit/test_gauss_lobatto_legendre.py b/test/unit/test_gauss_lobatto_legendre.py index defa77dfb..b8df3037b 100644 --- a/test/unit/test_gauss_lobatto_legendre.py +++ b/test/unit/test_gauss_lobatto_legendre.py @@ -52,7 +52,7 @@ def test_gll_basis_values(dim, degree): v = lambda x: sum(x)**test_degree coefs = [n(v) for n in fe.dual.nodes] integral = np.dot(coefs, np.dot(tab, q.wts)) - reference = np.dot([v(x) for x in q.pts], q.wts) + reference = q.integrate(v) assert np.allclose(integral, reference, rtol=1e-14) diff --git a/test/unit/test_hierarchical.py b/test/unit/test_hierarchical.py index c802cfd23..e0455e399 100644 --- a/test/unit/test_hierarchical.py +++ b/test/unit/test_hierarchical.py @@ -23,26 +23,27 @@ import numpy as np -@pytest.mark.parametrize("family, degree", [(f, degree - 1 if f == "DG" else degree) - for f in ("CG", "DG") - for degree in range(1, 7)]) -def test_hierarchical_basis_values(family, degree): +@pytest.mark.parametrize("dim, family, degree", [(dim, f, degree - 1 if f == "DG" else degree) + for f in ("CG", "DG") + for dim in range(1, 4 if f == "DG" else 2) + for degree in range(1, 7)]) +def test_hierarchical_basis_values(dim, family, degree): """Ensure that integrating a simple monomial produces the expected results.""" - from FIAT import ufc_simplex, Legendre, IntegratedLegendre, make_quadrature + from FIAT import ufc_simplex, Legendre, IntegratedLegendre, create_quadrature - s = ufc_simplex(1) - q = make_quadrature(s, degree + 1) + s = ufc_simplex(dim) + q = create_quadrature(s, degree + 1) if family == "CG": fe = IntegratedLegendre(s, degree) else: fe = Legendre(s, degree) - tab = fe.tabulate(0, q.pts)[(0,)] + tab = fe.tabulate(0, q.pts)[(0,)*dim] for test_degree in range(degree + 1): - coefs = [n(lambda x: x[0]**test_degree) for n in fe.dual.nodes] + v = lambda x: sum(x)**test_degree + coefs = [n(v) for n in fe.dual.nodes] integral = np.dot(coefs, np.dot(tab, q.wts)) - reference = np.dot([x[0]**test_degree - for x in q.pts], q.wts) + reference = q.integrate(v) assert np.allclose(integral, reference, rtol=1e-14) From 5ab74f238e98d79fca243bc950bb95392026a9df Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Sun, 3 Dec 2023 11:47:24 +0000 Subject: [PATCH 02/93] unify N2curl dofs --- FIAT/nedelec_second_kind.py | 112 +++++++++++------------------------- 1 file changed, 35 insertions(+), 77 deletions(-) diff --git a/FIAT/nedelec_second_kind.py b/FIAT/nedelec_second_kind.py index cc9bc8f66..f127b3f24 100644 --- a/FIAT/nedelec_second_kind.py +++ b/FIAT/nedelec_second_kind.py @@ -17,8 +17,6 @@ from FIAT.quadrature_schemes import create_quadrature from FIAT.check_format_variant import check_format_variant -from FIAT import polynomial_set, functional - class NedelecSecondKindDual(DualSet): r""" @@ -69,46 +67,34 @@ def generate_degrees_of_freedom(self, cell, degree, variant, interpolant_deg): # Zero vertex-based degrees of freedom (d+1 of these) ids[0] = dict(list(zip(list(range(d + 1)), ([] for i in range(d + 1))))) - # (d+1) degrees of freedom per entity of codimension 1 (edges) - (edge_dofs, edge_ids) = self._generate_edge_dofs(cell, degree, 0, variant, interpolant_deg) + # (degree+1) degrees of freedom per entity of codimension 1 (edges) + (edge_dofs, ids[1]) = self._generate_edge_dofs(cell, degree, 0, variant, interpolant_deg) dofs.extend(edge_dofs) - ids[1] = edge_ids # Include face degrees of freedom if 3D if d == 3: - (face_dofs, face_ids) = self._generate_face_dofs(cell, degree, - len(dofs), variant, interpolant_deg) + face_dofs, ids[d-1] = self._generate_facet_dofs(d-1, cell, degree, + len(dofs), variant, interpolant_deg) dofs.extend(face_dofs) - ids[2] = face_ids # Varying degrees of freedom (possibly zero) per cell - (cell_dofs, cell_ids) = self._generate_cell_dofs(cell, degree, len(dofs), variant, interpolant_deg) + cell_dofs, ids[d] = self._generate_facet_dofs(d, cell, degree, len(dofs), variant, interpolant_deg) dofs.extend(cell_dofs) - ids[d] = cell_ids return (dofs, ids) def _generate_edge_dofs(self, cell, degree, offset, variant, interpolant_deg): - """Generate degrees of freedoms (dofs) for entities of + """Generate degrees of freedom (dofs) for entities of codimension 1 (edges).""" + if variant == "integral": + return self._generate_facet_dofs(1, cell, degree, offset, variant, interpolant_deg) + # (degree+1) tangential component point evaluation degrees of # freedom per entity of codimension 1 (edges) dofs = [] ids = {} - - if variant == "integral": - edge = cell.construct_subelement(1) - Q = create_quadrature(edge, degree + interpolant_deg) - Pq = polynomial_set.ONPolynomialSet(edge, degree) - Pq_at_qpts = Pq.tabulate(Q.get_points())[(0,)] - for e in range(len(cell.get_topology()[1])): - dofs.extend(functional.IntegralMomentOfEdgeTangentEvaluation(cell, Q, phi, e) - for phi in Pq_at_qpts) - jj = Pq_at_qpts.shape[0] * e - ids[e] = list(range(offset + jj, offset + jj + Pq_at_qpts.shape[0])) - - elif variant == "point": + if variant == "point": for edge in range(len(cell.get_topology()[1])): # Create points for evaluation of tangential components @@ -123,44 +109,45 @@ def _generate_edge_dofs(self, cell, degree, offset, variant, interpolant_deg): return (dofs, ids) - def _generate_face_dofs(self, cell, degree, offset, variant, interpolant_deg): - """Generate degrees of freedoms (dofs) for faces.""" + def _generate_facet_dofs(self, codim, cell, degree, offset, variant, interpolant_deg): + """Generate degrees of freedom (dofs) for facets.""" # Initialize empty dofs and identifiers (ids) + num_facets = len(cell.get_topology()[codim]) dofs = [] - ids = dict(list(zip(list(range(4)), ([] for i in range(4))))) + ids = dict(list(zip(list(range(num_facets)), ([] for i in range(num_facets))))) # Return empty info if not applicable - d = cell.get_spatial_dimension() - if degree < 2: + rt_degree = degree - codim + 1 + if rt_degree < 1: return (dofs, ids) if interpolant_deg is None: interpolant_deg = degree - # Construct quadrature scheme for the reference face - ref_face = cell.get_facet_element() - Q_ref = create_quadrature(ref_face, interpolant_deg + degree - 1) - - # Construct Raviart-Thomas of (degree - 1) on the reference face - RT = RaviartThomas(ref_face, degree - 1, variant) - num_rts = RT.space_dimension() - - # Evaluate RT basis functions at reference quadrature points - Phi = RT.get_nodal_basis() - Phis = Phi.tabulate(Q_ref.get_points())[(0, 0)] + # Construct quadrature scheme for the reference facet + ref_facet = cell.construct_subelement(codim) + Q_ref = create_quadrature(ref_facet, interpolant_deg + rt_degree) + if codim == 1: + Phi = ONPolynomialSet(ref_facet, rt_degree, (codim,)) + else: + # Construct Raviart-Thomas on the reference facet + RT = RaviartThomas(ref_facet, rt_degree, variant) + Phi = RT.get_nodal_basis() + + # Evaluate basis functions at reference quadrature points + Phis = Phi.tabulate(Q_ref.get_points())[(0,) * codim] # Note: Phis has dimensions: # num_basis_functions x num_components x num_quad_points Phis = numpy.transpose(Phis, (0, 2, 1)) # Note: Phis has dimensions: # num_basis_functions x num_quad_points x num_components - # Iterate over the faces of the tet - num_faces = len(cell.get_topology()[d-1]) - for face in range(num_faces): - # Get the quadrature and Jacobian on this face - Q_face = FacetQuadratureRule(cell, d-1, face, Q_ref) - J = Q_face.jacobian() + # Iterate over the facets + for facet in range(num_facets): + # Get the quadrature and Jacobian on this facet + Q_facet = FacetQuadratureRule(cell, codim, facet, Q_ref) + J = Q_facet.jacobian() # Map Phis -> phis (reference values to physical values) piola_map = J / numpy.sqrt(numpy.linalg.det(numpy.dot(J.T, J))) @@ -170,40 +157,11 @@ def _generate_face_dofs(self, cell, degree, offset, variant, interpolant_deg): # Construct degrees of freedom as integral moments on this cell, # using the face quadrature weighted against the values # of the (physical) Raviart--Thomas'es on the face - dofs.extend(IntegralMoment(cell, Q_face, phi) for phi in phis) + dofs.extend(IntegralMoment(cell, Q_facet, phi) for phi in phis) # Assign identifiers (num RTs per face + previous edge dofs) - ids[face] = list(range(offset + num_rts*face, offset + num_rts*(face + 1))) - - return (dofs, ids) - - def _generate_cell_dofs(self, cell, degree, offset, variant, interpolant_deg): - """Generate degrees of freedoms (dofs) for entities of - codimension d (cells).""" - - # Return empty info if not applicable - d = cell.get_spatial_dimension() - rt_degree = degree - d + 1 - if rt_degree < 1: - return ([], {0: []}) - - # Create quadrature points - interpolant_deg = interpolant_deg or degree - Q = create_quadrature(cell, interpolant_deg + rt_degree) - - # Create Raviart-Thomas nodal basis - RT = RaviartThomas(cell, rt_degree, variant) - phi = RT.get_nodal_basis() - - # Evaluate Raviart-Thomas basis at quadrature points - phi_at_qs = phi.tabulate(Q.get_points())[(0,) * d] - - # Use (Frobenius) integral moments against RTs as dofs - dofs = [IntegralMoment(cell, Q, phi) - for phi in phi_at_qs] + ids[facet] = list(range(offset + len(phis)*facet, offset + len(phis)*(facet + 1))) - # Associate these dofs with the interior - ids = {0: list(range(offset, offset + len(dofs)))} return (dofs, ids) From b96a3a9cc02f55d0127ae1e5a2b87822c68b0608 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Sun, 3 Dec 2023 17:12:34 +0000 Subject: [PATCH 03/93] IntegratedLagrange on simplices --- FIAT/hierarchical.py | 96 ++++++++++++++++++++++++---------- FIAT/nedelec_second_kind.py | 8 +-- test/unit/test_hierarchical.py | 2 +- 3 files changed, 74 insertions(+), 32 deletions(-) diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index f1c1bcbd5..b9774317d 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -8,12 +8,13 @@ import numpy -from FIAT import (finite_element, dual_set, functional, reference_element, - jacobi, polynomial_set) -from FIAT.reference_element import POINT, LINE, TRIANGLE, TETRAHEDRON, make_affine_mapping +from FIAT import finite_element, dual_set, functional, polynomial_set +from FIAT.reference_element import POINT, LINE, TRIANGLE, TETRAHEDRON from FIAT.orientation_utils import make_entity_permutations_simplex -from FIAT.barycentric_interpolation import LagrangePolynomialSet, make_dmat +from FIAT.barycentric_interpolation import make_dmat +from FIAT.quadrature import FacetQuadratureRule from FIAT.quadrature_schemes import create_quadrature +from FIAT.expansions import polynomial_dimension class LegendreDual(dual_set.DualSet): @@ -52,26 +53,66 @@ def __init__(self, ref_el, degree): class IntegratedLegendreDual(dual_set.DualSet): """The dual basis for integrated Legendre elements.""" - def __init__(self, ref_el, degree, rule): - v1 = ref_el.get_vertices() - A, b = make_affine_mapping(v1, [(-1.0,), (1.0,)]) - mapping = lambda x: numpy.dot(A, x) + b - xhat = numpy.array([mapping(pt) for pt in rule.pts]) - - W = rule.get_weights() - D, _ = make_dmat(numpy.array(rule.pts).flatten()) - P = jacobi.eval_jacobi_batch(0, 0, degree-1, xhat) - basis = numpy.dot(numpy.multiply(P, W), numpy.multiply(D.T, 1.0/W)) - - nodes = [functional.PointEvaluation(ref_el, x) for x in v1] - nodes += [functional.IntegralMoment(ref_el, rule, f) for f in basis[2::2]] - nodes += [functional.IntegralMoment(ref_el, rule, f) for f in basis[1::2]] - - entity_ids = {0: {0: [0], 1: [1]}, - 1: {0: list(range(2, degree+1))}} + def __init__(self, ref_el, degree, poly_set): + quad_degree = 2 * degree + entity_ids = {} entity_permutations = {} - entity_permutations[0] = {0: {0: [0]}, 1: {0: [0]}} - entity_permutations[1] = {0: make_entity_permutations_simplex(1, degree - 1)} + + # vertex dofs + top = ref_el.get_topology() + nodes = [functional.PointEvaluation(ref_el, pt) for pt in ref_el.vertices] + nvertices = len(top[0]) + entity_ids[0] = {k: [k] for k in range(nvertices)} + entity_permutations[0] = {k: {0: [0]} for k in range(nvertices)} + + # facet dofs + for dim in range(1, len(top)-1): + entity_ids[dim] = {} + entity_permutations[dim] = {} + perms = {0: [0]} if dim == 0 else make_entity_permutations_simplex(dim, degree - dim) + + ref_facet = ref_el.construct_subelement(dim) + Q_ref = create_quadrature(ref_facet, quad_degree) + + # get a basis for P_k int H^1_0 on the reference facet + P = IntegratedLegendre(ref_facet, degree) + idofs = P.entity_dofs()[dim][0] + P0 = P.get_nodal_basis().take(idofs) + + phis = P0.tabulate(Q_ref.get_points())[(0,) * dim] + for entity in range(len(top[dim])): + Q = FacetQuadratureRule(ref_el, dim, entity, Q_ref) + + cur = len(nodes) + nodes.extend(functional.IntegralMoment(ref_el, Q, phi) for phi in phis) + entity_ids[dim][entity] = list(range(cur, cur + len(phis))) + entity_permutations[dim][entity] = perms + + # cell dofs + cur = len(nodes) + dim = ref_el.get_spatial_dimension() + Q = create_quadrature(ref_el, quad_degree) + pts = Q.get_points() + P = poly_set.tabulate(pts)[(0,) * dim] + + if dim == 1: + P = P[1:polynomial_dimension(ref_el, degree-1)] + wts = Q.get_weights() + dmat, _ = make_dmat(pts.flatten()) + phis = numpy.dot(numpy.multiply(P, wts), numpy.multiply(dmat.T, 1.0/wts)) + nodes.extend(functional.IntegralMoment(ref_el, Q, phi) for phi in phis[1::2]) + nodes.extend(functional.IntegralMoment(ref_el, Q, phi) for phi in phis[0::2]) + else: + # TODO use the L2-duals of the H1-orthonormal bubbles + # TODO get barycentric coordinates + x = pts.T + bubble = (1 - numpy.sum(x, axis=0)) * numpy.prod(x, axis=0) + phis = P[:polynomial_dimension(ref_el, degree - dim - 1)] * bubble + nodes.extend(functional.IntegralMoment(ref_el, Q, phi) for phi in phis) + + entity_ids[dim] = {0: list(range(cur, cur + len(phis)))} + entity_permutations[dim] = {0: make_entity_permutations_simplex(dim, degree - dim)} + super(IntegratedLegendreDual, self).__init__(nodes, ref_el, entity_ids, entity_permutations) @@ -79,10 +120,9 @@ class IntegratedLegendre(finite_element.CiarletElement): """1D continuous element with integrated Legendre polynomials.""" def __init__(self, ref_el, degree): - if ref_el.shape != reference_element.LINE: - raise ValueError("%s is only defined in one dimension." % type(self)) - rule = create_quadrature(ref_el, 2 * degree) - poly_set = LagrangePolynomialSet(ref_el, rule.get_points()) - dual = IntegratedLegendreDual(ref_el, degree, rule) + if ref_el.shape not in {POINT, LINE, TRIANGLE, TETRAHEDRON}: + raise ValueError("%s is only defined on simplices." % type(self)) + poly_set = polynomial_set.ONPolynomialSet(ref_el, degree) + dual = IntegratedLegendreDual(ref_el, degree, poly_set) formdegree = 0 # 0-form super(IntegratedLegendre, self).__init__(poly_set, dual, degree, formdegree) diff --git a/FIAT/nedelec_second_kind.py b/FIAT/nedelec_second_kind.py index f127b3f24..1115a8617 100644 --- a/FIAT/nedelec_second_kind.py +++ b/FIAT/nedelec_second_kind.py @@ -65,7 +65,7 @@ def generate_degrees_of_freedom(self, cell, degree, variant, interpolant_deg): assert (d in (2, 3)), "Second kind Nedelecs only implemented in 2/3D." # Zero vertex-based degrees of freedom (d+1 of these) - ids[0] = dict(list(zip(list(range(d + 1)), ([] for i in range(d + 1))))) + ids[0] = {i: [] for i in range(d + 1)} # (degree+1) degrees of freedom per entity of codimension 1 (edges) (edge_dofs, ids[1]) = self._generate_edge_dofs(cell, degree, 0, variant, interpolant_deg) @@ -115,7 +115,7 @@ def _generate_facet_dofs(self, codim, cell, degree, offset, variant, interpolant # Initialize empty dofs and identifiers (ids) num_facets = len(cell.get_topology()[codim]) dofs = [] - ids = dict(list(zip(list(range(num_facets)), ([] for i in range(num_facets))))) + ids = {i: [] for i in range(num_facets)} # Return empty info if not applicable rt_degree = degree - codim + 1 @@ -144,6 +144,7 @@ def _generate_facet_dofs(self, codim, cell, degree, offset, variant, interpolant # num_basis_functions x num_quad_points x num_components # Iterate over the facets + cur = offset for facet in range(num_facets): # Get the quadrature and Jacobian on this facet Q_facet = FacetQuadratureRule(cell, codim, facet, Q_ref) @@ -160,7 +161,8 @@ def _generate_facet_dofs(self, codim, cell, degree, offset, variant, interpolant dofs.extend(IntegralMoment(cell, Q_facet, phi) for phi in phis) # Assign identifiers (num RTs per face + previous edge dofs) - ids[facet] = list(range(offset + len(phis)*facet, offset + len(phis)*(facet + 1))) + ids[facet].extend(range(cur, cur + len(phis))) + cur += len(phis) return (dofs, ids) diff --git a/test/unit/test_hierarchical.py b/test/unit/test_hierarchical.py index e0455e399..8084fab30 100644 --- a/test/unit/test_hierarchical.py +++ b/test/unit/test_hierarchical.py @@ -25,7 +25,7 @@ @pytest.mark.parametrize("dim, family, degree", [(dim, f, degree - 1 if f == "DG" else degree) for f in ("CG", "DG") - for dim in range(1, 4 if f == "DG" else 2) + for dim in range(1, 4) for degree in range(1, 7)]) def test_hierarchical_basis_values(dim, family, degree): """Ensure that integrating a simple monomial produces the expected results.""" From 1a3ee2c7ecd948af3fe601eae5f710c0781350eb Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Sun, 3 Dec 2023 22:08:39 +0000 Subject: [PATCH 04/93] refactoring --- FIAT/hierarchical.py | 58 ++++++++++++++++++-------------------------- 1 file changed, 23 insertions(+), 35 deletions(-) diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index b9774317d..c0a501a83 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -8,13 +8,14 @@ import numpy -from FIAT import finite_element, dual_set, functional, polynomial_set -from FIAT.reference_element import POINT, LINE, TRIANGLE, TETRAHEDRON +from FIAT import finite_element, dual_set, functional +from FIAT.reference_element import POINT, LINE, TRIANGLE, TETRAHEDRON, ufc_simplex from FIAT.orientation_utils import make_entity_permutations_simplex from FIAT.barycentric_interpolation import make_dmat from FIAT.quadrature import FacetQuadratureRule from FIAT.quadrature_schemes import create_quadrature from FIAT.expansions import polynomial_dimension +from FIAT.polynomial_set import ONPolynomialSet class LegendreDual(dual_set.DualSet): @@ -45,7 +46,7 @@ class Legendre(finite_element.CiarletElement): def __init__(self, ref_el, degree): if ref_el.shape not in {POINT, LINE, TRIANGLE, TETRAHEDRON}: raise ValueError("%s is only defined on simplices." % type(self)) - poly_set = polynomial_set.ONPolynomialSet(ref_el, degree) + poly_set = ONPolynomialSet(ref_el, degree) dual = LegendreDual(ref_el, degree, poly_set) formdegree = ref_el.get_spatial_dimension() # n-form super(Legendre, self).__init__(poly_set, dual, degree, formdegree) @@ -65,55 +66,42 @@ def __init__(self, ref_el, degree, poly_set): entity_ids[0] = {k: [k] for k in range(nvertices)} entity_permutations[0] = {k: {0: [0]} for k in range(nvertices)} - # facet dofs - for dim in range(1, len(top)-1): + for dim in range(1, len(top)): entity_ids[dim] = {} entity_permutations[dim] = {} perms = {0: [0]} if dim == 0 else make_entity_permutations_simplex(dim, degree - dim) - ref_facet = ref_el.construct_subelement(dim) + ref_facet = ufc_simplex(dim) Q_ref = create_quadrature(ref_facet, quad_degree) + phis = self._tabulate_bubbles(ref_facet, degree, Q_ref) - # get a basis for P_k int H^1_0 on the reference facet - P = IntegratedLegendre(ref_facet, degree) - idofs = P.entity_dofs()[dim][0] - P0 = P.get_nodal_basis().take(idofs) - - phis = P0.tabulate(Q_ref.get_points())[(0,) * dim] for entity in range(len(top[dim])): - Q = FacetQuadratureRule(ref_el, dim, entity, Q_ref) - cur = len(nodes) + Q = FacetQuadratureRule(ref_el, dim, entity, Q_ref) nodes.extend(functional.IntegralMoment(ref_el, Q, phi) for phi in phis) entity_ids[dim][entity] = list(range(cur, cur + len(phis))) entity_permutations[dim][entity] = perms - # cell dofs - cur = len(nodes) + super(IntegratedLegendreDual, self).__init__(nodes, ref_el, entity_ids, entity_permutations) + + def _tabulate_bubbles(self, ref_el, degree, Q): + P = ONPolynomialSet(ref_el, degree) dim = ref_el.get_spatial_dimension() - Q = create_quadrature(ref_el, quad_degree) - pts = Q.get_points() - P = poly_set.tabulate(pts)[(0,) * dim] + qpts = Q.get_points() + P_at_qpts = P.tabulate(qpts)[(0,) * dim] if dim == 1: - P = P[1:polynomial_dimension(ref_el, degree-1)] + Ps = P_at_qpts[1:polynomial_dimension(ref_el, degree-1)] wts = Q.get_weights() - dmat, _ = make_dmat(pts.flatten()) - phis = numpy.dot(numpy.multiply(P, wts), numpy.multiply(dmat.T, 1.0/wts)) - nodes.extend(functional.IntegralMoment(ref_el, Q, phi) for phi in phis[1::2]) - nodes.extend(functional.IntegralMoment(ref_el, Q, phi) for phi in phis[0::2]) + dmat, _ = make_dmat(qpts.flatten()) + phis = numpy.dot(numpy.multiply(Ps, wts), numpy.multiply(dmat.T, 1.0/wts)) + phis = numpy.concatenate([phis[1::2], phis[0::2]]) else: # TODO use the L2-duals of the H1-orthonormal bubbles - # TODO get barycentric coordinates - x = pts.T - bubble = (1 - numpy.sum(x, axis=0)) * numpy.prod(x, axis=0) - phis = P[:polynomial_dimension(ref_el, degree - dim - 1)] * bubble - nodes.extend(functional.IntegralMoment(ref_el, Q, phi) for phi in phis) - - entity_ids[dim] = {0: list(range(cur, cur + len(phis)))} - entity_permutations[dim] = {0: make_entity_permutations_simplex(dim, degree - dim)} - - super(IntegratedLegendreDual, self).__init__(nodes, ref_el, entity_ids, entity_permutations) + x = qpts.T + bubble = (1.0 - numpy.sum(x, axis=0)) * numpy.prod(x, axis=0) + phis = P_at_qpts[:polynomial_dimension(ref_el, degree - dim - 1)] * bubble + return phis class IntegratedLegendre(finite_element.CiarletElement): @@ -122,7 +110,7 @@ class IntegratedLegendre(finite_element.CiarletElement): def __init__(self, ref_el, degree): if ref_el.shape not in {POINT, LINE, TRIANGLE, TETRAHEDRON}: raise ValueError("%s is only defined on simplices." % type(self)) - poly_set = polynomial_set.ONPolynomialSet(ref_el, degree) + poly_set = ONPolynomialSet(ref_el, degree) dual = IntegratedLegendreDual(ref_el, degree, poly_set) formdegree = 0 # 0-form super(IntegratedLegendre, self).__init__(poly_set, dual, degree, formdegree) From 3cd0acadc4b727e011b332d8a20400772d6aacca Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Sun, 3 Dec 2023 22:26:39 +0000 Subject: [PATCH 05/93] small fix --- FIAT/hierarchical.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index c0a501a83..ba2b42061 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -69,7 +69,7 @@ def __init__(self, ref_el, degree, poly_set): for dim in range(1, len(top)): entity_ids[dim] = {} entity_permutations[dim] = {} - perms = {0: [0]} if dim == 0 else make_entity_permutations_simplex(dim, degree - dim) + perms = make_entity_permutations_simplex(dim, degree - dim) ref_facet = ufc_simplex(dim) Q_ref = create_quadrature(ref_facet, quad_degree) From e3cc4430f18a02ffbb940fc6c5e4a86a0c014060 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Wed, 6 Dec 2023 00:24:26 +0000 Subject: [PATCH 06/93] DOFs are now moments of grad(v) against grad(bubble) --- FIAT/expansions.py | 46 +++++++++++++++++++--------------- FIAT/hierarchical.py | 36 +++++++++++++++++--------- FIAT/polynomial_set.py | 4 +-- test/unit/test_hierarchical.py | 9 ++++--- 4 files changed, 57 insertions(+), 38 deletions(-) diff --git a/FIAT/expansions.py b/FIAT/expansions.py index c17cffa0d..96ff66a3c 100644 --- a/FIAT/expansions.py +++ b/FIAT/expansions.py @@ -52,7 +52,7 @@ def jacobi_factors(x, y, z, dx, dy, dz): return fa, fb, fc, dfa, dfb, dfc -def dubiner_recurrence(dim, n, order, ref_pts, jacobian): +def dubiner_recurrence(dim, n, order, ref_pts, jacobian, alpha=0, beta=0): """Dubiner recurrence from (Kirby 2010)""" if order > 2: raise ValueError("Higher order derivatives not supported") @@ -78,18 +78,22 @@ def dubiner_recurrence(dim, n, order, ref_pts, jacobian): X = pad_coordinates(ref_pts, pad_dim) idx = (lambda p: p, morton_index2, morton_index3)[dim-1] - + base_alpha = alpha for codim in range(dim): # Extend the basis from codim to codim + 1 fa, fb, fc, dfa, dfb, dfc = jacobi_factors(*X[codim:codim+3], *dX[codim:codim+3]) ddfc = 2 * outer(dfb, dfb) for sub_index in reference_element.lattice_iter(0, n, codim): + alpha = base_alpha + 2 * sum(sub_index) + len(sub_index) # handle i = 1 icur = idx(*sub_index, 0) inext = idx(*sub_index, 1) - alpha = 2 * sum(sub_index) + len(sub_index) - b = 0.5 * alpha - a = b + 1.0 + apb = alpha + beta + if apb == 0 or apb == -1: + a = 0.5 * (alpha + beta) + 1.0 + b = 0.5 * (alpha - beta) + else: + a, b, c = jrc(alpha, beta, 0) factor = a * fa - b * fb phi[inext] = factor * phi[icur] if dphi is not None: @@ -101,7 +105,7 @@ def dubiner_recurrence(dim, n, order, ref_pts, jacobian): # general i by recurrence for i in range(1, n - sum(sub_index)): iprev, icur, inext = icur, inext, idx(*sub_index, i + 1) - a, b, c = jrc(alpha, 0, i) + a, b, c = jrc(alpha, beta, i) factor = a * fa - b * fb phi[inext] = factor * phi[icur] - c * (fc * phi[iprev]) if dphi is None: @@ -115,9 +119,9 @@ def dubiner_recurrence(dim, n, order, ref_pts, jacobian): c * (fc * ddphi[iprev] + sym_outer(dphi[iprev], dfc) + phi[iprev] * ddfc)) # normalize - for alpha in reference_element.lattice_iter(0, n+1, codim+1): - icur = idx(*alpha) - scale = math.sqrt(sum(alpha) + 0.5 * len(alpha)) + for index in reference_element.lattice_iter(0, n+1, codim+1): + icur = idx(*index) + scale = math.sqrt(sum(index) + 0.5 * len(index)) for result in results: result[icur] *= scale return results @@ -157,8 +161,10 @@ def __new__(cls, ref_el, *args, **kwargs): else: raise ValueError("Invalid reference element type.") - def __init__(self, ref_el): + def __init__(self, ref_el, alpha=0, beta=0): self.ref_el = ref_el + self.alpha = alpha + self.beta = beta dim = ref_el.get_spatial_dimension() self.base_ref_el = reference_element.default_simplex(dim) v1 = ref_el.get_vertices() @@ -184,7 +190,7 @@ def _tabulate(self, n, pts, order=0): """A version of tabulate() that also works for a single point. """ D = self.ref_el.get_spatial_dimension() - return dubiner_recurrence(D, n, order, self._mapping(pts), self.A) + return dubiner_recurrence(D, n, order, self._mapping(pts), self.A, alpha=self.alpha, beta=self.beta) def get_dmats(self, degree): """Returns a numpy array with the expansion coefficients dmat[k, j, i] @@ -266,10 +272,10 @@ def tabulate_jet(self, n, pts, order=1): class PointExpansionSet(ExpansionSet): """Evaluates the point basis on a point reference element.""" - def __init__(self, ref_el): + def __init__(self, ref_el, **kwargs): if ref_el.get_spatial_dimension() != 0: raise ValueError("Must have a point") - super(PointExpansionSet, self).__init__(ref_el) + super(PointExpansionSet, self).__init__(ref_el, **kwargs) def tabulate(self, n, pts): """Returns a numpy array A[i,j] = phi_i(pts[j]) = 1.0.""" @@ -279,10 +285,10 @@ def tabulate(self, n, pts): class LineExpansionSet(ExpansionSet): """Evaluates the Legendre basis on a line reference element.""" - def __init__(self, ref_el): + def __init__(self, ref_el, **kwargs): if ref_el.get_spatial_dimension() != 1: raise Exception("Must have a line") - super(LineExpansionSet, self).__init__(ref_el) + super(LineExpansionSet, self).__init__(ref_el, **kwargs) def _tabulate(self, n, pts, order=0): """Returns a tuple of (vals, derivs) such that @@ -293,7 +299,7 @@ def _tabulate(self, n, pts, order=0): for k in range(order+1): v = numpy.zeros((n + 1, len(xs)), xs.dtype) if n >= k: - v[k:] = jacobi.eval_jacobi_batch(k, k, n-k, xs) + v[k:] = jacobi.eval_jacobi_batch(self.alpha+k, self.beta+k, n-k, xs) for p in range(n + 1): v[p] *= scale[p] scale[p] *= 0.5 * (p + k + 1) * self.A[0, 0] @@ -306,18 +312,18 @@ def _tabulate(self, n, pts, order=0): class TriangleExpansionSet(ExpansionSet): """Evaluates the orthonormal Dubiner basis on a triangular reference element.""" - def __init__(self, ref_el): + def __init__(self, ref_el, **kwargs): if ref_el.get_spatial_dimension() != 2: raise Exception("Must have a triangle") - super(TriangleExpansionSet, self).__init__(ref_el) + super(TriangleExpansionSet, self).__init__(ref_el, **kwargs) class TetrahedronExpansionSet(ExpansionSet): """Collapsed orthonormal polynomial expansion on a tetrahedron.""" - def __init__(self, ref_el): + def __init__(self, ref_el, **kwargs): if ref_el.get_spatial_dimension() != 3: raise Exception("Must be a tetrahedron") - super(TetrahedronExpansionSet, self).__init__(ref_el) + super(TetrahedronExpansionSet, self).__init__(ref_el, **kwargs) def polynomial_dimension(ref_el, degree): diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index ba2b42061..1a93bab4e 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -14,7 +14,6 @@ from FIAT.barycentric_interpolation import make_dmat from FIAT.quadrature import FacetQuadratureRule from FIAT.quadrature_schemes import create_quadrature -from FIAT.expansions import polynomial_dimension from FIAT.polynomial_set import ONPolynomialSet @@ -55,7 +54,6 @@ def __init__(self, ref_el, degree): class IntegratedLegendreDual(dual_set.DualSet): """The dual basis for integrated Legendre elements.""" def __init__(self, ref_el, degree, poly_set): - quad_degree = 2 * degree entity_ids = {} entity_permutations = {} @@ -71,6 +69,7 @@ def __init__(self, ref_el, degree, poly_set): entity_permutations[dim] = {} perms = make_entity_permutations_simplex(dim, degree - dim) + quad_degree = 2 * degree ref_facet = ufc_simplex(dim) Q_ref = create_quadrature(ref_facet, quad_degree) phis = self._tabulate_bubbles(ref_facet, degree, Q_ref) @@ -85,22 +84,35 @@ def __init__(self, ref_el, degree, poly_set): super(IntegratedLegendreDual, self).__init__(nodes, ref_el, entity_ids, entity_permutations) def _tabulate_bubbles(self, ref_el, degree, Q): - P = ONPolynomialSet(ref_el, degree) - dim = ref_el.get_spatial_dimension() qpts = Q.get_points() + dim = ref_el.get_spatial_dimension() + k = degree - dim - 1 + if k < 0: + return numpy.zeros((0, len(qpts))) + P = ONPolynomialSet(ref_el, k, alpha=1, beta=1) P_at_qpts = P.tabulate(qpts)[(0,) * dim] + x = qpts.T + bubble = (1.0 - numpy.sum(x, axis=0)) * numpy.prod(x, axis=0) + bubbles = P_at_qpts * bubble + + W = Q.get_weights() if dim == 1: - Ps = P_at_qpts[1:polynomial_dimension(ref_el, degree-1)] - wts = Q.get_weights() dmat, _ = make_dmat(qpts.flatten()) - phis = numpy.dot(numpy.multiply(Ps, wts), numpy.multiply(dmat.T, 1.0/wts)) - phis = numpy.concatenate([phis[1::2], phis[0::2]]) + K = numpy.dot(numpy.multiply(dmat, W), dmat.T) else: - # TODO use the L2-duals of the H1-orthonormal bubbles - x = qpts.T - bubble = (1.0 - numpy.sum(x, axis=0)) * numpy.prod(x, axis=0) - phis = P_at_qpts[:polynomial_dimension(ref_el, degree - dim - 1)] * bubble + # Get an ON basis + P = ONPolynomialSet(ref_el, degree) + tab = P.tabulate(qpts, 1) + # Assemble a stiffness matrix in the ON basis + moments = lambda dv: numpy.dot(numpy.multiply(dv, W), dv.T) + K = sum(moments(tab[alpha]) for alpha in tab if sum(alpha) == 1) + # Change of basis to Lagrange polynomials in the quadrauture nodes + v = numpy.multiply(tab[(0, ) * dim], W) + K = numpy.dot(numpy.dot(v.T, K), v) + + phis = numpy.multiply(numpy.dot(bubbles, K), 1/W) + phis = numpy.concatenate([phis[1::2], phis[0::2]]) return phis diff --git a/FIAT/polynomial_set.py b/FIAT/polynomial_set.py index 3a8d376be..81fbf81f6 100644 --- a/FIAT/polynomial_set.py +++ b/FIAT/polynomial_set.py @@ -120,7 +120,7 @@ class ONPolynomialSet(PolynomialSet): """ - def __init__(self, ref_el, degree, shape=tuple()): + def __init__(self, ref_el, degree, shape=tuple(), alpha=0, beta=0): if shape == tuple(): num_components = 1 @@ -130,7 +130,7 @@ def __init__(self, ref_el, degree, shape=tuple()): num_exp_functions = expansions.polynomial_dimension(ref_el, degree) num_members = num_components * num_exp_functions embedded_degree = degree - expansion_set = expansions.ExpansionSet(ref_el) + expansion_set = expansions.ExpansionSet(ref_el, alpha=alpha, beta=beta) # set up coefficients if shape == tuple(): diff --git a/test/unit/test_hierarchical.py b/test/unit/test_hierarchical.py index 8084fab30..75c4a640d 100644 --- a/test/unit/test_hierarchical.py +++ b/test/unit/test_hierarchical.py @@ -29,10 +29,10 @@ for degree in range(1, 7)]) def test_hierarchical_basis_values(dim, family, degree): """Ensure that integrating a simple monomial produces the expected results.""" - from FIAT import ufc_simplex, Legendre, IntegratedLegendre, create_quadrature + from FIAT import ufc_simplex, Legendre, IntegratedLegendre, make_quadrature s = ufc_simplex(dim) - q = create_quadrature(s, degree + 1) + q = make_quadrature(s, degree+1) if family == "CG": fe = IntegratedLegendre(s, degree) else: @@ -49,7 +49,7 @@ def test_hierarchical_basis_values(dim, family, degree): @pytest.mark.parametrize("family, degree", [(f, degree - 1 if f == "DG" else degree) for f in ("CG", "DG") - for degree in range(1, 7)]) + for degree in range(1, 10, 2)]) def test_hierarchical_sparsity(family, degree): from FIAT import ufc_simplex, Legendre, IntegratedLegendre, make_quadrature @@ -66,7 +66,8 @@ def test_hierarchical_sparsity(family, degree): moments = lambda v, u: np.dot(np.multiply(v, q.get_weights()), u.T) tab = fe.tabulate(len(expected)-1, q.get_points()) for k, ennz in enumerate(expected): - assert nnz(moments(tab[(k, )], tab[(k, )])) == ennz + A = sum(moments(tab[alpha], tab[alpha]) for alpha in tab if sum(alpha) == k) + assert nnz(A) == ennz if __name__ == '__main__': From 3064e07edaba6ac65c53d194fee2f9fa12358571 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Wed, 6 Dec 2023 00:42:31 +0000 Subject: [PATCH 07/93] small changes --- FIAT/hierarchical.py | 4 ++-- test/unit/test_hierarchical.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index 1a93bab4e..c01a5c8b1 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -101,13 +101,13 @@ def _tabulate_bubbles(self, ref_el, degree, Q): dmat, _ = make_dmat(qpts.flatten()) K = numpy.dot(numpy.multiply(dmat, W), dmat.T) else: - # Get an ON basis + # Get ON basis P = ONPolynomialSet(ref_el, degree) tab = P.tabulate(qpts, 1) # Assemble a stiffness matrix in the ON basis moments = lambda dv: numpy.dot(numpy.multiply(dv, W), dv.T) K = sum(moments(tab[alpha]) for alpha in tab if sum(alpha) == 1) - # Change of basis to Lagrange polynomials in the quadrauture nodes + # Change of basis to Lagrange polynomials at the quadrature nodes v = numpy.multiply(tab[(0, ) * dim], W) K = numpy.dot(numpy.dot(v.T, K), v) diff --git a/test/unit/test_hierarchical.py b/test/unit/test_hierarchical.py index 75c4a640d..df2f51e6c 100644 --- a/test/unit/test_hierarchical.py +++ b/test/unit/test_hierarchical.py @@ -49,7 +49,7 @@ def test_hierarchical_basis_values(dim, family, degree): @pytest.mark.parametrize("family, degree", [(f, degree - 1 if f == "DG" else degree) for f in ("CG", "DG") - for degree in range(1, 10, 2)]) + for degree in range(1, 7)]) def test_hierarchical_sparsity(family, degree): from FIAT import ufc_simplex, Legendre, IntegratedLegendre, make_quadrature From 8782f77c7291ac906f1f7ff77954c9cbaf432d8e Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Wed, 6 Dec 2023 18:48:36 +0000 Subject: [PATCH 08/93] fix ExpansionSet.__new__ --- FIAT/expansions.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/FIAT/expansions.py b/FIAT/expansions.py index 96ff66a3c..901583b7a 100644 --- a/FIAT/expansions.py +++ b/FIAT/expansions.py @@ -145,19 +145,20 @@ def xi_tetrahedron(eta): class ExpansionSet(object): - def __new__(cls, ref_el, *args, **kwargs): + def __new__(cls, *args, **kwargs): """Returns an ExpansionSet instance appopriate for the given reference element.""" if cls is not ExpansionSet: return super(ExpansionSet, cls).__new__(cls) + ref_el = args[0] if ref_el.get_shape() == reference_element.POINT: - return PointExpansionSet(ref_el) + return PointExpansionSet(*args, **kwargs) elif ref_el.get_shape() == reference_element.LINE: - return LineExpansionSet(ref_el) + return LineExpansionSet(*args, **kwargs) elif ref_el.get_shape() == reference_element.TRIANGLE: - return TriangleExpansionSet(ref_el) + return TriangleExpansionSet(*args, **kwargs) elif ref_el.get_shape() == reference_element.TETRAHEDRON: - return TetrahedronExpansionSet(ref_el) + return TetrahedronExpansionSet(*args, **kwargs) else: raise ValueError("Invalid reference element type.") From be59b279e74833bd40cf619ab398f861fe68b416 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Sat, 9 Dec 2023 22:51:30 +0000 Subject: [PATCH 09/93] add bubbles from Beuchler and Schoberl --- FIAT/expansions.py | 47 ++++++++++++++++++++++++++++-------------- FIAT/hierarchical.py | 37 +++++++++++++++++++++------------ FIAT/polynomial_set.py | 4 ++-- 3 files changed, 58 insertions(+), 30 deletions(-) diff --git a/FIAT/expansions.py b/FIAT/expansions.py index 901583b7a..28ea6a26d 100644 --- a/FIAT/expansions.py +++ b/FIAT/expansions.py @@ -29,6 +29,20 @@ def jrc(a, b, n): return an, bn, cn +def bubble_jrc(a, b, n): + """Integrated Jacobi recurrence coefficients""" + if n == 1: + an = (a + b + 2) / 4 + bn = (a - 3*b - 2) / 4 + cn = 0.0 + else: + # TODO check dependence on b + an = (2*n-1+a+b) * (2*n+a+b) / (2*(n+1)*(n+a+b)) + bn = (a+b)*(a-b-2) * (2*n-1+a+b) / (2*(n+1)*(2*n-2+a+b)*(n+a+b)) + cn = (n+a-2)*(n+b-1)*(2*n+a+b) / ((n+1)*(n+a+b)*(2*n-2+a+b)) + return an, bn, cn + + def pad_coordinates(ref_pts, embedded_dim): """Pad reference coordinates by appending -1.0.""" return tuple(ref_pts) + (-1.0, )*(embedded_dim - len(ref_pts)) @@ -52,7 +66,7 @@ def jacobi_factors(x, y, z, dx, dy, dz): return fa, fb, fc, dfa, dfb, dfc -def dubiner_recurrence(dim, n, order, ref_pts, jacobian, alpha=0, beta=0): +def dubiner_recurrence(dim, n, order, ref_pts, jacobian, bubble=False): """Dubiner recurrence from (Kirby 2010)""" if order > 2: raise ValueError("Higher order derivatives not supported") @@ -76,24 +90,23 @@ def dubiner_recurrence(dim, n, order, ref_pts, jacobian, alpha=0, beta=0): if dim > 3 or dim < 0: raise ValueError("Invalid number of spatial dimensions") + coefficients = bubble_jrc if bubble else jrc X = pad_coordinates(ref_pts, pad_dim) idx = (lambda p: p, morton_index2, morton_index3)[dim-1] - base_alpha = alpha for codim in range(dim): # Extend the basis from codim to codim + 1 fa, fb, fc, dfa, dfb, dfc = jacobi_factors(*X[codim:codim+3], *dX[codim:codim+3]) ddfc = 2 * outer(dfb, dfb) for sub_index in reference_element.lattice_iter(0, n, codim): - alpha = base_alpha + 2 * sum(sub_index) + len(sub_index) + alpha = 2 * sum(sub_index) + len(sub_index) # handle i = 1 icur = idx(*sub_index, 0) inext = idx(*sub_index, 1) - apb = alpha + beta - if apb == 0 or apb == -1: - a = 0.5 * (alpha + beta) + 1.0 - b = 0.5 * (alpha - beta) + if bubble: + a = b = 1.0 else: - a, b, c = jrc(alpha, beta, 0) + a = 0.5 * alpha + 1.0 + b = 0.5 * alpha factor = a * fa - b * fb phi[inext] = factor * phi[icur] if dphi is not None: @@ -105,7 +118,7 @@ def dubiner_recurrence(dim, n, order, ref_pts, jacobian, alpha=0, beta=0): # general i by recurrence for i in range(1, n - sum(sub_index)): iprev, icur, inext = icur, inext, idx(*sub_index, i + 1) - a, b, c = jrc(alpha, beta, i) + a, b, c = coefficients(alpha, 0, i) factor = a * fa - b * fb phi[inext] = factor * phi[icur] - c * (fc * phi[iprev]) if dphi is None: @@ -162,17 +175,17 @@ def __new__(cls, *args, **kwargs): else: raise ValueError("Invalid reference element type.") - def __init__(self, ref_el, alpha=0, beta=0): + def __init__(self, ref_el, bubble=False): self.ref_el = ref_el - self.alpha = alpha - self.beta = beta + self.bubble = bubble dim = ref_el.get_spatial_dimension() self.base_ref_el = reference_element.default_simplex(dim) v1 = ref_el.get_vertices() v2 = self.base_ref_el.get_vertices() self.A, self.b = reference_element.make_affine_mapping(v1, v2) self.mapping = lambda x: numpy.dot(self.A, x) + self.b - self.scale = numpy.sqrt(numpy.linalg.det(self.A)) + detA = numpy.sqrt(numpy.linalg.det(numpy.dot(self.A.T, self.A))) + self.scale = numpy.sqrt(detA) self._dmats_cache = {} def get_num_members(self, n): @@ -191,7 +204,7 @@ def _tabulate(self, n, pts, order=0): """A version of tabulate() that also works for a single point. """ D = self.ref_el.get_spatial_dimension() - return dubiner_recurrence(D, n, order, self._mapping(pts), self.A, alpha=self.alpha, beta=self.beta) + return dubiner_recurrence(D, n, order, self._mapping(pts), self.A, bubble=self.bubble) def get_dmats(self, degree): """Returns a numpy array with the expansion coefficients dmat[k, j, i] @@ -294,13 +307,17 @@ def __init__(self, ref_el, **kwargs): def _tabulate(self, n, pts, order=0): """Returns a tuple of (vals, derivs) such that vals[i,j] = phi_i(pts[j]), derivs[i,j] = D vals[i,j].""" + if self.bubble: + return super(LineExpansionSet, self)._tabulate(n, pts, order=order) + xs = self._mapping(pts).T results = [] scale = numpy.sqrt(0.5 + numpy.arange(n+1)) for k in range(order+1): v = numpy.zeros((n + 1, len(xs)), xs.dtype) if n >= k: - v[k:] = jacobi.eval_jacobi_batch(self.alpha+k, self.beta+k, n-k, xs) + v[k:] = jacobi.eval_jacobi_batch(k, k, n-k, xs) + for p in range(n + 1): v[p] *= scale[p] scale[p] *= 0.5 * (p + k + 1) * self.A[0, 0] diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index c01a5c8b1..72cf217f3 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -14,7 +14,8 @@ from FIAT.barycentric_interpolation import make_dmat from FIAT.quadrature import FacetQuadratureRule from FIAT.quadrature_schemes import create_quadrature -from FIAT.polynomial_set import ONPolynomialSet +from FIAT.polynomial_set import ONPolynomialSet, mis +from FIAT.expansions import morton_index2, morton_index3, polynomial_dimension class LegendreDual(dual_set.DualSet): @@ -72,7 +73,7 @@ def __init__(self, ref_el, degree, poly_set): quad_degree = 2 * degree ref_facet = ufc_simplex(dim) Q_ref = create_quadrature(ref_facet, quad_degree) - phis = self._tabulate_bubbles(ref_facet, degree, Q_ref) + phis = self._tabulate_bubbles(ref_facet, degree, Q_ref, P=poly_set if dim == len(top)-1 else None) for entity in range(len(top[dim])): cur = len(nodes) @@ -83,18 +84,28 @@ def __init__(self, ref_el, degree, poly_set): super(IntegratedLegendreDual, self).__init__(nodes, ref_el, entity_ids, entity_permutations) - def _tabulate_bubbles(self, ref_el, degree, Q): + def _tabulate_bubbles(self, ref_el, degree, Q, P=None): qpts = Q.get_points() dim = ref_el.get_spatial_dimension() k = degree - dim - 1 if k < 0: return numpy.zeros((0, len(qpts))) - P = ONPolynomialSet(ref_el, k, alpha=1, beta=1) - P_at_qpts = P.tabulate(qpts)[(0,) * dim] - x = qpts.T - bubble = (1.0 - numpy.sum(x, axis=0)) * numpy.prod(x, axis=0) - bubbles = P_at_qpts * bubble + if P is None: + P = ONPolynomialSet(ref_el, degree, bubble=True) + if dim == 1: + indices = list(range(2, degree+1)) + else: + idx = (morton_index2, morton_index3)[dim-2] + indices = [] + for p in range(1, degree+1): + for alpha in mis(dim, p): + if alpha[0] > 1 and min(alpha[1:]) > 0: + indices.append(idx(*alpha)) + + assert len(indices) == polynomial_dimension(ref_el, k) + bubbles = P.take(indices) + bubbles_table = bubbles.tabulate(qpts)[(0,) * dim] W = Q.get_weights() if dim == 1: @@ -103,15 +114,15 @@ def _tabulate_bubbles(self, ref_el, degree, Q): else: # Get ON basis P = ONPolynomialSet(ref_el, degree) - tab = P.tabulate(qpts, 1) + P_table = P.tabulate(qpts, 1) # Assemble a stiffness matrix in the ON basis moments = lambda dv: numpy.dot(numpy.multiply(dv, W), dv.T) - K = sum(moments(tab[alpha]) for alpha in tab if sum(alpha) == 1) + K = sum(moments(P_table[alpha]) for alpha in P_table if sum(alpha) == 1) # Change of basis to Lagrange polynomials at the quadrature nodes - v = numpy.multiply(tab[(0, ) * dim], W) + v = numpy.multiply(P_table[(0, ) * dim], W) K = numpy.dot(numpy.dot(v.T, K), v) - phis = numpy.multiply(numpy.dot(bubbles, K), 1/W) + phis = numpy.multiply(numpy.dot(bubbles_table, K), 1/W) phis = numpy.concatenate([phis[1::2], phis[0::2]]) return phis @@ -122,7 +133,7 @@ class IntegratedLegendre(finite_element.CiarletElement): def __init__(self, ref_el, degree): if ref_el.shape not in {POINT, LINE, TRIANGLE, TETRAHEDRON}: raise ValueError("%s is only defined on simplices." % type(self)) - poly_set = ONPolynomialSet(ref_el, degree) + poly_set = ONPolynomialSet(ref_el, degree, bubble=True) dual = IntegratedLegendreDual(ref_el, degree, poly_set) formdegree = 0 # 0-form super(IntegratedLegendre, self).__init__(poly_set, dual, degree, formdegree) diff --git a/FIAT/polynomial_set.py b/FIAT/polynomial_set.py index 81fbf81f6..c05332955 100644 --- a/FIAT/polynomial_set.py +++ b/FIAT/polynomial_set.py @@ -120,7 +120,7 @@ class ONPolynomialSet(PolynomialSet): """ - def __init__(self, ref_el, degree, shape=tuple(), alpha=0, beta=0): + def __init__(self, ref_el, degree, shape=tuple(), bubble=False): if shape == tuple(): num_components = 1 @@ -130,7 +130,7 @@ def __init__(self, ref_el, degree, shape=tuple(), alpha=0, beta=0): num_exp_functions = expansions.polynomial_dimension(ref_el, degree) num_members = num_components * num_exp_functions embedded_degree = degree - expansion_set = expansions.ExpansionSet(ref_el, alpha=alpha, beta=beta) + expansion_set = expansions.ExpansionSet(ref_el, bubble=bubble) # set up coefficients if shape == tuple(): From f57309e3f65c24e1a514de6d0fbf27abd480b04b Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Sun, 10 Dec 2023 23:17:38 +0000 Subject: [PATCH 10/93] refactor bubbles, add a test --- FIAT/expansions.py | 11 +++++---- FIAT/hierarchical.py | 54 ++++++++++++++---------------------------- FIAT/polynomial_set.py | 25 +++++++++++++++++++ test/unit/test_fiat.py | 26 ++++++++++++++++---- 4 files changed, 70 insertions(+), 46 deletions(-) diff --git a/FIAT/expansions.py b/FIAT/expansions.py index 28ea6a26d..2dd42ae0a 100644 --- a/FIAT/expansions.py +++ b/FIAT/expansions.py @@ -24,7 +24,7 @@ def morton_index3(p, q=0, r=0): def jrc(a, b, n): """Jacobi recurrence coefficients""" an = (2*n+1+a+b)*(2*n+2+a+b) / (2*(n+1)*(n+1+a+b)) - bn = (a*a-b*b) * (2*n+1+a+b) / (2*(n+1)*(2*n+a+b)*(n+1+a+b)) + bn = (a+b)*(a-b)*(2*n+1+a+b) / (2*(n+1)*(n+1+a+b)*(2*n+a+b)) cn = (n+a)*(n+b)*(2*n+2+a+b) / ((n+1)*(n+1+a+b)*(2*n+a+b)) return an, bn, cn @@ -36,9 +36,10 @@ def bubble_jrc(a, b, n): bn = (a - 3*b - 2) / 4 cn = 0.0 else: + # an, bn, cn = jrc(a-1, b+1, n-1) # TODO check dependence on b - an = (2*n-1+a+b) * (2*n+a+b) / (2*(n+1)*(n+a+b)) - bn = (a+b)*(a-b-2) * (2*n-1+a+b) / (2*(n+1)*(2*n-2+a+b)*(n+a+b)) + an = (2*n-1+a+b)*(2*n+a+b) / (2*(n+1)*(n+a+b)) + bn = (a+b)*(a-b-2)*(2*n-1+a+b) / (2*(n+1)*(n+a+b)*(2*n-2+a+b)) cn = (n+a-2)*(n+b-1)*(2*n+a+b) / ((n+1)*(n+a+b)*(2*n-2+a+b)) return an, bn, cn @@ -98,13 +99,14 @@ def dubiner_recurrence(dim, n, order, ref_pts, jacobian, bubble=False): fa, fb, fc, dfa, dfb, dfc = jacobi_factors(*X[codim:codim+3], *dX[codim:codim+3]) ddfc = 2 * outer(dfb, dfb) for sub_index in reference_element.lattice_iter(0, n, codim): - alpha = 2 * sum(sub_index) + len(sub_index) # handle i = 1 icur = idx(*sub_index, 0) inext = idx(*sub_index, 1) if bubble: + alpha = 2 * sum(sub_index) a = b = 1.0 else: + alpha = 2 * sum(sub_index) + len(sub_index) a = 0.5 * alpha + 1.0 b = 0.5 * alpha factor = a * fa - b * fb @@ -317,7 +319,6 @@ def _tabulate(self, n, pts, order=0): v = numpy.zeros((n + 1, len(xs)), xs.dtype) if n >= k: v[k:] = jacobi.eval_jacobi_batch(k, k, n-k, xs) - for p in range(n + 1): v[p] *= scale[p] scale[p] *= 0.5 * (p + k + 1) * self.A[0, 0] diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index 72cf217f3..afb42720b 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -14,8 +14,7 @@ from FIAT.barycentric_interpolation import make_dmat from FIAT.quadrature import FacetQuadratureRule from FIAT.quadrature_schemes import create_quadrature -from FIAT.polynomial_set import ONPolynomialSet, mis -from FIAT.expansions import morton_index2, morton_index3, polynomial_dimension +from FIAT.polynomial_set import ONPolynomialSet, make_bubbles class LegendreDual(dual_set.DualSet): @@ -59,12 +58,12 @@ def __init__(self, ref_el, degree, poly_set): entity_permutations = {} # vertex dofs - top = ref_el.get_topology() - nodes = [functional.PointEvaluation(ref_el, pt) for pt in ref_el.vertices] - nvertices = len(top[0]) - entity_ids[0] = {k: [k] for k in range(nvertices)} - entity_permutations[0] = {k: {0: [0]} for k in range(nvertices)} + vertices = ref_el.get_vertices() + nodes = [functional.PointEvaluation(ref_el, pt) for pt in vertices] + entity_ids[0] = {k: [k] for k in range(len(vertices))} + entity_permutations[0] = {k: {0: [0]} for k in range(len(vertices))} + top = ref_el.get_topology() for dim in range(1, len(top)): entity_ids[dim] = {} entity_permutations[dim] = {} @@ -73,7 +72,8 @@ def __init__(self, ref_el, degree, poly_set): quad_degree = 2 * degree ref_facet = ufc_simplex(dim) Q_ref = create_quadrature(ref_facet, quad_degree) - phis = self._tabulate_bubbles(ref_facet, degree, Q_ref, P=poly_set if dim == len(top)-1 else None) + P = poly_set if dim == len(top)-1 else None + phis = self._tabulate_L2_duals(ref_facet, degree, Q_ref, poly_set=P) for entity in range(len(top[dim])): cur = len(nodes) @@ -84,51 +84,33 @@ def __init__(self, ref_el, degree, poly_set): super(IntegratedLegendreDual, self).__init__(nodes, ref_el, entity_ids, entity_permutations) - def _tabulate_bubbles(self, ref_el, degree, Q, P=None): + def _tabulate_L2_duals(self, ref_el, degree, Q, poly_set=None): qpts = Q.get_points() + qwts = Q.get_weights() dim = ref_el.get_spatial_dimension() - k = degree - dim - 1 - if k < 0: - return numpy.zeros((0, len(qpts))) - - if P is None: - P = ONPolynomialSet(ref_el, degree, bubble=True) - if dim == 1: - indices = list(range(2, degree+1)) - else: - idx = (morton_index2, morton_index3)[dim-2] - indices = [] - for p in range(1, degree+1): - for alpha in mis(dim, p): - if alpha[0] > 1 and min(alpha[1:]) > 0: - indices.append(idx(*alpha)) - - assert len(indices) == polynomial_dimension(ref_el, k) - bubbles = P.take(indices) - bubbles_table = bubbles.tabulate(qpts)[(0,) * dim] - - W = Q.get_weights() + moments = lambda v: numpy.dot(numpy.multiply(v, qwts), v.T) if dim == 1: + # Assemble a stiffness matrix in the Lagrange basis dmat, _ = make_dmat(qpts.flatten()) - K = numpy.dot(numpy.multiply(dmat, W), dmat.T) + K = numpy.dot(numpy.multiply(dmat, qwts), dmat.T) else: # Get ON basis P = ONPolynomialSet(ref_el, degree) P_table = P.tabulate(qpts, 1) # Assemble a stiffness matrix in the ON basis - moments = lambda dv: numpy.dot(numpy.multiply(dv, W), dv.T) K = sum(moments(P_table[alpha]) for alpha in P_table if sum(alpha) == 1) # Change of basis to Lagrange polynomials at the quadrature nodes - v = numpy.multiply(P_table[(0, ) * dim], W) + v = numpy.multiply(P_table[(0, ) * dim], qwts) K = numpy.dot(numpy.dot(v.T, K), v) - phis = numpy.multiply(numpy.dot(bubbles_table, K), 1/W) - phis = numpy.concatenate([phis[1::2], phis[0::2]]) + B = make_bubbles(ref_el, degree, poly_set=poly_set) + B_at_qpts = B.tabulate(qpts)[(0,) * dim] + phis = numpy.multiply(numpy.dot(B_at_qpts, K), 1/qwts) return phis class IntegratedLegendre(finite_element.CiarletElement): - """1D continuous element with integrated Legendre polynomials.""" + """Simplicial continuous element with integrated Legendre polynomials.""" def __init__(self, ref_el, degree): if ref_el.shape not in {POINT, LINE, TRIANGLE, TETRAHEDRON}: diff --git a/FIAT/polynomial_set.py b/FIAT/polynomial_set.py index c05332955..d167b1fb2 100644 --- a/FIAT/polynomial_set.py +++ b/FIAT/polynomial_set.py @@ -241,3 +241,28 @@ def __init__(self, ref_el, degree, size=None): super(ONSymTensorPolynomialSet, self).__init__(ref_el, degree, embedded_degree, expansion_set, coeffs) + + +def make_bubbles(ref_el, degree, shape=(), poly_set=None): + """Construct a polynomial set with bubbles up to the given degree. + + """ + from itertools import chain + + dim = ref_el.get_spatial_dimension() + degrees = chain(range(3, degree+1, 2), range(2, degree+1, 2)) + if dim == 1: + indices = list(degrees) + else: + idx = (expansions.morton_index2, expansions.morton_index3)[dim-2] + indices = [] + for p in degrees: + for alpha in mis(dim, p): + if alpha[0] > 1 and min(alpha[1:]) > 0: + indices.append(idx(*alpha)) + + assert len(indices) == expansions.polynomial_dimension(ref_el, degree - dim - 1) + if poly_set is None: + poly_set = ONPolynomialSet(ref_el, degree, shape=shape, bubble=True) + bubbles = poly_set.take(indices) + return bubbles diff --git a/test/unit/test_fiat.py b/test/unit/test_fiat.py index 3a393c6b6..72c4a9229 100644 --- a/test/unit/test_fiat.py +++ b/test/unit/test_fiat.py @@ -590,11 +590,7 @@ def basis(dim, p, q=0, r=0): return f def eval_basis(f, pt): - fval = f - for coord, pval in zip(eta, duffy_coords(pt)): - fval = fval.subs(coord, pval) - fval = float(fval) - return fval + return float(f.subs(dict(zip(eta, duffy_coords(pt))))) for i in range(n + 1): for indices in polynomial_set.mis(dim, i): @@ -604,6 +600,26 @@ def eval_basis(f, pt): assert np.allclose(uh, exact, atol=1E-14) +@pytest.mark.parametrize('cell', [I, T, S]) +def test_make_bubbles(cell): + from FIAT.expansions import polynomial_dimension + from FIAT.polynomial_set import make_bubbles, PolynomialSet + degree = 10 + + top = cell.get_topology() + points = [] + for dim in range(len(top)-1): + for entity in range(len(top[dim])): + points.extend(cell.make_points(dim, entity, degree, variant="gl")) + + sd = cell.get_spatial_dimension() + B = make_bubbles(cell, degree) + assert isinstance(B, PolynomialSet) + assert B.degree == degree + assert B.get_num_members() == polynomial_dimension(cell, degree - sd - 1) + assert np.allclose(B.tabulate(points)[(0,)*sd], 0) + + if __name__ == '__main__': import os pytest.main(os.path.abspath(__file__)) From dad23037c968a526e581dead0c31a89db43321fd Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Mon, 11 Dec 2023 10:15:41 +0000 Subject: [PATCH 11/93] update test --- test/unit/test_fiat.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/test/unit/test_fiat.py b/test/unit/test_fiat.py index 72c4a9229..5eb82d56f 100644 --- a/test/unit/test_fiat.py +++ b/test/unit/test_fiat.py @@ -602,22 +602,33 @@ def eval_basis(f, pt): @pytest.mark.parametrize('cell', [I, T, S]) def test_make_bubbles(cell): + from FIAT.reference_element import make_lattice from FIAT.expansions import polynomial_dimension from FIAT.polynomial_set import make_bubbles, PolynomialSet + degree = 10 + sd = cell.get_spatial_dimension() + B = make_bubbles(cell, degree) + assert isinstance(B, PolynomialSet) + assert B.degree == degree + assert B.get_num_members() == polynomial_dimension(cell, degree - sd - 1) + # test values on the boundary top = cell.get_topology() points = [] for dim in range(len(top)-1): for entity in range(len(top[dim])): - points.extend(cell.make_points(dim, entity, degree, variant="gl")) + points.extend(cell.make_points(dim, entity, degree + 1)) + values = B.tabulate(points)[(0,) * sd] + assert np.allclose(values, 0, atol=1E-14) + + # test linear independence + m = B.get_num_members() + points = make_lattice(cell.get_vertices(), degree, interior=1) + values = B.tabulate(points)[(0,) * sd] + assert values.shape == (m, m) + assert np.linalg.matrix_rank(values.T) == m - sd = cell.get_spatial_dimension() - B = make_bubbles(cell, degree) - assert isinstance(B, PolynomialSet) - assert B.degree == degree - assert B.get_num_members() == polynomial_dimension(cell, degree - sd - 1) - assert np.allclose(B.tabulate(points)[(0,)*sd], 0) if __name__ == '__main__': From 9ece63452e5221f0265f2bc97eed32461ceb1c6c Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Mon, 11 Dec 2023 16:45:29 +0000 Subject: [PATCH 12/93] moments against L2 duals fo bubbles --- FIAT/expansions.py | 36 +++++++++++++++++++----------------- FIAT/hierarchical.py | 33 ++++++++++++++++++++++----------- FIAT/polynomial_set.py | 21 +++++++++------------ test/unit/test_fiat.py | 18 ++++++++++++++++-- 4 files changed, 66 insertions(+), 42 deletions(-) diff --git a/FIAT/expansions.py b/FIAT/expansions.py index 2dd42ae0a..e0f6a46d0 100644 --- a/FIAT/expansions.py +++ b/FIAT/expansions.py @@ -29,18 +29,17 @@ def jrc(a, b, n): return an, bn, cn -def bubble_jrc(a, b, n): +def integrated_jrc(a, b, n): """Integrated Jacobi recurrence coefficients""" if n == 1: an = (a + b + 2) / 4 bn = (a - 3*b - 2) / 4 cn = 0.0 else: - # an, bn, cn = jrc(a-1, b+1, n-1) - # TODO check dependence on b - an = (2*n-1+a+b)*(2*n+a+b) / (2*(n+1)*(n+a+b)) - bn = (a+b)*(a-b-2)*(2*n-1+a+b) / (2*(n+1)*(n+a+b)*(2*n-2+a+b)) - cn = (n+a-2)*(n+b-1)*(2*n+a+b) / ((n+1)*(n+a+b)*(2*n-2+a+b)) + an, bn, cn = jrc(a-1, b+1, n-1) + an *= n / (n+1) + bn *= n / (n+1) + cn *= (n-1) / (n+1) return an, bn, cn @@ -67,7 +66,7 @@ def jacobi_factors(x, y, z, dx, dy, dz): return fa, fb, fc, dfa, dfb, dfc -def dubiner_recurrence(dim, n, order, ref_pts, jacobian, bubble=False): +def dubiner_recurrence(dim, n, order, ref_pts, jacobian, variant=None): """Dubiner recurrence from (Kirby 2010)""" if order > 2: raise ValueError("Higher order derivatives not supported") @@ -91,7 +90,7 @@ def dubiner_recurrence(dim, n, order, ref_pts, jacobian, bubble=False): if dim > 3 or dim < 0: raise ValueError("Invalid number of spatial dimensions") - coefficients = bubble_jrc if bubble else jrc + coefficients = integrated_jrc if variant == "integral" else jrc X = pad_coordinates(ref_pts, pad_dim) idx = (lambda p: p, morton_index2, morton_index3)[dim-1] for codim in range(dim): @@ -102,13 +101,15 @@ def dubiner_recurrence(dim, n, order, ref_pts, jacobian, bubble=False): # handle i = 1 icur = idx(*sub_index, 0) inext = idx(*sub_index, 1) - if bubble: + + beta = 0 + if variant == "integral": alpha = 2 * sum(sub_index) a = b = 1.0 else: alpha = 2 * sum(sub_index) + len(sub_index) - a = 0.5 * alpha + 1.0 - b = 0.5 * alpha + a = 0.5 * (alpha + beta) + 1.0 + b = 0.5 * (alpha - beta) factor = a * fa - b * fb phi[inext] = factor * phi[icur] if dphi is not None: @@ -120,7 +121,7 @@ def dubiner_recurrence(dim, n, order, ref_pts, jacobian, bubble=False): # general i by recurrence for i in range(1, n - sum(sub_index)): iprev, icur, inext = icur, inext, idx(*sub_index, i + 1) - a, b, c = coefficients(alpha, 0, i) + a, b, c = coefficients(alpha, beta, i) factor = a * fa - b * fb phi[inext] = factor * phi[icur] - c * (fc * phi[iprev]) if dphi is None: @@ -135,8 +136,9 @@ def dubiner_recurrence(dim, n, order, ref_pts, jacobian, bubble=False): # normalize for index in reference_element.lattice_iter(0, n+1, codim+1): + alpha = 2 * sum(index) + len(index) + scale = math.sqrt(0.5 * alpha) icur = idx(*index) - scale = math.sqrt(sum(index) + 0.5 * len(index)) for result in results: result[icur] *= scale return results @@ -177,9 +179,9 @@ def __new__(cls, *args, **kwargs): else: raise ValueError("Invalid reference element type.") - def __init__(self, ref_el, bubble=False): + def __init__(self, ref_el, variant=None): self.ref_el = ref_el - self.bubble = bubble + self.variant = variant dim = ref_el.get_spatial_dimension() self.base_ref_el = reference_element.default_simplex(dim) v1 = ref_el.get_vertices() @@ -206,7 +208,7 @@ def _tabulate(self, n, pts, order=0): """A version of tabulate() that also works for a single point. """ D = self.ref_el.get_spatial_dimension() - return dubiner_recurrence(D, n, order, self._mapping(pts), self.A, bubble=self.bubble) + return dubiner_recurrence(D, n, order, self._mapping(pts), self.A, variant=self.variant) def get_dmats(self, degree): """Returns a numpy array with the expansion coefficients dmat[k, j, i] @@ -309,7 +311,7 @@ def __init__(self, ref_el, **kwargs): def _tabulate(self, n, pts, order=0): """Returns a tuple of (vals, derivs) such that vals[i,j] = phi_i(pts[j]), derivs[i,j] = D vals[i,j].""" - if self.bubble: + if self.variant is not None: return super(LineExpansionSet, self)._tabulate(n, pts, order=order) xs = self._mapping(pts).T diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index afb42720b..a9b9e20c1 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -69,11 +69,13 @@ def __init__(self, ref_el, degree, poly_set): entity_permutations[dim] = {} perms = make_entity_permutations_simplex(dim, degree - dim) - quad_degree = 2 * degree ref_facet = ufc_simplex(dim) - Q_ref = create_quadrature(ref_facet, quad_degree) - P = poly_set if dim == len(top)-1 else None - phis = self._tabulate_L2_duals(ref_facet, degree, Q_ref, poly_set=P) + if dim == 1: + Q_ref = create_quadrature(ref_facet, 2 * degree) + phis = self._tabulate_H1_duals(ref_facet, degree, Q_ref) + else: + Q_ref = create_quadrature(ref_facet, max(0, 2 * degree - dim - 1)) + phis = self._tabulate_L2_duals(ref_facet, degree, Q_ref) for entity in range(len(top[dim])): cur = len(nodes) @@ -84,15 +86,16 @@ def __init__(self, ref_el, degree, poly_set): super(IntegratedLegendreDual, self).__init__(nodes, ref_el, entity_ids, entity_permutations) - def _tabulate_L2_duals(self, ref_el, degree, Q, poly_set=None): + def _tabulate_H1_duals(self, ref_el, degree, Q): qpts = Q.get_points() qwts = Q.get_weights() - dim = ref_el.get_spatial_dimension() moments = lambda v: numpy.dot(numpy.multiply(v, qwts), v.T) + + dim = ref_el.get_spatial_dimension() if dim == 1: # Assemble a stiffness matrix in the Lagrange basis dmat, _ = make_dmat(qpts.flatten()) - K = numpy.dot(numpy.multiply(dmat, qwts), dmat.T) + K = moments(dmat) else: # Get ON basis P = ONPolynomialSet(ref_el, degree) @@ -103,9 +106,17 @@ def _tabulate_L2_duals(self, ref_el, degree, Q, poly_set=None): v = numpy.multiply(P_table[(0, ) * dim], qwts) K = numpy.dot(numpy.dot(v.T, K), v) - B = make_bubbles(ref_el, degree, poly_set=poly_set) - B_at_qpts = B.tabulate(qpts)[(0,) * dim] - phis = numpy.multiply(numpy.dot(B_at_qpts, K), 1/qwts) + B = make_bubbles(ref_el, degree) + phis = B.tabulate(qpts)[(0,) * dim] + phis = numpy.multiply(numpy.dot(phis, K), 1/qwts) + return phis + + def _tabulate_L2_duals(self, ref_el, degree, Q): + dim = ref_el.get_spatial_dimension() + B = make_bubbles(ref_el, degree) + phis = B.tabulate(Q.pts)[(0,) * dim] + if len(phis) > 0: + phis = phis / phis[0] return phis @@ -115,7 +126,7 @@ class IntegratedLegendre(finite_element.CiarletElement): def __init__(self, ref_el, degree): if ref_el.shape not in {POINT, LINE, TRIANGLE, TETRAHEDRON}: raise ValueError("%s is only defined on simplices." % type(self)) - poly_set = ONPolynomialSet(ref_el, degree, bubble=True) + poly_set = ONPolynomialSet(ref_el, degree, variant="integral") dual = IntegratedLegendreDual(ref_el, degree, poly_set) formdegree = 0 # 0-form super(IntegratedLegendre, self).__init__(poly_set, dual, degree, formdegree) diff --git a/FIAT/polynomial_set.py b/FIAT/polynomial_set.py index d167b1fb2..376f328f2 100644 --- a/FIAT/polynomial_set.py +++ b/FIAT/polynomial_set.py @@ -16,6 +16,7 @@ # an entire set of polynomials) import numpy +from itertools import chain from FIAT import expansions from FIAT.functional import index_iterator @@ -120,7 +121,7 @@ class ONPolynomialSet(PolynomialSet): """ - def __init__(self, ref_el, degree, shape=tuple(), bubble=False): + def __init__(self, ref_el, degree, shape=tuple(), variant=None): if shape == tuple(): num_components = 1 @@ -130,7 +131,7 @@ def __init__(self, ref_el, degree, shape=tuple(), bubble=False): num_exp_functions = expansions.polynomial_dimension(ref_el, degree) num_members = num_components * num_exp_functions embedded_degree = degree - expansion_set = expansions.ExpansionSet(ref_el, bubble=bubble) + expansion_set = expansions.ExpansionSet(ref_el, variant=variant) # set up coefficients if shape == tuple(): @@ -243,14 +244,14 @@ def __init__(self, ref_el, degree, size=None): expansion_set, coeffs) -def make_bubbles(ref_el, degree, shape=(), poly_set=None): +def make_bubbles(ref_el, degree, shape=()): """Construct a polynomial set with bubbles up to the given degree. """ - from itertools import chain - dim = ref_el.get_spatial_dimension() - degrees = chain(range(3, degree+1, 2), range(2, degree+1, 2)) + poly_set = ONPolynomialSet(ref_el, degree, shape=shape, variant="integral") + degrees = chain(range(dim + 1, degree+1, 2), range(dim + 2, degree+1, 2)) + if dim == 1: indices = list(degrees) else: @@ -260,9 +261,5 @@ def make_bubbles(ref_el, degree, shape=(), poly_set=None): for alpha in mis(dim, p): if alpha[0] > 1 and min(alpha[1:]) > 0: indices.append(idx(*alpha)) - - assert len(indices) == expansions.polynomial_dimension(ref_el, degree - dim - 1) - if poly_set is None: - poly_set = ONPolynomialSet(ref_el, degree, shape=shape, bubble=True) - bubbles = poly_set.take(indices) - return bubbles + poly_set = poly_set.take(indices) + return poly_set diff --git a/test/unit/test_fiat.py b/test/unit/test_fiat.py index 5eb82d56f..2ddae0a8f 100644 --- a/test/unit/test_fiat.py +++ b/test/unit/test_fiat.py @@ -603,12 +603,15 @@ def eval_basis(f, pt): @pytest.mark.parametrize('cell', [I, T, S]) def test_make_bubbles(cell): from FIAT.reference_element import make_lattice + from FIAT.quadrature_schemes import create_quadrature from FIAT.expansions import polynomial_dimension - from FIAT.polynomial_set import make_bubbles, PolynomialSet + from FIAT.polynomial_set import make_bubbles, PolynomialSet, ONPolynomialSet degree = 10 - sd = cell.get_spatial_dimension() B = make_bubbles(cell, degree) + + # basic tests + sd = cell.get_spatial_dimension() assert isinstance(B, PolynomialSet) assert B.degree == degree assert B.get_num_members() == polynomial_dimension(cell, degree - sd - 1) @@ -629,6 +632,17 @@ def test_make_bubbles(cell): assert values.shape == (m, m) assert np.linalg.matrix_rank(values.T) == m + # test that B intersected with span(P_{degree+2} \ P_{degree}) is empty + P = ONPolynomialSet(cell, degree + 2) + P = P.take(list(range(polynomial_dimension(cell, degree), + P.get_num_members()))) + + Q = create_quadrature(cell, P.degree + B.degree) + qpts = Q.get_points() + qwts = Q.get_weights() + P_at_qpts = P.tabulate(qpts)[(0,) * sd] + B_at_qpts = B.tabulate(qpts)[(0,) * sd] + assert np.allclose(np.dot(np.multiply(P_at_qpts, qwts), B_at_qpts.T), 0.0) if __name__ == '__main__': From cc97e812ebd7a3a84f0ae1f6d0d7be03c33a96dd Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Mon, 11 Dec 2023 18:23:03 +0000 Subject: [PATCH 13/93] normalization of 1D bubbles --- FIAT/expansions.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/FIAT/expansions.py b/FIAT/expansions.py index e0f6a46d0..e784dfa4e 100644 --- a/FIAT/expansions.py +++ b/FIAT/expansions.py @@ -137,6 +137,11 @@ def dubiner_recurrence(dim, n, order, ref_pts, jacobian, variant=None): # normalize for index in reference_element.lattice_iter(0, n+1, codim+1): alpha = 2 * sum(index) + len(index) + + if variant == "integral" and index[-1] > 1: + n1 = index[-1] - 1 + alpha = 3*n1 * (n1 * alpha + 1) + scale = math.sqrt(0.5 * alpha) icur = idx(*index) for result in results: From 503bc152bcd7b1e689b2aa3542f4ef3decff46a3 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Mon, 11 Dec 2023 23:24:12 +0000 Subject: [PATCH 14/93] different normalization --- FIAT/expansions.py | 14 +++++--------- test/unit/test_fiat.py | 2 +- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/FIAT/expansions.py b/FIAT/expansions.py index e784dfa4e..12c48be04 100644 --- a/FIAT/expansions.py +++ b/FIAT/expansions.py @@ -32,14 +32,11 @@ def jrc(a, b, n): def integrated_jrc(a, b, n): """Integrated Jacobi recurrence coefficients""" if n == 1: - an = (a + b + 2) / 4 - bn = (a - 3*b - 2) / 4 + an = (a + b + 2) / 2 + bn = (a - 3*b - 2) / 2 cn = 0.0 else: an, bn, cn = jrc(a-1, b+1, n-1) - an *= n / (n+1) - bn *= n / (n+1) - cn *= (n-1) / (n+1) return an, bn, cn @@ -137,10 +134,9 @@ def dubiner_recurrence(dim, n, order, ref_pts, jacobian, variant=None): # normalize for index in reference_element.lattice_iter(0, n+1, codim+1): alpha = 2 * sum(index) + len(index) - - if variant == "integral" and index[-1] > 1: - n1 = index[-1] - 1 - alpha = 3*n1 * (n1 * alpha + 1) + if variant == "integral" and sum(index) > 1: + n1 = sum(index) - 1 + alpha = 3*n1*(n1*alpha + len(index)) / (n1+1)**2 scale = math.sqrt(0.5 * alpha) icur = idx(*index) diff --git a/test/unit/test_fiat.py b/test/unit/test_fiat.py index 2ddae0a8f..2d17cfe97 100644 --- a/test/unit/test_fiat.py +++ b/test/unit/test_fiat.py @@ -623,7 +623,7 @@ def test_make_bubbles(cell): for entity in range(len(top[dim])): points.extend(cell.make_points(dim, entity, degree + 1)) values = B.tabulate(points)[(0,) * sd] - assert np.allclose(values, 0, atol=1E-14) + assert np.allclose(values, 0, atol=1E-12) # test linear independence m = B.get_num_members() From b22f0b825590c24129cdcaf4d3a9a8671b40f0f2 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Tue, 12 Dec 2023 00:29:07 +0000 Subject: [PATCH 15/93] absorb norm of constant basis function --- FIAT/expansions.py | 4 ++-- test/unit/test_fiat.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/FIAT/expansions.py b/FIAT/expansions.py index 12c48be04..79a877b5f 100644 --- a/FIAT/expansions.py +++ b/FIAT/expansions.py @@ -77,7 +77,7 @@ def dubiner_recurrence(dim, n, order, ref_pts, jacobian, variant=None): pad_dim = dim + 2 dX = pad_jacobian(jacobian, pad_dim) - phi[0] = sum((ref_pts[i] - ref_pts[i] for i in range(dim)), 1.) + phi[0] = sum((ref_pts[i] - ref_pts[i] for i in range(dim)), math.sqrt(0.5)**dim) if dphi is not None: dphi[0] = (phi[0] - phi[0]) * dX[0] if ddphi is not None: @@ -138,7 +138,7 @@ def dubiner_recurrence(dim, n, order, ref_pts, jacobian, variant=None): n1 = sum(index) - 1 alpha = 3*n1*(n1*alpha + len(index)) / (n1+1)**2 - scale = math.sqrt(0.5 * alpha) + scale = math.sqrt(alpha) icur = idx(*index) for result in results: result[icur] *= scale diff --git a/test/unit/test_fiat.py b/test/unit/test_fiat.py index 2d17cfe97..87b920c6f 100644 --- a/test/unit/test_fiat.py +++ b/test/unit/test_fiat.py @@ -602,7 +602,6 @@ def eval_basis(f, pt): @pytest.mark.parametrize('cell', [I, T, S]) def test_make_bubbles(cell): - from FIAT.reference_element import make_lattice from FIAT.quadrature_schemes import create_quadrature from FIAT.expansions import polynomial_dimension from FIAT.polynomial_set import make_bubbles, PolynomialSet, ONPolynomialSet @@ -627,7 +626,7 @@ def test_make_bubbles(cell): # test linear independence m = B.get_num_members() - points = make_lattice(cell.get_vertices(), degree, interior=1) + points = cell.make_points(sd, 0, degree + 1) values = B.tabulate(points)[(0,) * sd] assert values.shape == (m, m) assert np.linalg.matrix_rank(values.T) == m From a3f98794f6fe366871cfc54d8c3aa9836bfd9a90 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Tue, 12 Dec 2023 09:19:14 +0000 Subject: [PATCH 16/93] fix tests --- FIAT/expansions.py | 4 ++-- test/unit/test_fiat.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/FIAT/expansions.py b/FIAT/expansions.py index 79a877b5f..12c48be04 100644 --- a/FIAT/expansions.py +++ b/FIAT/expansions.py @@ -77,7 +77,7 @@ def dubiner_recurrence(dim, n, order, ref_pts, jacobian, variant=None): pad_dim = dim + 2 dX = pad_jacobian(jacobian, pad_dim) - phi[0] = sum((ref_pts[i] - ref_pts[i] for i in range(dim)), math.sqrt(0.5)**dim) + phi[0] = sum((ref_pts[i] - ref_pts[i] for i in range(dim)), 1.) if dphi is not None: dphi[0] = (phi[0] - phi[0]) * dX[0] if ddphi is not None: @@ -138,7 +138,7 @@ def dubiner_recurrence(dim, n, order, ref_pts, jacobian, variant=None): n1 = sum(index) - 1 alpha = 3*n1*(n1*alpha + len(index)) / (n1+1)**2 - scale = math.sqrt(alpha) + scale = math.sqrt(0.5 * alpha) icur = idx(*index) for result in results: result[icur] *= scale diff --git a/test/unit/test_fiat.py b/test/unit/test_fiat.py index 87b920c6f..49fa77688 100644 --- a/test/unit/test_fiat.py +++ b/test/unit/test_fiat.py @@ -620,18 +620,18 @@ def test_make_bubbles(cell): points = [] for dim in range(len(top)-1): for entity in range(len(top[dim])): - points.extend(cell.make_points(dim, entity, degree + 1)) + points.extend(cell.make_points(dim, entity, degree)) values = B.tabulate(points)[(0,) * sd] assert np.allclose(values, 0, atol=1E-12) # test linear independence m = B.get_num_members() - points = cell.make_points(sd, 0, degree + 1) + points = cell.make_points(sd, 0, degree) values = B.tabulate(points)[(0,) * sd] assert values.shape == (m, m) assert np.linalg.matrix_rank(values.T) == m - # test that B intersected with span(P_{degree+2} \ P_{degree}) is empty + # test that B does not have components in span(P_{degree+2} \ P_{degree}) P = ONPolynomialSet(cell, degree + 2) P = P.take(list(range(polynomial_dimension(cell, degree), P.get_num_members()))) From 1a26375d553404a465854cab8c5f648783240648 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Tue, 12 Dec 2023 12:22:54 +0000 Subject: [PATCH 17/93] normalize bubbles --- FIAT/expansions.py | 13 ++++++++----- FIAT/hierarchical.py | 8 ++++---- test/unit/test_fiat.py | 27 +++++++++++++++++++++++---- 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/FIAT/expansions.py b/FIAT/expansions.py index 12c48be04..08a79f0d6 100644 --- a/FIAT/expansions.py +++ b/FIAT/expansions.py @@ -133,12 +133,15 @@ def dubiner_recurrence(dim, n, order, ref_pts, jacobian, variant=None): # normalize for index in reference_element.lattice_iter(0, n+1, codim+1): - alpha = 2 * sum(index) + len(index) - if variant == "integral" and sum(index) > 1: - n1 = sum(index) - 1 - alpha = 3*n1*(n1*alpha + len(index)) / (n1+1)**2 + p = index[-1] + d = len(index) + if variant == "integral" and p >= 1 + (d == 1): + alpha = 2 * sum(index) - 1 + norm2 = (d*(2*d+1)*(alpha - p)*alpha) / p + else: + norm2 = 2 * sum(index) + len(index) - scale = math.sqrt(0.5 * alpha) + scale = math.sqrt(0.5 * norm2) icur = idx(*index) for result in results: result[icur] *= scale diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index a9b9e20c1..2232628cc 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -53,7 +53,7 @@ def __init__(self, ref_el, degree): class IntegratedLegendreDual(dual_set.DualSet): """The dual basis for integrated Legendre elements.""" - def __init__(self, ref_el, degree, poly_set): + def __init__(self, ref_el, degree): entity_ids = {} entity_permutations = {} @@ -116,7 +116,7 @@ def _tabulate_L2_duals(self, ref_el, degree, Q): B = make_bubbles(ref_el, degree) phis = B.tabulate(Q.pts)[(0,) * dim] if len(phis) > 0: - phis = phis / phis[0] + phis = phis / abs(phis[0]) return phis @@ -126,7 +126,7 @@ class IntegratedLegendre(finite_element.CiarletElement): def __init__(self, ref_el, degree): if ref_el.shape not in {POINT, LINE, TRIANGLE, TETRAHEDRON}: raise ValueError("%s is only defined on simplices." % type(self)) - poly_set = ONPolynomialSet(ref_el, degree, variant="integral") - dual = IntegratedLegendreDual(ref_el, degree, poly_set) + poly_set = ONPolynomialSet(ref_el, degree) + dual = IntegratedLegendreDual(ref_el, degree) formdegree = 0 # 0-form super(IntegratedLegendre, self).__init__(poly_set, dual, degree, formdegree) diff --git a/test/unit/test_fiat.py b/test/unit/test_fiat.py index 49fa77688..a015bf05d 100644 --- a/test/unit/test_fiat.py +++ b/test/unit/test_fiat.py @@ -532,14 +532,17 @@ def test_error_point_high_order(element): @pytest.mark.parametrize('cell', [I, T, S]) def test_expansion_orthonormality(cell): - from FIAT import expansions, quadrature + from FIAT import expansions + from FIAT.quadrature_schemes import create_quadrature U = expansions.ExpansionSet(cell) degree = 10 - rule = quadrature.make_quadrature(cell, degree + 1) + rule = create_quadrature(cell, 2*degree) phi = U.tabulate(degree, rule.pts) - w = rule.get_weights() + qwts = rule.get_weights() scale = 0.5 ** -cell.get_spatial_dimension() - results = scale * np.dot(phi, w[:, None] * phi.T) + results = scale * np.dot(np.multiply(phi, qwts), phi.T) + + assert np.allclose(np.diag(results), 1.0) assert np.allclose(results, np.eye(results.shape[0])) @@ -644,6 +647,22 @@ def test_make_bubbles(cell): assert np.allclose(np.dot(np.multiply(P_at_qpts, qwts), B_at_qpts.T), 0.0) +@pytest.mark.parametrize('cell', [I, T, S]) +def test_bubble_duality(cell): + from FIAT.polynomial_set import make_bubbles + from FIAT.quadrature_schemes import create_quadrature + degree = 10 + sd = cell.get_spatial_dimension() + B = make_bubbles(cell, degree) + rule = create_quadrature(cell, 2*degree) + phi = B.tabulate(rule.pts)[(0,) * sd] + qwts = rule.get_weights() / abs(phi[0]) + results = np.dot(np.multiply(phi, qwts), phi.T) + + assert np.allclose(np.diag(results), 1.0) + assert np.allclose(results, np.eye(results.shape[0])) + + if __name__ == '__main__': import os pytest.main(os.path.abspath(__file__)) From 46498e06be0f238f08cf122f17fbb54f123aa27f Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Tue, 12 Dec 2023 12:48:31 +0000 Subject: [PATCH 18/93] avoid rescaling by 0 --- FIAT/expansions.py | 4 ++-- FIAT/hierarchical.py | 2 +- test/unit/test_fiat.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/FIAT/expansions.py b/FIAT/expansions.py index 08a79f0d6..1f2f00958 100644 --- a/FIAT/expansions.py +++ b/FIAT/expansions.py @@ -135,8 +135,8 @@ def dubiner_recurrence(dim, n, order, ref_pts, jacobian, variant=None): for index in reference_element.lattice_iter(0, n+1, codim+1): p = index[-1] d = len(index) - if variant == "integral" and p >= 1 + (d == 1): - alpha = 2 * sum(index) - 1 + alpha = 2 * sum(index) - 1 + if variant == "integral" and p > (d == 1) and alpha > p: norm2 = (d*(2*d+1)*(alpha - p)*alpha) / p else: norm2 = 2 * sum(index) + len(index) diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index 2232628cc..d359b9c4b 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -126,7 +126,7 @@ class IntegratedLegendre(finite_element.CiarletElement): def __init__(self, ref_el, degree): if ref_el.shape not in {POINT, LINE, TRIANGLE, TETRAHEDRON}: raise ValueError("%s is only defined on simplices." % type(self)) - poly_set = ONPolynomialSet(ref_el, degree) + poly_set = ONPolynomialSet(ref_el, degree, variant="integral") dual = IntegratedLegendreDual(ref_el, degree) formdegree = 0 # 0-form super(IntegratedLegendre, self).__init__(poly_set, dual, degree, formdegree) diff --git a/test/unit/test_fiat.py b/test/unit/test_fiat.py index a015bf05d..201bc1f7d 100644 --- a/test/unit/test_fiat.py +++ b/test/unit/test_fiat.py @@ -542,8 +542,8 @@ def test_expansion_orthonormality(cell): scale = 0.5 ** -cell.get_spatial_dimension() results = scale * np.dot(np.multiply(phi, qwts), phi.T) + assert np.allclose(results, np.diag(np.diag(results))) assert np.allclose(np.diag(results), 1.0) - assert np.allclose(results, np.eye(results.shape[0])) @pytest.mark.parametrize('dim', range(1, 4)) @@ -659,8 +659,8 @@ def test_bubble_duality(cell): qwts = rule.get_weights() / abs(phi[0]) results = np.dot(np.multiply(phi, qwts), phi.T) + assert np.allclose(results, np.diag(np.diag(results))) assert np.allclose(np.diag(results), 1.0) - assert np.allclose(results, np.eye(results.shape[0])) if __name__ == '__main__': From d74a3bf16421584a61e420d3ed64becd179001a2 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Tue, 12 Dec 2023 15:55:45 +0000 Subject: [PATCH 19/93] clean up --- FIAT/expansions.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/FIAT/expansions.py b/FIAT/expansions.py index 1f2f00958..06df4e9bb 100644 --- a/FIAT/expansions.py +++ b/FIAT/expansions.py @@ -171,16 +171,16 @@ def __new__(cls, *args, **kwargs): reference element.""" if cls is not ExpansionSet: return super(ExpansionSet, cls).__new__(cls) - ref_el = args[0] - if ref_el.get_shape() == reference_element.POINT: - return PointExpansionSet(*args, **kwargs) - elif ref_el.get_shape() == reference_element.LINE: - return LineExpansionSet(*args, **kwargs) - elif ref_el.get_shape() == reference_element.TRIANGLE: - return TriangleExpansionSet(*args, **kwargs) - elif ref_el.get_shape() == reference_element.TETRAHEDRON: - return TetrahedronExpansionSet(*args, **kwargs) - else: + try: + ref_el = args[0] + expansion_set = { + reference_element.POINT: PointExpansionSet, + reference_element.LINE: LineExpansionSet, + reference_element.TRIANGLE: TriangleExpansionSet, + reference_element.TETRAHEDRON: TetrahedronExpansionSet, + }[ref_el.get_shape()] + return expansion_set(*args, **kwargs) + except KeyError: raise ValueError("Invalid reference element type.") def __init__(self, ref_el, variant=None): From d5923e7d409ca304358f7e91332769e5124760e9 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Tue, 12 Dec 2023 15:58:01 +0000 Subject: [PATCH 20/93] add super_quadrature --- FIAT/hierarchical.py | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index d359b9c4b..439172480 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -7,12 +7,13 @@ # Written by Pablo D. Brubeck (brubeck@protonmail.com), 2022 import numpy +from itertools import chain from FIAT import finite_element, dual_set, functional -from FIAT.reference_element import POINT, LINE, TRIANGLE, TETRAHEDRON, ufc_simplex +from FIAT.reference_element import POINT, LINE, TRIANGLE, TETRAHEDRON from FIAT.orientation_utils import make_entity_permutations_simplex from FIAT.barycentric_interpolation import make_dmat -from FIAT.quadrature import FacetQuadratureRule +from FIAT.quadrature import QuadratureRule, FacetQuadratureRule from FIAT.quadrature_schemes import create_quadrature from FIAT.polynomial_set import ONPolynomialSet, make_bubbles @@ -69,7 +70,7 @@ def __init__(self, ref_el, degree): entity_permutations[dim] = {} perms = make_entity_permutations_simplex(dim, degree - dim) - ref_facet = ufc_simplex(dim) + ref_facet = ref_el.construct_subelement(dim) if dim == 1: Q_ref = create_quadrature(ref_facet, 2 * degree) phis = self._tabulate_H1_duals(ref_facet, degree, Q_ref) @@ -80,7 +81,7 @@ def __init__(self, ref_el, degree): for entity in range(len(top[dim])): cur = len(nodes) Q = FacetQuadratureRule(ref_el, dim, entity, Q_ref) - nodes.extend(functional.IntegralMoment(ref_el, Q, phi) for phi in phis) + nodes.extend(functional.IntegralMoment(ref_el, Q, phi) for phi in reversed(phis)) entity_ids[dim][entity] = list(range(cur, cur + len(phis))) entity_permutations[dim][entity] = perms @@ -116,6 +117,7 @@ def _tabulate_L2_duals(self, ref_el, degree, Q): B = make_bubbles(ref_el, degree) phis = B.tabulate(Q.pts)[(0,) * dim] if len(phis) > 0: + phis[:, abs(phis[0]) <= 1E-12] = 1.0 phis = phis / abs(phis[0]) return phis @@ -130,3 +132,25 @@ def __init__(self, ref_el, degree): dual = IntegratedLegendreDual(ref_el, degree) formdegree = 0 # 0-form super(IntegratedLegendre, self).__init__(poly_set, dual, degree, formdegree) + + +def super_quadrature(cell, degree): + sd = cell.get_spatial_dimension() + Q = create_quadrature(cell, degree) + qpts = list(Q.pts) + qwts = list(Q.wts) + + top = cell.get_topology() + for dim in reversed(top): + if dim == sd: + continue + if dim == 0: + qpts.extend(cell.vertices) + qwts.extend((1, ) * len(cell.vertices)) + else: + facet = cell.construct_subelement(dim) + Qfacet = create_quadrature(facet, degree + sd - dim) + Qs = [FacetQuadratureRule(cell, dim, entity, Qfacet) for entity in top[dim]] + qpts.extend(chain.from_iterable(Q.pts for Q in Qs)) + qwts.extend(chain.from_iterable(Q.wts for Q in Qs)) + return QuadratureRule(cell, tuple(qpts), tuple(qwts)) From 9210cf6570d6b8ea463c7fab83406295518f5435 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Wed, 13 Dec 2023 18:03:02 +0000 Subject: [PATCH 21/93] try with other L2-orthgonal decomposition for mass block sparsity --- FIAT/expansions.py | 1 + FIAT/hierarchical.py | 45 +++++++++++++++++++++++++++----------------- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/FIAT/expansions.py b/FIAT/expansions.py index 06df4e9bb..3b2c07349 100644 --- a/FIAT/expansions.py +++ b/FIAT/expansions.py @@ -107,6 +107,7 @@ def dubiner_recurrence(dim, n, order, ref_pts, jacobian, variant=None): alpha = 2 * sum(sub_index) + len(sub_index) a = 0.5 * (alpha + beta) + 1.0 b = 0.5 * (alpha - beta) + factor = a * fa - b * fb phi[inext] = factor * phi[icur] if dphi is not None: diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index 439172480..e5bf3b11c 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -71,11 +71,10 @@ def __init__(self, ref_el, degree): perms = make_entity_permutations_simplex(dim, degree - dim) ref_facet = ref_el.construct_subelement(dim) - if dim == 1: - Q_ref = create_quadrature(ref_facet, 2 * degree) + Q_ref = create_quadrature(ref_facet, 2 * degree) + if dim == 1 and False: phis = self._tabulate_H1_duals(ref_facet, degree, Q_ref) else: - Q_ref = create_quadrature(ref_facet, max(0, 2 * degree - dim - 1)) phis = self._tabulate_L2_duals(ref_facet, degree, Q_ref) for entity in range(len(top[dim])): @@ -113,12 +112,27 @@ def _tabulate_H1_duals(self, ref_el, degree, Q): return phis def _tabulate_L2_duals(self, ref_el, degree, Q): + qpts = Q.get_points() + qwts = Q.get_weights() + moments = lambda v, u: numpy.dot(numpy.multiply(v, qwts), u.T) dim = ref_el.get_spatial_dimension() + B = make_bubbles(ref_el, degree) - phis = B.tabulate(Q.pts)[(0,) * dim] + B_table = B.tabulate(qpts, 1) + + phis = B_table[(0,) * dim] if len(phis) > 0: phis[:, abs(phis[0]) <= 1E-12] = 1.0 phis = phis / abs(phis[0]) + + P = ONPolynomialSet(ref_el, degree) + P_table = P.tabulate(qpts, 1) + phis = P_table[(0,) * dim] + + k = dim == 1 + K00 = sum(moments(B_table[alpha], B_table[alpha]) for alpha in B_table if sum(alpha) == k) + K01 = sum(moments(B_table[alpha], P_table[alpha]) for alpha in B_table if sum(alpha) == k) + phis = numpy.linalg.solve(K00, numpy.dot(K01, phis)) return phis @@ -136,21 +150,18 @@ def __init__(self, ref_el, degree): def super_quadrature(cell, degree): sd = cell.get_spatial_dimension() - Q = create_quadrature(cell, degree) - qpts = list(Q.pts) - qwts = list(Q.wts) - top = cell.get_topology() - for dim in reversed(top): - if dim == sd: - continue + Qs = [] + for dim in sorted(top, reverse=True): if dim == 0: - qpts.extend(cell.vertices) - qwts.extend((1, ) * len(cell.vertices)) + Qs.append(QuadratureRule(cell, cell.vertices, (1.,)*len(cell.vertices))) + elif dim == sd: + Qs.append(create_quadrature(cell, degree)) else: facet = cell.construct_subelement(dim) Qfacet = create_quadrature(facet, degree + sd - dim) - Qs = [FacetQuadratureRule(cell, dim, entity, Qfacet) for entity in top[dim]] - qpts.extend(chain.from_iterable(Q.pts for Q in Qs)) - qwts.extend(chain.from_iterable(Q.wts for Q in Qs)) - return QuadratureRule(cell, tuple(qpts), tuple(qwts)) + Qs.extend(FacetQuadratureRule(cell, dim, entity, Qfacet) for entity in top[dim]) + + qpts = tuple(chain.from_iterable(Q.pts for Q in Qs)) + qwts = tuple(chain.from_iterable(Q.wts for Q in Qs)) + return QuadratureRule(cell, qpts, qwts) From f95428d53a7918cb411776ea22f0c10e5c9808bc Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 14 Dec 2023 15:29:21 +0000 Subject: [PATCH 22/93] Define Beuchler DOFs by rotating Lagrange DOFs --- FIAT/hierarchical.py | 87 ++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 43 deletions(-) diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index e5bf3b11c..5eeba6ef1 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -7,10 +7,11 @@ # Written by Pablo D. Brubeck (brubeck@protonmail.com), 2022 import numpy -from itertools import chain +import scipy from FIAT import finite_element, dual_set, functional from FIAT.reference_element import POINT, LINE, TRIANGLE, TETRAHEDRON +from FIAT.reference_element import make_lattice from FIAT.orientation_utils import make_entity_permutations_simplex from FIAT.barycentric_interpolation import make_dmat from FIAT.quadrature import QuadratureRule, FacetQuadratureRule @@ -54,7 +55,15 @@ def __init__(self, ref_el, degree): class IntegratedLegendreDual(dual_set.DualSet): """The dual basis for integrated Legendre elements.""" - def __init__(self, ref_el, degree): + def __init__(self, ref_el, degree, variant=None): + if variant is None: + variant = "beuchler" + duals = { + "beuchler": self._beuchler_duals, + "demkowitz": self._demkowitz_duals, + "orthonormal": self._orthonormal_duals, + }[variant] + entity_ids = {} entity_permutations = {} @@ -70,12 +79,10 @@ def __init__(self, ref_el, degree): entity_permutations[dim] = {} perms = make_entity_permutations_simplex(dim, degree - dim) - ref_facet = ref_el.construct_subelement(dim) - Q_ref = create_quadrature(ref_facet, 2 * degree) - if dim == 1 and False: - phis = self._tabulate_H1_duals(ref_facet, degree, Q_ref) - else: - phis = self._tabulate_L2_duals(ref_facet, degree, Q_ref) + ref_facet = ref_el + if dim != ref_el.get_spatial_dimension(): + ref_facet = ref_el.construct_subelement(dim) + Q_ref, phis = duals(ref_facet, degree) for entity in range(len(top[dim])): cur = len(nodes) @@ -86,7 +93,19 @@ def __init__(self, ref_el, degree): super(IntegratedLegendreDual, self).__init__(nodes, ref_el, entity_ids, entity_permutations) - def _tabulate_H1_duals(self, ref_el, degree, Q): + def _beuchler_duals(self, ref_el, degree, variant="gll"): + points = make_lattice(ref_el.vertices, degree) + weights = (1,) * len(points) + Q = QuadratureRule(ref_el, points, weights) + B = make_bubbles(ref_el, degree) + V = numpy.transpose(B.expansion_set.tabulate(degree, points)) + + PLU = scipy.linalg.lu_factor(V) + phis = scipy.linalg.lu_solve(PLU, B.get_coeffs().T, trans=1).T + return Q, phis + + def _demkowitz_duals(self, ref_el, degree): + Q = create_quadrature(ref_el, 2 * degree) qpts = Q.get_points() qwts = Q.get_weights() moments = lambda v: numpy.dot(numpy.multiply(v, qwts), v.T) @@ -109,59 +128,41 @@ def _tabulate_H1_duals(self, ref_el, degree, Q): B = make_bubbles(ref_el, degree) phis = B.tabulate(qpts)[(0,) * dim] phis = numpy.multiply(numpy.dot(phis, K), 1/qwts) - return phis + return Q, phis - def _tabulate_L2_duals(self, ref_el, degree, Q): + def _orthonormal_duals(self, ref_el, degree): + Q = create_quadrature(ref_el, 2 * degree) qpts = Q.get_points() qwts = Q.get_weights() - moments = lambda v, u: numpy.dot(numpy.multiply(v, qwts), u.T) + inner = lambda v, u: numpy.dot(numpy.multiply(v, qwts), u.T) + h1_inner = lambda v, u: sum(inner(v[k], u[k]) for k in v if sum(k) == 1) + dim = ref_el.get_spatial_dimension() B = make_bubbles(ref_el, degree) B_table = B.tabulate(qpts, 1) - phis = B_table[(0,) * dim] - if len(phis) > 0: - phis[:, abs(phis[0]) <= 1E-12] = 1.0 - phis = phis / abs(phis[0]) - P = ONPolynomialSet(ref_el, degree) P_table = P.tabulate(qpts, 1) + + KBB = h1_inner(B_table, B_table) + KBP = h1_inner(B_table, P_table) + phis = P_table[(0,) * dim] + phis = numpy.dot(KBP, phis) - k = dim == 1 - K00 = sum(moments(B_table[alpha], B_table[alpha]) for alpha in B_table if sum(alpha) == k) - K01 = sum(moments(B_table[alpha], P_table[alpha]) for alpha in B_table if sum(alpha) == k) - phis = numpy.linalg.solve(K00, numpy.dot(K01, phis)) - return phis + V = numpy.linalg.cholesky(KBB) + phis = numpy.linalg.solve(V, phis) + return Q, phis class IntegratedLegendre(finite_element.CiarletElement): """Simplicial continuous element with integrated Legendre polynomials.""" - def __init__(self, ref_el, degree): + def __init__(self, ref_el, degree, variant=None): if ref_el.shape not in {POINT, LINE, TRIANGLE, TETRAHEDRON}: raise ValueError("%s is only defined on simplices." % type(self)) poly_set = ONPolynomialSet(ref_el, degree, variant="integral") - dual = IntegratedLegendreDual(ref_el, degree) + dual = IntegratedLegendreDual(ref_el, degree, variant=variant) formdegree = 0 # 0-form super(IntegratedLegendre, self).__init__(poly_set, dual, degree, formdegree) - - -def super_quadrature(cell, degree): - sd = cell.get_spatial_dimension() - top = cell.get_topology() - Qs = [] - for dim in sorted(top, reverse=True): - if dim == 0: - Qs.append(QuadratureRule(cell, cell.vertices, (1.,)*len(cell.vertices))) - elif dim == sd: - Qs.append(create_quadrature(cell, degree)) - else: - facet = cell.construct_subelement(dim) - Qfacet = create_quadrature(facet, degree + sd - dim) - Qs.extend(FacetQuadratureRule(cell, dim, entity, Qfacet) for entity in top[dim]) - - qpts = tuple(chain.from_iterable(Q.pts for Q in Qs)) - qwts = tuple(chain.from_iterable(Q.wts for Q in Qs)) - return QuadratureRule(cell, qpts, qwts) From 7c052a6f85a4b4d7ecdaaf7d46f2377f37c99455 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 14 Dec 2023 18:47:21 +0000 Subject: [PATCH 23/93] phis must transform like a d-form to undo the measure transformation --- FIAT/hierarchical.py | 61 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 14 deletions(-) diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index 5eeba6ef1..c574f9495 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -58,42 +58,55 @@ class IntegratedLegendreDual(dual_set.DualSet): def __init__(self, ref_el, degree, variant=None): if variant is None: variant = "beuchler" + variant = "orthonormal" + duals = { - "beuchler": self._beuchler_duals, + "beuchler": self._beuchler_integral_duals, + "beuchler_point": self._beuchler_point_duals, "demkowitz": self._demkowitz_duals, "orthonormal": self._orthonormal_duals, }[variant] + nodes = [] entity_ids = {} entity_permutations = {} - # vertex dofs - vertices = ref_el.get_vertices() - nodes = [functional.PointEvaluation(ref_el, pt) for pt in vertices] - entity_ids[0] = {k: [k] for k in range(len(vertices))} - entity_permutations[0] = {k: {0: [0]} for k in range(len(vertices))} - top = ref_el.get_topology() - for dim in range(1, len(top)): + for dim in sorted(top): entity_ids[dim] = {} entity_permutations[dim] = {} - perms = make_entity_permutations_simplex(dim, degree - dim) + if dim == 0: + perms = {0: [0]} + for entity in sorted(top[dim]): + cur = len(nodes) + pt, = ref_el.make_points(0, entity, degree) + nodes.append(functional.PointEvaluation(ref_el, pt)) + entity_ids[dim][entity] = list(range(cur, len(nodes))) + entity_permutations[dim][entity] = perms + continue ref_facet = ref_el if dim != ref_el.get_spatial_dimension(): ref_facet = ref_el.construct_subelement(dim) - Q_ref, phis = duals(ref_facet, degree) - for entity in range(len(top[dim])): + Q_ref, phis = duals(ref_facet, degree) + perms = make_entity_permutations_simplex(dim, degree - dim) + for entity in sorted(top[dim]): cur = len(nodes) Q = FacetQuadratureRule(ref_el, dim, entity, Q_ref) - nodes.extend(functional.IntegralMoment(ref_el, Q, phi) for phi in reversed(phis)) - entity_ids[dim][entity] = list(range(cur, cur + len(phis))) + + # phis must transform like a d-form to undo the measure transformation + J = Q.jacobian() + scale = 1/numpy.sqrt(abs(numpy.linalg.det(numpy.dot(J.T, J)))) + Jphis = scale * phis + + nodes.extend(functional.IntegralMoment(ref_el, Q, phi) for phi in reversed(Jphis)) + entity_ids[dim][entity] = list(range(cur, len(nodes))) entity_permutations[dim][entity] = perms super(IntegratedLegendreDual, self).__init__(nodes, ref_el, entity_ids, entity_permutations) - def _beuchler_duals(self, ref_el, degree, variant="gll"): + def _beuchler_point_duals(self, ref_el, degree, variant="gll"): points = make_lattice(ref_el.vertices, degree) weights = (1,) * len(points) Q = QuadratureRule(ref_el, points, weights) @@ -104,6 +117,26 @@ def _beuchler_duals(self, ref_el, degree, variant="gll"): phis = scipy.linalg.lu_solve(PLU, B.get_coeffs().T, trans=1).T return Q, phis + def _beuchler_integral_duals(self, ref_el, degree): + Q = create_quadrature(ref_el, 2 * degree) + qpts = Q.get_points() + qwts = Q.get_weights() + inner = lambda v, u: numpy.dot(numpy.multiply(v, qwts), u.T) + dim = ref_el.get_spatial_dimension() + + B = make_bubbles(ref_el, degree) + V = B.expansion_set.tabulate(degree, qpts) + + P = ONPolynomialSet(ref_el, degree) + phis = P.tabulate(qpts, 0)[(0,) * dim] + + # TODO sparse LU + A = inner(phis, V) + PLU = scipy.linalg.lu_factor(A) + phis = scipy.linalg.lu_solve(PLU, phis) + phis = numpy.dot(B.get_coeffs(), phis) + return Q, phis + def _demkowitz_duals(self, ref_el, degree): Q = create_quadrature(ref_el, 2 * degree) qpts = Q.get_points() From c2f7ead13a7cd8d3605474c0dc9eacf8c1bcb89b Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 14 Dec 2023 21:34:45 +0000 Subject: [PATCH 24/93] fix typo --- FIAT/hierarchical.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index c574f9495..7f45dd372 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -63,7 +63,7 @@ def __init__(self, ref_el, degree, variant=None): duals = { "beuchler": self._beuchler_integral_duals, "beuchler_point": self._beuchler_point_duals, - "demkowitz": self._demkowitz_duals, + "demkowicz": self._demkowicz_duals, "orthonormal": self._orthonormal_duals, }[variant] @@ -137,7 +137,7 @@ def _beuchler_integral_duals(self, ref_el, degree): phis = numpy.dot(B.get_coeffs(), phis) return Q, phis - def _demkowitz_duals(self, ref_el, degree): + def _demkowicz_duals(self, ref_el, degree): Q = create_quadrature(ref_el, 2 * degree) qpts = Q.get_points() qwts = Q.get_weights() From 6bae790f49bbf54e69b1db350a817109cf6adaf2 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Fri, 15 Dec 2023 14:03:50 +0000 Subject: [PATCH 25/93] Add FDM variant, probably this is the wrong place --- FIAT/hierarchical.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index 7f45dd372..a9edd4b64 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -163,14 +163,16 @@ def _demkowicz_duals(self, ref_el, degree): phis = numpy.multiply(numpy.dot(phis, K), 1/qwts) return Q, phis - def _orthonormal_duals(self, ref_el, degree): + def _orthonormal_duals(self, ref_el, degree, solver="cholesky"): + dim = ref_el.get_spatial_dimension() + if dim > 1: + solver = "eig" + Q = create_quadrature(ref_el, 2 * degree) qpts = Q.get_points() qwts = Q.get_weights() inner = lambda v, u: numpy.dot(numpy.multiply(v, qwts), u.T) - h1_inner = lambda v, u: sum(inner(v[k], u[k]) for k in v if sum(k) == 1) - - dim = ref_el.get_spatial_dimension() + Hk_inner = lambda order, v, u: sum(inner(v[k], u[k]) for k in v if sum(k) == order) B = make_bubbles(ref_el, degree) B_table = B.tabulate(qpts, 1) @@ -178,14 +180,19 @@ def _orthonormal_duals(self, ref_el, degree): P = ONPolynomialSet(ref_el, degree) P_table = P.tabulate(qpts, 1) - KBB = h1_inner(B_table, B_table) - KBP = h1_inner(B_table, P_table) - + KBP = Hk_inner(1, B_table, P_table) phis = P_table[(0,) * dim] phis = numpy.dot(KBP, phis) - V = numpy.linalg.cholesky(KBB) - phis = numpy.linalg.solve(V, phis) + if len(phis): + KBB = Hk_inner(1, B_table, B_table) + if solver == "eig": + MBB = Hk_inner(0, B_table, B_table) + _, S = scipy.linalg.eigh(MBB, KBB) + phis = numpy.dot(S.T, phis) + elif solver == "cholesky": + V = numpy.linalg.cholesky(KBB) + phis = numpy.linalg.solve(V, phis) return Q, phis From e359a1d04f00016bf6deeb60df0603bee8cab947 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Sat, 16 Dec 2023 09:41:25 +0000 Subject: [PATCH 26/93] add entity ids Legendre element --- FIAT/gauss_legendre.py | 1 + FIAT/hierarchical.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/FIAT/gauss_legendre.py b/FIAT/gauss_legendre.py index 4d3e1c0e8..18efa22de 100644 --- a/FIAT/gauss_legendre.py +++ b/FIAT/gauss_legendre.py @@ -32,6 +32,7 @@ def __init__(self, ref_el, degree): entity_permutations[dim][entity] = perms # make nodes by getting points + dim = ref_el.get_spatial_dimension() pts = make_lattice(ref_el.get_vertices(), degree, variant="gl") nodes = [functional.PointEvaluation(ref_el, x) for x in pts] entity_ids[dim][0] = list(range(len(nodes))) diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index a9edd4b64..38016901f 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -37,6 +37,7 @@ def __init__(self, ref_el, degree, poly_set): Q = create_quadrature(ref_el, 2 * degree) phis = poly_set.tabulate(Q.get_points())[(0,) * dim] nodes = [functional.IntegralMoment(ref_el, Q, phi) for phi in phis] + entity_ids[dim][0] = list(range(len(nodes))) super(LegendreDual, self).__init__(nodes, ref_el, entity_ids, entity_permutations) @@ -184,7 +185,7 @@ def _orthonormal_duals(self, ref_el, degree, solver="cholesky"): phis = P_table[(0,) * dim] phis = numpy.dot(KBP, phis) - if len(phis): + if len(phis) > 0: KBB = Hk_inner(1, B_table, B_table) if solver == "eig": MBB = Hk_inner(0, B_table, B_table) From 6b27a0e8a70ffdd8b60f9fc6326404aa352d8360 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Sun, 17 Dec 2023 14:54:15 +0000 Subject: [PATCH 27/93] add symmetric_simplex, use it to define IntegratedLegendre facet dofs --- FIAT/hierarchical.py | 20 ++++----- FIAT/quadrature_schemes.py | 18 +------- FIAT/reference_element.py | 57 +++++++++++++++++++----- test/unit/test_gauss_legendre.py | 35 +++++---------- test/unit/test_gauss_lobatto_legendre.py | 35 +++++---------- 5 files changed, 80 insertions(+), 85 deletions(-) diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index 38016901f..fcbf8b0bc 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -10,8 +10,8 @@ import scipy from FIAT import finite_element, dual_set, functional -from FIAT.reference_element import POINT, LINE, TRIANGLE, TETRAHEDRON -from FIAT.reference_element import make_lattice +from FIAT.reference_element import (POINT, LINE, TRIANGLE, TETRAHEDRON, + make_lattice, symmetric_simplex) from FIAT.orientation_utils import make_entity_permutations_simplex from FIAT.barycentric_interpolation import make_dmat from FIAT.quadrature import QuadratureRule, FacetQuadratureRule @@ -86,9 +86,7 @@ def __init__(self, ref_el, degree, variant=None): entity_permutations[dim][entity] = perms continue - ref_facet = ref_el - if dim != ref_el.get_spatial_dimension(): - ref_facet = ref_el.construct_subelement(dim) + ref_facet = symmetric_simplex(dim) Q_ref, phis = duals(ref_facet, degree) perms = make_entity_permutations_simplex(dim, degree - dim) @@ -126,15 +124,15 @@ def _beuchler_integral_duals(self, ref_el, degree): dim = ref_el.get_spatial_dimension() B = make_bubbles(ref_el, degree) - V = B.expansion_set.tabulate(degree, qpts) + B_table = B.expansion_set.tabulate(degree, qpts) P = ONPolynomialSet(ref_el, degree) - phis = P.tabulate(qpts, 0)[(0,) * dim] + P_table = P.tabulate(qpts, 0)[(0,) * dim] # TODO sparse LU - A = inner(phis, V) - PLU = scipy.linalg.lu_factor(A) - phis = scipy.linalg.lu_solve(PLU, phis) + V = inner(P_table, B_table) + PLU = scipy.linalg.lu_factor(V) + phis = scipy.linalg.lu_solve(PLU, P_table) phis = numpy.dot(B.get_coeffs(), phis) return Q, phis @@ -164,7 +162,7 @@ def _demkowicz_duals(self, ref_el, degree): phis = numpy.multiply(numpy.dot(phis, K), 1/qwts) return Q, phis - def _orthonormal_duals(self, ref_el, degree, solver="cholesky"): + def _orthonormal_duals(self, ref_el, degree, solver=None): dim = ref_el.get_spatial_dimension() if dim > 1: solver = "eig" diff --git a/FIAT/quadrature_schemes.py b/FIAT/quadrature_schemes.py index 31297dd14..114a94af8 100644 --- a/FIAT/quadrature_schemes.py +++ b/FIAT/quadrature_schemes.py @@ -35,7 +35,7 @@ # FIAT from FIAT.reference_element import (HEXAHEDRON, QUADRILATERAL, TENSORPRODUCT, TETRAHEDRON, TRIANGLE, UFCTetrahedron, - UFCTriangle, default_simplex) + UFCTriangle, symmetric_simplex) def create_quadrature(ref_el, degree, scheme="default"): @@ -351,23 +351,9 @@ def xg_scheme(ref_el, degree): except KeyError: raise ValueError(f"Xiao-Gambutas rule not availale for degree {degree}.") - # Get affine map from the (-1,1)^d triangle to the G-X equilateral triangle - if dim == 2: - A = numpy.array([[1, 1/2], - [0, numpy.sqrt(3)/2]]) - b = A.sum(axis=1)/3 - else: - A = numpy.array([[1, 1/2, 1/2], - [0, numpy.sqrt(3)/2, numpy.sqrt(3)/6], - [0, 0, numpy.sqrt(6)/3]]) - b = A.sum(axis=1)/2 - - Ref1 = default_simplex(dim) - v = numpy.dot(Ref1.vertices, A.T) + b[None, :] - Ref1.vertices = tuple(map(tuple, v)) - pts_ref = order_table["points"] wts_ref = order_table["weights"] + Ref1 = symmetric_simplex(dim) pts, wts = map_quadrature(pts_ref, wts_ref, Ref1, ref_el) return QuadratureRule(ref_el, pts, wts) diff --git a/FIAT/reference_element.py b/FIAT/reference_element.py index 66225a674..dcef5d0a1 100644 --- a/FIAT/reference_element.py +++ b/FIAT/reference_element.py @@ -687,6 +687,36 @@ def distance_to_point_l1(self, point): return abs(l1_dist) +class DefaultSimplex(Simplex): + + def get_facet_element(self): + dimension = self.get_spatial_dimension() + return self.construct_subelement(dimension - 1) + + def construct_subelement(self, dimension): + """Constructs the reference element of a cell subentity + specified by subelement dimension. + + :arg dimension: subentity dimension (integer) + """ + return default_simplex(dimension) + + +class SymmetricSimplex(Simplex): + + def get_facet_element(self): + dimension = self.get_spatial_dimension() + return self.construct_subelement(dimension - 1) + + def construct_subelement(self, dimension): + """Constructs the reference element of a cell subentity + specified by subelement dimension. + + :arg dimension: subentity dimension (integer) + """ + return symmetric_simplex(dimension) + + class Point(Simplex): """This is the reference point.""" @@ -705,7 +735,7 @@ def construct_subelement(self, dimension): return self -class DefaultLine(Simplex): +class DefaultLine(DefaultSimplex): """This is the reference line with vertices (-1.0,) and (1.0,).""" def __init__(self): @@ -715,9 +745,6 @@ def __init__(self): 1: edges} super(DefaultLine, self).__init__(LINE, verts, topology) - def get_facet_element(self): - raise NotImplementedError() - class UFCInterval(UFCSimplex): """This is the reference interval with vertices (0.0,) and (1.0,).""" @@ -730,7 +757,7 @@ def __init__(self): super(UFCInterval, self).__init__(LINE, verts, topology) -class DefaultTriangle(Simplex): +class DefaultTriangle(DefaultSimplex): """This is the reference triangle with vertices (-1.0,-1.0), (1.0,-1.0), and (-1.0,1.0).""" @@ -744,9 +771,6 @@ def __init__(self): 1: edges, 2: faces} super(DefaultTriangle, self).__init__(TRIANGLE, verts, topology) - def get_facet_element(self): - return DefaultLine() - class UFCTriangle(UFCSimplex): """This is the reference triangle with vertices (0.0,0.0), @@ -786,7 +810,7 @@ def get_facet_element(self): return UFCInterval() -class DefaultTetrahedron(Simplex): +class DefaultTetrahedron(DefaultSimplex): """This is the reference tetrahedron with vertices (-1,-1,-1), (1,-1,-1),(-1,1,-1), and (-1,-1,1).""" @@ -811,9 +835,6 @@ def __init__(self): topology = {0: vs, 1: edges, 2: faces, 3: tets} super(DefaultTetrahedron, self).__init__(TETRAHEDRON, verts, topology) - def get_facet_element(self): - return DefaultTriangle() - class IntrepidTetrahedron(Simplex): """This is the reference tetrahedron with vertices (0,0,0), @@ -1308,6 +1329,18 @@ def ufc_simplex(spatial_dim): raise RuntimeError("Can't create UFC simplex of dimension %s." % str(spatial_dim)) +def symmetric_simplex(spatial_dim): + A = numpy.array([[2, 1, 1], + [0, numpy.sqrt(3), numpy.sqrt(3)/3], + [0, 0, numpy.sqrt(6)*(2/3)]]) + A = A[:spatial_dim, :][:, :spatial_dim] + b = A.sum(axis=1) * (-1 / (1 + spatial_dim)) + Ref1 = ufc_simplex(spatial_dim) + v = numpy.dot(Ref1.get_vertices(), A.T) + b[None, :] + vertices = tuple(map(tuple, v)) + return SymmetricSimplex(Ref1.get_shape(), vertices, Ref1.get_topology()) + + def ufc_cell(cell): """Handle incoming calls from FFC.""" diff --git a/test/unit/test_gauss_legendre.py b/test/unit/test_gauss_legendre.py index 22babd157..676a4ea26 100644 --- a/test/unit/test_gauss_legendre.py +++ b/test/unit/test_gauss_legendre.py @@ -23,28 +23,14 @@ import numpy as np -def symmetric_simplex(dim): - from FIAT.reference_element import ufc_simplex - s = ufc_simplex(dim) - if dim == 1: - s.vertices = [(-1.,), (1.,)] - elif dim == 2: - h = 3.**0.5 / dim - s.vertices = [(0., 1.), (-h, -0.5), (h, -0.5)] - elif dim == 3: - h = 3.**0.5 / dim - s.vertices = [(h, -h, -h), (-h, h, -h), (-h, -h, h), (h, h, h)] - return s - - @pytest.mark.parametrize("degree", range(0, 8)) @pytest.mark.parametrize("dim", (1, 2, 3)) def test_gl_basis_values(dim, degree): """Ensure that integrating a simple monomial produces the expected results.""" - from FIAT import GaussLegendre, make_quadrature + from FIAT import GaussLegendre, create_quadrature, reference_element - s = symmetric_simplex(dim) - q = make_quadrature(s, degree + 1) + s = reference_element.symmetric_simplex(dim) + q = create_quadrature(s, 2*degree) fe = GaussLegendre(s, degree) tab = fe.tabulate(0, q.pts)[(0,)*dim] @@ -59,9 +45,9 @@ def test_gl_basis_values(dim, degree): @pytest.mark.parametrize("dim, degree", [(1, 4), (2, 4), (3, 4)]) def test_edge_dofs(dim, degree): """ Ensure edge DOFs are point evaluations at GL points.""" - from FIAT import GaussLegendre, quadrature, expansions + from FIAT import GaussLegendre, quadrature, expansions, reference_element - s = symmetric_simplex(dim) + s = reference_element.symmetric_simplex(dim) fe = GaussLegendre(s, degree) ndof = fe.space_dimension() assert ndof == expansions.polynomial_dimension(s, degree) @@ -87,12 +73,15 @@ def test_edge_dofs(dim, degree): def test_interpolation(dim, degree): from FIAT import GaussLegendre, reference_element + s = reference_element.symmetric_simplex(dim) + radius = max(np.linalg.norm(s.vertices, axis=-1)) + s.vertices = tuple(map(tuple, np.array(s.vertices) / radius)) + # f = Runge radial function A = 25 r2 = lambda x: np.linalg.norm(x, axis=-1)**2 f = lambda x: 1/(1 + A*r2(x)) - s = symmetric_simplex(dim) points = reference_element.make_lattice(s.get_vertices(), 2*degree+1, variant="gl") points = np.array(points) f_at_pts = f(points) @@ -122,10 +111,10 @@ def test_interpolation(dim, degree): @pytest.mark.parametrize("degree", [4, 8, 12, 16]) @pytest.mark.parametrize("dim", [1, 2, 3]) def test_conditioning(dim, degree): - from FIAT import GaussLegendre, quadrature + from FIAT import GaussLegendre, create_quadrature, reference_element - s = symmetric_simplex(dim) - rule = quadrature.make_quadrature(s, degree + 1) + s = reference_element.symmetric_simplex(dim) + rule = create_quadrature(s, 2*degree) points = rule.get_points() weights = rule.get_weights() diff --git a/test/unit/test_gauss_lobatto_legendre.py b/test/unit/test_gauss_lobatto_legendre.py index b8df3037b..37abe5a13 100644 --- a/test/unit/test_gauss_lobatto_legendre.py +++ b/test/unit/test_gauss_lobatto_legendre.py @@ -23,28 +23,14 @@ import numpy as np -def symmetric_simplex(dim): - from FIAT.reference_element import ufc_simplex - s = ufc_simplex(dim) - if dim == 1: - s.vertices = [(-1.,), (1.,)] - elif dim == 2: - h = 3.**0.5 / dim - s.vertices = [(0., 1.), (-h, -0.5), (h, -0.5)] - elif dim == 3: - h = 3.**0.5 / dim - s.vertices = [(h, -h, -h), (-h, h, -h), (-h, -h, h), (h, h, h)] - return s - - @pytest.mark.parametrize("degree", range(1, 8)) @pytest.mark.parametrize("dim", (1, 2, 3)) def test_gll_basis_values(dim, degree): """Ensure that integrating a simple monomial produces the expected results.""" - from FIAT import GaussLobattoLegendre, make_quadrature + from FIAT import GaussLobattoLegendre, create_quadrature, reference_element - s = symmetric_simplex(dim) - q = make_quadrature(s, degree + 1) + s = reference_element.symmetric_simplex(dim) + q = create_quadrature(s, 2*degree) fe = GaussLobattoLegendre(s, degree) tab = fe.tabulate(0, q.pts)[(0,)*dim] @@ -59,9 +45,9 @@ def test_gll_basis_values(dim, degree): @pytest.mark.parametrize("dim, degree", [(1, 4), (2, 4), (3, 4)]) def test_edge_dofs(dim, degree): """ Ensure edge DOFs are point evaluations at GL points.""" - from FIAT import GaussLobattoLegendre, quadrature, expansions + from FIAT import GaussLobattoLegendre, quadrature, expansions, reference_element - s = symmetric_simplex(dim) + s = reference_element.symmetric_simplex(dim) fe = GaussLobattoLegendre(s, degree) ndof = fe.space_dimension() assert ndof == expansions.polynomial_dimension(s, degree) @@ -88,12 +74,15 @@ def test_edge_dofs(dim, degree): def test_interpolation(dim, degree): from FIAT import GaussLobattoLegendre, reference_element + s = reference_element.symmetric_simplex(dim) + radius = max(np.linalg.norm(s.vertices, axis=-1)) + s.vertices = tuple(map(tuple, np.array(s.vertices) / radius)) + # f = Runge radial function A = 25 r2 = lambda x: np.linalg.norm(x, axis=-1)**2 f = lambda x: 1/(1 + A*r2(x)) - s = symmetric_simplex(dim) points = reference_element.make_lattice(s.get_vertices(), 2*degree+1, variant="gl") points = np.array(points) f_at_pts = f(points) @@ -123,10 +112,10 @@ def test_interpolation(dim, degree): @pytest.mark.parametrize("degree", [4, 8, 12, 16]) @pytest.mark.parametrize("dim", [1, 2, 3]) def test_conditioning(dim, degree): - from FIAT import GaussLobattoLegendre, quadrature + from FIAT import GaussLobattoLegendre, create_quadrature, reference_element - s = symmetric_simplex(dim) - rule = quadrature.make_quadrature(s, degree + 1) + s = reference_element.symmetric_simplex(dim) + rule = create_quadrature(s, 2*degree) points = rule.get_points() weights = rule.get_weights() From 312dd3cf78fb7dd030b1acafa7cc75d0f35231ce Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Tue, 19 Dec 2023 20:34:41 -0600 Subject: [PATCH 28/93] add f_at_qpts attribute to IntegralMoment --- FIAT/functional.py | 1 + 1 file changed, 1 insertion(+) diff --git a/FIAT/functional.py b/FIAT/functional.py index 44711ca6d..aa6ac8d02 100644 --- a/FIAT/functional.py +++ b/FIAT/functional.py @@ -292,6 +292,7 @@ class IntegralMoment(Functional): def __init__(self, ref_el, Q, f_at_qpts, comp=tuple(), shp=tuple()): self.Q = Q + self.f_at_qpts = f_at_qpts qpts, qwts = Q.get_points(), Q.get_weights() pt_dict = OrderedDict() self.comp = comp From 4da29b1ce86c4d04ad3f9846a944ae0d5291240b Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Wed, 20 Dec 2023 09:09:07 -0600 Subject: [PATCH 29/93] optimize IntegralMoment --- FIAT/dual_set.py | 21 ++++++++++++++++++--- FIAT/functional.py | 5 +---- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/FIAT/dual_set.py b/FIAT/dual_set.py index 34884f31b..93e3ce727 100644 --- a/FIAT/dual_set.py +++ b/FIAT/dual_set.py @@ -9,7 +9,7 @@ import numpy import collections -from FIAT import polynomial_set +from FIAT import polynomial_set, functional class DualSet(object): @@ -110,10 +110,25 @@ def to_riesz(self, poly_set): mat = numpy.zeros(riesz_shape, "d") # Dictionaries mapping pts to which functionals they come from - pts_to_ells = collections.OrderedDict() - dpts_to_ells = collections.OrderedDict() + pts_to_ells = dict() + dpts_to_ells = dict() + # Dictionary mapping quadratures to which functionals they come from + Qs_to_ells = dict() for i, ell in enumerate(self.nodes): + if isinstance(ell, functional.IntegralMoment): + Q = ell.Q + if Q in Qs_to_ells: + Qs_to_ells[Q].append(i) + else: + Qs_to_ells[Q] = [i] + + for Q in Qs_to_ells: + pts_to_ells.update(dict.fromkeys(map(tuple, Q.pts), Qs_to_ells[Q])) + + for i, ell in enumerate(self.nodes): + if isinstance(ell, functional.IntegralMoment): + continue for pt in ell.pt_dict: if pt in pts_to_ells: pts_to_ells[pt].append(i) diff --git a/FIAT/functional.py b/FIAT/functional.py index aa6ac8d02..c1f888688 100644 --- a/FIAT/functional.py +++ b/FIAT/functional.py @@ -294,11 +294,8 @@ def __init__(self, ref_el, Q, f_at_qpts, comp=tuple(), shp=tuple()): self.Q = Q self.f_at_qpts = f_at_qpts qpts, qwts = Q.get_points(), Q.get_weights() - pt_dict = OrderedDict() self.comp = comp - for i in range(len(qpts)): - pt_cur = tuple(qpts[i]) - pt_dict[pt_cur] = [(qwts[i] * f_at_qpts[i], comp)] + pt_dict = {tuple(pt): [(wt * f, comp)] for pt, wt, f in zip(qpts, qwts, f_at_qpts)} Functional.__init__(self, ref_el, shp, pt_dict, {}, "IntegralMoment") def __call__(self, fn): From 80d6a3d0eea0f4eb983c1799a17bc8039941974b Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Wed, 20 Dec 2023 10:27:33 -0600 Subject: [PATCH 30/93] Implement FDMLagrange on simplices --- FIAT/dual_set.py | 9 +++-- FIAT/fdm_element.py | 82 ++++++++++++++++++++++++++++++++++++--------- FIAT/functional.py | 3 +- 3 files changed, 75 insertions(+), 19 deletions(-) diff --git a/FIAT/dual_set.py b/FIAT/dual_set.py index 93e3ce727..acee76893 100644 --- a/FIAT/dual_set.py +++ b/FIAT/dual_set.py @@ -7,7 +7,6 @@ # SPDX-License-Identifier: LGPL-3.0-or-later import numpy -import collections from FIAT import polynomial_set, functional @@ -105,7 +104,7 @@ def to_riesz(self, poly_set): ed = poly_set.get_embedded_degree() num_exp = es.get_num_members(poly_set.get_embedded_degree()) - riesz_shape = tuple([num_nodes] + list(tshape) + [num_exp]) + riesz_shape = (num_nodes, *tshape, num_exp) mat = numpy.zeros(riesz_shape, "d") @@ -124,12 +123,16 @@ def to_riesz(self, poly_set): Qs_to_ells[Q] = [i] for Q in Qs_to_ells: - pts_to_ells.update(dict.fromkeys(map(tuple, Q.pts), Qs_to_ells[Q])) + pts_to_ells.update(dict.fromkeys(Q.pts, Qs_to_ells[Q])) + Qpts = set(pts_to_ells.keys()) for i, ell in enumerate(self.nodes): if isinstance(ell, functional.IntegralMoment): continue for pt in ell.pt_dict: + if pt in Qpts: + pts_to_ells[pt] = list(pts_to_ells[pt]) + Qpts.remove(pt) if pt in pts_to_ells: pts_to_ells[pt].append(i) else: diff --git a/FIAT/fdm_element.py b/FIAT/fdm_element.py index 554cea7e3..6b9cbaf40 100644 --- a/FIAT/fdm_element.py +++ b/FIAT/fdm_element.py @@ -8,13 +8,15 @@ import abc import numpy +import scipy -from FIAT import finite_element, dual_set, functional, quadrature -from FIAT.reference_element import LINE -from FIAT.orientation_utils import make_entity_permutations_simplex -from FIAT.hierarchical import IntegratedLegendre +from FIAT import dual_set, finite_element, functional, polynomial_set, quadrature from FIAT.barycentric_interpolation import LagrangePolynomialSet +from FIAT.hierarchical import IntegratedLegendre +from FIAT.orientation_utils import make_entity_permutations_simplex from FIAT.P0 import P0Dual +from FIAT.quadrature_schemes import create_quadrature +from FIAT.reference_element import LINE, symmetric_simplex def sym_eig(A, B): @@ -143,6 +145,52 @@ def __init__(self, ref_el, degree, bc_order=1, formdegree=0, orthogonalize=False super(FDMDual, self).__init__(nodes, ref_el, entity_ids, entity_permutations) +class SimplexFDMDualSet(dual_set.DualSet): + + def __init__(self, ref_el, degree): + sd = ref_el.get_spatial_dimension() + S = symmetric_simplex(sd) + CG = IntegratedLegendre(S, degree, variant="demkowicz") + + Q = create_quadrature(S, 2 * degree) + X, W = Q.get_points(), Q.get_weights() + V = CG.tabulate(1, X) + inner = lambda v: numpy.dot(numpy.multiply(v, W), v.T) + galerkin = lambda order, dofs: sum(inner(V[k][dofs]) for k in V if sum(k) == order) + + nodes = [] + dual = CG.dual_basis() + entity_dofs = CG.entity_dofs() + for dim in sorted(entity_dofs): + dofs = entity_dofs[dim][0] + if isinstance(dual[dofs[0]], functional.IntegralMoment): + B = galerkin(0, dofs) + A = galerkin(1, dofs) + _, S = scipy.linalg.eigh(B, A) + Sinv = numpy.dot(S.T, A) + phis = numpy.array([dual[i].f_at_qpts for i in dofs]) + phis = numpy.dot(Sinv, phis) + + Q = dual[dofs[0]].Q + Q_ref = Q.reference_rule() + for entity in sorted(entity_dofs[dim]): + dofs = entity_dofs[dim][entity] + if len(dofs) == 0: + continue + + Q = quadrature.FacetQuadratureRule(ref_el, dim, entity, Q_ref) + J = Q.jacobian() + scale = 1 / numpy.sqrt(abs(numpy.linalg.det(numpy.dot(J.T, J)))) + Jphis = scale * phis + nodes.extend(functional.IntegralMoment(ref_el, Q, phi) for phi in Jphis) + else: + for entity in sorted(entity_dofs[dim]): + nodes.extend(dual[i] for i in entity_dofs[dim][entity]) + + entity_permutations = CG.entity_permutations() + super(SimplexFDMDualSet, self).__init__(nodes, ref_el, entity_dofs, entity_permutations) + + class FDMFiniteElement(finite_element.CiarletElement): """1D element that diagonalizes bilinear forms with BCs.""" @@ -159,19 +207,23 @@ def _formdegree(self): pass def __init__(self, ref_el, degree): - if ref_el.shape != LINE: - raise ValueError("%s is only defined in one dimension." % type(self)) - if degree == 0: - dual = P0Dual(ref_el) + if ref_el.shape == LINE: + if degree == 0: + dual = P0Dual(ref_el) + else: + dual = FDMDual(ref_el, degree, bc_order=self._bc_order, + formdegree=self._formdegree, orthogonalize=self._orthogonalize) + if self._formdegree == 0: + poly_set = dual.embedded.poly_set + else: + lr = quadrature.GaussLegendreQuadratureLineRule(ref_el, degree+1) + poly_set = LagrangePolynomialSet(ref_el, lr.get_points()) else: - dual = FDMDual(ref_el, degree, bc_order=self._bc_order, - formdegree=self._formdegree, orthogonalize=self._orthogonalize) + assert self._formdegree == 0 + assert self._bc_order == 1 + poly_set = polynomial_set.ONPolynomialSet(ref_el, degree, variant="integral") + dual = SimplexFDMDualSet(ref_el, degree) - if self._formdegree == 0: - poly_set = dual.embedded.poly_set - else: - lr = quadrature.GaussLegendreQuadratureLineRule(ref_el, degree+1) - poly_set = LagrangePolynomialSet(ref_el, lr.get_points()) super(FDMFiniteElement, self).__init__(poly_set, dual, degree, self._formdegree) diff --git a/FIAT/functional.py b/FIAT/functional.py index c1f888688..86a9f6d8a 100644 --- a/FIAT/functional.py +++ b/FIAT/functional.py @@ -295,7 +295,8 @@ def __init__(self, ref_el, Q, f_at_qpts, comp=tuple(), shp=tuple()): self.f_at_qpts = f_at_qpts qpts, qwts = Q.get_points(), Q.get_weights() self.comp = comp - pt_dict = {tuple(pt): [(wt * f, comp)] for pt, wt, f in zip(qpts, qwts, f_at_qpts)} + weights = numpy.multiply(f_at_qpts, qwts) + pt_dict = {tuple(pt): [(wt, comp)] for pt, wt in zip(qpts, weights)} Functional.__init__(self, ref_el, shp, pt_dict, {}, "IntegralMoment") def __call__(self, fn): From 68f51183380a2a687da659fdd8e6a4a9b25382e5 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Wed, 20 Dec 2023 11:27:32 -0600 Subject: [PATCH 31/93] fix vertex DOFs --- FIAT/dual_set.py | 7 ++++--- FIAT/fdm_element.py | 18 +++++++----------- FIAT/hierarchical.py | 1 - 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/FIAT/dual_set.py b/FIAT/dual_set.py index acee76893..3bd737b6a 100644 --- a/FIAT/dual_set.py +++ b/FIAT/dual_set.py @@ -124,15 +124,16 @@ def to_riesz(self, poly_set): for Q in Qs_to_ells: pts_to_ells.update(dict.fromkeys(Q.pts, Qs_to_ells[Q])) - Qpts = set(pts_to_ells.keys()) + # keep track of keys that map to the same list + pts_sharing_ells = set(pts_to_ells.keys()) for i, ell in enumerate(self.nodes): if isinstance(ell, functional.IntegralMoment): continue for pt in ell.pt_dict: - if pt in Qpts: + if pt in pts_sharing_ells: pts_to_ells[pt] = list(pts_to_ells[pt]) - Qpts.remove(pt) + pts_sharing_ells.remove(pt) if pt in pts_to_ells: pts_to_ells[pt].append(i) else: diff --git a/FIAT/fdm_element.py b/FIAT/fdm_element.py index 6b9cbaf40..7935fa98a 100644 --- a/FIAT/fdm_element.py +++ b/FIAT/fdm_element.py @@ -161,31 +161,27 @@ def __init__(self, ref_el, degree): nodes = [] dual = CG.dual_basis() entity_dofs = CG.entity_dofs() - for dim in sorted(entity_dofs): + for dim in entity_dofs: dofs = entity_dofs[dim][0] - if isinstance(dual[dofs[0]], functional.IntegralMoment): + if len(dofs) > 0 and all(isinstance(dual[i], functional.IntegralMoment) for i in dofs): B = galerkin(0, dofs) A = galerkin(1, dofs) _, S = scipy.linalg.eigh(B, A) Sinv = numpy.dot(S.T, A) phis = numpy.array([dual[i].f_at_qpts for i in dofs]) phis = numpy.dot(Sinv, phis) + Q_ref = dual[dofs[0]].Q.reference_rule() - Q = dual[dofs[0]].Q - Q_ref = Q.reference_rule() - for entity in sorted(entity_dofs[dim]): - dofs = entity_dofs[dim][entity] - if len(dofs) == 0: - continue - + for entity in entity_dofs[dim]: Q = quadrature.FacetQuadratureRule(ref_el, dim, entity, Q_ref) J = Q.jacobian() scale = 1 / numpy.sqrt(abs(numpy.linalg.det(numpy.dot(J.T, J)))) Jphis = scale * phis nodes.extend(functional.IntegralMoment(ref_el, Q, phi) for phi in Jphis) else: - for entity in sorted(entity_dofs[dim]): - nodes.extend(dual[i] for i in entity_dofs[dim][entity]) + for entity in entity_dofs[dim]: + points = ref_el.make_points(dim, entity, degree, variant="gll") + nodes.extend(functional.PointEvaluation(ref_el, pt) for pt in points) entity_permutations = CG.entity_permutations() super(SimplexFDMDualSet, self).__init__(nodes, ref_el, entity_dofs, entity_permutations) diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index fcbf8b0bc..dadf6d7db 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -59,7 +59,6 @@ class IntegratedLegendreDual(dual_set.DualSet): def __init__(self, ref_el, degree, variant=None): if variant is None: variant = "beuchler" - variant = "orthonormal" duals = { "beuchler": self._beuchler_integral_duals, From a69637a61a21af29470b83032817329c2b84968d Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 21 Dec 2023 12:32:25 -0600 Subject: [PATCH 32/93] optimize DualSet.to_riesz --- FIAT/dual_set.py | 53 +++++++++++++++++++------------------------- FIAT/fdm_element.py | 9 ++++++-- FIAT/hierarchical.py | 35 ++++++++++------------------- 3 files changed, 42 insertions(+), 55 deletions(-) diff --git a/FIAT/dual_set.py b/FIAT/dual_set.py index 3bd737b6a..f90673e33 100644 --- a/FIAT/dual_set.py +++ b/FIAT/dual_set.py @@ -7,6 +7,7 @@ # SPDX-License-Identifier: LGPL-3.0-or-later import numpy +from itertools import chain from FIAT import polynomial_set, functional @@ -114,6 +115,7 @@ def to_riesz(self, poly_set): # Dictionary mapping quadratures to which functionals they come from Qs_to_ells = dict() + for i, ell in enumerate(self.nodes): if isinstance(ell, functional.IntegralMoment): Q = ell.Q @@ -121,19 +123,9 @@ def to_riesz(self, poly_set): Qs_to_ells[Q].append(i) else: Qs_to_ells[Q] = [i] - - for Q in Qs_to_ells: - pts_to_ells.update(dict.fromkeys(Q.pts, Qs_to_ells[Q])) - # keep track of keys that map to the same list - pts_sharing_ells = set(pts_to_ells.keys()) - - for i, ell in enumerate(self.nodes): - if isinstance(ell, functional.IntegralMoment): continue + for pt in ell.pt_dict: - if pt in pts_sharing_ells: - pts_to_ells[pt] = list(pts_to_ells[pt]) - pts_sharing_ells.remove(pt) if pt in pts_to_ells: pts_to_ells[pt].append(i) else: @@ -145,20 +137,26 @@ def to_riesz(self, poly_set): else: dpts_to_ells[pt] = [i] + for pt in pts_to_ells: + pts_to_ells[pt] = [pts_to_ells[pt]] + + for Q in Qs_to_ells: + ells = Qs_to_ells[Q] + for pt in map(tuple, Q.pts): + if pt in pts_to_ells: + pts_to_ells[pt].append(ells) + else: + pts_to_ells[pt] = [ells] + # Now tabulate the function values pts = list(pts_to_ells.keys()) expansion_values = es.tabulate(ed, pts) for j, pt in enumerate(pts): - which_ells = pts_to_ells[pt] - - for k in which_ells: - pt_dict = self.nodes[k].pt_dict - wc_list = pt_dict[pt] - - for i in range(num_exp): - for (w, c) in wc_list: - mat[k][c][i] += w*expansion_values[i, j] + vals = expansion_values[:, j] + for k in chain(*pts_to_ells[pt]): + for (w, c) in self.nodes[k].pt_dict[pt]: + mat[k][c][:] += w * vals # Tabulate the derivative values that are needed max_deriv_order = max([ell.max_deriv_order for ell in self.nodes]) @@ -167,19 +165,14 @@ def to_riesz(self, poly_set): # It's easiest/most efficient to get derivatives of the # expansion set through the polynomial set interface. # This is creating a short-lived set to do just this. - expansion = polynomial_set.ONPolynomialSet(self.ref_el, ed) + coeffs = numpy.eye(poly_set.get_num_members()) + expansion = polynomial_set.PolynomialSet(self.ref_el, ed, ed, es, coeffs) dexpansion_values = expansion.tabulate(dpts, max_deriv_order) for j, pt in enumerate(dpts): - which_ells = dpts_to_ells[pt] - - for k in which_ells: - dpt_dict = self.nodes[k].deriv_dict - wac_list = dpt_dict[pt] - - for i in range(num_exp): - for (w, alpha, c) in wac_list: - mat[k][c][i] += w*dexpansion_values[alpha][i, j] + for k in dpts_to_ells[pt]: + for (w, alpha, c) in self.nodes[k].deriv_dict[pt]: + mat[k][c][:] += w*dexpansion_values[alpha][:, j] return mat diff --git a/FIAT/fdm_element.py b/FIAT/fdm_element.py index 7935fa98a..7225c9ef9 100644 --- a/FIAT/fdm_element.py +++ b/FIAT/fdm_element.py @@ -170,12 +170,17 @@ def __init__(self, ref_el, degree): Sinv = numpy.dot(S.T, A) phis = numpy.array([dual[i].f_at_qpts for i in dofs]) phis = numpy.dot(Sinv, phis) - Q_ref = dual[dofs[0]].Q.reference_rule() + + Q = dual[dofs[0]].Q + J = Q.jacobian() + Q_ref = Q.reference_rule() + rscale = numpy.sqrt(abs(numpy.linalg.det(numpy.dot(J.T, J)))) + phis *= rscale for entity in entity_dofs[dim]: Q = quadrature.FacetQuadratureRule(ref_el, dim, entity, Q_ref) J = Q.jacobian() - scale = 1 / numpy.sqrt(abs(numpy.linalg.det(numpy.dot(J.T, J)))) + scale = 1/numpy.sqrt(abs(numpy.linalg.det(numpy.dot(J.T, J)))) Jphis = scale * phis nodes.extend(functional.IntegralMoment(ref_el, Q, phi) for phi in Jphis) else: diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index dadf6d7db..0e0c40018 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -85,16 +85,15 @@ def __init__(self, ref_el, degree, variant=None): entity_permutations[dim][entity] = perms continue + perms = make_entity_permutations_simplex(dim, degree - dim) ref_facet = symmetric_simplex(dim) - Q_ref, phis = duals(ref_facet, degree) - perms = make_entity_permutations_simplex(dim, degree - dim) for entity in sorted(top[dim]): cur = len(nodes) Q = FacetQuadratureRule(ref_el, dim, entity, Q_ref) + J = Q.jacobian() # phis must transform like a d-form to undo the measure transformation - J = Q.jacobian() scale = 1/numpy.sqrt(abs(numpy.linalg.det(numpy.dot(J.T, J)))) Jphis = scale * phis @@ -105,8 +104,8 @@ def __init__(self, ref_el, degree, variant=None): super(IntegratedLegendreDual, self).__init__(nodes, ref_el, entity_ids, entity_permutations) def _beuchler_point_duals(self, ref_el, degree, variant="gll"): - points = make_lattice(ref_el.vertices, degree) - weights = (1,) * len(points) + points = make_lattice(ref_el.vertices, degree, variant=variant) + weights = (1.0,) * len(points) Q = QuadratureRule(ref_el, points, weights) B = make_bubbles(ref_el, degree) V = numpy.transpose(B.expansion_set.tabulate(degree, points)) @@ -117,8 +116,7 @@ def _beuchler_point_duals(self, ref_el, degree, variant="gll"): def _beuchler_integral_duals(self, ref_el, degree): Q = create_quadrature(ref_el, 2 * degree) - qpts = Q.get_points() - qwts = Q.get_weights() + qpts, qwts = Q.get_points(), Q.get_weights() inner = lambda v, u: numpy.dot(numpy.multiply(v, qwts), u.T) dim = ref_el.get_spatial_dimension() @@ -137,8 +135,7 @@ def _beuchler_integral_duals(self, ref_el, degree): def _demkowicz_duals(self, ref_el, degree): Q = create_quadrature(ref_el, 2 * degree) - qpts = Q.get_points() - qwts = Q.get_weights() + qpts, qwts = Q.get_points(), Q.get_weights() moments = lambda v: numpy.dot(numpy.multiply(v, qwts), v.T) dim = ref_el.get_spatial_dimension() @@ -163,14 +160,11 @@ def _demkowicz_duals(self, ref_el, degree): def _orthonormal_duals(self, ref_el, degree, solver=None): dim = ref_el.get_spatial_dimension() - if dim > 1: - solver = "eig" Q = create_quadrature(ref_el, 2 * degree) - qpts = Q.get_points() - qwts = Q.get_weights() + qpts, qwts = Q.get_points(), Q.get_weights() inner = lambda v, u: numpy.dot(numpy.multiply(v, qwts), u.T) - Hk_inner = lambda order, v, u: sum(inner(v[k], u[k]) for k in v if sum(k) == order) + galerkin = lambda order, v, u: sum(inner(v[k], u[k]) for k in v if sum(k) == order) B = make_bubbles(ref_el, degree) B_table = B.tabulate(qpts, 1) @@ -178,19 +172,14 @@ def _orthonormal_duals(self, ref_el, degree, solver=None): P = ONPolynomialSet(ref_el, degree) P_table = P.tabulate(qpts, 1) - KBP = Hk_inner(1, B_table, P_table) + KBP = galerkin(1, B_table, P_table) phis = P_table[(0,) * dim] phis = numpy.dot(KBP, phis) if len(phis) > 0: - KBB = Hk_inner(1, B_table, B_table) - if solver == "eig": - MBB = Hk_inner(0, B_table, B_table) - _, S = scipy.linalg.eigh(MBB, KBB) - phis = numpy.dot(S.T, phis) - elif solver == "cholesky": - V = numpy.linalg.cholesky(KBB) - phis = numpy.linalg.solve(V, phis) + KBB = galerkin(1, B_table, B_table) + V = numpy.linalg.cholesky(KBB) + phis = numpy.linalg.solve(V, phis) return Q, phis From 366d63978e61e706caf9bfdb2b480730a73b0ded Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 21 Dec 2023 12:41:22 -0600 Subject: [PATCH 33/93] fix coefficients shape --- FIAT/dual_set.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FIAT/dual_set.py b/FIAT/dual_set.py index f90673e33..f48c539a8 100644 --- a/FIAT/dual_set.py +++ b/FIAT/dual_set.py @@ -165,7 +165,7 @@ def to_riesz(self, poly_set): # It's easiest/most efficient to get derivatives of the # expansion set through the polynomial set interface. # This is creating a short-lived set to do just this. - coeffs = numpy.eye(poly_set.get_num_members()) + coeffs = numpy.eye(num_exp) expansion = polynomial_set.PolynomialSet(self.ref_el, ed, ed, es, coeffs) dexpansion_values = expansion.tabulate(dpts, max_deriv_order) From cb744bb1c7f98e484cd6f35f2aff546ce02427f2 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 21 Dec 2023 13:14:46 -0600 Subject: [PATCH 34/93] BLAS-3 optimization of DualSet.to_riesz --- FIAT/dual_set.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/FIAT/dual_set.py b/FIAT/dual_set.py index f48c539a8..c4e3578d2 100644 --- a/FIAT/dual_set.py +++ b/FIAT/dual_set.py @@ -105,10 +105,6 @@ def to_riesz(self, poly_set): ed = poly_set.get_embedded_degree() num_exp = es.get_num_members(poly_set.get_embedded_degree()) - riesz_shape = (num_nodes, *tshape, num_exp) - - mat = numpy.zeros(riesz_shape, "d") - # Dictionaries mapping pts to which functionals they come from pts_to_ells = dict() dpts_to_ells = dict() @@ -152,11 +148,14 @@ def to_riesz(self, poly_set): pts = list(pts_to_ells.keys()) expansion_values = es.tabulate(ed, pts) + wshape = (num_nodes, *tshape, len(pts)) + wts = numpy.zeros(wshape, "d") for j, pt in enumerate(pts): - vals = expansion_values[:, j] for k in chain(*pts_to_ells[pt]): for (w, c) in self.nodes[k].pt_dict[pt]: - mat[k][c][:] += w * vals + wts[k][c][j] += w + + mat = numpy.dot(wts, expansion_values.T) # Tabulate the derivative values that are needed max_deriv_order = max([ell.max_deriv_order for ell in self.nodes]) @@ -169,11 +168,14 @@ def to_riesz(self, poly_set): expansion = polynomial_set.PolynomialSet(self.ref_el, ed, ed, es, coeffs) dexpansion_values = expansion.tabulate(dpts, max_deriv_order) + wshape = (num_nodes, *tshape, len(dpts)) + dwts = {alpha: numpy.zeros(wshape, "d") for alpha in dexpansion_values if sum(alpha) > 0} for j, pt in enumerate(dpts): for k in dpts_to_ells[pt]: for (w, alpha, c) in self.nodes[k].deriv_dict[pt]: - mat[k][c][:] += w*dexpansion_values[alpha][:, j] - + dwts[alpha][k][c][j] += w + for alpha in dwts: + mat += numpy.dot(dwts[alpha], dexpansion_values[alpha].T) return mat From ca52c0a47dde9fdb922d3e3170257b711875c927 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 21 Dec 2023 22:54:42 -0600 Subject: [PATCH 35/93] add pseudodeterminant function --- FIAT/fdm_element.py | 50 +++++++++++++++++++++---------------- FIAT/hierarchical.py | 7 +++--- FIAT/nedelec_second_kind.py | 3 ++- FIAT/quadrature.py | 9 ++++++- 4 files changed, 42 insertions(+), 27 deletions(-) diff --git a/FIAT/fdm_element.py b/FIAT/fdm_element.py index 7225c9ef9..5f9e80252 100644 --- a/FIAT/fdm_element.py +++ b/FIAT/fdm_element.py @@ -8,7 +8,6 @@ import abc import numpy -import scipy from FIAT import dual_set, finite_element, functional, polynomial_set, quadrature from FIAT.barycentric_interpolation import LagrangePolynomialSet @@ -154,42 +153,51 @@ def __init__(self, ref_el, degree): Q = create_quadrature(S, 2 * degree) X, W = Q.get_points(), Q.get_weights() + V = CG.tabulate(1, X) inner = lambda v: numpy.dot(numpy.multiply(v, W), v.T) galerkin = lambda order, dofs: sum(inner(V[k][dofs]) for k in V if sum(k) == order) nodes = [] - dual = CG.dual_basis() + entity_ids = {} + + CG_nodes = CG.dual_basis() entity_dofs = CG.entity_dofs() - for dim in entity_dofs: + for dim in sorted(entity_dofs): + entity_ids[dim] = {} + dofs = entity_dofs[dim][0] - if len(dofs) > 0 and all(isinstance(dual[i], functional.IntegralMoment) for i in dofs): + if len(dofs) > 0 and all(isinstance(CG_nodes[i], functional.IntegralMoment) for i in dofs): B = galerkin(0, dofs) A = galerkin(1, dofs) - _, S = scipy.linalg.eigh(B, A) + _, S = sym_eig(B, A) Sinv = numpy.dot(S.T, A) - phis = numpy.array([dual[i].f_at_qpts for i in dofs]) - phis = numpy.dot(Sinv, phis) - - Q = dual[dofs[0]].Q - J = Q.jacobian() - Q_ref = Q.reference_rule() - rscale = numpy.sqrt(abs(numpy.linalg.det(numpy.dot(J.T, J)))) - phis *= rscale - - for entity in entity_dofs[dim]: - Q = quadrature.FacetQuadratureRule(ref_el, dim, entity, Q_ref) - J = Q.jacobian() - scale = 1/numpy.sqrt(abs(numpy.linalg.det(numpy.dot(J.T, J)))) + + fs_at_qpts = numpy.array([CG_nodes[i].f_at_qpts for i in dofs]) + phis = numpy.dot(Sinv, fs_at_qpts) + + Q_dof = CG_nodes[dofs[0]].Q + Q_ref = Q_dof.reference_rule() + phis *= Q_dof.jacobian_determinant() + for entity in sorted(entity_dofs[dim]): + cur = len(nodes) + Q_facet = quadrature.FacetQuadratureRule(ref_el, dim, entity, Q_ref) + + # phis must transform like a d-form to undo the measure transformation + scale = 1 / Q_facet.jacobian_determinant() Jphis = scale * phis - nodes.extend(functional.IntegralMoment(ref_el, Q, phi) for phi in Jphis) + + nodes.extend(functional.IntegralMoment(ref_el, Q_facet, phi) for phi in Jphis) + entity_ids[dim][entity] = list(range(cur, len(nodes))) else: - for entity in entity_dofs[dim]: + for entity in sorted(entity_dofs[dim]): + cur = len(nodes) points = ref_el.make_points(dim, entity, degree, variant="gll") nodes.extend(functional.PointEvaluation(ref_el, pt) for pt in points) + entity_ids[dim][entity] = list(range(cur, len(nodes))) entity_permutations = CG.entity_permutations() - super(SimplexFDMDualSet, self).__init__(nodes, ref_el, entity_dofs, entity_permutations) + super(SimplexFDMDualSet, self).__init__(nodes, ref_el, entity_ids, entity_permutations) class FDMFiniteElement(finite_element.CiarletElement): diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index 0e0c40018..4ea51558c 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -90,14 +90,13 @@ def __init__(self, ref_el, degree, variant=None): Q_ref, phis = duals(ref_facet, degree) for entity in sorted(top[dim]): cur = len(nodes) - Q = FacetQuadratureRule(ref_el, dim, entity, Q_ref) - J = Q.jacobian() + Q_facet = FacetQuadratureRule(ref_el, dim, entity, Q_ref) # phis must transform like a d-form to undo the measure transformation - scale = 1/numpy.sqrt(abs(numpy.linalg.det(numpy.dot(J.T, J)))) + scale = 1 / Q_facet.jacobian_determinant() Jphis = scale * phis - nodes.extend(functional.IntegralMoment(ref_el, Q, phi) for phi in reversed(Jphis)) + nodes.extend(functional.IntegralMoment(ref_el, Q_facet, phi) for phi in reversed(Jphis)) entity_ids[dim][entity] = list(range(cur, len(nodes))) entity_permutations[dim][entity] = perms diff --git a/FIAT/nedelec_second_kind.py b/FIAT/nedelec_second_kind.py index 1115a8617..6e5269721 100644 --- a/FIAT/nedelec_second_kind.py +++ b/FIAT/nedelec_second_kind.py @@ -149,9 +149,10 @@ def _generate_facet_dofs(self, codim, cell, degree, offset, variant, interpolant # Get the quadrature and Jacobian on this facet Q_facet = FacetQuadratureRule(cell, codim, facet, Q_ref) J = Q_facet.jacobian() + detJ = Q_facet.jacobian_determinant() # Map Phis -> phis (reference values to physical values) - piola_map = J / numpy.sqrt(numpy.linalg.det(numpy.dot(J.T, J))) + piola_map = J / detJ phis = numpy.dot(Phis, piola_map.T) phis = numpy.transpose(phis, (0, 2, 1)) diff --git a/FIAT/quadrature.py b/FIAT/quadrature.py index 3f7cbc499..f6f237d39 100644 --- a/FIAT/quadrature.py +++ b/FIAT/quadrature.py @@ -14,12 +14,16 @@ from FIAT import reference_element +def pseudo_determinant(A): + return numpy.sqrt(abs(numpy.linalg.det(numpy.dot(A.T, A)))) + + def map_quadrature(pts_ref, wts_ref, source_cell, target_cell, jacobian=False): """Map quadrature points and weights defined on source_cell to target_cell. """ A, b = reference_element.make_affine_mapping(source_cell.get_vertices(), target_cell.get_vertices()) - scale = numpy.sqrt(numpy.linalg.det(numpy.dot(A.T, A))) + scale = pseudo_determinant(A) pts = numpy.dot(pts_ref.reshape((-1, A.shape[1])), A.T) + b[None, :] wts = scale * wts_ref # return immutable types @@ -173,6 +177,9 @@ def reference_rule(self): def jacobian(self): return self._J + def jacobian_determinant(self): + return pseudo_determinant(self._J) + def make_quadrature(ref_el, m): """Returns the collapsed quadrature rule using m points per From 490a0eb02c6c374664051f81b5d98e1aada8d548 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Sun, 24 Dec 2023 15:52:23 -0600 Subject: [PATCH 36/93] demkowicz_point variant --- FIAT/fdm_element.py | 51 ++++++++++++++++++++++---------------------- FIAT/hierarchical.py | 37 +++++++++++++++++++++++++------- 2 files changed, 54 insertions(+), 34 deletions(-) diff --git a/FIAT/fdm_element.py b/FIAT/fdm_element.py index 5f9e80252..1f7bd8f72 100644 --- a/FIAT/fdm_element.py +++ b/FIAT/fdm_element.py @@ -165,36 +165,35 @@ def __init__(self, ref_el, degree): entity_dofs = CG.entity_dofs() for dim in sorted(entity_dofs): entity_ids[dim] = {} - dofs = entity_dofs[dim][0] - if len(dofs) > 0 and all(isinstance(CG_nodes[i], functional.IntegralMoment) for i in dofs): - B = galerkin(0, dofs) - A = galerkin(1, dofs) - _, S = sym_eig(B, A) - Sinv = numpy.dot(S.T, A) - - fs_at_qpts = numpy.array([CG_nodes[i].f_at_qpts for i in dofs]) - phis = numpy.dot(Sinv, fs_at_qpts) - - Q_dof = CG_nodes[dofs[0]].Q - Q_ref = Q_dof.reference_rule() - phis *= Q_dof.jacobian_determinant() - for entity in sorted(entity_dofs[dim]): - cur = len(nodes) - Q_facet = quadrature.FacetQuadratureRule(ref_el, dim, entity, Q_ref) - - # phis must transform like a d-form to undo the measure transformation - scale = 1 / Q_facet.jacobian_determinant() - Jphis = scale * phis - - nodes.extend(functional.IntegralMoment(ref_el, Q_facet, phi) for phi in Jphis) - entity_ids[dim][entity] = list(range(cur, len(nodes))) - else: + if len(dofs) == 0 or dim == 0: for entity in sorted(entity_dofs[dim]): cur = len(nodes) - points = ref_el.make_points(dim, entity, degree, variant="gll") + points = ref_el.make_points(dim, entity, degree) nodes.extend(functional.PointEvaluation(ref_el, pt) for pt in points) entity_ids[dim][entity] = list(range(cur, len(nodes))) + continue + + B = galerkin(0, dofs) + A = galerkin(1, dofs) + _, S = sym_eig(B, A) + Sinv = numpy.dot(S.T, A) + fs_at_qpts = numpy.array([CG_nodes[i].f_at_qpts for i in dofs]) + phis = numpy.dot(Sinv, fs_at_qpts) + + Q_dof = CG_nodes[dofs[0]].Q + Q_ref = Q_dof.reference_rule() + phis *= Q_dof.jacobian_determinant() + for entity in sorted(entity_dofs[dim]): + cur = len(nodes) + Q_facet = quadrature.FacetQuadratureRule(ref_el, dim, entity, Q_ref) + + # phis must transform like a d-form to undo the measure transformation + scale = 1 / Q_facet.jacobian_determinant() + Jphis = scale * phis + + nodes.extend(functional.IntegralMoment(ref_el, Q_facet, phi) for phi in Jphis) + entity_ids[dim][entity] = list(range(cur, len(nodes))) entity_permutations = CG.entity_permutations() super(SimplexFDMDualSet, self).__init__(nodes, ref_el, entity_ids, entity_permutations) @@ -230,8 +229,8 @@ def __init__(self, ref_el, degree): else: assert self._formdegree == 0 assert self._bc_order == 1 - poly_set = polynomial_set.ONPolynomialSet(ref_el, degree, variant="integral") dual = SimplexFDMDualSet(ref_el, degree) + poly_set = polynomial_set.ONPolynomialSet(ref_el, degree, variant="integral") super(FDMFiniteElement, self).__init__(poly_set, dual, degree, self._formdegree) diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index 4ea51558c..2bceb6b97 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -9,7 +9,7 @@ import numpy import scipy -from FIAT import finite_element, dual_set, functional +from FIAT import finite_element, dual_set, functional, Lagrange from FIAT.reference_element import (POINT, LINE, TRIANGLE, TETRAHEDRON, make_lattice, symmetric_simplex) from FIAT.orientation_utils import make_entity_permutations_simplex @@ -63,7 +63,8 @@ def __init__(self, ref_el, degree, variant=None): duals = { "beuchler": self._beuchler_integral_duals, "beuchler_point": self._beuchler_point_duals, - "demkowicz": self._demkowicz_duals, + "demkowicz": self._demkowicz_integral_duals, + "demkowicz_point": self._demkowicz_point_duals, "orthonormal": self._orthonormal_duals, }[variant] @@ -96,7 +97,7 @@ def __init__(self, ref_el, degree, variant=None): scale = 1 / Q_facet.jacobian_determinant() Jphis = scale * phis - nodes.extend(functional.IntegralMoment(ref_el, Q_facet, phi) for phi in reversed(Jphis)) + nodes.extend(functional.IntegralMoment(ref_el, Q_facet, phi) for phi in Jphis) entity_ids[dim][entity] = list(range(cur, len(nodes))) entity_permutations[dim][entity] = perms @@ -132,7 +133,27 @@ def _beuchler_integral_duals(self, ref_el, degree): phis = numpy.dot(B.get_coeffs(), phis) return Q, phis - def _demkowicz_duals(self, ref_el, degree): + def _demkowicz_point_duals(self, ref_el, degree, variant="gll"): + Qkm1 = create_quadrature(ref_el, 2 * (degree-1)) + qpts, qwts = Qkm1.get_points(), Qkm1.get_weights() + inner = lambda v, u: numpy.dot(numpy.multiply(v, qwts), u.T) + galerkin = lambda order, V, U: sum(inner(V[k], U[k]) for k in V if sum(k) == order) + + P = Lagrange(ref_el, degree, variant=variant) + P_table = P.tabulate(1, qpts) + B = make_bubbles(ref_el, degree) + B_table = B.tabulate(qpts, 1) + phis = galerkin(1, B_table, P_table) + + points = [] + for node in P.dual_basis(): + pt, = node.get_point_dict() + points.append(pt) + weights = (1.0,) * len(points) + Q = QuadratureRule(ref_el, points, weights) + return Q, phis + + def _demkowicz_integral_duals(self, ref_el, degree): Q = create_quadrature(ref_el, 2 * degree) qpts, qwts = Q.get_points(), Q.get_weights() moments = lambda v: numpy.dot(numpy.multiply(v, qwts), v.T) @@ -149,21 +170,21 @@ def _demkowicz_duals(self, ref_el, degree): # Assemble a stiffness matrix in the ON basis K = sum(moments(P_table[alpha]) for alpha in P_table if sum(alpha) == 1) # Change of basis to Lagrange polynomials at the quadrature nodes - v = numpy.multiply(P_table[(0, ) * dim], qwts) - K = numpy.dot(numpy.dot(v.T, K), v) + V = numpy.multiply(P_table[(0, ) * dim], qwts) + K = numpy.dot(numpy.dot(V.T, K), V) B = make_bubbles(ref_el, degree) phis = B.tabulate(qpts)[(0,) * dim] phis = numpy.multiply(numpy.dot(phis, K), 1/qwts) return Q, phis - def _orthonormal_duals(self, ref_el, degree, solver=None): + def _orthonormal_duals(self, ref_el, degree): dim = ref_el.get_spatial_dimension() Q = create_quadrature(ref_el, 2 * degree) qpts, qwts = Q.get_points(), Q.get_weights() inner = lambda v, u: numpy.dot(numpy.multiply(v, qwts), u.T) - galerkin = lambda order, v, u: sum(inner(v[k], u[k]) for k in v if sum(k) == order) + galerkin = lambda order, V, U: sum(inner(V[k], U[k]) for k in v if sum(k) == order) B = make_bubbles(ref_el, degree) B_table = B.tabulate(qpts, 1) From 9d0a90c7f2810bf239163fefd29f343643f7fbf8 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Sun, 24 Dec 2023 16:47:30 -0600 Subject: [PATCH 37/93] Avoid pts_to_ells dict via transpose access to dualmat --- FIAT/dual_set.py | 65 ++++++++++++++------------------------------ FIAT/hierarchical.py | 2 +- 2 files changed, 22 insertions(+), 45 deletions(-) diff --git a/FIAT/dual_set.py b/FIAT/dual_set.py index c4e3578d2..80dc24a39 100644 --- a/FIAT/dual_set.py +++ b/FIAT/dual_set.py @@ -7,7 +7,6 @@ # SPDX-License-Identifier: LGPL-3.0-or-later import numpy -from itertools import chain from FIAT import polynomial_set, functional @@ -105,54 +104,30 @@ def to_riesz(self, poly_set): ed = poly_set.get_embedded_degree() num_exp = es.get_num_members(poly_set.get_embedded_degree()) - # Dictionaries mapping pts to which functionals they come from - pts_to_ells = dict() - dpts_to_ells = dict() - - # Dictionary mapping quadratures to which functionals they come from - Qs_to_ells = dict() + pts = set() + dpts = set() + Qs = set() for i, ell in enumerate(self.nodes): if isinstance(ell, functional.IntegralMoment): - Q = ell.Q - if Q in Qs_to_ells: - Qs_to_ells[Q].append(i) - else: - Qs_to_ells[Q] = [i] - continue - - for pt in ell.pt_dict: - if pt in pts_to_ells: - pts_to_ells[pt].append(i) - else: - pts_to_ells[pt] = [i] - - for pt in ell.deriv_dict: - if pt in dpts_to_ells: - dpts_to_ells[pt].append(i) - else: - dpts_to_ells[pt] = [i] - - for pt in pts_to_ells: - pts_to_ells[pt] = [pts_to_ells[pt]] - - for Q in Qs_to_ells: - ells = Qs_to_ells[Q] - for pt in map(tuple, Q.pts): - if pt in pts_to_ells: - pts_to_ells[pt].append(ells) - else: - pts_to_ells[pt] = [ells] + Qs.add(ell.Q) + else: + pts.update(ell.pt_dict) + dpts.update(ell.deriv_dict) + + for Q in Qs: + pts.update(map(tuple, Q.pts)) # Now tabulate the function values - pts = list(pts_to_ells.keys()) + pts = list(sorted(pts)) expansion_values = es.tabulate(ed, pts) wshape = (num_nodes, *tshape, len(pts)) wts = numpy.zeros(wshape, "d") - for j, pt in enumerate(pts): - for k in chain(*pts_to_ells[pt]): - for (w, c) in self.nodes[k].pt_dict[pt]: + for k, node in enumerate(self.nodes): + for pt in node.pt_dict: + j = pts.index(pt) + for (w, c) in node.pt_dict[pt]: wts[k][c][j] += w mat = numpy.dot(wts, expansion_values.T) @@ -160,7 +135,7 @@ def to_riesz(self, poly_set): # Tabulate the derivative values that are needed max_deriv_order = max([ell.max_deriv_order for ell in self.nodes]) if max_deriv_order > 0: - dpts = list(dpts_to_ells.keys()) + dpts = list(sorted(dpts)) # It's easiest/most efficient to get derivatives of the # expansion set through the polynomial set interface. # This is creating a short-lived set to do just this. @@ -170,10 +145,12 @@ def to_riesz(self, poly_set): wshape = (num_nodes, *tshape, len(dpts)) dwts = {alpha: numpy.zeros(wshape, "d") for alpha in dexpansion_values if sum(alpha) > 0} - for j, pt in enumerate(dpts): - for k in dpts_to_ells[pt]: - for (w, alpha, c) in self.nodes[k].deriv_dict[pt]: + for k, node in enumerate(self.nodes): + for pt in node.deriv_dict: + j = dpts.index(pt) + for (w, alpha, c) in node.deriv_dict[pt]: dwts[alpha][k][c][j] += w + for alpha in dwts: mat += numpy.dot(dwts[alpha], dexpansion_values[alpha].T) return mat diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index 2bceb6b97..7db3978ff 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -184,7 +184,7 @@ def _orthonormal_duals(self, ref_el, degree): Q = create_quadrature(ref_el, 2 * degree) qpts, qwts = Q.get_points(), Q.get_weights() inner = lambda v, u: numpy.dot(numpy.multiply(v, qwts), u.T) - galerkin = lambda order, V, U: sum(inner(V[k], U[k]) for k in v if sum(k) == order) + galerkin = lambda order, V, U: sum(inner(V[k], U[k]) for k in V if sum(k) == order) B = make_bubbles(ref_el, degree) B_table = B.tabulate(qpts, 1) From e85731aa1a2274ec753182f0835674e17adc2df5 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Sun, 24 Dec 2023 19:05:38 -0600 Subject: [PATCH 38/93] vectorize to_riesz for IntegralMoment --- FIAT/dual_set.py | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/FIAT/dual_set.py b/FIAT/dual_set.py index 80dc24a39..104ebe22e 100644 --- a/FIAT/dual_set.py +++ b/FIAT/dual_set.py @@ -106,16 +106,20 @@ def to_riesz(self, poly_set): pts = set() dpts = set() - Qs = set() + Qs_to_ells = dict() for i, ell in enumerate(self.nodes): if isinstance(ell, functional.IntegralMoment): - Qs.add(ell.Q) + Q = ell.Q + if Q in Qs_to_ells: + Qs_to_ells[Q].append(i) + else: + Qs_to_ells[Q] = [i] else: - pts.update(ell.pt_dict) - dpts.update(ell.deriv_dict) + pts.update(ell.pt_dict.keys()) + dpts.update(ell.deriv_dict.keys()) - for Q in Qs: + for Q in Qs_to_ells: pts.update(map(tuple, Q.pts)) # Now tabulate the function values @@ -124,12 +128,22 @@ def to_riesz(self, poly_set): wshape = (num_nodes, *tshape, len(pts)) wts = numpy.zeros(wshape, "d") - for k, node in enumerate(self.nodes): - for pt in node.pt_dict: + for k, ell in enumerate(self.nodes): + if isinstance(ell, functional.IntegralMoment): + continue + for pt, wc_list in ell.pt_dict.items(): j = pts.index(pt) - for (w, c) in node.pt_dict[pt]: + for (w, c) in wc_list: wts[k][c][j] += w + for Q in Qs_to_ells: + qwts = Q.get_weights() + qpts = tuple(map(tuple, Q.pts)) + indices = list(map(pts.index, qpts)) + for k in Qs_to_ells[Q]: + ell = self.nodes[k] + wts[k][ell.comp][indices] += numpy.multiply(ell.f_at_qpts, qwts) + mat = numpy.dot(wts, expansion_values.T) # Tabulate the derivative values that are needed @@ -145,10 +159,10 @@ def to_riesz(self, poly_set): wshape = (num_nodes, *tshape, len(dpts)) dwts = {alpha: numpy.zeros(wshape, "d") for alpha in dexpansion_values if sum(alpha) > 0} - for k, node in enumerate(self.nodes): - for pt in node.deriv_dict: + for k, ell in enumerate(self.nodes): + for pt, wac_list in ell.deriv_dict.items(): j = dpts.index(pt) - for (w, alpha, c) in node.deriv_dict[pt]: + for (w, alpha, c) in wac_list: dwts[alpha][k][c][j] += w for alpha in dwts: From a9dd53dec75a8c482cb483688a432a2b238887c6 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Mon, 25 Dec 2023 10:24:03 -0600 Subject: [PATCH 39/93] exploit block sparsity in DualSet.to_riesz --- FIAT/dual_set.py | 70 +++++++++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/FIAT/dual_set.py b/FIAT/dual_set.py index 104ebe22e..92f18edb4 100644 --- a/FIAT/dual_set.py +++ b/FIAT/dual_set.py @@ -104,47 +104,55 @@ def to_riesz(self, poly_set): ed = poly_set.get_embedded_degree() num_exp = es.get_num_members(poly_set.get_embedded_degree()) + riesz_shape = (num_nodes, *tshape, num_exp) + mat = numpy.zeros(riesz_shape, "d") + pts = set() dpts = set() Qs_to_ells = dict() - for i, ell in enumerate(self.nodes): if isinstance(ell, functional.IntegralMoment): Q = ell.Q - if Q in Qs_to_ells: - Qs_to_ells[Q].append(i) - else: - Qs_to_ells[Q] = [i] else: + Q = None pts.update(ell.pt_dict.keys()) dpts.update(ell.deriv_dict.keys()) + if Q in Qs_to_ells: + Qs_to_ells[Q].append(i) + else: + Qs_to_ells[Q] = [i] + Qs_to_pts = {None: tuple(sorted(pts))} for Q in Qs_to_ells: - pts.update(map(tuple, Q.pts)) + if Q is not None: + cur_pts = tuple(map(tuple, Q.pts)) + Qs_to_pts[Q] = cur_pts + pts.update(cur_pts) # Now tabulate the function values pts = list(sorted(pts)) - expansion_values = es.tabulate(ed, pts) - - wshape = (num_nodes, *tshape, len(pts)) - wts = numpy.zeros(wshape, "d") - for k, ell in enumerate(self.nodes): - if isinstance(ell, functional.IntegralMoment): - continue - for pt, wc_list in ell.pt_dict.items(): - j = pts.index(pt) - for (w, c) in wc_list: - wts[k][c][j] += w + expansion_values = numpy.transpose(es.tabulate(ed, pts)) for Q in Qs_to_ells: - qwts = Q.get_weights() - qpts = tuple(map(tuple, Q.pts)) - indices = list(map(pts.index, qpts)) - for k in Qs_to_ells[Q]: - ell = self.nodes[k] - wts[k][ell.comp][indices] += numpy.multiply(ell.f_at_qpts, qwts) - - mat = numpy.dot(wts, expansion_values.T) + ells = Qs_to_ells[Q] + cur_pts = Qs_to_pts[Q] + indices = list(map(pts.index, cur_pts)) + wshape = (len(ells), *tshape, len(indices)) + wts = numpy.zeros(wshape, "d") + if Q is None: + for i, k in enumerate(ells): + ell = self.nodes[k] + for pt, wc_list in ell.pt_dict.items(): + j = cur_pts.index(pt) + for (w, c) in wc_list: + wts[i][c][j] += w + else: + for i, k in enumerate(ells): + ell = self.nodes[k] + wts[i][ell.comp][:] = ell.f_at_qpts + qwts = Q.get_weights() + wts = numpy.multiply(wts, qwts, out=wts) + mat[ells] += numpy.dot(wts, expansion_values[indices]) # Tabulate the derivative values that are needed max_deriv_order = max([ell.max_deriv_order for ell in self.nodes]) @@ -157,16 +165,16 @@ def to_riesz(self, poly_set): expansion = polynomial_set.PolynomialSet(self.ref_el, ed, ed, es, coeffs) dexpansion_values = expansion.tabulate(dpts, max_deriv_order) - wshape = (num_nodes, *tshape, len(dpts)) + ells = [k for k, ell in enumerate(self.nodes) if len(ell.deriv_dict) > 0] + wshape = (len(ells), *tshape, len(dpts)) dwts = {alpha: numpy.zeros(wshape, "d") for alpha in dexpansion_values if sum(alpha) > 0} - for k, ell in enumerate(self.nodes): + for i, k in enumerate(ells): + ell = self.nodes[k] for pt, wac_list in ell.deriv_dict.items(): j = dpts.index(pt) for (w, alpha, c) in wac_list: - dwts[alpha][k][c][j] += w - - for alpha in dwts: - mat += numpy.dot(dwts[alpha], dexpansion_values[alpha].T) + dwts[alpha][i][c][j] += w + mat[ells] += sum(numpy.dot(dwts[alpha], dexpansion_values[alpha].T) for alpha in dwts) return mat From d01971aee44cc2de38261047174ed15a5edcc120 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Tue, 26 Dec 2023 15:54:22 -0600 Subject: [PATCH 40/93] optimize FrobeniusIntegralMoment --- FIAT/dual_set.py | 11 ++++++----- FIAT/functional.py | 20 +++++++++----------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/FIAT/dual_set.py b/FIAT/dual_set.py index 92f18edb4..ddf129491 100644 --- a/FIAT/dual_set.py +++ b/FIAT/dual_set.py @@ -137,7 +137,7 @@ def to_riesz(self, poly_set): ells = Qs_to_ells[Q] cur_pts = Qs_to_pts[Q] indices = list(map(pts.index, cur_pts)) - wshape = (len(ells), *tshape, len(indices)) + wshape = (len(ells), *tshape, len(cur_pts)) wts = numpy.zeros(wshape, "d") if Q is None: for i, k in enumerate(ells): @@ -145,7 +145,7 @@ def to_riesz(self, poly_set): for pt, wc_list in ell.pt_dict.items(): j = cur_pts.index(pt) for (w, c) in wc_list: - wts[i][c][j] += w + wts[i][c][j] = w else: for i, k in enumerate(ells): ell = self.nodes[k] @@ -155,7 +155,7 @@ def to_riesz(self, poly_set): mat[ells] += numpy.dot(wts, expansion_values[indices]) # Tabulate the derivative values that are needed - max_deriv_order = max([ell.max_deriv_order for ell in self.nodes]) + max_deriv_order = max(ell.max_deriv_order for ell in self.nodes) if max_deriv_order > 0: dpts = list(sorted(dpts)) # It's easiest/most efficient to get derivatives of the @@ -173,8 +173,9 @@ def to_riesz(self, poly_set): for pt, wac_list in ell.deriv_dict.items(): j = dpts.index(pt) for (w, alpha, c) in wac_list: - dwts[alpha][i][c][j] += w - mat[ells] += sum(numpy.dot(dwts[alpha], dexpansion_values[alpha].T) for alpha in dwts) + dwts[alpha][i][c][j] = w + for alpha in dwts: + mat[ells] += numpy.dot(dwts[alpha], dexpansion_values[alpha].T) return mat diff --git a/FIAT/functional.py b/FIAT/functional.py index 86a9f6d8a..fb46f9454 100644 --- a/FIAT/functional.py +++ b/FIAT/functional.py @@ -330,7 +330,7 @@ def __init__(self, ref_el, facet_no, Q, f_at_qpts): dpt_dict = OrderedDict() - alphas = [tuple([1 if j == i else 0 for j in range(sd)]) for i in range(sd)] + alphas = [tuple(1 if j == i else 0 for j in range(sd)) for i in range(sd)] for j, pt in enumerate(dpts): dpt_dict[tuple(pt)] = [(qwts[j]*n[i]*f_at_qpts[j], alphas[i], tuple()) for i in range(sd)] @@ -483,24 +483,22 @@ def __init__(self, ref_el, Q, f_at_qpts): "IntegralMomentOfDivergence") -class FrobeniusIntegralMoment(Functional): +class FrobeniusIntegralMoment(IntegralMoment): def __init__(self, ref_el, Q, f_at_qpts): # f_at_qpts is (some shape) x num_qpts shp = tuple(f_at_qpts.shape[:-1]) - if len(Q.get_points()) != f_at_qpts.shape[-1]: + if len(Q.pts) != f_at_qpts.shape[-1]: raise Exception("Mismatch in number of quadrature points and values") + self.Q = Q + self.comp = slice(None, None) + self.f_at_qpts = f_at_qpts qpts, qwts = Q.get_points(), Q.get_weights() - pt_dict = {} - - for i, (pt_cur, wt_cur) in enumerate(zip(map(tuple, qpts), qwts)): - pt_dict[pt_cur] = [] - for alfa in index_iterator(shp): - qpidx = tuple(alfa + [i]) - pt_dict[pt_cur].append((wt_cur * f_at_qpts[qpidx], tuple(alfa))) - super().__init__(ref_el, shp, pt_dict, {}, "FrobeniusIntegralMoment") + pt_dict = {tuple(pt) : [(wt * f_at_qpts[alpha][i], alpha) for alpha in map(tuple, index_iterator(shp))] + for i, (pt, wt) in enumerate(zip(qpts, qwts))} + Functional.__init__(self, ref_el, shp, pt_dict, {}, "FrobeniusIntegralMoment") class PointNormalEvaluation(Functional): From 81ea91ef209f47cb0b1bcd232b43459c75e95e26 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Tue, 26 Dec 2023 15:56:14 -0600 Subject: [PATCH 41/93] index_iterator returns a tuple --- FIAT/functional.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/FIAT/functional.py b/FIAT/functional.py index fb46f9454..d63b95e99 100644 --- a/FIAT/functional.py +++ b/FIAT/functional.py @@ -30,12 +30,12 @@ def index_iterator(shp): return elif len(shp) == 1: for i in range(shp[0]): - yield [i] + yield (i,) else: shp_foo = shp[1:] for i in range(shp[0]): for foo in index_iterator(shp_foo): - yield [i] + foo + yield (i,) + foo class Functional(object): @@ -496,7 +496,7 @@ def __init__(self, ref_el, Q, f_at_qpts): self.f_at_qpts = f_at_qpts qpts, qwts = Q.get_points(), Q.get_weights() - pt_dict = {tuple(pt) : [(wt * f_at_qpts[alpha][i], alpha) for alpha in map(tuple, index_iterator(shp))] + pt_dict = {tuple(pt) : [(wt * f_at_qpts[alpha][i], alpha) for alpha in index_iterator(shp)] for i, (pt, wt) in enumerate(zip(qpts, qwts))} Functional.__init__(self, ref_el, shp, pt_dict, {}, "FrobeniusIntegralMoment") From ac2a1f21fb7c06366c2842aa65f974e886395100 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Tue, 26 Dec 2023 20:24:15 -0600 Subject: [PATCH 42/93] clean up N1curl and N1div --- FIAT/functional.py | 16 +--- FIAT/nedelec.py | 197 ++++++++++------------------------------- FIAT/raviart_thomas.py | 72 +++++++-------- 3 files changed, 84 insertions(+), 201 deletions(-) diff --git a/FIAT/functional.py b/FIAT/functional.py index d63b95e99..adc0212e2 100644 --- a/FIAT/functional.py +++ b/FIAT/functional.py @@ -26,16 +26,7 @@ def index_iterator(shp): """Constructs a generator iterating over all indices in shp in generalized column-major order So if shp = (2,2), then we construct the sequence (0,0),(0,1),(1,0),(1,1)""" - if len(shp) == 0: - return - elif len(shp) == 1: - for i in range(shp[0]): - yield (i,) - else: - shp_foo = shp[1:] - for i in range(shp[0]): - for foo in index_iterator(shp_foo): - yield (i,) + foo + return numpy.ndindex(shp) class Functional(object): @@ -495,9 +486,10 @@ def __init__(self, ref_el, Q, f_at_qpts): self.comp = slice(None, None) self.f_at_qpts = f_at_qpts qpts, qwts = Q.get_points(), Q.get_weights() + weights = numpy.transpose(numpy.multiply(f_at_qpts, qwts), (-1,) + tuple(range(len(shp)))) + alphas = list(index_iterator(shp)) - pt_dict = {tuple(pt) : [(wt * f_at_qpts[alpha][i], alpha) for alpha in index_iterator(shp)] - for i, (pt, wt) in enumerate(zip(qpts, qwts))} + pt_dict = {tuple(pt): [(wt[alpha], alpha) for alpha in alphas] for pt, wt in zip(qpts, weights)} Functional.__init__(self, ref_el, shp, pt_dict, {}, "FrobeniusIntegralMoment") diff --git a/FIAT/nedelec.py b/FIAT/nedelec.py index ebbb6d27d..400690f66 100644 --- a/FIAT/nedelec.py +++ b/FIAT/nedelec.py @@ -11,6 +11,7 @@ import numpy from FIAT.check_format_variant import check_format_variant from FIAT.quadrature_schemes import create_quadrature +from FIAT.quadrature import FacetQuadratureRule def NedelecSpace2D(ref_el, degree): @@ -138,180 +139,81 @@ def NedelecSpace3D(ref_el, degree): return polynomial_set.polynomial_set_union_normalized(vec_Pk, PkCrossX) -class NedelecDual2D(dual_set.DualSet): - """Dual basis for first-kind Nedelec in 2D.""" +class NedelecDual(dual_set.DualSet): + """Dual basis for first-kind Nedelec.""" def __init__(self, ref_el, degree, variant, interpolant_deg): - sd = ref_el.get_spatial_dimension() - if sd != 2: - raise Exception("Nedelec2D only works on triangles") - nodes = [] - - t = ref_el.get_topology() - - if variant == "integral": - # edge nodes are \int_F v\cdot t p ds where p \in P_{q-1}(edge) - # degree is q - 1 - edge = ref_el.get_facet_element() - Q = create_quadrature(edge, interpolant_deg + degree - 1) - Pq = polynomial_set.ONPolynomialSet(edge, degree - 1) - Pq_at_qpts = Pq.tabulate(Q.get_points())[(0,)*(sd - 1)] - nodes.extend(functional.IntegralMomentOfEdgeTangentEvaluation(ref_el, Q, phi, e) - for e in range(len(t[sd - 1])) for phi in Pq_at_qpts) - - # internal nodes. These are \int_T v \cdot p dx where p \in P_{q-2}^2 - if degree > 1: - Q = create_quadrature(ref_el, interpolant_deg + degree - 2) - qpts = Q.get_points() - Pkm1 = polynomial_set.ONPolynomialSet(ref_el, degree - 2) - Pkm1_at_qpts = Pkm1.tabulate(qpts)[(0,) * sd] - nodes.extend(functional.IntegralMoment(ref_el, Q, phi, (d,), (sd,)) - for d in range(sd) for phi in Pkm1_at_qpts) - - elif variant == "point": - num_edges = len(t[1]) - - # edge tangents - for i in range(num_edges): - pts_cur = ref_el.make_points(1, i, degree + 1) - nodes.extend(functional.PointEdgeTangentEvaluation(ref_el, i, pt) for pt in pts_cur) - - # internal moments - if degree > 1: - Q = create_quadrature(ref_el, 2 * degree - 2) - qpts = Q.get_points() - Pkm1 = polynomial_set.ONPolynomialSet(ref_el, degree - 2) - Pkm1_at_qpts = Pkm1.tabulate(qpts)[(0,) * sd] - nodes.extend(functional.IntegralMoment(ref_el, Q, phi, (d,), (sd,)) - for d in range(sd) for phi in Pkm1_at_qpts) + sd = ref_el.get_spatial_dimension() + top = ref_el.get_topology() entity_ids = {} - # set to empty - for i in range(sd + 1): - entity_ids[i] = {} - for j in range(len(t[i])): - entity_ids[i][j] = [] - - cur = 0 - - # edges - num_edge_pts = len(ref_el.make_points(1, 0, degree + 1)) - - for i in range(len(t[1])): - entity_ids[1][i] = list(range(cur, cur + num_edge_pts)) - cur += num_edge_pts - - # moments against P_{degree-1} internally, if degree > 1 - if degree > 1: - num_internal_dof = sd * Pkm1_at_qpts.shape[0] - entity_ids[2][0] = list(range(cur, cur + num_internal_dof)) - - super(NedelecDual2D, self).__init__(nodes, ref_el, entity_ids) - - -class NedelecDual3D(dual_set.DualSet): - """Dual basis for first-kind Nedelec in 3D.""" - - def __init__(self, ref_el, degree, variant, interpolant_deg): - sd = ref_el.get_spatial_dimension() - if sd != 3: - raise Exception("NedelecDual3D only works on tetrahedra") - - nodes = [] - - t = ref_el.get_topology() + for dim in top: + entity_ids[dim] = {} + for entity in top[dim]: + entity_ids[dim][entity] = [] if variant == "integral": # edge nodes are \int_F v\cdot t p ds where p \in P_{q-1}(edge) # degree is q - 1 - edge = ref_el.get_facet_element().get_facet_element() - Q = create_quadrature(edge, interpolant_deg + degree - 1) - Pq = polynomial_set.ONPolynomialSet(edge, degree - 1) - Pq_at_qpts = Pq.tabulate(Q.get_points())[(0,)] - nodes.extend(functional.IntegralMomentOfEdgeTangentEvaluation(ref_el, Q, phi, e) - for e in range(len(t[1])) for phi in Pq_at_qpts) # face nodes are \int_F v\cdot p dA where p \in P_{q-2}(f)^3 with p \cdot n = 0 (cmp. Monk) # these are equivalent to dofs from Fenics book defined by # \int_F v\times n \cdot p ds where p \in P_{q-2}(f)^2 - if degree > 1: - facet = ref_el.get_facet_element() - Q = create_quadrature(facet, interpolant_deg + degree - 2) - Pq = polynomial_set.ONPolynomialSet(facet, degree - 2, (sd,)) - Pq_at_qpts = Pq.tabulate(Q.get_points())[(0, 0)] - - for f in range(len(t[2])): - # R is used to map [1,0,0] to tangent1 and [0,1,0] to tangent2 - R = ref_el.compute_face_tangents(f) - - # Skip last functionals because we only want p with p \cdot n = 0 - nodes.extend(functional.MonkIntegralMoment(ref_el, Q, numpy.dot(phi.T, R), f) - for phi in Pq_at_qpts[:2 * Pq.get_num_members() // 3, :-1, ...]) - - # internal nodes. These are \int_T v \cdot p dx where p \in P_{q-3}^3(T) - if degree > 2: - Q = create_quadrature(ref_el, interpolant_deg + degree - 3) - qpts = Q.get_points() - Pkm2 = polynomial_set.ONPolynomialSet(ref_el, degree - 3) - Pkm2_at_qpts = Pkm2.tabulate(qpts)[(0,) * sd] - nodes.extend(functional.IntegralMoment(ref_el, Q, phi, (d,), (sd,)) - for d in range(sd) for phi in Pkm2_at_qpts) + for dim in range(1, sd): + phi_deg = degree - dim + if phi_deg >= 0: + facet = ref_el.construct_subelement(dim) + Q_ref = create_quadrature(facet, interpolant_deg + phi_deg) + Pqmd = polynomial_set.ONPolynomialSet(facet, phi_deg, (dim,)) + Phis = Pqmd.tabulate(Q_ref.get_points())[(0,) * dim] + Phis = numpy.transpose(Phis, (0, 2, 1)) + + for entity in top[dim]: + cur = len(nodes) + R = ref_el.compute_tangents(dim, entity) + Q = FacetQuadratureRule(ref_el, dim, entity, Q_ref) + Jdet = Q.jacobian_determinant() + phis = numpy.dot(Phis, R / Jdet) + phis = numpy.transpose(phis, (0, 2, 1)) + nodes.extend(functional.FrobeniusIntegralMoment(ref_el, Q, phi) + for phi in phis) + entity_ids[dim][entity] = list(range(cur, len(nodes))) elif variant == "point": - num_edges = len(t[1]) - - for i in range(num_edges): + interpolant_deg = degree + for i in top[1]: + cur = len(nodes) # points to specify P_k on each edge pts_cur = ref_el.make_points(1, i, degree + 1) nodes.extend(functional.PointEdgeTangentEvaluation(ref_el, i, pt) for pt in pts_cur) + entity_ids[1][i] = list(range(cur, len(nodes))) - if degree > 1: # face tangents - num_faces = len(t[2]) - for i in range(num_faces): # loop over faces + if sd == 3 and degree > 1: # face tangents + for i in top[2]: # loop over faces + cur = len(nodes) pts_cur = ref_el.make_points(2, i, degree + 1) nodes.extend(functional.PointFaceTangentEvaluation(ref_el, i, k, pt) for k in range(2) # loop over tangents for pt in pts_cur # loop over points ) + entity_ids[2][i] = list(range(cur, len(nodes))) - if degree > 2: # internal moments - Q = create_quadrature(ref_el, 2 * degree - 3) - qpts = Q.get_points() - Pkm2 = polynomial_set.ONPolynomialSet(ref_el, degree - 3) - Pkm2_at_qpts = Pkm2.tabulate(qpts)[(0,) * sd] - nodes.extend(functional.IntegralMoment(ref_el, Q, phi, (d,), (sd,)) - for d in range(sd) for phi in Pkm2_at_qpts) - - entity_ids = {} - # set to empty - for i in range(sd + 1): - entity_ids[i] = {} - for j in range(len(t[i])): - entity_ids[i][j] = [] - - cur = 0 - - # edge dof - num_pts_per_edge = len(ref_el.make_points(1, 0, degree + 1)) - for i in range(len(t[1])): - entity_ids[1][i] = list(range(cur, cur + num_pts_per_edge)) - cur += num_pts_per_edge - - # face dof - if degree > 1: - num_pts_per_face = len(ref_el.make_points(2, 0, degree + 1)) - for i in range(len(t[2])): - entity_ids[2][i] = list(range(cur, cur + 2 * num_pts_per_face)) - cur += 2 * num_pts_per_face - - if degree > 2: - num_internal_dof = Pkm2_at_qpts.shape[0] * sd - entity_ids[3][0] = list(range(cur, cur + num_internal_dof)) + # internal nodes. These are \int_T v \cdot p dx where p \in P_{q-d}^3(T) + dim = sd + phi_deg = degree - dim + if phi_deg >= 0: + cur = len(nodes) + Q = create_quadrature(ref_el, interpolant_deg + phi_deg) + Pqmd = polynomial_set.ONPolynomialSet(ref_el, phi_deg) + Pqmd_at_qpts = Pqmd.tabulate(Q.get_points())[(0,) * dim] + nodes.extend(functional.IntegralMoment(ref_el, Q, phi, (d,), (dim,)) + for d in range(dim) for phi in Pqmd_at_qpts) + entity_ids[dim][0] = list(range(cur, len(nodes))) - super(NedelecDual3D, self).__init__(nodes, ref_el, entity_ids) + super(NedelecDual, self).__init__(nodes, ref_el, entity_ids) class Nedelec(finite_element.CiarletElement): @@ -339,12 +241,11 @@ def __init__(self, ref_el, degree, variant=None): variant, interpolant_deg = check_format_variant(variant, degree) if ref_el.get_spatial_dimension() == 3: poly_set = NedelecSpace3D(ref_el, degree) - dual = NedelecDual3D(ref_el, degree, variant, interpolant_deg) elif ref_el.get_spatial_dimension() == 2: poly_set = NedelecSpace2D(ref_el, degree) - dual = NedelecDual2D(ref_el, degree, variant, interpolant_deg) else: raise Exception("Not implemented") + dual = NedelecDual(ref_el, degree, variant, interpolant_deg) formdegree = 1 # 1-form super(Nedelec, self).__init__(poly_set, dual, degree, formdegree, mapping="covariant piola") diff --git a/FIAT/raviart_thomas.py b/FIAT/raviart_thomas.py index a1bca2d7a..d0c2e2e87 100644 --- a/FIAT/raviart_thomas.py +++ b/FIAT/raviart_thomas.py @@ -11,6 +11,7 @@ from itertools import chain from FIAT.check_format_variant import check_format_variant from FIAT.quadrature_schemes import create_quadrature +from FIAT.quadrature import FacetQuadratureRule def RTSpace(ref_el, degree): @@ -33,22 +34,16 @@ def RTSpace(ref_el, degree): PkH = Pkp1.take(list(range(dimPkm1, dimPk))) Q = create_quadrature(ref_el, 2 * (k + 1)) + Qpts, Qwts = Q.get_points(), Q.get_weights() # have to work on this through "tabulate" interface # first, tabulate PkH at quadrature points - Qpts = Q.get_points() - Qwts = Q.get_weights() - PkH_at_Qpts = PkH.tabulate(Qpts)[(0,) * sd] Pkp1_at_Qpts = Pkp1.tabulate(Qpts)[(0,) * sd] x = Qpts.T - xPkH_at_Qpts = numpy.zeros((PkH_at_Qpts.shape[0], - sd, - PkH_at_Qpts.shape[1]), "d") - for i in range(PkH_at_Qpts.shape[0]): - xPkH_at_Qpts[i] = PkH_at_Qpts[i] * x - PkHx_coeffs = numpy.dot(xPkH_at_Qpts, Qwts[:, None] * Pkp1_at_Qpts.T) + xPkH_at_Qpts = PkH_at_Qpts[:, None, :] * x[None, :, :] + PkHx_coeffs = numpy.dot(numpy.multiply(xPkH_at_Qpts, Qwts), Pkp1_at_Qpts.T) PkHx = polynomial_set.PolynomialSet(ref_el, k, @@ -65,68 +60,63 @@ class RTDualSet(dual_set.DualSet): moments against polynomials""" def __init__(self, ref_el, degree, variant, interpolant_deg): - entity_ids = {} nodes = [] - sd = ref_el.get_spatial_dimension() - t = ref_el.get_topology() + top = ref_el.get_topology() + + entity_ids = {} + # set to empty + for dim in top: + entity_ids[dim] = {} + for entity in top[dim]: + entity_ids[dim][entity] = [] if variant == "integral": facet = ref_el.get_facet_element() # Facet nodes are \int_F v\cdot n p ds where p \in P_{q-1} # degree is q - 1 - Q = create_quadrature(facet, interpolant_deg + degree - 1) + Q_ref = create_quadrature(facet, interpolant_deg + degree - 1) Pq = polynomial_set.ONPolynomialSet(facet, degree - 1) - Pq_at_qpts = Pq.tabulate(Q.get_points())[(0,)*(sd - 1)] - nodes.extend(functional.IntegralMomentOfScaledNormalEvaluation(ref_el, Q, phi, f) - for f in range(len(t[sd - 1])) - for phi in Pq_at_qpts) + Pq_at_qpts = Pq.tabulate(Q_ref.get_points())[(0,)*(sd - 1)] + + for f in top[sd - 1]: + cur = len(nodes) + Q = FacetQuadratureRule(ref_el, sd-1, f, Q_ref) + Jdet = Q.jacobian_determinant() + n = ref_el.compute_scaled_normal(f) / Jdet + phis = n[None, :, None] * Pq_at_qpts[:, None, :] + nodes.extend(functional.FrobeniusIntegralMoment(ref_el, Q, phi) + for phi in phis) + entity_ids[sd - 1][f] = list(range(cur, len(nodes))) # internal nodes. These are \int_T v \cdot p dx where p \in P_{q-2}^d if degree > 1: + cur = len(nodes) Q = create_quadrature(ref_el, interpolant_deg + degree - 2) Pkm1 = polynomial_set.ONPolynomialSet(ref_el, degree - 2) Pkm1_at_qpts = Pkm1.tabulate(Q.get_points())[(0,) * sd] nodes.extend(functional.IntegralMoment(ref_el, Q, phi, (d,), (sd,)) for d in range(sd) for phi in Pkm1_at_qpts) + entity_ids[sd][0] = list(range(cur, len(nodes))) elif variant == "point": # codimension 1 facets - for i in range(len(t[sd - 1])): + for i in top[sd - 1]: + cur = len(nodes) pts_cur = ref_el.make_points(sd - 1, i, sd + degree - 1) nodes.extend(functional.PointScaledNormalEvaluation(ref_el, i, pt) for pt in pts_cur) + entity_ids[sd - 1][i] = list(range(cur, len(nodes))) # internal nodes. Let's just use points at a lattice if degree > 1: + cur = len(nodes) pts = ref_el.make_points(sd, 0, sd + degree - 1) nodes.extend(functional.ComponentPointEvaluation(ref_el, d, (sd,), pt) for d in range(sd) for pt in pts) - - # sets vertices (and in 3d, edges) to have no nodes - for i in range(sd - 1): - entity_ids[i] = {} - for j in range(len(t[i])): - entity_ids[i][j] = [] - - cur = 0 - - # set codimension 1 (edges 2d, faces 3d) dof - pts_facet_0 = ref_el.make_points(sd - 1, 0, sd + degree - 1) - pts_per_facet = len(pts_facet_0) - entity_ids[sd - 1] = {} - for i in range(len(t[sd - 1])): - entity_ids[sd - 1][i] = list(range(cur, cur + pts_per_facet)) - cur += pts_per_facet - - # internal nodes, if applicable - entity_ids[sd] = {0: []} - if degree > 1: - num_internal_nodes = expansions.polynomial_dimension(ref_el, - degree - 2) - entity_ids[sd][0] = list(range(cur, cur + num_internal_nodes * sd)) + entity_ids[sd][0] = list(range(cur, len(nodes))) super(RTDualSet, self).__init__(nodes, ref_el, entity_ids) From b6c9b943b6099725a2b33eef2a9bf364379fc0ea Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Tue, 26 Dec 2023 20:36:07 -0600 Subject: [PATCH 43/93] small change --- FIAT/nedelec.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/FIAT/nedelec.py b/FIAT/nedelec.py index 400690f66..87fa51677 100644 --- a/FIAT/nedelec.py +++ b/FIAT/nedelec.py @@ -36,9 +36,7 @@ def NedelecSpace2D(ref_el, degree): PkH = Pkp1.take(list(range(dimPkm1, dimPk))) Q = create_quadrature(ref_el, 2 * (k + 1)) - - Qpts = Q.get_points() - Qwts = Q.get_weights() + Qpts, Qwts = Q.get_points(), Q.get_weights() PkH_at_Qpts = PkH.tabulate(Qpts)[(0,) * sd] Pkp1_at_Qpts = Pkp1.tabulate(Qpts)[(0,) * sd] @@ -103,9 +101,7 @@ def NedelecSpace3D(ref_el, degree): Pkp1 = polynomial_set.ONPolynomialSet(ref_el, k + 1) Q = create_quadrature(ref_el, 2 * (k + 1)) - - Qpts = Q.get_points() - Qwts = Q.get_weights() + Qpts, Qwts = Q.get_points(), Q.get_weights() PkCrossXcoeffs = numpy.zeros((vec_Pke.get_num_members(), sd, From b8137c218fd7bca8a70eec89744c358e293a1bff Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Tue, 26 Dec 2023 21:38:50 -0600 Subject: [PATCH 44/93] Revert DualSet changes --- FIAT/dual_set.py | 106 ++++++++----------- FIAT/functional.py | 39 ++++--- FIAT/nedelec.py | 205 +++++++++++++++++++++++++++--------- FIAT/nedelec_second_kind.py | 119 ++++++++++++++------- FIAT/quadrature.py | 9 +- FIAT/quadrature_schemes.py | 18 +++- FIAT/raviart_thomas.py | 72 +++++++------ FIAT/reference_element.py | 57 +++------- 8 files changed, 374 insertions(+), 251 deletions(-) diff --git a/FIAT/dual_set.py b/FIAT/dual_set.py index ddf129491..34884f31b 100644 --- a/FIAT/dual_set.py +++ b/FIAT/dual_set.py @@ -7,8 +7,9 @@ # SPDX-License-Identifier: LGPL-3.0-or-later import numpy +import collections -from FIAT import polynomial_set, functional +from FIAT import polynomial_set class DualSet(object): @@ -104,78 +105,63 @@ def to_riesz(self, poly_set): ed = poly_set.get_embedded_degree() num_exp = es.get_num_members(poly_set.get_embedded_degree()) - riesz_shape = (num_nodes, *tshape, num_exp) + riesz_shape = tuple([num_nodes] + list(tshape) + [num_exp]) + mat = numpy.zeros(riesz_shape, "d") - pts = set() - dpts = set() - Qs_to_ells = dict() + # Dictionaries mapping pts to which functionals they come from + pts_to_ells = collections.OrderedDict() + dpts_to_ells = collections.OrderedDict() + for i, ell in enumerate(self.nodes): - if isinstance(ell, functional.IntegralMoment): - Q = ell.Q - else: - Q = None - pts.update(ell.pt_dict.keys()) - dpts.update(ell.deriv_dict.keys()) - if Q in Qs_to_ells: - Qs_to_ells[Q].append(i) - else: - Qs_to_ells[Q] = [i] - - Qs_to_pts = {None: tuple(sorted(pts))} - for Q in Qs_to_ells: - if Q is not None: - cur_pts = tuple(map(tuple, Q.pts)) - Qs_to_pts[Q] = cur_pts - pts.update(cur_pts) + for pt in ell.pt_dict: + if pt in pts_to_ells: + pts_to_ells[pt].append(i) + else: + pts_to_ells[pt] = [i] + + for pt in ell.deriv_dict: + if pt in dpts_to_ells: + dpts_to_ells[pt].append(i) + else: + dpts_to_ells[pt] = [i] # Now tabulate the function values - pts = list(sorted(pts)) - expansion_values = numpy.transpose(es.tabulate(ed, pts)) - - for Q in Qs_to_ells: - ells = Qs_to_ells[Q] - cur_pts = Qs_to_pts[Q] - indices = list(map(pts.index, cur_pts)) - wshape = (len(ells), *tshape, len(cur_pts)) - wts = numpy.zeros(wshape, "d") - if Q is None: - for i, k in enumerate(ells): - ell = self.nodes[k] - for pt, wc_list in ell.pt_dict.items(): - j = cur_pts.index(pt) - for (w, c) in wc_list: - wts[i][c][j] = w - else: - for i, k in enumerate(ells): - ell = self.nodes[k] - wts[i][ell.comp][:] = ell.f_at_qpts - qwts = Q.get_weights() - wts = numpy.multiply(wts, qwts, out=wts) - mat[ells] += numpy.dot(wts, expansion_values[indices]) + pts = list(pts_to_ells.keys()) + expansion_values = es.tabulate(ed, pts) + + for j, pt in enumerate(pts): + which_ells = pts_to_ells[pt] + + for k in which_ells: + pt_dict = self.nodes[k].pt_dict + wc_list = pt_dict[pt] + + for i in range(num_exp): + for (w, c) in wc_list: + mat[k][c][i] += w*expansion_values[i, j] # Tabulate the derivative values that are needed - max_deriv_order = max(ell.max_deriv_order for ell in self.nodes) + max_deriv_order = max([ell.max_deriv_order for ell in self.nodes]) if max_deriv_order > 0: - dpts = list(sorted(dpts)) + dpts = list(dpts_to_ells.keys()) # It's easiest/most efficient to get derivatives of the # expansion set through the polynomial set interface. # This is creating a short-lived set to do just this. - coeffs = numpy.eye(num_exp) - expansion = polynomial_set.PolynomialSet(self.ref_el, ed, ed, es, coeffs) + expansion = polynomial_set.ONPolynomialSet(self.ref_el, ed) dexpansion_values = expansion.tabulate(dpts, max_deriv_order) - ells = [k for k, ell in enumerate(self.nodes) if len(ell.deriv_dict) > 0] - wshape = (len(ells), *tshape, len(dpts)) - dwts = {alpha: numpy.zeros(wshape, "d") for alpha in dexpansion_values if sum(alpha) > 0} - for i, k in enumerate(ells): - ell = self.nodes[k] - for pt, wac_list in ell.deriv_dict.items(): - j = dpts.index(pt) - for (w, alpha, c) in wac_list: - dwts[alpha][i][c][j] = w - for alpha in dwts: - mat[ells] += numpy.dot(dwts[alpha], dexpansion_values[alpha].T) + for j, pt in enumerate(dpts): + which_ells = dpts_to_ells[pt] + + for k in which_ells: + dpt_dict = self.nodes[k].deriv_dict + wac_list = dpt_dict[pt] + + for i in range(num_exp): + for (w, alpha, c) in wac_list: + mat[k][c][i] += w*dexpansion_values[alpha][i, j] + return mat diff --git a/FIAT/functional.py b/FIAT/functional.py index adc0212e2..44711ca6d 100644 --- a/FIAT/functional.py +++ b/FIAT/functional.py @@ -26,7 +26,16 @@ def index_iterator(shp): """Constructs a generator iterating over all indices in shp in generalized column-major order So if shp = (2,2), then we construct the sequence (0,0),(0,1),(1,0),(1,1)""" - return numpy.ndindex(shp) + if len(shp) == 0: + return + elif len(shp) == 1: + for i in range(shp[0]): + yield [i] + else: + shp_foo = shp[1:] + for i in range(shp[0]): + for foo in index_iterator(shp_foo): + yield [i] + foo class Functional(object): @@ -283,11 +292,12 @@ class IntegralMoment(Functional): def __init__(self, ref_el, Q, f_at_qpts, comp=tuple(), shp=tuple()): self.Q = Q - self.f_at_qpts = f_at_qpts qpts, qwts = Q.get_points(), Q.get_weights() + pt_dict = OrderedDict() self.comp = comp - weights = numpy.multiply(f_at_qpts, qwts) - pt_dict = {tuple(pt): [(wt, comp)] for pt, wt in zip(qpts, weights)} + for i in range(len(qpts)): + pt_cur = tuple(qpts[i]) + pt_dict[pt_cur] = [(qwts[i] * f_at_qpts[i], comp)] Functional.__init__(self, ref_el, shp, pt_dict, {}, "IntegralMoment") def __call__(self, fn): @@ -321,7 +331,7 @@ def __init__(self, ref_el, facet_no, Q, f_at_qpts): dpt_dict = OrderedDict() - alphas = [tuple(1 if j == i else 0 for j in range(sd)) for i in range(sd)] + alphas = [tuple([1 if j == i else 0 for j in range(sd)]) for i in range(sd)] for j, pt in enumerate(dpts): dpt_dict[tuple(pt)] = [(qwts[j]*n[i]*f_at_qpts[j], alphas[i], tuple()) for i in range(sd)] @@ -474,23 +484,24 @@ def __init__(self, ref_el, Q, f_at_qpts): "IntegralMomentOfDivergence") -class FrobeniusIntegralMoment(IntegralMoment): +class FrobeniusIntegralMoment(Functional): def __init__(self, ref_el, Q, f_at_qpts): # f_at_qpts is (some shape) x num_qpts shp = tuple(f_at_qpts.shape[:-1]) - if len(Q.pts) != f_at_qpts.shape[-1]: + if len(Q.get_points()) != f_at_qpts.shape[-1]: raise Exception("Mismatch in number of quadrature points and values") - self.Q = Q - self.comp = slice(None, None) - self.f_at_qpts = f_at_qpts qpts, qwts = Q.get_points(), Q.get_weights() - weights = numpy.transpose(numpy.multiply(f_at_qpts, qwts), (-1,) + tuple(range(len(shp)))) - alphas = list(index_iterator(shp)) + pt_dict = {} + + for i, (pt_cur, wt_cur) in enumerate(zip(map(tuple, qpts), qwts)): + pt_dict[pt_cur] = [] + for alfa in index_iterator(shp): + qpidx = tuple(alfa + [i]) + pt_dict[pt_cur].append((wt_cur * f_at_qpts[qpidx], tuple(alfa))) - pt_dict = {tuple(pt): [(wt[alpha], alpha) for alpha in alphas] for pt, wt in zip(qpts, weights)} - Functional.__init__(self, ref_el, shp, pt_dict, {}, "FrobeniusIntegralMoment") + super().__init__(ref_el, shp, pt_dict, {}, "FrobeniusIntegralMoment") class PointNormalEvaluation(Functional): diff --git a/FIAT/nedelec.py b/FIAT/nedelec.py index 87fa51677..ebbb6d27d 100644 --- a/FIAT/nedelec.py +++ b/FIAT/nedelec.py @@ -11,7 +11,6 @@ import numpy from FIAT.check_format_variant import check_format_variant from FIAT.quadrature_schemes import create_quadrature -from FIAT.quadrature import FacetQuadratureRule def NedelecSpace2D(ref_el, degree): @@ -36,7 +35,9 @@ def NedelecSpace2D(ref_el, degree): PkH = Pkp1.take(list(range(dimPkm1, dimPk))) Q = create_quadrature(ref_el, 2 * (k + 1)) - Qpts, Qwts = Q.get_points(), Q.get_weights() + + Qpts = Q.get_points() + Qwts = Q.get_weights() PkH_at_Qpts = PkH.tabulate(Qpts)[(0,) * sd] Pkp1_at_Qpts = Pkp1.tabulate(Qpts)[(0,) * sd] @@ -101,7 +102,9 @@ def NedelecSpace3D(ref_el, degree): Pkp1 = polynomial_set.ONPolynomialSet(ref_el, k + 1) Q = create_quadrature(ref_el, 2 * (k + 1)) - Qpts, Qwts = Q.get_points(), Q.get_weights() + + Qpts = Q.get_points() + Qwts = Q.get_weights() PkCrossXcoeffs = numpy.zeros((vec_Pke.get_num_members(), sd, @@ -135,81 +138,180 @@ def NedelecSpace3D(ref_el, degree): return polynomial_set.polynomial_set_union_normalized(vec_Pk, PkCrossX) -class NedelecDual(dual_set.DualSet): - """Dual basis for first-kind Nedelec.""" +class NedelecDual2D(dual_set.DualSet): + """Dual basis for first-kind Nedelec in 2D.""" def __init__(self, ref_el, degree, variant, interpolant_deg): - nodes = [] sd = ref_el.get_spatial_dimension() - top = ref_el.get_topology() + if sd != 2: + raise Exception("Nedelec2D only works on triangles") + + nodes = [] + + t = ref_el.get_topology() + + if variant == "integral": + # edge nodes are \int_F v\cdot t p ds where p \in P_{q-1}(edge) + # degree is q - 1 + edge = ref_el.get_facet_element() + Q = create_quadrature(edge, interpolant_deg + degree - 1) + Pq = polynomial_set.ONPolynomialSet(edge, degree - 1) + Pq_at_qpts = Pq.tabulate(Q.get_points())[(0,)*(sd - 1)] + nodes.extend(functional.IntegralMomentOfEdgeTangentEvaluation(ref_el, Q, phi, e) + for e in range(len(t[sd - 1])) for phi in Pq_at_qpts) + + # internal nodes. These are \int_T v \cdot p dx where p \in P_{q-2}^2 + if degree > 1: + Q = create_quadrature(ref_el, interpolant_deg + degree - 2) + qpts = Q.get_points() + Pkm1 = polynomial_set.ONPolynomialSet(ref_el, degree - 2) + Pkm1_at_qpts = Pkm1.tabulate(qpts)[(0,) * sd] + nodes.extend(functional.IntegralMoment(ref_el, Q, phi, (d,), (sd,)) + for d in range(sd) for phi in Pkm1_at_qpts) + + elif variant == "point": + num_edges = len(t[1]) + + # edge tangents + for i in range(num_edges): + pts_cur = ref_el.make_points(1, i, degree + 1) + nodes.extend(functional.PointEdgeTangentEvaluation(ref_el, i, pt) for pt in pts_cur) + + # internal moments + if degree > 1: + Q = create_quadrature(ref_el, 2 * degree - 2) + qpts = Q.get_points() + Pkm1 = polynomial_set.ONPolynomialSet(ref_el, degree - 2) + Pkm1_at_qpts = Pkm1.tabulate(qpts)[(0,) * sd] + nodes.extend(functional.IntegralMoment(ref_el, Q, phi, (d,), (sd,)) + for d in range(sd) for phi in Pkm1_at_qpts) entity_ids = {} + # set to empty - for dim in top: - entity_ids[dim] = {} - for entity in top[dim]: - entity_ids[dim][entity] = [] + for i in range(sd + 1): + entity_ids[i] = {} + for j in range(len(t[i])): + entity_ids[i][j] = [] + + cur = 0 + + # edges + num_edge_pts = len(ref_el.make_points(1, 0, degree + 1)) + + for i in range(len(t[1])): + entity_ids[1][i] = list(range(cur, cur + num_edge_pts)) + cur += num_edge_pts + + # moments against P_{degree-1} internally, if degree > 1 + if degree > 1: + num_internal_dof = sd * Pkm1_at_qpts.shape[0] + entity_ids[2][0] = list(range(cur, cur + num_internal_dof)) + + super(NedelecDual2D, self).__init__(nodes, ref_el, entity_ids) + + +class NedelecDual3D(dual_set.DualSet): + """Dual basis for first-kind Nedelec in 3D.""" + + def __init__(self, ref_el, degree, variant, interpolant_deg): + sd = ref_el.get_spatial_dimension() + if sd != 3: + raise Exception("NedelecDual3D only works on tetrahedra") + + nodes = [] + + t = ref_el.get_topology() if variant == "integral": # edge nodes are \int_F v\cdot t p ds where p \in P_{q-1}(edge) # degree is q - 1 + edge = ref_el.get_facet_element().get_facet_element() + Q = create_quadrature(edge, interpolant_deg + degree - 1) + Pq = polynomial_set.ONPolynomialSet(edge, degree - 1) + Pq_at_qpts = Pq.tabulate(Q.get_points())[(0,)] + nodes.extend(functional.IntegralMomentOfEdgeTangentEvaluation(ref_el, Q, phi, e) + for e in range(len(t[1])) for phi in Pq_at_qpts) # face nodes are \int_F v\cdot p dA where p \in P_{q-2}(f)^3 with p \cdot n = 0 (cmp. Monk) # these are equivalent to dofs from Fenics book defined by # \int_F v\times n \cdot p ds where p \in P_{q-2}(f)^2 - for dim in range(1, sd): - phi_deg = degree - dim - if phi_deg >= 0: - facet = ref_el.construct_subelement(dim) - Q_ref = create_quadrature(facet, interpolant_deg + phi_deg) - Pqmd = polynomial_set.ONPolynomialSet(facet, phi_deg, (dim,)) - Phis = Pqmd.tabulate(Q_ref.get_points())[(0,) * dim] - Phis = numpy.transpose(Phis, (0, 2, 1)) - - for entity in top[dim]: - cur = len(nodes) - R = ref_el.compute_tangents(dim, entity) - Q = FacetQuadratureRule(ref_el, dim, entity, Q_ref) - Jdet = Q.jacobian_determinant() - phis = numpy.dot(Phis, R / Jdet) - phis = numpy.transpose(phis, (0, 2, 1)) - nodes.extend(functional.FrobeniusIntegralMoment(ref_el, Q, phi) - for phi in phis) - entity_ids[dim][entity] = list(range(cur, len(nodes))) + if degree > 1: + facet = ref_el.get_facet_element() + Q = create_quadrature(facet, interpolant_deg + degree - 2) + Pq = polynomial_set.ONPolynomialSet(facet, degree - 2, (sd,)) + Pq_at_qpts = Pq.tabulate(Q.get_points())[(0, 0)] + + for f in range(len(t[2])): + # R is used to map [1,0,0] to tangent1 and [0,1,0] to tangent2 + R = ref_el.compute_face_tangents(f) + + # Skip last functionals because we only want p with p \cdot n = 0 + nodes.extend(functional.MonkIntegralMoment(ref_el, Q, numpy.dot(phi.T, R), f) + for phi in Pq_at_qpts[:2 * Pq.get_num_members() // 3, :-1, ...]) + + # internal nodes. These are \int_T v \cdot p dx where p \in P_{q-3}^3(T) + if degree > 2: + Q = create_quadrature(ref_el, interpolant_deg + degree - 3) + qpts = Q.get_points() + Pkm2 = polynomial_set.ONPolynomialSet(ref_el, degree - 3) + Pkm2_at_qpts = Pkm2.tabulate(qpts)[(0,) * sd] + nodes.extend(functional.IntegralMoment(ref_el, Q, phi, (d,), (sd,)) + for d in range(sd) for phi in Pkm2_at_qpts) elif variant == "point": - interpolant_deg = degree - for i in top[1]: - cur = len(nodes) + num_edges = len(t[1]) + + for i in range(num_edges): # points to specify P_k on each edge pts_cur = ref_el.make_points(1, i, degree + 1) nodes.extend(functional.PointEdgeTangentEvaluation(ref_el, i, pt) for pt in pts_cur) - entity_ids[1][i] = list(range(cur, len(nodes))) - if sd == 3 and degree > 1: # face tangents - for i in top[2]: # loop over faces - cur = len(nodes) + if degree > 1: # face tangents + num_faces = len(t[2]) + for i in range(num_faces): # loop over faces pts_cur = ref_el.make_points(2, i, degree + 1) nodes.extend(functional.PointFaceTangentEvaluation(ref_el, i, k, pt) for k in range(2) # loop over tangents for pt in pts_cur # loop over points ) - entity_ids[2][i] = list(range(cur, len(nodes))) - # internal nodes. These are \int_T v \cdot p dx where p \in P_{q-d}^3(T) - dim = sd - phi_deg = degree - dim - if phi_deg >= 0: - cur = len(nodes) - Q = create_quadrature(ref_el, interpolant_deg + phi_deg) - Pqmd = polynomial_set.ONPolynomialSet(ref_el, phi_deg) - Pqmd_at_qpts = Pqmd.tabulate(Q.get_points())[(0,) * dim] - nodes.extend(functional.IntegralMoment(ref_el, Q, phi, (d,), (dim,)) - for d in range(dim) for phi in Pqmd_at_qpts) - entity_ids[dim][0] = list(range(cur, len(nodes))) + if degree > 2: # internal moments + Q = create_quadrature(ref_el, 2 * degree - 3) + qpts = Q.get_points() + Pkm2 = polynomial_set.ONPolynomialSet(ref_el, degree - 3) + Pkm2_at_qpts = Pkm2.tabulate(qpts)[(0,) * sd] + nodes.extend(functional.IntegralMoment(ref_el, Q, phi, (d,), (sd,)) + for d in range(sd) for phi in Pkm2_at_qpts) + + entity_ids = {} + # set to empty + for i in range(sd + 1): + entity_ids[i] = {} + for j in range(len(t[i])): + entity_ids[i][j] = [] + + cur = 0 + + # edge dof + num_pts_per_edge = len(ref_el.make_points(1, 0, degree + 1)) + for i in range(len(t[1])): + entity_ids[1][i] = list(range(cur, cur + num_pts_per_edge)) + cur += num_pts_per_edge + + # face dof + if degree > 1: + num_pts_per_face = len(ref_el.make_points(2, 0, degree + 1)) + for i in range(len(t[2])): + entity_ids[2][i] = list(range(cur, cur + 2 * num_pts_per_face)) + cur += 2 * num_pts_per_face + + if degree > 2: + num_internal_dof = Pkm2_at_qpts.shape[0] * sd + entity_ids[3][0] = list(range(cur, cur + num_internal_dof)) - super(NedelecDual, self).__init__(nodes, ref_el, entity_ids) + super(NedelecDual3D, self).__init__(nodes, ref_el, entity_ids) class Nedelec(finite_element.CiarletElement): @@ -237,11 +339,12 @@ def __init__(self, ref_el, degree, variant=None): variant, interpolant_deg = check_format_variant(variant, degree) if ref_el.get_spatial_dimension() == 3: poly_set = NedelecSpace3D(ref_el, degree) + dual = NedelecDual3D(ref_el, degree, variant, interpolant_deg) elif ref_el.get_spatial_dimension() == 2: poly_set = NedelecSpace2D(ref_el, degree) + dual = NedelecDual2D(ref_el, degree, variant, interpolant_deg) else: raise Exception("Not implemented") - dual = NedelecDual(ref_el, degree, variant, interpolant_deg) formdegree = 1 # 1-form super(Nedelec, self).__init__(poly_set, dual, degree, formdegree, mapping="covariant piola") diff --git a/FIAT/nedelec_second_kind.py b/FIAT/nedelec_second_kind.py index 6e5269721..cc9bc8f66 100644 --- a/FIAT/nedelec_second_kind.py +++ b/FIAT/nedelec_second_kind.py @@ -17,6 +17,8 @@ from FIAT.quadrature_schemes import create_quadrature from FIAT.check_format_variant import check_format_variant +from FIAT import polynomial_set, functional + class NedelecSecondKindDual(DualSet): r""" @@ -65,36 +67,48 @@ def generate_degrees_of_freedom(self, cell, degree, variant, interpolant_deg): assert (d in (2, 3)), "Second kind Nedelecs only implemented in 2/3D." # Zero vertex-based degrees of freedom (d+1 of these) - ids[0] = {i: [] for i in range(d + 1)} + ids[0] = dict(list(zip(list(range(d + 1)), ([] for i in range(d + 1))))) - # (degree+1) degrees of freedom per entity of codimension 1 (edges) - (edge_dofs, ids[1]) = self._generate_edge_dofs(cell, degree, 0, variant, interpolant_deg) + # (d+1) degrees of freedom per entity of codimension 1 (edges) + (edge_dofs, edge_ids) = self._generate_edge_dofs(cell, degree, 0, variant, interpolant_deg) dofs.extend(edge_dofs) + ids[1] = edge_ids # Include face degrees of freedom if 3D if d == 3: - face_dofs, ids[d-1] = self._generate_facet_dofs(d-1, cell, degree, - len(dofs), variant, interpolant_deg) + (face_dofs, face_ids) = self._generate_face_dofs(cell, degree, + len(dofs), variant, interpolant_deg) dofs.extend(face_dofs) + ids[2] = face_ids # Varying degrees of freedom (possibly zero) per cell - cell_dofs, ids[d] = self._generate_facet_dofs(d, cell, degree, len(dofs), variant, interpolant_deg) + (cell_dofs, cell_ids) = self._generate_cell_dofs(cell, degree, len(dofs), variant, interpolant_deg) dofs.extend(cell_dofs) + ids[d] = cell_ids return (dofs, ids) def _generate_edge_dofs(self, cell, degree, offset, variant, interpolant_deg): - """Generate degrees of freedom (dofs) for entities of + """Generate degrees of freedoms (dofs) for entities of codimension 1 (edges).""" - if variant == "integral": - return self._generate_facet_dofs(1, cell, degree, offset, variant, interpolant_deg) - # (degree+1) tangential component point evaluation degrees of # freedom per entity of codimension 1 (edges) dofs = [] ids = {} - if variant == "point": + + if variant == "integral": + edge = cell.construct_subelement(1) + Q = create_quadrature(edge, degree + interpolant_deg) + Pq = polynomial_set.ONPolynomialSet(edge, degree) + Pq_at_qpts = Pq.tabulate(Q.get_points())[(0,)] + for e in range(len(cell.get_topology()[1])): + dofs.extend(functional.IntegralMomentOfEdgeTangentEvaluation(cell, Q, phi, e) + for phi in Pq_at_qpts) + jj = Pq_at_qpts.shape[0] * e + ids[e] = list(range(offset + jj, offset + jj + Pq_at_qpts.shape[0])) + + elif variant == "point": for edge in range(len(cell.get_topology()[1])): # Create points for evaluation of tangential components @@ -109,62 +123,87 @@ def _generate_edge_dofs(self, cell, degree, offset, variant, interpolant_deg): return (dofs, ids) - def _generate_facet_dofs(self, codim, cell, degree, offset, variant, interpolant_deg): - """Generate degrees of freedom (dofs) for facets.""" + def _generate_face_dofs(self, cell, degree, offset, variant, interpolant_deg): + """Generate degrees of freedoms (dofs) for faces.""" # Initialize empty dofs and identifiers (ids) - num_facets = len(cell.get_topology()[codim]) dofs = [] - ids = {i: [] for i in range(num_facets)} + ids = dict(list(zip(list(range(4)), ([] for i in range(4))))) # Return empty info if not applicable - rt_degree = degree - codim + 1 - if rt_degree < 1: + d = cell.get_spatial_dimension() + if degree < 2: return (dofs, ids) if interpolant_deg is None: interpolant_deg = degree - # Construct quadrature scheme for the reference facet - ref_facet = cell.construct_subelement(codim) - Q_ref = create_quadrature(ref_facet, interpolant_deg + rt_degree) - if codim == 1: - Phi = ONPolynomialSet(ref_facet, rt_degree, (codim,)) - else: - # Construct Raviart-Thomas on the reference facet - RT = RaviartThomas(ref_facet, rt_degree, variant) - Phi = RT.get_nodal_basis() - - # Evaluate basis functions at reference quadrature points - Phis = Phi.tabulate(Q_ref.get_points())[(0,) * codim] + # Construct quadrature scheme for the reference face + ref_face = cell.get_facet_element() + Q_ref = create_quadrature(ref_face, interpolant_deg + degree - 1) + + # Construct Raviart-Thomas of (degree - 1) on the reference face + RT = RaviartThomas(ref_face, degree - 1, variant) + num_rts = RT.space_dimension() + + # Evaluate RT basis functions at reference quadrature points + Phi = RT.get_nodal_basis() + Phis = Phi.tabulate(Q_ref.get_points())[(0, 0)] # Note: Phis has dimensions: # num_basis_functions x num_components x num_quad_points Phis = numpy.transpose(Phis, (0, 2, 1)) # Note: Phis has dimensions: # num_basis_functions x num_quad_points x num_components - # Iterate over the facets - cur = offset - for facet in range(num_facets): - # Get the quadrature and Jacobian on this facet - Q_facet = FacetQuadratureRule(cell, codim, facet, Q_ref) - J = Q_facet.jacobian() - detJ = Q_facet.jacobian_determinant() + # Iterate over the faces of the tet + num_faces = len(cell.get_topology()[d-1]) + for face in range(num_faces): + # Get the quadrature and Jacobian on this face + Q_face = FacetQuadratureRule(cell, d-1, face, Q_ref) + J = Q_face.jacobian() # Map Phis -> phis (reference values to physical values) - piola_map = J / detJ + piola_map = J / numpy.sqrt(numpy.linalg.det(numpy.dot(J.T, J))) phis = numpy.dot(Phis, piola_map.T) phis = numpy.transpose(phis, (0, 2, 1)) # Construct degrees of freedom as integral moments on this cell, # using the face quadrature weighted against the values # of the (physical) Raviart--Thomas'es on the face - dofs.extend(IntegralMoment(cell, Q_facet, phi) for phi in phis) + dofs.extend(IntegralMoment(cell, Q_face, phi) for phi in phis) # Assign identifiers (num RTs per face + previous edge dofs) - ids[facet].extend(range(cur, cur + len(phis))) - cur += len(phis) + ids[face] = list(range(offset + num_rts*face, offset + num_rts*(face + 1))) + + return (dofs, ids) + + def _generate_cell_dofs(self, cell, degree, offset, variant, interpolant_deg): + """Generate degrees of freedoms (dofs) for entities of + codimension d (cells).""" + + # Return empty info if not applicable + d = cell.get_spatial_dimension() + rt_degree = degree - d + 1 + if rt_degree < 1: + return ([], {0: []}) + + # Create quadrature points + interpolant_deg = interpolant_deg or degree + Q = create_quadrature(cell, interpolant_deg + rt_degree) + + # Create Raviart-Thomas nodal basis + RT = RaviartThomas(cell, rt_degree, variant) + phi = RT.get_nodal_basis() + + # Evaluate Raviart-Thomas basis at quadrature points + phi_at_qs = phi.tabulate(Q.get_points())[(0,) * d] + + # Use (Frobenius) integral moments against RTs as dofs + dofs = [IntegralMoment(cell, Q, phi) + for phi in phi_at_qs] + # Associate these dofs with the interior + ids = {0: list(range(offset, offset + len(dofs)))} return (dofs, ids) diff --git a/FIAT/quadrature.py b/FIAT/quadrature.py index f6f237d39..3f7cbc499 100644 --- a/FIAT/quadrature.py +++ b/FIAT/quadrature.py @@ -14,16 +14,12 @@ from FIAT import reference_element -def pseudo_determinant(A): - return numpy.sqrt(abs(numpy.linalg.det(numpy.dot(A.T, A)))) - - def map_quadrature(pts_ref, wts_ref, source_cell, target_cell, jacobian=False): """Map quadrature points and weights defined on source_cell to target_cell. """ A, b = reference_element.make_affine_mapping(source_cell.get_vertices(), target_cell.get_vertices()) - scale = pseudo_determinant(A) + scale = numpy.sqrt(numpy.linalg.det(numpy.dot(A.T, A))) pts = numpy.dot(pts_ref.reshape((-1, A.shape[1])), A.T) + b[None, :] wts = scale * wts_ref # return immutable types @@ -177,9 +173,6 @@ def reference_rule(self): def jacobian(self): return self._J - def jacobian_determinant(self): - return pseudo_determinant(self._J) - def make_quadrature(ref_el, m): """Returns the collapsed quadrature rule using m points per diff --git a/FIAT/quadrature_schemes.py b/FIAT/quadrature_schemes.py index 114a94af8..31297dd14 100644 --- a/FIAT/quadrature_schemes.py +++ b/FIAT/quadrature_schemes.py @@ -35,7 +35,7 @@ # FIAT from FIAT.reference_element import (HEXAHEDRON, QUADRILATERAL, TENSORPRODUCT, TETRAHEDRON, TRIANGLE, UFCTetrahedron, - UFCTriangle, symmetric_simplex) + UFCTriangle, default_simplex) def create_quadrature(ref_el, degree, scheme="default"): @@ -351,9 +351,23 @@ def xg_scheme(ref_el, degree): except KeyError: raise ValueError(f"Xiao-Gambutas rule not availale for degree {degree}.") + # Get affine map from the (-1,1)^d triangle to the G-X equilateral triangle + if dim == 2: + A = numpy.array([[1, 1/2], + [0, numpy.sqrt(3)/2]]) + b = A.sum(axis=1)/3 + else: + A = numpy.array([[1, 1/2, 1/2], + [0, numpy.sqrt(3)/2, numpy.sqrt(3)/6], + [0, 0, numpy.sqrt(6)/3]]) + b = A.sum(axis=1)/2 + + Ref1 = default_simplex(dim) + v = numpy.dot(Ref1.vertices, A.T) + b[None, :] + Ref1.vertices = tuple(map(tuple, v)) + pts_ref = order_table["points"] wts_ref = order_table["weights"] - Ref1 = symmetric_simplex(dim) pts, wts = map_quadrature(pts_ref, wts_ref, Ref1, ref_el) return QuadratureRule(ref_el, pts, wts) diff --git a/FIAT/raviart_thomas.py b/FIAT/raviart_thomas.py index d0c2e2e87..a1bca2d7a 100644 --- a/FIAT/raviart_thomas.py +++ b/FIAT/raviart_thomas.py @@ -11,7 +11,6 @@ from itertools import chain from FIAT.check_format_variant import check_format_variant from FIAT.quadrature_schemes import create_quadrature -from FIAT.quadrature import FacetQuadratureRule def RTSpace(ref_el, degree): @@ -34,16 +33,22 @@ def RTSpace(ref_el, degree): PkH = Pkp1.take(list(range(dimPkm1, dimPk))) Q = create_quadrature(ref_el, 2 * (k + 1)) - Qpts, Qwts = Q.get_points(), Q.get_weights() # have to work on this through "tabulate" interface # first, tabulate PkH at quadrature points + Qpts = Q.get_points() + Qwts = Q.get_weights() + PkH_at_Qpts = PkH.tabulate(Qpts)[(0,) * sd] Pkp1_at_Qpts = Pkp1.tabulate(Qpts)[(0,) * sd] x = Qpts.T - xPkH_at_Qpts = PkH_at_Qpts[:, None, :] * x[None, :, :] - PkHx_coeffs = numpy.dot(numpy.multiply(xPkH_at_Qpts, Qwts), Pkp1_at_Qpts.T) + xPkH_at_Qpts = numpy.zeros((PkH_at_Qpts.shape[0], + sd, + PkH_at_Qpts.shape[1]), "d") + for i in range(PkH_at_Qpts.shape[0]): + xPkH_at_Qpts[i] = PkH_at_Qpts[i] * x + PkHx_coeffs = numpy.dot(xPkH_at_Qpts, Qwts[:, None] * Pkp1_at_Qpts.T) PkHx = polynomial_set.PolynomialSet(ref_el, k, @@ -60,63 +65,68 @@ class RTDualSet(dual_set.DualSet): moments against polynomials""" def __init__(self, ref_el, degree, variant, interpolant_deg): + entity_ids = {} nodes = [] - sd = ref_el.get_spatial_dimension() - top = ref_el.get_topology() - entity_ids = {} - # set to empty - for dim in top: - entity_ids[dim] = {} - for entity in top[dim]: - entity_ids[dim][entity] = [] + sd = ref_el.get_spatial_dimension() + t = ref_el.get_topology() if variant == "integral": facet = ref_el.get_facet_element() # Facet nodes are \int_F v\cdot n p ds where p \in P_{q-1} # degree is q - 1 - Q_ref = create_quadrature(facet, interpolant_deg + degree - 1) + Q = create_quadrature(facet, interpolant_deg + degree - 1) Pq = polynomial_set.ONPolynomialSet(facet, degree - 1) - Pq_at_qpts = Pq.tabulate(Q_ref.get_points())[(0,)*(sd - 1)] - - for f in top[sd - 1]: - cur = len(nodes) - Q = FacetQuadratureRule(ref_el, sd-1, f, Q_ref) - Jdet = Q.jacobian_determinant() - n = ref_el.compute_scaled_normal(f) / Jdet - phis = n[None, :, None] * Pq_at_qpts[:, None, :] - nodes.extend(functional.FrobeniusIntegralMoment(ref_el, Q, phi) - for phi in phis) - entity_ids[sd - 1][f] = list(range(cur, len(nodes))) + Pq_at_qpts = Pq.tabulate(Q.get_points())[(0,)*(sd - 1)] + nodes.extend(functional.IntegralMomentOfScaledNormalEvaluation(ref_el, Q, phi, f) + for f in range(len(t[sd - 1])) + for phi in Pq_at_qpts) # internal nodes. These are \int_T v \cdot p dx where p \in P_{q-2}^d if degree > 1: - cur = len(nodes) Q = create_quadrature(ref_el, interpolant_deg + degree - 2) Pkm1 = polynomial_set.ONPolynomialSet(ref_el, degree - 2) Pkm1_at_qpts = Pkm1.tabulate(Q.get_points())[(0,) * sd] nodes.extend(functional.IntegralMoment(ref_el, Q, phi, (d,), (sd,)) for d in range(sd) for phi in Pkm1_at_qpts) - entity_ids[sd][0] = list(range(cur, len(nodes))) elif variant == "point": # codimension 1 facets - for i in top[sd - 1]: - cur = len(nodes) + for i in range(len(t[sd - 1])): pts_cur = ref_el.make_points(sd - 1, i, sd + degree - 1) nodes.extend(functional.PointScaledNormalEvaluation(ref_el, i, pt) for pt in pts_cur) - entity_ids[sd - 1][i] = list(range(cur, len(nodes))) # internal nodes. Let's just use points at a lattice if degree > 1: - cur = len(nodes) pts = ref_el.make_points(sd, 0, sd + degree - 1) nodes.extend(functional.ComponentPointEvaluation(ref_el, d, (sd,), pt) for d in range(sd) for pt in pts) - entity_ids[sd][0] = list(range(cur, len(nodes))) + + # sets vertices (and in 3d, edges) to have no nodes + for i in range(sd - 1): + entity_ids[i] = {} + for j in range(len(t[i])): + entity_ids[i][j] = [] + + cur = 0 + + # set codimension 1 (edges 2d, faces 3d) dof + pts_facet_0 = ref_el.make_points(sd - 1, 0, sd + degree - 1) + pts_per_facet = len(pts_facet_0) + entity_ids[sd - 1] = {} + for i in range(len(t[sd - 1])): + entity_ids[sd - 1][i] = list(range(cur, cur + pts_per_facet)) + cur += pts_per_facet + + # internal nodes, if applicable + entity_ids[sd] = {0: []} + if degree > 1: + num_internal_nodes = expansions.polynomial_dimension(ref_el, + degree - 2) + entity_ids[sd][0] = list(range(cur, cur + num_internal_nodes * sd)) super(RTDualSet, self).__init__(nodes, ref_el, entity_ids) diff --git a/FIAT/reference_element.py b/FIAT/reference_element.py index dcef5d0a1..66225a674 100644 --- a/FIAT/reference_element.py +++ b/FIAT/reference_element.py @@ -687,36 +687,6 @@ def distance_to_point_l1(self, point): return abs(l1_dist) -class DefaultSimplex(Simplex): - - def get_facet_element(self): - dimension = self.get_spatial_dimension() - return self.construct_subelement(dimension - 1) - - def construct_subelement(self, dimension): - """Constructs the reference element of a cell subentity - specified by subelement dimension. - - :arg dimension: subentity dimension (integer) - """ - return default_simplex(dimension) - - -class SymmetricSimplex(Simplex): - - def get_facet_element(self): - dimension = self.get_spatial_dimension() - return self.construct_subelement(dimension - 1) - - def construct_subelement(self, dimension): - """Constructs the reference element of a cell subentity - specified by subelement dimension. - - :arg dimension: subentity dimension (integer) - """ - return symmetric_simplex(dimension) - - class Point(Simplex): """This is the reference point.""" @@ -735,7 +705,7 @@ def construct_subelement(self, dimension): return self -class DefaultLine(DefaultSimplex): +class DefaultLine(Simplex): """This is the reference line with vertices (-1.0,) and (1.0,).""" def __init__(self): @@ -745,6 +715,9 @@ def __init__(self): 1: edges} super(DefaultLine, self).__init__(LINE, verts, topology) + def get_facet_element(self): + raise NotImplementedError() + class UFCInterval(UFCSimplex): """This is the reference interval with vertices (0.0,) and (1.0,).""" @@ -757,7 +730,7 @@ def __init__(self): super(UFCInterval, self).__init__(LINE, verts, topology) -class DefaultTriangle(DefaultSimplex): +class DefaultTriangle(Simplex): """This is the reference triangle with vertices (-1.0,-1.0), (1.0,-1.0), and (-1.0,1.0).""" @@ -771,6 +744,9 @@ def __init__(self): 1: edges, 2: faces} super(DefaultTriangle, self).__init__(TRIANGLE, verts, topology) + def get_facet_element(self): + return DefaultLine() + class UFCTriangle(UFCSimplex): """This is the reference triangle with vertices (0.0,0.0), @@ -810,7 +786,7 @@ def get_facet_element(self): return UFCInterval() -class DefaultTetrahedron(DefaultSimplex): +class DefaultTetrahedron(Simplex): """This is the reference tetrahedron with vertices (-1,-1,-1), (1,-1,-1),(-1,1,-1), and (-1,-1,1).""" @@ -835,6 +811,9 @@ def __init__(self): topology = {0: vs, 1: edges, 2: faces, 3: tets} super(DefaultTetrahedron, self).__init__(TETRAHEDRON, verts, topology) + def get_facet_element(self): + return DefaultTriangle() + class IntrepidTetrahedron(Simplex): """This is the reference tetrahedron with vertices (0,0,0), @@ -1329,18 +1308,6 @@ def ufc_simplex(spatial_dim): raise RuntimeError("Can't create UFC simplex of dimension %s." % str(spatial_dim)) -def symmetric_simplex(spatial_dim): - A = numpy.array([[2, 1, 1], - [0, numpy.sqrt(3), numpy.sqrt(3)/3], - [0, 0, numpy.sqrt(6)*(2/3)]]) - A = A[:spatial_dim, :][:, :spatial_dim] - b = A.sum(axis=1) * (-1 / (1 + spatial_dim)) - Ref1 = ufc_simplex(spatial_dim) - v = numpy.dot(Ref1.get_vertices(), A.T) + b[None, :] - vertices = tuple(map(tuple, v)) - return SymmetricSimplex(Ref1.get_shape(), vertices, Ref1.get_topology()) - - def ufc_cell(cell): """Handle incoming calls from FFC.""" From 214e1f5ae85008d079e22d2f22f9d35e71739b3b Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Tue, 26 Dec 2023 21:20:05 -0600 Subject: [PATCH 45/93] Tidy up DualSet --- FIAT/dual_set.py | 106 +++++++++++-------- FIAT/functional.py | 39 +++---- FIAT/nedelec.py | 205 +++++++++--------------------------- FIAT/nedelec_second_kind.py | 119 +++++++-------------- FIAT/quadrature.py | 9 +- FIAT/quadrature_schemes.py | 18 +--- FIAT/raviart_thomas.py | 72 ++++++------- 7 files changed, 206 insertions(+), 362 deletions(-) diff --git a/FIAT/dual_set.py b/FIAT/dual_set.py index 34884f31b..ddf129491 100644 --- a/FIAT/dual_set.py +++ b/FIAT/dual_set.py @@ -7,9 +7,8 @@ # SPDX-License-Identifier: LGPL-3.0-or-later import numpy -import collections -from FIAT import polynomial_set +from FIAT import polynomial_set, functional class DualSet(object): @@ -105,63 +104,78 @@ def to_riesz(self, poly_set): ed = poly_set.get_embedded_degree() num_exp = es.get_num_members(poly_set.get_embedded_degree()) - riesz_shape = tuple([num_nodes] + list(tshape) + [num_exp]) - + riesz_shape = (num_nodes, *tshape, num_exp) mat = numpy.zeros(riesz_shape, "d") - # Dictionaries mapping pts to which functionals they come from - pts_to_ells = collections.OrderedDict() - dpts_to_ells = collections.OrderedDict() - + pts = set() + dpts = set() + Qs_to_ells = dict() for i, ell in enumerate(self.nodes): - for pt in ell.pt_dict: - if pt in pts_to_ells: - pts_to_ells[pt].append(i) - else: - pts_to_ells[pt] = [i] - - for pt in ell.deriv_dict: - if pt in dpts_to_ells: - dpts_to_ells[pt].append(i) - else: - dpts_to_ells[pt] = [i] + if isinstance(ell, functional.IntegralMoment): + Q = ell.Q + else: + Q = None + pts.update(ell.pt_dict.keys()) + dpts.update(ell.deriv_dict.keys()) + if Q in Qs_to_ells: + Qs_to_ells[Q].append(i) + else: + Qs_to_ells[Q] = [i] + + Qs_to_pts = {None: tuple(sorted(pts))} + for Q in Qs_to_ells: + if Q is not None: + cur_pts = tuple(map(tuple, Q.pts)) + Qs_to_pts[Q] = cur_pts + pts.update(cur_pts) # Now tabulate the function values - pts = list(pts_to_ells.keys()) - expansion_values = es.tabulate(ed, pts) - - for j, pt in enumerate(pts): - which_ells = pts_to_ells[pt] - - for k in which_ells: - pt_dict = self.nodes[k].pt_dict - wc_list = pt_dict[pt] - - for i in range(num_exp): - for (w, c) in wc_list: - mat[k][c][i] += w*expansion_values[i, j] + pts = list(sorted(pts)) + expansion_values = numpy.transpose(es.tabulate(ed, pts)) + + for Q in Qs_to_ells: + ells = Qs_to_ells[Q] + cur_pts = Qs_to_pts[Q] + indices = list(map(pts.index, cur_pts)) + wshape = (len(ells), *tshape, len(cur_pts)) + wts = numpy.zeros(wshape, "d") + if Q is None: + for i, k in enumerate(ells): + ell = self.nodes[k] + for pt, wc_list in ell.pt_dict.items(): + j = cur_pts.index(pt) + for (w, c) in wc_list: + wts[i][c][j] = w + else: + for i, k in enumerate(ells): + ell = self.nodes[k] + wts[i][ell.comp][:] = ell.f_at_qpts + qwts = Q.get_weights() + wts = numpy.multiply(wts, qwts, out=wts) + mat[ells] += numpy.dot(wts, expansion_values[indices]) # Tabulate the derivative values that are needed - max_deriv_order = max([ell.max_deriv_order for ell in self.nodes]) + max_deriv_order = max(ell.max_deriv_order for ell in self.nodes) if max_deriv_order > 0: - dpts = list(dpts_to_ells.keys()) + dpts = list(sorted(dpts)) # It's easiest/most efficient to get derivatives of the # expansion set through the polynomial set interface. # This is creating a short-lived set to do just this. - expansion = polynomial_set.ONPolynomialSet(self.ref_el, ed) + coeffs = numpy.eye(num_exp) + expansion = polynomial_set.PolynomialSet(self.ref_el, ed, ed, es, coeffs) dexpansion_values = expansion.tabulate(dpts, max_deriv_order) - for j, pt in enumerate(dpts): - which_ells = dpts_to_ells[pt] - - for k in which_ells: - dpt_dict = self.nodes[k].deriv_dict - wac_list = dpt_dict[pt] - - for i in range(num_exp): - for (w, alpha, c) in wac_list: - mat[k][c][i] += w*dexpansion_values[alpha][i, j] - + ells = [k for k, ell in enumerate(self.nodes) if len(ell.deriv_dict) > 0] + wshape = (len(ells), *tshape, len(dpts)) + dwts = {alpha: numpy.zeros(wshape, "d") for alpha in dexpansion_values if sum(alpha) > 0} + for i, k in enumerate(ells): + ell = self.nodes[k] + for pt, wac_list in ell.deriv_dict.items(): + j = dpts.index(pt) + for (w, alpha, c) in wac_list: + dwts[alpha][i][c][j] = w + for alpha in dwts: + mat[ells] += numpy.dot(dwts[alpha], dexpansion_values[alpha].T) return mat diff --git a/FIAT/functional.py b/FIAT/functional.py index 44711ca6d..adc0212e2 100644 --- a/FIAT/functional.py +++ b/FIAT/functional.py @@ -26,16 +26,7 @@ def index_iterator(shp): """Constructs a generator iterating over all indices in shp in generalized column-major order So if shp = (2,2), then we construct the sequence (0,0),(0,1),(1,0),(1,1)""" - if len(shp) == 0: - return - elif len(shp) == 1: - for i in range(shp[0]): - yield [i] - else: - shp_foo = shp[1:] - for i in range(shp[0]): - for foo in index_iterator(shp_foo): - yield [i] + foo + return numpy.ndindex(shp) class Functional(object): @@ -292,12 +283,11 @@ class IntegralMoment(Functional): def __init__(self, ref_el, Q, f_at_qpts, comp=tuple(), shp=tuple()): self.Q = Q + self.f_at_qpts = f_at_qpts qpts, qwts = Q.get_points(), Q.get_weights() - pt_dict = OrderedDict() self.comp = comp - for i in range(len(qpts)): - pt_cur = tuple(qpts[i]) - pt_dict[pt_cur] = [(qwts[i] * f_at_qpts[i], comp)] + weights = numpy.multiply(f_at_qpts, qwts) + pt_dict = {tuple(pt): [(wt, comp)] for pt, wt in zip(qpts, weights)} Functional.__init__(self, ref_el, shp, pt_dict, {}, "IntegralMoment") def __call__(self, fn): @@ -331,7 +321,7 @@ def __init__(self, ref_el, facet_no, Q, f_at_qpts): dpt_dict = OrderedDict() - alphas = [tuple([1 if j == i else 0 for j in range(sd)]) for i in range(sd)] + alphas = [tuple(1 if j == i else 0 for j in range(sd)) for i in range(sd)] for j, pt in enumerate(dpts): dpt_dict[tuple(pt)] = [(qwts[j]*n[i]*f_at_qpts[j], alphas[i], tuple()) for i in range(sd)] @@ -484,24 +474,23 @@ def __init__(self, ref_el, Q, f_at_qpts): "IntegralMomentOfDivergence") -class FrobeniusIntegralMoment(Functional): +class FrobeniusIntegralMoment(IntegralMoment): def __init__(self, ref_el, Q, f_at_qpts): # f_at_qpts is (some shape) x num_qpts shp = tuple(f_at_qpts.shape[:-1]) - if len(Q.get_points()) != f_at_qpts.shape[-1]: + if len(Q.pts) != f_at_qpts.shape[-1]: raise Exception("Mismatch in number of quadrature points and values") + self.Q = Q + self.comp = slice(None, None) + self.f_at_qpts = f_at_qpts qpts, qwts = Q.get_points(), Q.get_weights() - pt_dict = {} - - for i, (pt_cur, wt_cur) in enumerate(zip(map(tuple, qpts), qwts)): - pt_dict[pt_cur] = [] - for alfa in index_iterator(shp): - qpidx = tuple(alfa + [i]) - pt_dict[pt_cur].append((wt_cur * f_at_qpts[qpidx], tuple(alfa))) + weights = numpy.transpose(numpy.multiply(f_at_qpts, qwts), (-1,) + tuple(range(len(shp)))) + alphas = list(index_iterator(shp)) - super().__init__(ref_el, shp, pt_dict, {}, "FrobeniusIntegralMoment") + pt_dict = {tuple(pt): [(wt[alpha], alpha) for alpha in alphas] for pt, wt in zip(qpts, weights)} + Functional.__init__(self, ref_el, shp, pt_dict, {}, "FrobeniusIntegralMoment") class PointNormalEvaluation(Functional): diff --git a/FIAT/nedelec.py b/FIAT/nedelec.py index ebbb6d27d..87fa51677 100644 --- a/FIAT/nedelec.py +++ b/FIAT/nedelec.py @@ -11,6 +11,7 @@ import numpy from FIAT.check_format_variant import check_format_variant from FIAT.quadrature_schemes import create_quadrature +from FIAT.quadrature import FacetQuadratureRule def NedelecSpace2D(ref_el, degree): @@ -35,9 +36,7 @@ def NedelecSpace2D(ref_el, degree): PkH = Pkp1.take(list(range(dimPkm1, dimPk))) Q = create_quadrature(ref_el, 2 * (k + 1)) - - Qpts = Q.get_points() - Qwts = Q.get_weights() + Qpts, Qwts = Q.get_points(), Q.get_weights() PkH_at_Qpts = PkH.tabulate(Qpts)[(0,) * sd] Pkp1_at_Qpts = Pkp1.tabulate(Qpts)[(0,) * sd] @@ -102,9 +101,7 @@ def NedelecSpace3D(ref_el, degree): Pkp1 = polynomial_set.ONPolynomialSet(ref_el, k + 1) Q = create_quadrature(ref_el, 2 * (k + 1)) - - Qpts = Q.get_points() - Qwts = Q.get_weights() + Qpts, Qwts = Q.get_points(), Q.get_weights() PkCrossXcoeffs = numpy.zeros((vec_Pke.get_num_members(), sd, @@ -138,180 +135,81 @@ def NedelecSpace3D(ref_el, degree): return polynomial_set.polynomial_set_union_normalized(vec_Pk, PkCrossX) -class NedelecDual2D(dual_set.DualSet): - """Dual basis for first-kind Nedelec in 2D.""" +class NedelecDual(dual_set.DualSet): + """Dual basis for first-kind Nedelec.""" def __init__(self, ref_el, degree, variant, interpolant_deg): - sd = ref_el.get_spatial_dimension() - if sd != 2: - raise Exception("Nedelec2D only works on triangles") - nodes = [] - - t = ref_el.get_topology() - - if variant == "integral": - # edge nodes are \int_F v\cdot t p ds where p \in P_{q-1}(edge) - # degree is q - 1 - edge = ref_el.get_facet_element() - Q = create_quadrature(edge, interpolant_deg + degree - 1) - Pq = polynomial_set.ONPolynomialSet(edge, degree - 1) - Pq_at_qpts = Pq.tabulate(Q.get_points())[(0,)*(sd - 1)] - nodes.extend(functional.IntegralMomentOfEdgeTangentEvaluation(ref_el, Q, phi, e) - for e in range(len(t[sd - 1])) for phi in Pq_at_qpts) - - # internal nodes. These are \int_T v \cdot p dx where p \in P_{q-2}^2 - if degree > 1: - Q = create_quadrature(ref_el, interpolant_deg + degree - 2) - qpts = Q.get_points() - Pkm1 = polynomial_set.ONPolynomialSet(ref_el, degree - 2) - Pkm1_at_qpts = Pkm1.tabulate(qpts)[(0,) * sd] - nodes.extend(functional.IntegralMoment(ref_el, Q, phi, (d,), (sd,)) - for d in range(sd) for phi in Pkm1_at_qpts) - - elif variant == "point": - num_edges = len(t[1]) - - # edge tangents - for i in range(num_edges): - pts_cur = ref_el.make_points(1, i, degree + 1) - nodes.extend(functional.PointEdgeTangentEvaluation(ref_el, i, pt) for pt in pts_cur) - - # internal moments - if degree > 1: - Q = create_quadrature(ref_el, 2 * degree - 2) - qpts = Q.get_points() - Pkm1 = polynomial_set.ONPolynomialSet(ref_el, degree - 2) - Pkm1_at_qpts = Pkm1.tabulate(qpts)[(0,) * sd] - nodes.extend(functional.IntegralMoment(ref_el, Q, phi, (d,), (sd,)) - for d in range(sd) for phi in Pkm1_at_qpts) + sd = ref_el.get_spatial_dimension() + top = ref_el.get_topology() entity_ids = {} - # set to empty - for i in range(sd + 1): - entity_ids[i] = {} - for j in range(len(t[i])): - entity_ids[i][j] = [] - - cur = 0 - - # edges - num_edge_pts = len(ref_el.make_points(1, 0, degree + 1)) - - for i in range(len(t[1])): - entity_ids[1][i] = list(range(cur, cur + num_edge_pts)) - cur += num_edge_pts - - # moments against P_{degree-1} internally, if degree > 1 - if degree > 1: - num_internal_dof = sd * Pkm1_at_qpts.shape[0] - entity_ids[2][0] = list(range(cur, cur + num_internal_dof)) - - super(NedelecDual2D, self).__init__(nodes, ref_el, entity_ids) - - -class NedelecDual3D(dual_set.DualSet): - """Dual basis for first-kind Nedelec in 3D.""" - - def __init__(self, ref_el, degree, variant, interpolant_deg): - sd = ref_el.get_spatial_dimension() - if sd != 3: - raise Exception("NedelecDual3D only works on tetrahedra") - - nodes = [] - - t = ref_el.get_topology() + for dim in top: + entity_ids[dim] = {} + for entity in top[dim]: + entity_ids[dim][entity] = [] if variant == "integral": # edge nodes are \int_F v\cdot t p ds where p \in P_{q-1}(edge) # degree is q - 1 - edge = ref_el.get_facet_element().get_facet_element() - Q = create_quadrature(edge, interpolant_deg + degree - 1) - Pq = polynomial_set.ONPolynomialSet(edge, degree - 1) - Pq_at_qpts = Pq.tabulate(Q.get_points())[(0,)] - nodes.extend(functional.IntegralMomentOfEdgeTangentEvaluation(ref_el, Q, phi, e) - for e in range(len(t[1])) for phi in Pq_at_qpts) # face nodes are \int_F v\cdot p dA where p \in P_{q-2}(f)^3 with p \cdot n = 0 (cmp. Monk) # these are equivalent to dofs from Fenics book defined by # \int_F v\times n \cdot p ds where p \in P_{q-2}(f)^2 - if degree > 1: - facet = ref_el.get_facet_element() - Q = create_quadrature(facet, interpolant_deg + degree - 2) - Pq = polynomial_set.ONPolynomialSet(facet, degree - 2, (sd,)) - Pq_at_qpts = Pq.tabulate(Q.get_points())[(0, 0)] - - for f in range(len(t[2])): - # R is used to map [1,0,0] to tangent1 and [0,1,0] to tangent2 - R = ref_el.compute_face_tangents(f) - - # Skip last functionals because we only want p with p \cdot n = 0 - nodes.extend(functional.MonkIntegralMoment(ref_el, Q, numpy.dot(phi.T, R), f) - for phi in Pq_at_qpts[:2 * Pq.get_num_members() // 3, :-1, ...]) - - # internal nodes. These are \int_T v \cdot p dx where p \in P_{q-3}^3(T) - if degree > 2: - Q = create_quadrature(ref_el, interpolant_deg + degree - 3) - qpts = Q.get_points() - Pkm2 = polynomial_set.ONPolynomialSet(ref_el, degree - 3) - Pkm2_at_qpts = Pkm2.tabulate(qpts)[(0,) * sd] - nodes.extend(functional.IntegralMoment(ref_el, Q, phi, (d,), (sd,)) - for d in range(sd) for phi in Pkm2_at_qpts) + for dim in range(1, sd): + phi_deg = degree - dim + if phi_deg >= 0: + facet = ref_el.construct_subelement(dim) + Q_ref = create_quadrature(facet, interpolant_deg + phi_deg) + Pqmd = polynomial_set.ONPolynomialSet(facet, phi_deg, (dim,)) + Phis = Pqmd.tabulate(Q_ref.get_points())[(0,) * dim] + Phis = numpy.transpose(Phis, (0, 2, 1)) + + for entity in top[dim]: + cur = len(nodes) + R = ref_el.compute_tangents(dim, entity) + Q = FacetQuadratureRule(ref_el, dim, entity, Q_ref) + Jdet = Q.jacobian_determinant() + phis = numpy.dot(Phis, R / Jdet) + phis = numpy.transpose(phis, (0, 2, 1)) + nodes.extend(functional.FrobeniusIntegralMoment(ref_el, Q, phi) + for phi in phis) + entity_ids[dim][entity] = list(range(cur, len(nodes))) elif variant == "point": - num_edges = len(t[1]) - - for i in range(num_edges): + interpolant_deg = degree + for i in top[1]: + cur = len(nodes) # points to specify P_k on each edge pts_cur = ref_el.make_points(1, i, degree + 1) nodes.extend(functional.PointEdgeTangentEvaluation(ref_el, i, pt) for pt in pts_cur) + entity_ids[1][i] = list(range(cur, len(nodes))) - if degree > 1: # face tangents - num_faces = len(t[2]) - for i in range(num_faces): # loop over faces + if sd == 3 and degree > 1: # face tangents + for i in top[2]: # loop over faces + cur = len(nodes) pts_cur = ref_el.make_points(2, i, degree + 1) nodes.extend(functional.PointFaceTangentEvaluation(ref_el, i, k, pt) for k in range(2) # loop over tangents for pt in pts_cur # loop over points ) + entity_ids[2][i] = list(range(cur, len(nodes))) - if degree > 2: # internal moments - Q = create_quadrature(ref_el, 2 * degree - 3) - qpts = Q.get_points() - Pkm2 = polynomial_set.ONPolynomialSet(ref_el, degree - 3) - Pkm2_at_qpts = Pkm2.tabulate(qpts)[(0,) * sd] - nodes.extend(functional.IntegralMoment(ref_el, Q, phi, (d,), (sd,)) - for d in range(sd) for phi in Pkm2_at_qpts) - - entity_ids = {} - # set to empty - for i in range(sd + 1): - entity_ids[i] = {} - for j in range(len(t[i])): - entity_ids[i][j] = [] - - cur = 0 - - # edge dof - num_pts_per_edge = len(ref_el.make_points(1, 0, degree + 1)) - for i in range(len(t[1])): - entity_ids[1][i] = list(range(cur, cur + num_pts_per_edge)) - cur += num_pts_per_edge - - # face dof - if degree > 1: - num_pts_per_face = len(ref_el.make_points(2, 0, degree + 1)) - for i in range(len(t[2])): - entity_ids[2][i] = list(range(cur, cur + 2 * num_pts_per_face)) - cur += 2 * num_pts_per_face - - if degree > 2: - num_internal_dof = Pkm2_at_qpts.shape[0] * sd - entity_ids[3][0] = list(range(cur, cur + num_internal_dof)) + # internal nodes. These are \int_T v \cdot p dx where p \in P_{q-d}^3(T) + dim = sd + phi_deg = degree - dim + if phi_deg >= 0: + cur = len(nodes) + Q = create_quadrature(ref_el, interpolant_deg + phi_deg) + Pqmd = polynomial_set.ONPolynomialSet(ref_el, phi_deg) + Pqmd_at_qpts = Pqmd.tabulate(Q.get_points())[(0,) * dim] + nodes.extend(functional.IntegralMoment(ref_el, Q, phi, (d,), (dim,)) + for d in range(dim) for phi in Pqmd_at_qpts) + entity_ids[dim][0] = list(range(cur, len(nodes))) - super(NedelecDual3D, self).__init__(nodes, ref_el, entity_ids) + super(NedelecDual, self).__init__(nodes, ref_el, entity_ids) class Nedelec(finite_element.CiarletElement): @@ -339,12 +237,11 @@ def __init__(self, ref_el, degree, variant=None): variant, interpolant_deg = check_format_variant(variant, degree) if ref_el.get_spatial_dimension() == 3: poly_set = NedelecSpace3D(ref_el, degree) - dual = NedelecDual3D(ref_el, degree, variant, interpolant_deg) elif ref_el.get_spatial_dimension() == 2: poly_set = NedelecSpace2D(ref_el, degree) - dual = NedelecDual2D(ref_el, degree, variant, interpolant_deg) else: raise Exception("Not implemented") + dual = NedelecDual(ref_el, degree, variant, interpolant_deg) formdegree = 1 # 1-form super(Nedelec, self).__init__(poly_set, dual, degree, formdegree, mapping="covariant piola") diff --git a/FIAT/nedelec_second_kind.py b/FIAT/nedelec_second_kind.py index cc9bc8f66..6e5269721 100644 --- a/FIAT/nedelec_second_kind.py +++ b/FIAT/nedelec_second_kind.py @@ -17,8 +17,6 @@ from FIAT.quadrature_schemes import create_quadrature from FIAT.check_format_variant import check_format_variant -from FIAT import polynomial_set, functional - class NedelecSecondKindDual(DualSet): r""" @@ -67,48 +65,36 @@ def generate_degrees_of_freedom(self, cell, degree, variant, interpolant_deg): assert (d in (2, 3)), "Second kind Nedelecs only implemented in 2/3D." # Zero vertex-based degrees of freedom (d+1 of these) - ids[0] = dict(list(zip(list(range(d + 1)), ([] for i in range(d + 1))))) + ids[0] = {i: [] for i in range(d + 1)} - # (d+1) degrees of freedom per entity of codimension 1 (edges) - (edge_dofs, edge_ids) = self._generate_edge_dofs(cell, degree, 0, variant, interpolant_deg) + # (degree+1) degrees of freedom per entity of codimension 1 (edges) + (edge_dofs, ids[1]) = self._generate_edge_dofs(cell, degree, 0, variant, interpolant_deg) dofs.extend(edge_dofs) - ids[1] = edge_ids # Include face degrees of freedom if 3D if d == 3: - (face_dofs, face_ids) = self._generate_face_dofs(cell, degree, - len(dofs), variant, interpolant_deg) + face_dofs, ids[d-1] = self._generate_facet_dofs(d-1, cell, degree, + len(dofs), variant, interpolant_deg) dofs.extend(face_dofs) - ids[2] = face_ids # Varying degrees of freedom (possibly zero) per cell - (cell_dofs, cell_ids) = self._generate_cell_dofs(cell, degree, len(dofs), variant, interpolant_deg) + cell_dofs, ids[d] = self._generate_facet_dofs(d, cell, degree, len(dofs), variant, interpolant_deg) dofs.extend(cell_dofs) - ids[d] = cell_ids return (dofs, ids) def _generate_edge_dofs(self, cell, degree, offset, variant, interpolant_deg): - """Generate degrees of freedoms (dofs) for entities of + """Generate degrees of freedom (dofs) for entities of codimension 1 (edges).""" + if variant == "integral": + return self._generate_facet_dofs(1, cell, degree, offset, variant, interpolant_deg) + # (degree+1) tangential component point evaluation degrees of # freedom per entity of codimension 1 (edges) dofs = [] ids = {} - - if variant == "integral": - edge = cell.construct_subelement(1) - Q = create_quadrature(edge, degree + interpolant_deg) - Pq = polynomial_set.ONPolynomialSet(edge, degree) - Pq_at_qpts = Pq.tabulate(Q.get_points())[(0,)] - for e in range(len(cell.get_topology()[1])): - dofs.extend(functional.IntegralMomentOfEdgeTangentEvaluation(cell, Q, phi, e) - for phi in Pq_at_qpts) - jj = Pq_at_qpts.shape[0] * e - ids[e] = list(range(offset + jj, offset + jj + Pq_at_qpts.shape[0])) - - elif variant == "point": + if variant == "point": for edge in range(len(cell.get_topology()[1])): # Create points for evaluation of tangential components @@ -123,87 +109,62 @@ def _generate_edge_dofs(self, cell, degree, offset, variant, interpolant_deg): return (dofs, ids) - def _generate_face_dofs(self, cell, degree, offset, variant, interpolant_deg): - """Generate degrees of freedoms (dofs) for faces.""" + def _generate_facet_dofs(self, codim, cell, degree, offset, variant, interpolant_deg): + """Generate degrees of freedom (dofs) for facets.""" # Initialize empty dofs and identifiers (ids) + num_facets = len(cell.get_topology()[codim]) dofs = [] - ids = dict(list(zip(list(range(4)), ([] for i in range(4))))) + ids = {i: [] for i in range(num_facets)} # Return empty info if not applicable - d = cell.get_spatial_dimension() - if degree < 2: + rt_degree = degree - codim + 1 + if rt_degree < 1: return (dofs, ids) if interpolant_deg is None: interpolant_deg = degree - # Construct quadrature scheme for the reference face - ref_face = cell.get_facet_element() - Q_ref = create_quadrature(ref_face, interpolant_deg + degree - 1) - - # Construct Raviart-Thomas of (degree - 1) on the reference face - RT = RaviartThomas(ref_face, degree - 1, variant) - num_rts = RT.space_dimension() - - # Evaluate RT basis functions at reference quadrature points - Phi = RT.get_nodal_basis() - Phis = Phi.tabulate(Q_ref.get_points())[(0, 0)] + # Construct quadrature scheme for the reference facet + ref_facet = cell.construct_subelement(codim) + Q_ref = create_quadrature(ref_facet, interpolant_deg + rt_degree) + if codim == 1: + Phi = ONPolynomialSet(ref_facet, rt_degree, (codim,)) + else: + # Construct Raviart-Thomas on the reference facet + RT = RaviartThomas(ref_facet, rt_degree, variant) + Phi = RT.get_nodal_basis() + + # Evaluate basis functions at reference quadrature points + Phis = Phi.tabulate(Q_ref.get_points())[(0,) * codim] # Note: Phis has dimensions: # num_basis_functions x num_components x num_quad_points Phis = numpy.transpose(Phis, (0, 2, 1)) # Note: Phis has dimensions: # num_basis_functions x num_quad_points x num_components - # Iterate over the faces of the tet - num_faces = len(cell.get_topology()[d-1]) - for face in range(num_faces): - # Get the quadrature and Jacobian on this face - Q_face = FacetQuadratureRule(cell, d-1, face, Q_ref) - J = Q_face.jacobian() + # Iterate over the facets + cur = offset + for facet in range(num_facets): + # Get the quadrature and Jacobian on this facet + Q_facet = FacetQuadratureRule(cell, codim, facet, Q_ref) + J = Q_facet.jacobian() + detJ = Q_facet.jacobian_determinant() # Map Phis -> phis (reference values to physical values) - piola_map = J / numpy.sqrt(numpy.linalg.det(numpy.dot(J.T, J))) + piola_map = J / detJ phis = numpy.dot(Phis, piola_map.T) phis = numpy.transpose(phis, (0, 2, 1)) # Construct degrees of freedom as integral moments on this cell, # using the face quadrature weighted against the values # of the (physical) Raviart--Thomas'es on the face - dofs.extend(IntegralMoment(cell, Q_face, phi) for phi in phis) + dofs.extend(IntegralMoment(cell, Q_facet, phi) for phi in phis) # Assign identifiers (num RTs per face + previous edge dofs) - ids[face] = list(range(offset + num_rts*face, offset + num_rts*(face + 1))) - - return (dofs, ids) - - def _generate_cell_dofs(self, cell, degree, offset, variant, interpolant_deg): - """Generate degrees of freedoms (dofs) for entities of - codimension d (cells).""" - - # Return empty info if not applicable - d = cell.get_spatial_dimension() - rt_degree = degree - d + 1 - if rt_degree < 1: - return ([], {0: []}) - - # Create quadrature points - interpolant_deg = interpolant_deg or degree - Q = create_quadrature(cell, interpolant_deg + rt_degree) - - # Create Raviart-Thomas nodal basis - RT = RaviartThomas(cell, rt_degree, variant) - phi = RT.get_nodal_basis() - - # Evaluate Raviart-Thomas basis at quadrature points - phi_at_qs = phi.tabulate(Q.get_points())[(0,) * d] - - # Use (Frobenius) integral moments against RTs as dofs - dofs = [IntegralMoment(cell, Q, phi) - for phi in phi_at_qs] + ids[facet].extend(range(cur, cur + len(phis))) + cur += len(phis) - # Associate these dofs with the interior - ids = {0: list(range(offset, offset + len(dofs)))} return (dofs, ids) diff --git a/FIAT/quadrature.py b/FIAT/quadrature.py index 3f7cbc499..f6f237d39 100644 --- a/FIAT/quadrature.py +++ b/FIAT/quadrature.py @@ -14,12 +14,16 @@ from FIAT import reference_element +def pseudo_determinant(A): + return numpy.sqrt(abs(numpy.linalg.det(numpy.dot(A.T, A)))) + + def map_quadrature(pts_ref, wts_ref, source_cell, target_cell, jacobian=False): """Map quadrature points and weights defined on source_cell to target_cell. """ A, b = reference_element.make_affine_mapping(source_cell.get_vertices(), target_cell.get_vertices()) - scale = numpy.sqrt(numpy.linalg.det(numpy.dot(A.T, A))) + scale = pseudo_determinant(A) pts = numpy.dot(pts_ref.reshape((-1, A.shape[1])), A.T) + b[None, :] wts = scale * wts_ref # return immutable types @@ -173,6 +177,9 @@ def reference_rule(self): def jacobian(self): return self._J + def jacobian_determinant(self): + return pseudo_determinant(self._J) + def make_quadrature(ref_el, m): """Returns the collapsed quadrature rule using m points per diff --git a/FIAT/quadrature_schemes.py b/FIAT/quadrature_schemes.py index 31297dd14..114a94af8 100644 --- a/FIAT/quadrature_schemes.py +++ b/FIAT/quadrature_schemes.py @@ -35,7 +35,7 @@ # FIAT from FIAT.reference_element import (HEXAHEDRON, QUADRILATERAL, TENSORPRODUCT, TETRAHEDRON, TRIANGLE, UFCTetrahedron, - UFCTriangle, default_simplex) + UFCTriangle, symmetric_simplex) def create_quadrature(ref_el, degree, scheme="default"): @@ -351,23 +351,9 @@ def xg_scheme(ref_el, degree): except KeyError: raise ValueError(f"Xiao-Gambutas rule not availale for degree {degree}.") - # Get affine map from the (-1,1)^d triangle to the G-X equilateral triangle - if dim == 2: - A = numpy.array([[1, 1/2], - [0, numpy.sqrt(3)/2]]) - b = A.sum(axis=1)/3 - else: - A = numpy.array([[1, 1/2, 1/2], - [0, numpy.sqrt(3)/2, numpy.sqrt(3)/6], - [0, 0, numpy.sqrt(6)/3]]) - b = A.sum(axis=1)/2 - - Ref1 = default_simplex(dim) - v = numpy.dot(Ref1.vertices, A.T) + b[None, :] - Ref1.vertices = tuple(map(tuple, v)) - pts_ref = order_table["points"] wts_ref = order_table["weights"] + Ref1 = symmetric_simplex(dim) pts, wts = map_quadrature(pts_ref, wts_ref, Ref1, ref_el) return QuadratureRule(ref_el, pts, wts) diff --git a/FIAT/raviart_thomas.py b/FIAT/raviart_thomas.py index a1bca2d7a..d0c2e2e87 100644 --- a/FIAT/raviart_thomas.py +++ b/FIAT/raviart_thomas.py @@ -11,6 +11,7 @@ from itertools import chain from FIAT.check_format_variant import check_format_variant from FIAT.quadrature_schemes import create_quadrature +from FIAT.quadrature import FacetQuadratureRule def RTSpace(ref_el, degree): @@ -33,22 +34,16 @@ def RTSpace(ref_el, degree): PkH = Pkp1.take(list(range(dimPkm1, dimPk))) Q = create_quadrature(ref_el, 2 * (k + 1)) + Qpts, Qwts = Q.get_points(), Q.get_weights() # have to work on this through "tabulate" interface # first, tabulate PkH at quadrature points - Qpts = Q.get_points() - Qwts = Q.get_weights() - PkH_at_Qpts = PkH.tabulate(Qpts)[(0,) * sd] Pkp1_at_Qpts = Pkp1.tabulate(Qpts)[(0,) * sd] x = Qpts.T - xPkH_at_Qpts = numpy.zeros((PkH_at_Qpts.shape[0], - sd, - PkH_at_Qpts.shape[1]), "d") - for i in range(PkH_at_Qpts.shape[0]): - xPkH_at_Qpts[i] = PkH_at_Qpts[i] * x - PkHx_coeffs = numpy.dot(xPkH_at_Qpts, Qwts[:, None] * Pkp1_at_Qpts.T) + xPkH_at_Qpts = PkH_at_Qpts[:, None, :] * x[None, :, :] + PkHx_coeffs = numpy.dot(numpy.multiply(xPkH_at_Qpts, Qwts), Pkp1_at_Qpts.T) PkHx = polynomial_set.PolynomialSet(ref_el, k, @@ -65,68 +60,63 @@ class RTDualSet(dual_set.DualSet): moments against polynomials""" def __init__(self, ref_el, degree, variant, interpolant_deg): - entity_ids = {} nodes = [] - sd = ref_el.get_spatial_dimension() - t = ref_el.get_topology() + top = ref_el.get_topology() + + entity_ids = {} + # set to empty + for dim in top: + entity_ids[dim] = {} + for entity in top[dim]: + entity_ids[dim][entity] = [] if variant == "integral": facet = ref_el.get_facet_element() # Facet nodes are \int_F v\cdot n p ds where p \in P_{q-1} # degree is q - 1 - Q = create_quadrature(facet, interpolant_deg + degree - 1) + Q_ref = create_quadrature(facet, interpolant_deg + degree - 1) Pq = polynomial_set.ONPolynomialSet(facet, degree - 1) - Pq_at_qpts = Pq.tabulate(Q.get_points())[(0,)*(sd - 1)] - nodes.extend(functional.IntegralMomentOfScaledNormalEvaluation(ref_el, Q, phi, f) - for f in range(len(t[sd - 1])) - for phi in Pq_at_qpts) + Pq_at_qpts = Pq.tabulate(Q_ref.get_points())[(0,)*(sd - 1)] + + for f in top[sd - 1]: + cur = len(nodes) + Q = FacetQuadratureRule(ref_el, sd-1, f, Q_ref) + Jdet = Q.jacobian_determinant() + n = ref_el.compute_scaled_normal(f) / Jdet + phis = n[None, :, None] * Pq_at_qpts[:, None, :] + nodes.extend(functional.FrobeniusIntegralMoment(ref_el, Q, phi) + for phi in phis) + entity_ids[sd - 1][f] = list(range(cur, len(nodes))) # internal nodes. These are \int_T v \cdot p dx where p \in P_{q-2}^d if degree > 1: + cur = len(nodes) Q = create_quadrature(ref_el, interpolant_deg + degree - 2) Pkm1 = polynomial_set.ONPolynomialSet(ref_el, degree - 2) Pkm1_at_qpts = Pkm1.tabulate(Q.get_points())[(0,) * sd] nodes.extend(functional.IntegralMoment(ref_el, Q, phi, (d,), (sd,)) for d in range(sd) for phi in Pkm1_at_qpts) + entity_ids[sd][0] = list(range(cur, len(nodes))) elif variant == "point": # codimension 1 facets - for i in range(len(t[sd - 1])): + for i in top[sd - 1]: + cur = len(nodes) pts_cur = ref_el.make_points(sd - 1, i, sd + degree - 1) nodes.extend(functional.PointScaledNormalEvaluation(ref_el, i, pt) for pt in pts_cur) + entity_ids[sd - 1][i] = list(range(cur, len(nodes))) # internal nodes. Let's just use points at a lattice if degree > 1: + cur = len(nodes) pts = ref_el.make_points(sd, 0, sd + degree - 1) nodes.extend(functional.ComponentPointEvaluation(ref_el, d, (sd,), pt) for d in range(sd) for pt in pts) - - # sets vertices (and in 3d, edges) to have no nodes - for i in range(sd - 1): - entity_ids[i] = {} - for j in range(len(t[i])): - entity_ids[i][j] = [] - - cur = 0 - - # set codimension 1 (edges 2d, faces 3d) dof - pts_facet_0 = ref_el.make_points(sd - 1, 0, sd + degree - 1) - pts_per_facet = len(pts_facet_0) - entity_ids[sd - 1] = {} - for i in range(len(t[sd - 1])): - entity_ids[sd - 1][i] = list(range(cur, cur + pts_per_facet)) - cur += pts_per_facet - - # internal nodes, if applicable - entity_ids[sd] = {0: []} - if degree > 1: - num_internal_nodes = expansions.polynomial_dimension(ref_el, - degree - 2) - entity_ids[sd][0] = list(range(cur, cur + num_internal_nodes * sd)) + entity_ids[sd][0] = list(range(cur, len(nodes))) super(RTDualSet, self).__init__(nodes, ref_el, entity_ids) From a9878dffcdced8e2eebd025b86ffeb7a982a0871 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Tue, 26 Dec 2023 21:26:19 -0600 Subject: [PATCH 46/93] add SymmetricSimplex --- FIAT/reference_element.py | 57 ++++++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/FIAT/reference_element.py b/FIAT/reference_element.py index 66225a674..dcef5d0a1 100644 --- a/FIAT/reference_element.py +++ b/FIAT/reference_element.py @@ -687,6 +687,36 @@ def distance_to_point_l1(self, point): return abs(l1_dist) +class DefaultSimplex(Simplex): + + def get_facet_element(self): + dimension = self.get_spatial_dimension() + return self.construct_subelement(dimension - 1) + + def construct_subelement(self, dimension): + """Constructs the reference element of a cell subentity + specified by subelement dimension. + + :arg dimension: subentity dimension (integer) + """ + return default_simplex(dimension) + + +class SymmetricSimplex(Simplex): + + def get_facet_element(self): + dimension = self.get_spatial_dimension() + return self.construct_subelement(dimension - 1) + + def construct_subelement(self, dimension): + """Constructs the reference element of a cell subentity + specified by subelement dimension. + + :arg dimension: subentity dimension (integer) + """ + return symmetric_simplex(dimension) + + class Point(Simplex): """This is the reference point.""" @@ -705,7 +735,7 @@ def construct_subelement(self, dimension): return self -class DefaultLine(Simplex): +class DefaultLine(DefaultSimplex): """This is the reference line with vertices (-1.0,) and (1.0,).""" def __init__(self): @@ -715,9 +745,6 @@ def __init__(self): 1: edges} super(DefaultLine, self).__init__(LINE, verts, topology) - def get_facet_element(self): - raise NotImplementedError() - class UFCInterval(UFCSimplex): """This is the reference interval with vertices (0.0,) and (1.0,).""" @@ -730,7 +757,7 @@ def __init__(self): super(UFCInterval, self).__init__(LINE, verts, topology) -class DefaultTriangle(Simplex): +class DefaultTriangle(DefaultSimplex): """This is the reference triangle with vertices (-1.0,-1.0), (1.0,-1.0), and (-1.0,1.0).""" @@ -744,9 +771,6 @@ def __init__(self): 1: edges, 2: faces} super(DefaultTriangle, self).__init__(TRIANGLE, verts, topology) - def get_facet_element(self): - return DefaultLine() - class UFCTriangle(UFCSimplex): """This is the reference triangle with vertices (0.0,0.0), @@ -786,7 +810,7 @@ def get_facet_element(self): return UFCInterval() -class DefaultTetrahedron(Simplex): +class DefaultTetrahedron(DefaultSimplex): """This is the reference tetrahedron with vertices (-1,-1,-1), (1,-1,-1),(-1,1,-1), and (-1,-1,1).""" @@ -811,9 +835,6 @@ def __init__(self): topology = {0: vs, 1: edges, 2: faces, 3: tets} super(DefaultTetrahedron, self).__init__(TETRAHEDRON, verts, topology) - def get_facet_element(self): - return DefaultTriangle() - class IntrepidTetrahedron(Simplex): """This is the reference tetrahedron with vertices (0,0,0), @@ -1308,6 +1329,18 @@ def ufc_simplex(spatial_dim): raise RuntimeError("Can't create UFC simplex of dimension %s." % str(spatial_dim)) +def symmetric_simplex(spatial_dim): + A = numpy.array([[2, 1, 1], + [0, numpy.sqrt(3), numpy.sqrt(3)/3], + [0, 0, numpy.sqrt(6)*(2/3)]]) + A = A[:spatial_dim, :][:, :spatial_dim] + b = A.sum(axis=1) * (-1 / (1 + spatial_dim)) + Ref1 = ufc_simplex(spatial_dim) + v = numpy.dot(Ref1.get_vertices(), A.T) + b[None, :] + vertices = tuple(map(tuple, v)) + return SymmetricSimplex(Ref1.get_shape(), vertices, Ref1.get_topology()) + + def ufc_cell(cell): """Handle incoming calls from FFC.""" From a5564de1ff6d482090db02c0a2139587d74b1cc7 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Fri, 29 Dec 2023 19:12:22 -0600 Subject: [PATCH 47/93] Demkowicz N2Curl and N2Div elements --- FIAT/demkowicz.py | 211 +++++++++++++++++++++++++++++++++++++++++ FIAT/polynomial_set.py | 5 + 2 files changed, 216 insertions(+) create mode 100644 FIAT/demkowicz.py diff --git a/FIAT/demkowicz.py b/FIAT/demkowicz.py new file mode 100644 index 000000000..58b9d78dc --- /dev/null +++ b/FIAT/demkowicz.py @@ -0,0 +1,211 @@ +# Copyright (C) 2023 Pablo D. Brubeck (University of Oxford) +# +# This file is part of FIAT (https://www.fenicsproject.org) +# +# SPDX-License-Identifier: LGPL-3.0-or-later + +from FIAT import (polynomial_set, dual_set, + finite_element, functional) +import numpy +from FIAT.quadrature_schemes import create_quadrature +from FIAT.quadrature import FacetQuadratureRule +from FIAT.nedelec import Nedelec +from FIAT.raviart_thomas import RaviartThomas + + +def as_table(P): + PT = numpy.transpose(P, (1, 0, 2)) + P_table = dict(zip(polynomial_set.mis(len(PT), 1), PT)) + return P_table + + +def grad(table_u): + return table_u + + +def curl(table_u): + grad_u = [None for alpha in table_u if sum(alpha) == 1] + for alpha in table_u: + if sum(alpha) == 1: + grad_u[alpha.index(1)] = table_u[alpha] + + nbfs, *_, npts = grad_u[0].shape + d = len(grad_u) + ncomp = (d * (d - 1)) // 2 + indices = ((i, j) for i in range(d) for j in range(i+1, d)) + curl_u = numpy.empty((nbfs, ncomp, npts), "d") + for k, (i, j) in enumerate(indices): + s = (-1)**k + curl_u[:, k, :] = s*grad_u[i][:, j, :] - s*grad_u[j][:, i, :] + return as_table(curl_u) + + +def div(table_u): + grad_u = [None for alpha in table_u if sum(alpha) == 1] + for alpha in table_u: + if sum(alpha) == 1: + grad_u[alpha.index(1)] = table_u[alpha] + nbfs, *_, npts = grad_u[0].shape + div_u = numpy.empty((nbfs, 1, npts), "d") + div_u[:, 0, :] = sum(grad_u[i][:, i, :] for i in range(len(grad_u))) + return as_table(div_u) + + +class DemkowiczDual(dual_set.DualSet): + + def __init__(self, ref_el, degree, sobolev_space): + nodes = [] + entity_ids = {} + top = ref_el.get_topology() + sd = ref_el.get_spatial_dimension() + self.formdegree = 1 if sobolev_space == "HCurl" else sd - 1 + + for dim in top: + entity_ids[dim] = {} + if dim < self.formdegree or degree < dim: + for entity in top[dim]: + entity_ids[dim][entity] = [] + else: + Q_ref, Phis = self._reference_duals(ref_el, dim, degree, sobolev_space) + if dim == sd: + trace = None + else: + trace = "tangential" if sobolev_space == "HCurl" else "normal" + if trace == "tangential": + Phis = numpy.transpose(Phis, (0, 2, 1)) + + for entity in top[dim]: + cur = len(nodes) + Q = FacetQuadratureRule(ref_el, dim, entity, Q_ref) + if trace == "normal": + Jdet = Q.jacobian_determinant() + n = ref_el.compute_scaled_normal(entity) / Jdet + phis = n[None, :, None] * Phis + elif trace == "tangential": + J = Q.jacobian() + piola_map = numpy.linalg.pinv(J.T) + phis = numpy.dot(Phis, piola_map.T) + phis = numpy.transpose(phis, (0, 2, 1)) + else: + phis = Phis + nodes.extend(functional.FrobeniusIntegralMoment(ref_el, Q, phi) + for phi in phis) + entity_ids[dim][entity] = list(range(cur, len(nodes))) + + super(DemkowiczDual, self).__init__(nodes, ref_el, entity_ids) + + def _reference_duals(self, ref_el, dim, degree, sobolev_space): + facet = ref_el.construct_subelement(dim) + Q = create_quadrature(facet, 2 * degree) + if dim == self.formdegree: + P = polynomial_set.ONPolynomialSet(facet, degree, (1,)) + duals = P.tabulate(Q.get_points())[(0,) * dim] + return Q, duals + + Qpts, Qwts = Q.get_points(), Q.get_weights() + inner = lambda v, u: numpy.dot(numpy.multiply(v, Qwts), u.T) + galerkin = lambda order, V, U: sum(inner(V[k], U[k]) for k in V if sum(k) == order) + P = polynomial_set.ONPolynomialSet(facet, degree, (dim,)) + P_table = P.tabulate(Qpts, 1) + + if self.formdegree == 1: + B = polynomial_set.make_bubbles(facet, degree+1) + grad_basis = B.tabulate(Qpts, 1) + + if sobolev_space == "HCurl": + curl_coeffs, curl_basis = self.exterior_derivative_bubbles(facet, degree, 1, Qpts, galerkin) + K1 = numpy.dot(curl_coeffs, galerkin(1, curl_basis, curl(P_table))) + + elif sobolev_space == "HDiv": + # Swap grad -> rot, and curl -> div + grad_basis[(1, 0)], grad_basis[(0, 1)] = grad_basis[(0, 1)], -grad_basis[(1, 0)] + div_coeffs, div_basis = self.exterior_derivative_bubbles(facet, degree, 2, Qpts, galerkin) + K1 = numpy.dot(div_coeffs, galerkin(1, div_basis, div(P_table))) + else: + raise ValueError("Invalid Sobolev space") + + K0 = galerkin(1, grad_basis, as_table(P_table[(0,) * dim])) + K = numpy.vstack((K0, K1)) + + elif self.formdegree == 2: + curl_coeffs, curl_basis = self.exterior_derivative_bubbles(facet, degree+1, 1, Qpts, galerkin) + K1 = numpy.dot(curl_coeffs, galerkin(1, curl_basis, as_table(P_table[(0,) * dim]))) + + div_coeffs, div_basis = self.exterior_derivative_bubbles(facet, degree, 2, Qpts, galerkin) + K2 = numpy.dot(div_coeffs, galerkin(1, div_basis, div(P_table))) + K = numpy.vstack((K1, K2)) + else: + raise ValueError("Invalid form degree") + + duals = P_table[(0,) * dim] + shp = (-1, ) + duals.shape[1:] + duals = numpy.dot(K, duals.reshape((K.shape[1], -1))).reshape(shp) + return Q, duals + + def exterior_derivative_bubbles(self, facet, degree, formdegree, Qpts, galerkin): + dim = facet.get_spatial_dimension() + comp = (dim, dim*(dim-1)//2, 1)[formdegree] + Pkm1 = polynomial_set.ONPolynomialSet(facet, degree-1, (comp,)) + basis = as_table(Pkm1.tabulate(Qpts)[(0,) * dim]) + + family = (None, Nedelec, RaviartThomas)[formdegree] + d = (grad, curl, div)[formdegree] + + N1 = family(facet, degree) + B = N1.get_nodal_basis().take(N1.entity_dofs()[dim][0]) + dB = d(B.tabulate(Qpts, 1)) + + new_coeffs = galerkin(1, dB, basis) + U, S, VT = numpy.linalg.svd(new_coeffs) + num_sv = len([s for s in S if s > 1E-10]) + coeffs = VT[:num_sv] + return coeffs, basis + + +class N2Curl(finite_element.CiarletElement): + + def __init__(self, ref_el, degree): + sd = ref_el.get_spatial_dimension() + dual = DemkowiczDual(ref_el, degree, "HCurl") + poly_set = polynomial_set.ONPolynomialSet(ref_el, degree, (sd,)) + super(N2Curl, self).__init__(poly_set, dual, degree, dual.formdegree, + mapping="covariant piola") + + +class N2Div(finite_element.CiarletElement): + + def __init__(self, ref_el, degree): + sd = ref_el.get_spatial_dimension() + dual = DemkowiczDual(ref_el, degree, "HDiv") + poly_set = polynomial_set.ONPolynomialSet(ref_el, degree, (sd,)) + super(N2Div, self).__init__(poly_set, dual, degree, dual.formdegree, + mapping="contravariant piola") + + +if __name__ == "__main__": + import matplotlib.pyplot as plt + from FIAT.reference_element import symmetric_simplex + dim = 3 + degree = 7 + ref_el = symmetric_simplex(dim) + Q = create_quadrature(ref_el, 2 * degree) + Qpts, Qwts = Q.get_points(), Q.get_weights() + inner = lambda v, u: numpy.dot(numpy.multiply(v, Qwts), u.T) + galerkin = lambda order, V, U: sum(inner(V[k], U[k]) for k in V if sum(k) == order) + + d, fe = curl, N2Curl(ref_el, degree) + # d, fe = div, N2Div(ref_el, degree) + + phi = fe.tabulate(1, Qpts) + dphi = d(phi) + A = galerkin(1, dphi, dphi) + A[abs(A) < 1E-10] = 0.0 + + phi_table = as_table(phi[(0,) * dim]) + B = galerkin(1, phi_table, phi_table) + B[abs(B) < 1E-10] = 0.0 + + # A = B + plt.spy(A, markersize=0) + plt.pcolor(numpy.log(abs(A))) + plt.show() diff --git a/FIAT/polynomial_set.py b/FIAT/polynomial_set.py index 376f328f2..8997aad5a 100644 --- a/FIAT/polynomial_set.py +++ b/FIAT/polynomial_set.py @@ -261,5 +261,10 @@ def make_bubbles(ref_el, degree, shape=()): for alpha in mis(dim, p): if alpha[0] > 1 and min(alpha[1:]) > 0: indices.append(idx(*alpha)) + + if shape != (): + ncomp = numpy.prod(shape) + dimPk = poly_set.get_num_members() // ncomp + indices = list((numpy.array(indices)[:, None] + dimPk * numpy.arange(ncomp)[None, :]).flat) poly_set = poly_set.take(indices) return poly_set From 8e0440f037e66c266acf9064eaf7b1d7b0e3ab08 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Sat, 30 Dec 2023 13:29:07 -0600 Subject: [PATCH 48/93] N2Curl FDM variant --- FIAT/demkowicz.py | 109 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 99 insertions(+), 10 deletions(-) diff --git a/FIAT/demkowicz.py b/FIAT/demkowicz.py index 5cc5a7363..a9249c440 100644 --- a/FIAT/demkowicz.py +++ b/FIAT/demkowicz.py @@ -11,6 +11,8 @@ from FIAT.quadrature import FacetQuadratureRule from FIAT.nedelec import Nedelec from FIAT.raviart_thomas import RaviartThomas +from FIAT.reference_element import symmetric_simplex +from FIAT.fdm_element import sym_eig def as_table(P): @@ -148,17 +150,103 @@ def _bubble_moments(self, facet, degree, formdegree, Qpts, Qwts, trial, rot=Fals phis = as_table(Pkm1.tabulate(Qpts)[(0,) * dim]) new_coeffs = galerkin(1, dtest, phis) - U, S, VT = numpy.linalg.svd(new_coeffs) - num_sv = len([s for s in S if s > 1E-10]) - coeffs = VT[:num_sv] + u, sig, vt = numpy.linalg.svd(new_coeffs) + num_sv = len([s for s in sig if abs(s) > 1.e-10]) + coeffs = vt[:num_sv] return numpy.dot(coeffs, galerkin(1, phis, trial)) +class FDMDual(dual_set.DualSet): + + def __init__(self, ref_el, degree, sobolev_space): + nodes = [] + entity_ids = {} + sd = ref_el.get_spatial_dimension() + if sobolev_space == "HCurl": + element = N2Curl + d = curl + elif sobolev_space == "HDiv": + element = N2Div + d = div + else: + raise ValueError("Invalid Sobolev space") + Ref_el = symmetric_simplex(sd) + fe = element(Ref_el, degree) + ells = fe.dual_basis() + entity_dofs = fe.entity_dofs() + self.formdegree = fe.formdegree + + Q = create_quadrature(Ref_el, 2 * degree) + X, W = Q.get_points(), Q.get_weights() + + inner = lambda v: numpy.dot(numpy.multiply(v, W), v.T) + galerkin = lambda V, dofs: sum(inner(V[k][dofs]) for k in V if sum(k) == 1) + V = fe.tabulate(1, X) + V1 = d(V) + V0 = as_table(V[(0,) * sd]) + + for dim in sorted(entity_dofs): + entity_ids[dim] = {} + for entity in entity_dofs[dim]: + entity_ids[dim][entity] = [] + + dofs = entity_dofs[dim][0] + if len(dofs) > 0: + A = galerkin(V1, dofs) + B = galerkin(V0, dofs) + _, S = sym_eig(A, B) + Sinv = numpy.dot(S.T, B) + + Q_dof = ells[dofs[0]].Q + Q_ref = Q_dof.reference_rule() + Phis = numpy.array([ells[i].f_at_qpts for i in dofs]) + if dim == sd or self.formdegree == 0: + trace = None + else: + trace = "tangential" if sobolev_space == "HCurl" else "normal" + + # apply pushforward + Jdet = Q_dof.jacobian_determinant() + if trace == "normal": + n = Ref_el.compute_scaled_normal(0) / Jdet + n *= 1 / numpy.dot(n, n) + Phis = numpy.dot(n[None, :], Phis).transpose((1, 0, 2)) + elif trace == "tangential": + J = Q_dof.jacobian() + Phis = numpy.dot(J.T, Phis).transpose((1, 0, 2)) + else: + Phis *= Jdet + + shp = Phis.shape + Phis = numpy.dot(Sinv, Phis.reshape((Sinv.shape[0], -1))).reshape(shp) + for entity in sorted(entity_dofs[dim]): + cur = len(nodes) + Q_facet = FacetQuadratureRule(ref_el, dim, entity, Q_ref) + # apply pullback + Jdet = Q_facet.jacobian_determinant() + if trace == "normal": + n = ref_el.compute_scaled_normal(entity) / Jdet + phis = n[None, :, None] * Phis + elif trace == "tangential": + J = Q_facet.jacobian() + piola_map = numpy.linalg.pinv(J.T) + phis = numpy.dot(piola_map, Phis).transpose((1, 0, 2)) + else: + phis = (1 / Jdet) * Phis + + nodes.extend(functional.FrobeniusIntegralMoment(ref_el, Q_facet, phi) + for phi in phis) + entity_ids[dim][entity] = list(range(cur, len(nodes))) + + super(FDMDual, self).__init__(nodes, ref_el, entity_ids) + + class N2Curl(finite_element.CiarletElement): - def __init__(self, ref_el, degree): + def __init__(self, ref_el, degree, variant=None): sd = ref_el.get_spatial_dimension() - dual = DemkowiczDual(ref_el, degree, "HCurl") + make_dual = FDMDual if variant == "fdm" else DemkowiczDual + dual = make_dual(ref_el, degree, "HCurl") poly_set = polynomial_set.ONPolynomialSet(ref_el, degree, (sd,)) super(N2Curl, self).__init__(poly_set, dual, degree, dual.formdegree, mapping="covariant piola") @@ -166,9 +254,10 @@ def __init__(self, ref_el, degree): class N2Div(finite_element.CiarletElement): - def __init__(self, ref_el, degree): + def __init__(self, ref_el, degree, variant=None): sd = ref_el.get_spatial_dimension() - dual = DemkowiczDual(ref_el, degree, "HDiv") + make_dual = FDMDual if variant == "fdm" else DemkowiczDual + dual = make_dual(ref_el, degree, "HDiv") poly_set = polynomial_set.ONPolynomialSet(ref_el, degree, (sd,)) super(N2Div, self).__init__(poly_set, dual, degree, dual.formdegree, mapping="contravariant piola") @@ -176,7 +265,6 @@ def __init__(self, ref_el, degree): if __name__ == "__main__": import matplotlib.pyplot as plt - from FIAT.reference_element import symmetric_simplex dim = 3 degree = 7 ref_el = symmetric_simplex(dim) @@ -185,8 +273,9 @@ def __init__(self, ref_el, degree): inner = lambda v, u: numpy.dot(numpy.multiply(v, Qwts), u.T) galerkin = lambda order, V, U: sum(inner(V[k], U[k]) for k in V if sum(k) == order) - d, fe = curl, N2Curl(ref_el, degree) - # d, fe = div, N2Div(ref_el, degree) + variant = "fdm" + d, fe = curl, N2Curl(ref_el, degree, variant) + # d, fe = div, N2Div(ref_el, degree, variant) phi = fe.tabulate(1, Qpts) dphi = d(phi) From 2d006074c2f6c2f977bc147dbd81fff44fff8b4f Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Sat, 30 Dec 2023 14:23:44 -0600 Subject: [PATCH 49/93] N2Div FDM variant --- FIAT/demkowicz.py | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/FIAT/demkowicz.py b/FIAT/demkowicz.py index a9249c440..82f5b6e72 100644 --- a/FIAT/demkowicz.py +++ b/FIAT/demkowicz.py @@ -7,12 +7,12 @@ from FIAT import (polynomial_set, dual_set, finite_element, functional) import numpy +import scipy from FIAT.quadrature_schemes import create_quadrature from FIAT.quadrature import FacetQuadratureRule from FIAT.nedelec import Nedelec from FIAT.raviart_thomas import RaviartThomas from FIAT.reference_element import symmetric_simplex -from FIAT.fdm_element import sym_eig def as_table(P): @@ -64,7 +64,7 @@ def __init__(self, ref_el, degree, sobolev_space): sd = ref_el.get_spatial_dimension() self.formdegree = 1 if sobolev_space == "HCurl" else sd - 1 - for dim in top: + for dim in sorted(top): entity_ids[dim] = {} if dim < self.formdegree or degree < dim: for entity in top[dim]: @@ -76,7 +76,7 @@ def __init__(self, ref_el, degree, sobolev_space): else: trace = "tangential" if sobolev_space == "HCurl" else "normal" - for entity in top[dim]: + for entity in sorted(top[dim]): cur = len(nodes) Q = FacetQuadratureRule(ref_el, dim, entity, Q_ref) if trace == "normal": @@ -194,18 +194,19 @@ def __init__(self, ref_el, degree, sobolev_space): if len(dofs) > 0: A = galerkin(V1, dofs) B = galerkin(V0, dofs) - _, S = sym_eig(A, B) - Sinv = numpy.dot(S.T, B) + A += B + _, S = scipy.linalg.eigh(B, A) + Sinv = numpy.dot(S.T, A) Q_dof = ells[dofs[0]].Q Q_ref = Q_dof.reference_rule() - Phis = numpy.array([ells[i].f_at_qpts for i in dofs]) if dim == sd or self.formdegree == 0: trace = None else: trace = "tangential" if sobolev_space == "HCurl" else "normal" # apply pushforward + Phis = numpy.array([ells[i].f_at_qpts for i in dofs]) Jdet = Q_dof.jacobian_determinant() if trace == "normal": n = Ref_el.compute_scaled_normal(0) / Jdet @@ -217,14 +218,28 @@ def __init__(self, ref_el, degree, sobolev_space): else: Phis *= Jdet - shp = Phis.shape - Phis = numpy.dot(Sinv, Phis.reshape((Sinv.shape[0], -1))).reshape(shp) + Phis = numpy.dot(Sinv, Phis.reshape((Sinv.shape[0], -1))).reshape(Phis.shape) + for entity in sorted(entity_dofs[dim]): cur = len(nodes) Q_facet = FacetQuadratureRule(ref_el, dim, entity, Q_ref) # apply pullback Jdet = Q_facet.jacobian_determinant() if trace == "normal": + if entity != 0: + # FIXME need to figure out correct transformation + dofs = entity_dofs[dim][entity] + A = galerkin(V1, dofs) + B = galerkin(V0, dofs) + A += B + _, S = scipy.linalg.eigh(B, A) + Sinv = numpy.dot(S.T, A) + Phis = numpy.array([ells[i].f_at_qpts for i in dofs]) + n = Ref_el.compute_scaled_normal(entity) / Q_dof.jacobian_determinant() + n *= 1 / numpy.dot(n, n) + Phis = numpy.dot(n[None, :], Phis).transpose((1, 0, 2)) + Phis = numpy.dot(Sinv, Phis.reshape((Sinv.shape[0], -1))).reshape(Phis.shape) + n = ref_el.compute_scaled_normal(entity) / Jdet phis = n[None, :, None] * Phis elif trace == "tangential": @@ -274,8 +289,9 @@ def __init__(self, ref_el, degree, variant=None): galerkin = lambda order, V, U: sum(inner(V[k], U[k]) for k in V if sum(k) == order) variant = "fdm" - d, fe = curl, N2Curl(ref_el, degree, variant) - # d, fe = div, N2Div(ref_el, degree, variant) + # variant = None + # d, fe = curl, N2Curl(ref_el, degree, variant) + d, fe = div, N2Div(ref_el, degree, variant) phi = fe.tabulate(1, Qpts) dphi = d(phi) From 071def1ed9eb1b87df3dc6edaf0ae59d22654200 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Sat, 30 Dec 2023 21:10:53 -0600 Subject: [PATCH 50/93] Apparently working N2curl/N2div FDM variants --- FIAT/demkowicz.py | 80 ++++++++++++++++-------------------------- FIAT/raviart_thomas.py | 1 + 2 files changed, 32 insertions(+), 49 deletions(-) diff --git a/FIAT/demkowicz.py b/FIAT/demkowicz.py index 82f5b6e72..931ebb058 100644 --- a/FIAT/demkowicz.py +++ b/FIAT/demkowicz.py @@ -34,13 +34,12 @@ def curl(table_u): nbfs, *_, npts = grad_u[0].shape d = len(grad_u) ncomp = (d * (d - 1)) // 2 - indices = ((i, j) for i in range(d) for j in range(i+1, d)) - curl_u = numpy.empty((nbfs, ncomp, npts), "d") + indices = ((i, j) for i in reversed(range(d)) for j in reversed(range(i+1, d))) + curl_u = numpy.empty((nbfs, ncomp, npts), grad_u[0].dtype) for k, (i, j) in enumerate(indices): s = (-1)**k - curl_u[:, k, :] = s*grad_u[i][:, j, :] - s*grad_u[j][:, i, :] - new_table = as_table(curl_u) - return new_table + curl_u[:, k, :] = s*grad_u[j][:, i, :] - s*grad_u[i][:, j, :] + return as_table(curl_u) def div(table_u): @@ -51,8 +50,7 @@ def div(table_u): nbfs, *_, npts = grad_u[0].shape div_u = numpy.empty((nbfs, 1, npts), "d") div_u[:, 0, :] = sum(grad_u[i][:, i, :] for i in range(len(grad_u))) - new_table = as_table(div_u) - return new_table + return as_table(div_u) class DemkowiczDual(dual_set.DualSet): @@ -88,7 +86,8 @@ def __init__(self, ref_el, degree, sobolev_space): piola_map = numpy.linalg.pinv(J.T) phis = numpy.dot(piola_map, Phis).transpose((1, 0, 2)) else: - phis = Phis + Jdet = Q.jacobian_determinant() + phis = Phis / Jdet nodes.extend(functional.FrobeniusIntegralMoment(ref_el, Q, phi) for phi in phis) entity_ids[dim][entity] = list(range(cur, len(nodes))) @@ -97,6 +96,7 @@ def __init__(self, ref_el, degree, sobolev_space): def _reference_duals(self, ref_el, dim, degree, sobolev_space): facet = ref_el.construct_subelement(dim) + # facet = symmetric_simplex(dim) Q = create_quadrature(facet, 2 * degree) if dim == self.formdegree: P = polynomial_set.ONPolynomialSet(facet, degree, (1,)) @@ -108,19 +108,14 @@ def _reference_duals(self, ref_el, dim, degree, sobolev_space): P_table = P.tabulate(Qpts, 1) trial = as_table(P_table[(0,) * dim]) - rot = sobolev_space == "HDiv" - dtrial = div(P_table) if rot else curl(P_table) - - if self.formdegree == 1: - K0 = self._bubble_moments(facet, degree+1, 0, Qpts, Qwts, trial, rot=rot) - K1 = self._bubble_moments(facet, degree, 1 + rot, Qpts, Qwts, dtrial) - elif self.formdegree == 2: - K0 = self._bubble_moments(facet, degree+1, 1, Qpts, Qwts, trial) - K1 = self._bubble_moments(facet, degree, 2, Qpts, Qwts, dtrial) - else: - raise ValueError("Invalid form degree") + fd = self.formdegree + rot = fd == 1 and sobolev_space == "HDiv" + dtrial = div(P_table) if sobolev_space == "HDiv" else curl(P_table) + K0 = self._bubble_moments(facet, degree+1, fd-1, Qpts, Qwts, trial, rot=rot) + K1 = self._bubble_moments(facet, degree, fd, Qpts, Qwts, dtrial) K = numpy.vstack((K0, K1)) + duals = P_table[(0,) * dim] shp = (-1, ) + duals.shape[1:] duals = numpy.dot(K, duals.reshape((K.shape[1], -1))).reshape(shp) @@ -131,6 +126,12 @@ def _bubble_moments(self, facet, degree, formdegree, Qpts, Qwts, trial, rot=Fals galerkin = lambda order, V, U: sum(inner(V[k], U[k]) for k in V if sum(k) == order) dim = facet.get_spatial_dimension() + if formdegree >= dim - 1: + Pkm1 = polynomial_set.ONPolynomialSet(facet, degree-1, (1,)) + P0 = Pkm1.take(list(range(1, Pkm1.get_num_members()))) + dtest = as_table(P0.tabulate(Qpts)[(0,) * dim]) + return galerkin(1, dtest, trial) + element = (None, Nedelec, RaviartThomas)[formdegree] if element is None: B = polynomial_set.make_bubbles(facet, degree) @@ -141,19 +142,15 @@ def _bubble_moments(self, facet, degree, formdegree, Qpts, Qwts, trial, rot=Fals d = (grad, curl, div)[formdegree] dtest = d(B.tabulate(Qpts, 1)) if rot: + assert dim == 2 dtest[(1, 0)], dtest[(0, 1)] = dtest[(0, 1)], -dtest[(1, 0)] - # if formdegree == 0: - # return galerkin(1, dtest, trial) - comp = (dim, dim*(dim-1)//2, 1)[formdegree] - Pkm1 = polynomial_set.ONPolynomialSet(facet, degree-1, (comp,)) - phis = as_table(Pkm1.tabulate(Qpts)[(0,) * dim]) - - new_coeffs = galerkin(1, dtest, phis) - u, sig, vt = numpy.linalg.svd(new_coeffs) - num_sv = len([s for s in sig if abs(s) > 1.e-10]) - coeffs = vt[:num_sv] - return numpy.dot(coeffs, galerkin(1, phis, trial)) + A = galerkin(1, dtest, dtest) + sig, S = scipy.linalg.eigh(A) + ind = abs(sig) > 1.e-10 + S = S[:, ind] + S = S * numpy.sqrt(1 / sig[None, ind]) + return numpy.dot(S.T, galerkin(1, dtest, trial)) class FDMDual(dual_set.DualSet): @@ -170,6 +167,7 @@ def __init__(self, ref_el, degree, sobolev_space): d = div else: raise ValueError("Invalid Sobolev space") + Ref_el = symmetric_simplex(sd) fe = element(Ref_el, degree) ells = fe.dual_basis() @@ -194,9 +192,8 @@ def __init__(self, ref_el, degree, sobolev_space): if len(dofs) > 0: A = galerkin(V1, dofs) B = galerkin(V0, dofs) - A += B - _, S = scipy.linalg.eigh(B, A) - Sinv = numpy.dot(S.T, A) + _, S = scipy.linalg.eigh(A, B) + Sinv = numpy.dot(S.T, B) Q_dof = ells[dofs[0]].Q Q_ref = Q_dof.reference_rule() @@ -226,20 +223,6 @@ def __init__(self, ref_el, degree, sobolev_space): # apply pullback Jdet = Q_facet.jacobian_determinant() if trace == "normal": - if entity != 0: - # FIXME need to figure out correct transformation - dofs = entity_dofs[dim][entity] - A = galerkin(V1, dofs) - B = galerkin(V0, dofs) - A += B - _, S = scipy.linalg.eigh(B, A) - Sinv = numpy.dot(S.T, A) - Phis = numpy.array([ells[i].f_at_qpts for i in dofs]) - n = Ref_el.compute_scaled_normal(entity) / Q_dof.jacobian_determinant() - n *= 1 / numpy.dot(n, n) - Phis = numpy.dot(n[None, :], Phis).transpose((1, 0, 2)) - Phis = numpy.dot(Sinv, Phis.reshape((Sinv.shape[0], -1))).reshape(Phis.shape) - n = ref_el.compute_scaled_normal(entity) / Jdet phis = n[None, :, None] * Phis elif trace == "tangential": @@ -281,7 +264,7 @@ def __init__(self, ref_el, degree, variant=None): if __name__ == "__main__": import matplotlib.pyplot as plt dim = 3 - degree = 7 + degree = 5 ref_el = symmetric_simplex(dim) Q = create_quadrature(ref_el, 2 * degree) Qpts, Qwts = Q.get_points(), Q.get_weights() @@ -292,7 +275,6 @@ def __init__(self, ref_el, degree, variant=None): # variant = None # d, fe = curl, N2Curl(ref_el, degree, variant) d, fe = div, N2Div(ref_el, degree, variant) - phi = fe.tabulate(1, Qpts) dphi = d(phi) stiff = galerkin(1, dphi, dphi) diff --git a/FIAT/raviart_thomas.py b/FIAT/raviart_thomas.py index c66c4ff46..38b6dc051 100644 --- a/FIAT/raviart_thomas.py +++ b/FIAT/raviart_thomas.py @@ -77,6 +77,7 @@ def __init__(self, ref_el, degree, variant, interpolant_deg): Q_ref = create_quadrature(facet, interpolant_deg + degree - 1) Pq = polynomial_set.ONPolynomialSet(facet, degree - 1) Pq_at_qpts = Pq.tabulate(Q_ref.get_points())[(0,)*(sd - 1)] + for f in top[sd - 1]: cur = len(nodes) Q = FacetQuadratureRule(ref_el, sd-1, f, Q_ref) From b2a9ebbced9646a54a2629cadbff045e3f3fb0f7 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Sun, 31 Dec 2023 09:08:59 -0600 Subject: [PATCH 51/93] Add CG FDM element --- FIAT/demkowicz.py | 235 +++++++++++++++++++++++++++------------------- 1 file changed, 138 insertions(+), 97 deletions(-) diff --git a/FIAT/demkowicz.py b/FIAT/demkowicz.py index 931ebb058..6de776ed0 100644 --- a/FIAT/demkowicz.py +++ b/FIAT/demkowicz.py @@ -16,7 +16,10 @@ def as_table(P): - PT = numpy.transpose(P, (1, 0, 2)) + if len(P.shape) == 3: + PT = numpy.transpose(P, (1, 0, 2)) + else: + PT = P[None, :, :] P_table = dict(zip(polynomial_set.mis(len(PT), 1), PT)) return P_table @@ -37,8 +40,7 @@ def curl(table_u): indices = ((i, j) for i in reversed(range(d)) for j in reversed(range(i+1, d))) curl_u = numpy.empty((nbfs, ncomp, npts), grad_u[0].dtype) for k, (i, j) in enumerate(indices): - s = (-1)**k - curl_u[:, k, :] = s*grad_u[j][:, i, :] - s*grad_u[i][:, j, :] + curl_u[:, k, :] = ((-1)**k)*(grad_u[j][:, i, :] - grad_u[i][:, j, :]) return as_table(curl_u) @@ -47,9 +49,8 @@ def div(table_u): for alpha in table_u: if sum(alpha) == 1: grad_u[alpha.index(1)] = table_u[alpha] - nbfs, *_, npts = grad_u[0].shape - div_u = numpy.empty((nbfs, 1, npts), "d") - div_u[:, 0, :] = sum(grad_u[i][:, i, :] for i in range(len(grad_u))) + + div_u = sum(grad_u[i][:, i, :] for i in range(len(grad_u))) return as_table(div_u) @@ -60,20 +61,25 @@ def __init__(self, ref_el, degree, sobolev_space): entity_ids = {} top = ref_el.get_topology() sd = ref_el.get_spatial_dimension() - self.formdegree = 1 if sobolev_space == "HCurl" else sd - 1 + self.formdegree = {"H1": 0, "HCurl": 1, "HDiv": sd-1, "L2": sd}[sobolev_space] for dim in sorted(top): entity_ids[dim] = {} if dim < self.formdegree or degree < dim: for entity in top[dim]: entity_ids[dim][entity] = [] + elif dim == 0 and self.formdegree == 0: + for entity in sorted(top[dim]): + cur = len(nodes) + pts = ref_el.make_points(dim, entity, degree) + nodes.extend(functional.PointEvaluation(ref_el, pt) for pt in pts) + entity_ids[dim][entity] = list(range(cur, len(nodes))) else: - Q_ref, Phis = self._reference_duals(ref_el, dim, degree, sobolev_space) + Q_ref, Phis = self._reference_duals(dim, degree, sobolev_space) if dim == sd or self.formdegree == 0: trace = None else: trace = "tangential" if sobolev_space == "HCurl" else "normal" - for entity in sorted(top[dim]): cur = len(nodes) Q = FacetQuadratureRule(ref_el, dim, entity, Q_ref) @@ -94,9 +100,8 @@ def __init__(self, ref_el, degree, sobolev_space): super(DemkowiczDual, self).__init__(nodes, ref_el, entity_ids) - def _reference_duals(self, ref_el, dim, degree, sobolev_space): - facet = ref_el.construct_subelement(dim) - # facet = symmetric_simplex(dim) + def _reference_duals(self, dim, degree, sobolev_space): + facet = symmetric_simplex(dim) Q = create_quadrature(facet, 2 * degree) if dim == self.formdegree: P = polynomial_set.ONPolynomialSet(facet, degree, (1,)) @@ -104,17 +109,20 @@ def _reference_duals(self, ref_el, dim, degree, sobolev_space): return Q, duals Qpts, Qwts = Q.get_points(), Q.get_weights() - P = polynomial_set.ONPolynomialSet(facet, degree, (dim,)) - P_table = P.tabulate(Qpts, 1) - trial = as_table(P_table[(0,) * dim]) - fd = self.formdegree - rot = fd == 1 and sobolev_space == "HDiv" - dtrial = div(P_table) if sobolev_space == "HDiv" else curl(P_table) - - K0 = self._bubble_moments(facet, degree+1, fd-1, Qpts, Qwts, trial, rot=rot) - K1 = self._bubble_moments(facet, degree, fd, Qpts, Qwts, dtrial) - K = numpy.vstack((K0, K1)) + shp = () if fd == 0 else (dim,) + P = polynomial_set.ONPolynomialSet(facet, degree, shp) + P_table = P.tabulate(Qpts, 1) + if sobolev_space == "H1": + dtrial = grad(P_table) + K = self._bubble_moments(facet, degree, fd, Qpts, Qwts, dtrial) + else: + rot = fd == 1 and sobolev_space == "HDiv" + trial = as_table(P_table[(0,) * dim]) + dtrial = div(P_table) if sobolev_space == "HDiv" else curl(P_table) + K0 = self._bubble_moments(facet, degree+1, fd-1, Qpts, Qwts, trial, rot=rot) + K1 = self._bubble_moments(facet, degree, fd, Qpts, Qwts, dtrial) + K = numpy.vstack((K0, K1)) duals = P_table[(0,) * dim] shp = (-1, ) + duals.shape[1:] @@ -123,14 +131,14 @@ def _reference_duals(self, ref_el, dim, degree, sobolev_space): def _bubble_moments(self, facet, degree, formdegree, Qpts, Qwts, trial, rot=False): inner = lambda v, u: numpy.dot(numpy.multiply(v, Qwts), u.T) - galerkin = lambda order, V, U: sum(inner(V[k], U[k]) for k in V if sum(k) == order) + galerkin = lambda V, U: sum(inner(V[k], U[k]) for k in V if sum(k) == 1) dim = facet.get_spatial_dimension() if formdegree >= dim - 1: Pkm1 = polynomial_set.ONPolynomialSet(facet, degree-1, (1,)) P0 = Pkm1.take(list(range(1, Pkm1.get_num_members()))) dtest = as_table(P0.tabulate(Qpts)[(0,) * dim]) - return galerkin(1, dtest, trial) + return galerkin(dtest, trial) element = (None, Nedelec, RaviartThomas)[formdegree] if element is None: @@ -145,12 +153,12 @@ def _bubble_moments(self, facet, degree, formdegree, Qpts, Qwts, trial, rot=Fals assert dim == 2 dtest[(1, 0)], dtest[(0, 1)] = dtest[(0, 1)], -dtest[(1, 0)] - A = galerkin(1, dtest, dtest) + A = galerkin(dtest, dtest) sig, S = scipy.linalg.eigh(A) - ind = abs(sig) > 1.e-10 - S = S[:, ind] - S = S * numpy.sqrt(1 / sig[None, ind]) - return numpy.dot(S.T, galerkin(1, dtest, trial)) + nullspace_dim = len([s for s in sig if abs(s) <= 1.e-10]) + S = S[:, nullspace_dim:] + S *= numpy.sqrt(1 / sig[None, nullspace_dim:]) + return numpy.dot(S.T, galerkin(dtest, trial)) class FDMDual(dual_set.DualSet): @@ -158,16 +166,12 @@ class FDMDual(dual_set.DualSet): def __init__(self, ref_el, degree, sobolev_space): nodes = [] entity_ids = {} - sd = ref_el.get_spatial_dimension() - if sobolev_space == "HCurl": - element = N2Curl - d = curl - elif sobolev_space == "HDiv": - element = N2Div - d = div - else: - raise ValueError("Invalid Sobolev space") + element, d = {"H1": (CG, grad), + "HCurl": (N2Curl, curl), + "HDiv": (N2Div, div), + }[sobolev_space] + sd = ref_el.get_spatial_dimension() Ref_el = symmetric_simplex(sd) fe = element(Ref_el, degree) ells = fe.dual_basis() @@ -176,67 +180,88 @@ def __init__(self, ref_el, degree, sobolev_space): Q = create_quadrature(Ref_el, 2 * degree) X, W = Q.get_points(), Q.get_weights() - inner = lambda v: numpy.dot(numpy.multiply(v, W), v.T) galerkin = lambda V, dofs: sum(inner(V[k][dofs]) for k in V if sum(k) == 1) + V = fe.tabulate(1, X) - V1 = d(V) V0 = as_table(V[(0,) * sd]) + V1 = d(V) for dim in sorted(entity_dofs): entity_ids[dim] = {} - for entity in entity_dofs[dim]: - entity_ids[dim][entity] = [] - + if dim == 0 and self.formdegree == 0: + for entity in sorted(entity_dofs[dim]): + cur = len(nodes) + pts = ref_el.make_points(dim, entity, degree) + nodes.extend(functional.PointEvaluation(ref_el, pt) for pt in pts) + entity_ids[dim][entity] = list(range(cur, len(nodes))) + continue dofs = entity_dofs[dim][0] - if len(dofs) > 0: - A = galerkin(V1, dofs) - B = galerkin(V0, dofs) - _, S = scipy.linalg.eigh(A, B) - Sinv = numpy.dot(S.T, B) - - Q_dof = ells[dofs[0]].Q - Q_ref = Q_dof.reference_rule() - if dim == sd or self.formdegree == 0: - trace = None - else: - trace = "tangential" if sobolev_space == "HCurl" else "normal" + if len(dofs) == 0: + for entity in sorted(entity_dofs[dim]): + entity_ids[dim][entity] = [] + continue + + A = galerkin(V1, dofs) + B = galerkin(V0, dofs) + nullspace = [i for i, a in enumerate(A.diagonal()) if a < 1E-10] + if len(nullspace) == 0: + A, B = B, A + _, S = scipy.linalg.eigh(A, B) + Sinv = numpy.dot(S.T, B) + + Q_dof = ells[dofs[0]].Q + Q_ref = Q_dof.reference_rule() + if dim == sd or self.formdegree == 0: + trace = None + else: + trace = "tangential" if sobolev_space == "HCurl" else "normal" + + # apply pushforward + Phis = numpy.array([ells[i].f_at_qpts for i in dofs]) + Jdet = Q_dof.jacobian_determinant() + if trace == "normal": + n = Ref_el.compute_scaled_normal(0) / Jdet + n *= 1 / numpy.dot(n, n) + Phis, = numpy.dot(n[None, :], Phis) + elif trace == "tangential": + J = Q_dof.jacobian() + Phis = numpy.dot(J.T, Phis).transpose((1, 0, 2)) + else: + Phis *= Jdet - # apply pushforward - Phis = numpy.array([ells[i].f_at_qpts for i in dofs]) - Jdet = Q_dof.jacobian_determinant() + Phis = numpy.dot(Sinv, Phis.reshape((Sinv.shape[0], -1))).reshape(Phis.shape) + + for entity in sorted(entity_dofs[dim]): + cur = len(nodes) + Q_facet = FacetQuadratureRule(ref_el, dim, entity, Q_ref) + # apply pullback + Jdet = Q_facet.jacobian_determinant() if trace == "normal": - n = Ref_el.compute_scaled_normal(0) / Jdet - n *= 1 / numpy.dot(n, n) - Phis = numpy.dot(n[None, :], Phis).transpose((1, 0, 2)) + n = ref_el.compute_scaled_normal(entity) / Jdet + phis = n[None, :, None] * Phis[:, None, :] elif trace == "tangential": - J = Q_dof.jacobian() - Phis = numpy.dot(J.T, Phis).transpose((1, 0, 2)) + J = Q_facet.jacobian() + piola_map = numpy.linalg.pinv(J.T) + phis = numpy.dot(piola_map, Phis).transpose((1, 0, 2)) else: - Phis *= Jdet + phis = (1 / Jdet) * Phis - Phis = numpy.dot(Sinv, Phis.reshape((Sinv.shape[0], -1))).reshape(Phis.shape) + nodes.extend(functional.FrobeniusIntegralMoment(ref_el, Q_facet, phi) + for phi in reversed(phis)) + entity_ids[dim][entity] = list(range(cur, len(nodes))) - for entity in sorted(entity_dofs[dim]): - cur = len(nodes) - Q_facet = FacetQuadratureRule(ref_el, dim, entity, Q_ref) - # apply pullback - Jdet = Q_facet.jacobian_determinant() - if trace == "normal": - n = ref_el.compute_scaled_normal(entity) / Jdet - phis = n[None, :, None] * Phis - elif trace == "tangential": - J = Q_facet.jacobian() - piola_map = numpy.linalg.pinv(J.T) - phis = numpy.dot(piola_map, Phis).transpose((1, 0, 2)) - else: - phis = (1 / Jdet) * Phis + super(FDMDual, self).__init__(nodes, ref_el, entity_ids) - nodes.extend(functional.FrobeniusIntegralMoment(ref_el, Q_facet, phi) - for phi in phis) - entity_ids[dim][entity] = list(range(cur, len(nodes))) - super(FDMDual, self).__init__(nodes, ref_el, entity_ids) +class CG(finite_element.CiarletElement): + + def __init__(self, ref_el, degree, variant=None): + make_dual = FDMDual if variant == "fdm" else DemkowiczDual + dual = make_dual(ref_el, degree, "H1") + poly_set = polynomial_set.ONPolynomialSet(ref_el, degree) + super(CG, self).__init__(poly_set, dual, degree, dual.formdegree, + mapping="affine") class N2Curl(finite_element.CiarletElement): @@ -264,7 +289,7 @@ def __init__(self, ref_el, degree, variant=None): if __name__ == "__main__": import matplotlib.pyplot as plt dim = 3 - degree = 5 + degree = 7 ref_el = symmetric_simplex(dim) Q = create_quadrature(ref_el, 2 * degree) Qpts, Qwts = Q.get_points(), Q.get_weights() @@ -273,17 +298,33 @@ def __init__(self, ref_el, degree, variant=None): variant = "fdm" # variant = None - # d, fe = curl, N2Curl(ref_el, degree, variant) - d, fe = div, N2Div(ref_el, degree, variant) - phi = fe.tabulate(1, Qpts) - dphi = d(phi) - stiff = galerkin(1, dphi, dphi) - - phi_table = as_table(phi[(0,) * dim]) - mass = galerkin(1, phi_table, phi_table) - - A = numpy.hstack([stiff, mass]) - A[abs(A) < 1E-10] = 0.0 - plt.spy(A, markersize=0) - plt.pcolor(numpy.log(abs(A))) + spaces = ("H1", "HCurl", "HDiv") + + space_dict = {"H1": (CG, grad), + "HCurl": (N2Curl, curl), + "HDiv": (N2Div, div), + } + + fig, axes = plt.subplots(ncols=len(spaces), nrows=2, figsize=(6*len(spaces), 12)) + axes = axes.T.flat + for space in spaces: + element, d = space_dict[space] + fe = element(ref_el, degree, variant) + phi = fe.tabulate(1, Qpts) + dphi = d(phi) + stiff = galerkin(1, dphi, dphi) + + phi_table = as_table(phi[(0,) * dim]) + mass = galerkin(1, phi_table, phi_table) + + mats = (stiff, mass) + title = f"{type(fe).__name__}({degree})" + names = (f"{title} stiff", f"{title} mass") + for name, A in zip(names, mats): + A[abs(A) < 1E-10] = 0.0 + nnz = numpy.count_nonzero(A) + ax = next(axes) + ax.spy(A, markersize=0) + ax.pcolor(numpy.log(abs(A))) + ax.set_title(f"{name} nnz {nnz}") plt.show() From 94a3a1a0352b6c828e2a9c9fb17d454cbd386d23 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Sun, 31 Dec 2023 10:51:50 -0600 Subject: [PATCH 52/93] Fix vector-valued bubbles --- FIAT/polynomial_set.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/FIAT/polynomial_set.py b/FIAT/polynomial_set.py index 376f328f2..8997aad5a 100644 --- a/FIAT/polynomial_set.py +++ b/FIAT/polynomial_set.py @@ -261,5 +261,10 @@ def make_bubbles(ref_el, degree, shape=()): for alpha in mis(dim, p): if alpha[0] > 1 and min(alpha[1:]) > 0: indices.append(idx(*alpha)) + + if shape != (): + ncomp = numpy.prod(shape) + dimPk = poly_set.get_num_members() // ncomp + indices = list((numpy.array(indices)[:, None] + dimPk * numpy.arange(ncomp)[None, :]).flat) poly_set = poly_set.take(indices) return poly_set From 757801ec387e30b66340a2443ed59718312f69d1 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Sun, 31 Dec 2023 12:33:33 -0600 Subject: [PATCH 53/93] Plumbing for CG, N2curl, N2Div variants --- FIAT/brezzi_douglas_marini.py | 15 +++-- FIAT/demkowicz.py | 103 ++++++++++++---------------------- FIAT/fdm_element.py | 66 +--------------------- FIAT/hierarchical.py | 64 ++++----------------- FIAT/nedelec_second_kind.py | 36 +++++------- FIAT/raviart_thomas.py | 1 - 6 files changed, 73 insertions(+), 212 deletions(-) diff --git a/FIAT/brezzi_douglas_marini.py b/FIAT/brezzi_douglas_marini.py index 2c9aeb892..bbee8e54d 100644 --- a/FIAT/brezzi_douglas_marini.py +++ b/FIAT/brezzi_douglas_marini.py @@ -6,7 +6,7 @@ # SPDX-License-Identifier: LGPL-3.0-or-later from FIAT import (finite_element, functional, dual_set, - polynomial_set, nedelec) + polynomial_set, nedelec, demkowicz) from FIAT.check_format_variant import check_format_variant from FIAT.quadrature_schemes import create_quadrature from FIAT.quadrature import FacetQuadratureRule @@ -89,15 +89,18 @@ class BrezziDouglasMarini(finite_element.CiarletElement): """ def __init__(self, ref_el, degree, variant=None): - - variant, interpolant_deg = check_format_variant(variant, degree) - if degree < 1: - raise Exception("BDM_k elements only valid for k >= 1") + raise ValueError(f"{type(self).__name__} elements only valid for k >= 1") sd = ref_el.get_spatial_dimension() poly_set = polynomial_set.ONPolynomialSet(ref_el, degree, (sd, )) - dual = BDMDualSet(ref_el, degree, variant, interpolant_deg) + if variant == "demkowicz": + dual = demkowicz.DemkowiczDual(ref_el, degree, "HDiv") + elif variant == "fdm": + dual = demkowicz.FDMDual(ref_el, degree, "HDiv", type(self)) + else: + variant, interpolant_deg = check_format_variant(variant, degree) + dual = BDMDualSet(ref_el, degree, variant, interpolant_deg) formdegree = sd - 1 # (n-1)-form super(BrezziDouglasMarini, self).__init__(poly_set, dual, degree, formdegree, mapping="contravariant piola") diff --git a/FIAT/demkowicz.py b/FIAT/demkowicz.py index 6de776ed0..b2be4d4a1 100644 --- a/FIAT/demkowicz.py +++ b/FIAT/demkowicz.py @@ -4,8 +4,7 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from FIAT import (polynomial_set, dual_set, - finite_element, functional) +from FIAT import polynomial_set, dual_set, functional import numpy import scipy from FIAT.quadrature_schemes import create_quadrature @@ -61,22 +60,22 @@ def __init__(self, ref_el, degree, sobolev_space): entity_ids = {} top = ref_el.get_topology() sd = ref_el.get_spatial_dimension() - self.formdegree = {"H1": 0, "HCurl": 1, "HDiv": sd-1, "L2": sd}[sobolev_space] + formdegree = {"H1": 0, "HCurl": 1, "HDiv": sd-1, "L2": sd}[sobolev_space] for dim in sorted(top): entity_ids[dim] = {} - if dim < self.formdegree or degree < dim: + if dim < formdegree or degree <= dim - formdegree: for entity in top[dim]: entity_ids[dim][entity] = [] - elif dim == 0 and self.formdegree == 0: + elif dim == 0 and formdegree == 0: for entity in sorted(top[dim]): cur = len(nodes) pts = ref_el.make_points(dim, entity, degree) nodes.extend(functional.PointEvaluation(ref_el, pt) for pt in pts) entity_ids[dim][entity] = list(range(cur, len(nodes))) else: - Q_ref, Phis = self._reference_duals(dim, degree, sobolev_space) - if dim == sd or self.formdegree == 0: + Q_ref, Phis = self._reference_duals(dim, degree, formdegree, sobolev_space) + if dim == sd or formdegree == 0: trace = None else: trace = "tangential" if sobolev_space == "HCurl" else "normal" @@ -100,29 +99,28 @@ def __init__(self, ref_el, degree, sobolev_space): super(DemkowiczDual, self).__init__(nodes, ref_el, entity_ids) - def _reference_duals(self, dim, degree, sobolev_space): + def _reference_duals(self, dim, degree, formdegree, sobolev_space): facet = symmetric_simplex(dim) Q = create_quadrature(facet, 2 * degree) - if dim == self.formdegree: + if dim == formdegree: P = polynomial_set.ONPolynomialSet(facet, degree, (1,)) duals = P.tabulate(Q.get_points())[(0,) * dim] return Q, duals Qpts, Qwts = Q.get_points(), Q.get_weights() - fd = self.formdegree - shp = () if fd == 0 else (dim,) + shp = () if formdegree == 0 else (dim,) P = polynomial_set.ONPolynomialSet(facet, degree, shp) P_table = P.tabulate(Qpts, 1) if sobolev_space == "H1": dtrial = grad(P_table) - K = self._bubble_moments(facet, degree, fd, Qpts, Qwts, dtrial) + K = self._bubble_moments(facet, degree, formdegree, Qpts, Qwts, dtrial) else: - rot = fd == 1 and sobolev_space == "HDiv" + rot = formdegree == 1 and sobolev_space == "HDiv" trial = as_table(P_table[(0,) * dim]) dtrial = div(P_table) if sobolev_space == "HDiv" else curl(P_table) - K0 = self._bubble_moments(facet, degree+1, fd-1, Qpts, Qwts, trial, rot=rot) - K1 = self._bubble_moments(facet, degree, fd, Qpts, Qwts, dtrial) - K = numpy.vstack((K0, K1)) + K1 = self._bubble_moments(facet, degree, formdegree, Qpts, Qwts, dtrial) + K2 = self._bubble_moments(facet, degree+1, formdegree-1, Qpts, Qwts, trial, rot=rot) + K = numpy.vstack((K1, K2)) duals = P_table[(0,) * dim] shp = (-1, ) + duals.shape[1:] @@ -163,20 +161,21 @@ def _bubble_moments(self, facet, degree, formdegree, Qpts, Qwts, trial, rot=Fals class FDMDual(dual_set.DualSet): - def __init__(self, ref_el, degree, sobolev_space): + def __init__(self, ref_el, degree, sobolev_space, element): nodes = [] entity_ids = {} - element, d = {"H1": (CG, grad), - "HCurl": (N2Curl, curl), - "HDiv": (N2Div, div), - }[sobolev_space] + d = {"H1": grad, + "HCurl": curl, + "HDiv": div, + }[sobolev_space] sd = ref_el.get_spatial_dimension() Ref_el = symmetric_simplex(sd) - fe = element(Ref_el, degree) + + fe = element(Ref_el, degree, variant="demkowicz") ells = fe.dual_basis() entity_dofs = fe.entity_dofs() - self.formdegree = fe.formdegree + formdegree = fe.formdegree Q = create_quadrature(Ref_el, 2 * degree) X, W = Q.get_points(), Q.get_weights() @@ -189,7 +188,7 @@ def __init__(self, ref_el, degree, sobolev_space): for dim in sorted(entity_dofs): entity_ids[dim] = {} - if dim == 0 and self.formdegree == 0: + if dim == 0 and formdegree == 0: for entity in sorted(entity_dofs[dim]): cur = len(nodes) pts = ref_el.make_points(dim, entity, degree) @@ -205,14 +204,14 @@ def __init__(self, ref_el, degree, sobolev_space): A = galerkin(V1, dofs) B = galerkin(V0, dofs) nullspace = [i for i, a in enumerate(A.diagonal()) if a < 1E-10] - if len(nullspace) == 0: - A, B = B, A - _, S = scipy.linalg.eigh(A, B) - Sinv = numpy.dot(S.T, B) + if len(nullspace) > 0: + A += B + _, S = scipy.linalg.eigh(B, A) + Sinv = numpy.dot(S.T, A) Q_dof = ells[dofs[0]].Q Q_ref = Q_dof.reference_rule() - if dim == sd or self.formdegree == 0: + if dim == sd or formdegree == 0: trace = None else: trace = "tangential" if sobolev_space == "HCurl" else "normal" @@ -248,56 +247,28 @@ def __init__(self, ref_el, degree, sobolev_space): phis = (1 / Jdet) * Phis nodes.extend(functional.FrobeniusIntegralMoment(ref_el, Q_facet, phi) - for phi in reversed(phis)) + for phi in phis) entity_ids[dim][entity] = list(range(cur, len(nodes))) super(FDMDual, self).__init__(nodes, ref_el, entity_ids) -class CG(finite_element.CiarletElement): - - def __init__(self, ref_el, degree, variant=None): - make_dual = FDMDual if variant == "fdm" else DemkowiczDual - dual = make_dual(ref_el, degree, "H1") - poly_set = polynomial_set.ONPolynomialSet(ref_el, degree) - super(CG, self).__init__(poly_set, dual, degree, dual.formdegree, - mapping="affine") - - -class N2Curl(finite_element.CiarletElement): - - def __init__(self, ref_el, degree, variant=None): - sd = ref_el.get_spatial_dimension() - make_dual = FDMDual if variant == "fdm" else DemkowiczDual - dual = make_dual(ref_el, degree, "HCurl") - poly_set = polynomial_set.ONPolynomialSet(ref_el, degree, (sd,)) - super(N2Curl, self).__init__(poly_set, dual, degree, dual.formdegree, - mapping="covariant piola") - - -class N2Div(finite_element.CiarletElement): - - def __init__(self, ref_el, degree, variant=None): - sd = ref_el.get_spatial_dimension() - make_dual = FDMDual if variant == "fdm" else DemkowiczDual - dual = make_dual(ref_el, degree, "HDiv") - poly_set = polynomial_set.ONPolynomialSet(ref_el, degree, (sd,)) - super(N2Div, self).__init__(poly_set, dual, degree, dual.formdegree, - mapping="contravariant piola") - - if __name__ == "__main__": import matplotlib.pyplot as plt + from FIAT import IntegratedLegendre as CG + from FIAT import NedelecSecondKind as N2Curl + from FIAT import BrezziDouglasMarini as N2Div + dim = 3 degree = 7 ref_el = symmetric_simplex(dim) Q = create_quadrature(ref_el, 2 * degree) Qpts, Qwts = Q.get_points(), Q.get_weights() inner = lambda v, u: numpy.dot(numpy.multiply(v, Qwts), u.T) - galerkin = lambda order, V, U: sum(inner(V[k], U[k]) for k in V if sum(k) == order) + galerkin = lambda V, U: sum(inner(V[k], U[k]) for k in V if sum(k) == 1) variant = "fdm" - # variant = None + # variant = "demkowicz" spaces = ("H1", "HCurl", "HDiv") space_dict = {"H1": (CG, grad), @@ -312,10 +283,10 @@ def __init__(self, ref_el, degree, variant=None): fe = element(ref_el, degree, variant) phi = fe.tabulate(1, Qpts) dphi = d(phi) - stiff = galerkin(1, dphi, dphi) + stiff = galerkin(dphi, dphi) phi_table = as_table(phi[(0,) * dim]) - mass = galerkin(1, phi_table, phi_table) + mass = galerkin(phi_table, phi_table) mats = (stiff, mass) title = f"{type(fe).__name__}({degree})" diff --git a/FIAT/fdm_element.py b/FIAT/fdm_element.py index 1f7bd8f72..90a88c3e9 100644 --- a/FIAT/fdm_element.py +++ b/FIAT/fdm_element.py @@ -9,13 +9,12 @@ import abc import numpy -from FIAT import dual_set, finite_element, functional, polynomial_set, quadrature +from FIAT import dual_set, finite_element, functional, quadrature from FIAT.barycentric_interpolation import LagrangePolynomialSet from FIAT.hierarchical import IntegratedLegendre from FIAT.orientation_utils import make_entity_permutations_simplex from FIAT.P0 import P0Dual -from FIAT.quadrature_schemes import create_quadrature -from FIAT.reference_element import LINE, symmetric_simplex +from FIAT.reference_element import LINE def sym_eig(A, B): @@ -144,61 +143,6 @@ def __init__(self, ref_el, degree, bc_order=1, formdegree=0, orthogonalize=False super(FDMDual, self).__init__(nodes, ref_el, entity_ids, entity_permutations) -class SimplexFDMDualSet(dual_set.DualSet): - - def __init__(self, ref_el, degree): - sd = ref_el.get_spatial_dimension() - S = symmetric_simplex(sd) - CG = IntegratedLegendre(S, degree, variant="demkowicz") - - Q = create_quadrature(S, 2 * degree) - X, W = Q.get_points(), Q.get_weights() - - V = CG.tabulate(1, X) - inner = lambda v: numpy.dot(numpy.multiply(v, W), v.T) - galerkin = lambda order, dofs: sum(inner(V[k][dofs]) for k in V if sum(k) == order) - - nodes = [] - entity_ids = {} - - CG_nodes = CG.dual_basis() - entity_dofs = CG.entity_dofs() - for dim in sorted(entity_dofs): - entity_ids[dim] = {} - dofs = entity_dofs[dim][0] - if len(dofs) == 0 or dim == 0: - for entity in sorted(entity_dofs[dim]): - cur = len(nodes) - points = ref_el.make_points(dim, entity, degree) - nodes.extend(functional.PointEvaluation(ref_el, pt) for pt in points) - entity_ids[dim][entity] = list(range(cur, len(nodes))) - continue - - B = galerkin(0, dofs) - A = galerkin(1, dofs) - _, S = sym_eig(B, A) - Sinv = numpy.dot(S.T, A) - fs_at_qpts = numpy.array([CG_nodes[i].f_at_qpts for i in dofs]) - phis = numpy.dot(Sinv, fs_at_qpts) - - Q_dof = CG_nodes[dofs[0]].Q - Q_ref = Q_dof.reference_rule() - phis *= Q_dof.jacobian_determinant() - for entity in sorted(entity_dofs[dim]): - cur = len(nodes) - Q_facet = quadrature.FacetQuadratureRule(ref_el, dim, entity, Q_ref) - - # phis must transform like a d-form to undo the measure transformation - scale = 1 / Q_facet.jacobian_determinant() - Jphis = scale * phis - - nodes.extend(functional.IntegralMoment(ref_el, Q_facet, phi) for phi in Jphis) - entity_ids[dim][entity] = list(range(cur, len(nodes))) - - entity_permutations = CG.entity_permutations() - super(SimplexFDMDualSet, self).__init__(nodes, ref_el, entity_ids, entity_permutations) - - class FDMFiniteElement(finite_element.CiarletElement): """1D element that diagonalizes bilinear forms with BCs.""" @@ -227,11 +171,7 @@ def __init__(self, ref_el, degree): lr = quadrature.GaussLegendreQuadratureLineRule(ref_el, degree+1) poly_set = LagrangePolynomialSet(ref_el, lr.get_points()) else: - assert self._formdegree == 0 - assert self._bc_order == 1 - dual = SimplexFDMDualSet(ref_el, degree) - poly_set = polynomial_set.ONPolynomialSet(ref_el, degree, variant="integral") - + raise ValueError(f"{type(self).__name__} is only defined on the interval") super(FDMFiniteElement, self).__init__(poly_set, dual, degree, self._formdegree) diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index 7db3978ff..ef3950f41 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -9,11 +9,10 @@ import numpy import scipy -from FIAT import finite_element, dual_set, functional, Lagrange +from FIAT import finite_element, dual_set, functional, demkowicz, Lagrange from FIAT.reference_element import (POINT, LINE, TRIANGLE, TETRAHEDRON, make_lattice, symmetric_simplex) from FIAT.orientation_utils import make_entity_permutations_simplex -from FIAT.barycentric_interpolation import make_dmat from FIAT.quadrature import QuadratureRule, FacetQuadratureRule from FIAT.quadrature_schemes import create_quadrature from FIAT.polynomial_set import ONPolynomialSet, make_bubbles @@ -63,9 +62,7 @@ def __init__(self, ref_el, degree, variant=None): duals = { "beuchler": self._beuchler_integral_duals, "beuchler_point": self._beuchler_point_duals, - "demkowicz": self._demkowicz_integral_duals, "demkowicz_point": self._demkowicz_point_duals, - "orthonormal": self._orthonormal_duals, }[variant] nodes = [] @@ -153,55 +150,6 @@ def _demkowicz_point_duals(self, ref_el, degree, variant="gll"): Q = QuadratureRule(ref_el, points, weights) return Q, phis - def _demkowicz_integral_duals(self, ref_el, degree): - Q = create_quadrature(ref_el, 2 * degree) - qpts, qwts = Q.get_points(), Q.get_weights() - moments = lambda v: numpy.dot(numpy.multiply(v, qwts), v.T) - - dim = ref_el.get_spatial_dimension() - if dim == 1: - # Assemble a stiffness matrix in the Lagrange basis - dmat, _ = make_dmat(qpts.flatten()) - K = moments(dmat) - else: - # Get ON basis - P = ONPolynomialSet(ref_el, degree) - P_table = P.tabulate(qpts, 1) - # Assemble a stiffness matrix in the ON basis - K = sum(moments(P_table[alpha]) for alpha in P_table if sum(alpha) == 1) - # Change of basis to Lagrange polynomials at the quadrature nodes - V = numpy.multiply(P_table[(0, ) * dim], qwts) - K = numpy.dot(numpy.dot(V.T, K), V) - - B = make_bubbles(ref_el, degree) - phis = B.tabulate(qpts)[(0,) * dim] - phis = numpy.multiply(numpy.dot(phis, K), 1/qwts) - return Q, phis - - def _orthonormal_duals(self, ref_el, degree): - dim = ref_el.get_spatial_dimension() - - Q = create_quadrature(ref_el, 2 * degree) - qpts, qwts = Q.get_points(), Q.get_weights() - inner = lambda v, u: numpy.dot(numpy.multiply(v, qwts), u.T) - galerkin = lambda order, V, U: sum(inner(V[k], U[k]) for k in V if sum(k) == order) - - B = make_bubbles(ref_el, degree) - B_table = B.tabulate(qpts, 1) - - P = ONPolynomialSet(ref_el, degree) - P_table = P.tabulate(qpts, 1) - - KBP = galerkin(1, B_table, P_table) - phis = P_table[(0,) * dim] - phis = numpy.dot(KBP, phis) - - if len(phis) > 0: - KBB = galerkin(1, B_table, B_table) - V = numpy.linalg.cholesky(KBB) - phis = numpy.linalg.solve(V, phis) - return Q, phis - class IntegratedLegendre(finite_element.CiarletElement): """Simplicial continuous element with integrated Legendre polynomials.""" @@ -209,7 +157,15 @@ class IntegratedLegendre(finite_element.CiarletElement): def __init__(self, ref_el, degree, variant=None): if ref_el.shape not in {POINT, LINE, TRIANGLE, TETRAHEDRON}: raise ValueError("%s is only defined on simplices." % type(self)) + if degree < 1: + raise ValueError(f"{type(self).__name__} elements only valid for k >= 1") + poly_set = ONPolynomialSet(ref_el, degree, variant="integral") - dual = IntegratedLegendreDual(ref_el, degree, variant=variant) + if variant == "demkowicz": + dual = demkowicz.DemkowiczDual(ref_el, degree, "H1") + elif variant == "fdm": + dual = demkowicz.FDMDual(ref_el, degree, "H1", type(self)) + else: + dual = IntegratedLegendreDual(ref_el, degree, variant=variant) formdegree = 0 # 0-form super(IntegratedLegendre, self).__init__(poly_set, dual, degree, formdegree) diff --git a/FIAT/nedelec_second_kind.py b/FIAT/nedelec_second_kind.py index 6e5269721..561da64fb 100644 --- a/FIAT/nedelec_second_kind.py +++ b/FIAT/nedelec_second_kind.py @@ -16,6 +16,7 @@ from FIAT.quadrature import FacetQuadratureRule from FIAT.quadrature_schemes import create_quadrature from FIAT.check_format_variant import check_format_variant +from FIAT import demkowicz class NedelecSecondKindDual(DualSet): @@ -191,27 +192,18 @@ class NedelecSecondKind(CiarletElement): interpolation. """ - def __init__(self, cell, degree, variant=None): + def __init__(self, ref_el, degree, variant=None): + if degree < 1: + raise ValueError(f"{type(self).__name__} elements only valid for k >= 1") - variant, interpolant_deg = check_format_variant(variant, degree) - - # Check degree - assert degree >= 1, "Second kind Nedelecs start at 1!" - - # Get dimension - d = cell.get_spatial_dimension() - - # Construct polynomial basis for d-vector fields - Ps = ONPolynomialSet(cell, degree, (d, )) - - # Construct dual space - Ls = NedelecSecondKindDual(cell, degree, variant, interpolant_deg) - - # Set form degree + sd = ref_el.get_spatial_dimension() + poly_set = ONPolynomialSet(ref_el, degree, (sd, )) + if variant == "demkowicz": + dual = demkowicz.DemkowiczDual(ref_el, degree, "HCurl") + elif variant == "fdm": + dual = demkowicz.FDMDual(ref_el, degree, "HCurl", type(self)) + else: + variant, interpolant_deg = check_format_variant(variant, degree) + dual = NedelecSecondKindDual(ref_el, degree, variant, interpolant_deg) formdegree = 1 # 1-form - - # Set mapping - mapping = "covariant piola" - - # Call init of super-class - super(NedelecSecondKind, self).__init__(Ps, Ls, degree, formdegree, mapping=mapping) + super(NedelecSecondKind, self).__init__(poly_set, dual, degree, formdegree, mapping="covariant piola") diff --git a/FIAT/raviart_thomas.py b/FIAT/raviart_thomas.py index 38b6dc051..c66c4ff46 100644 --- a/FIAT/raviart_thomas.py +++ b/FIAT/raviart_thomas.py @@ -77,7 +77,6 @@ def __init__(self, ref_el, degree, variant, interpolant_deg): Q_ref = create_quadrature(facet, interpolant_deg + degree - 1) Pq = polynomial_set.ONPolynomialSet(facet, degree - 1) Pq_at_qpts = Pq.tabulate(Q_ref.get_points())[(0,)*(sd - 1)] - for f in top[sd - 1]: cur = len(nodes) Q = FacetQuadratureRule(ref_el, sd-1, f, Q_ref) From f4102ac0c2dbb8918a62fe02241c375daf7ed245 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Mon, 1 Jan 2024 21:43:53 -0600 Subject: [PATCH 54/93] Fix dual mapping --- FIAT/demkowicz.py | 268 ++++++++++++++++++++----------------------- FIAT/hierarchical.py | 4 +- 2 files changed, 126 insertions(+), 146 deletions(-) diff --git a/FIAT/demkowicz.py b/FIAT/demkowicz.py index b2be4d4a1..0521c59d4 100644 --- a/FIAT/demkowicz.py +++ b/FIAT/demkowicz.py @@ -4,56 +4,57 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from FIAT import polynomial_set, dual_set, functional import numpy import scipy -from FIAT.quadrature_schemes import create_quadrature + +from FIAT.dual_set import DualSet +from FIAT.functional import PointEvaluation, FrobeniusIntegralMoment +from FIAT.polynomial_set import ONPolynomialSet, make_bubbles from FIAT.quadrature import FacetQuadratureRule +from FIAT.quadrature_schemes import create_quadrature +from FIAT.reference_element import symmetric_simplex from FIAT.nedelec import Nedelec from FIAT.raviart_thomas import RaviartThomas -from FIAT.reference_element import symmetric_simplex - - -def as_table(P): - if len(P.shape) == 3: - PT = numpy.transpose(P, (1, 0, 2)) - else: - PT = P[None, :, :] - P_table = dict(zip(polynomial_set.mis(len(PT), 1), PT)) - return P_table def grad(table_u): - return table_u + grad_u = {alpha.index(1): table_u[alpha] for alpha in table_u if sum(alpha) == 1} + grad_u = [grad_u[k] for k in sorted(grad_u)] + return numpy.transpose(grad_u, (1, 0, 2)) def curl(table_u): - grad_u = [None for alpha in table_u if sum(alpha) == 1] - for alpha in table_u: - if sum(alpha) == 1: - grad_u[alpha.index(1)] = table_u[alpha] - - nbfs, *_, npts = grad_u[0].shape + grad_u = {alpha.index(1): table_u[alpha] for alpha in table_u if sum(alpha) == 1} d = len(grad_u) - ncomp = (d * (d - 1)) // 2 indices = ((i, j) for i in reversed(range(d)) for j in reversed(range(i+1, d))) - curl_u = numpy.empty((nbfs, ncomp, npts), grad_u[0].dtype) - for k, (i, j) in enumerate(indices): - curl_u[:, k, :] = ((-1)**k)*(grad_u[j][:, i, :] - grad_u[i][:, j, :]) - return as_table(curl_u) + curl_u = [((-1)**k) * (grad_u[j][:, i, :] - grad_u[i][:, j, :]) for k, (i, j) in enumerate(indices)] + return numpy.transpose(curl_u, (1, 0, 2)) def div(table_u): - grad_u = [None for alpha in table_u if sum(alpha) == 1] - for alpha in table_u: - if sum(alpha) == 1: - grad_u[alpha.index(1)] = table_u[alpha] - - div_u = sum(grad_u[i][:, i, :] for i in range(len(grad_u))) - return as_table(div_u) + grad_u = {alpha.index(1): table_u[alpha] for alpha in table_u if sum(alpha) == 1} + div_u = sum(grad_u[i][:, i, :] for i in grad_u) + return div_u + + +def map_duals(ref_el, dim, entity, mapping, Q_ref, Phis): + Q = FacetQuadratureRule(ref_el, dim, entity, Q_ref) + if mapping == "normal": + n = ref_el.compute_normal(entity) + phis = n[None, :, None] * Phis[:, None, :] + elif mapping == "covariant": + piola_map = numpy.linalg.pinv(Q.jacobian().T) + phis = numpy.dot(piola_map, Phis).transpose((1, 0, 2)) + elif mapping == "contravariant": + piola_map = Q.jacobian() / Q.jacobian_determinant() + phis = numpy.dot(piola_map, Phis).transpose((1, 0, 2)) + else: + Jdet = Q.jacobian_determinant() + phis = (1 / Jdet) * Phis + return Q, phis -class DemkowiczDual(dual_set.DualSet): +class DemkowiczDual(DualSet): def __init__(self, ref_el, degree, sobolev_space): nodes = [] @@ -61,6 +62,8 @@ def __init__(self, ref_el, degree, sobolev_space): top = ref_el.get_topology() sd = ref_el.get_spatial_dimension() formdegree = {"H1": 0, "HCurl": 1, "HDiv": sd-1, "L2": sd}[sobolev_space] + trace = {"HCurl": "contravariant", "HDiv": "normal"}.get(sobolev_space, None) + dual_mapping = {"HCurl": "contravariant", "HDiv": "covariant"}.get(sobolev_space, None) for dim in sorted(top): entity_ids[dim] = {} @@ -71,30 +74,15 @@ def __init__(self, ref_el, degree, sobolev_space): for entity in sorted(top[dim]): cur = len(nodes) pts = ref_el.make_points(dim, entity, degree) - nodes.extend(functional.PointEvaluation(ref_el, pt) for pt in pts) + nodes.extend(PointEvaluation(ref_el, pt) for pt in pts) entity_ids[dim][entity] = list(range(cur, len(nodes))) else: Q_ref, Phis = self._reference_duals(dim, degree, formdegree, sobolev_space) - if dim == sd or formdegree == 0: - trace = None - else: - trace = "tangential" if sobolev_space == "HCurl" else "normal" + mapping = dual_mapping if dim == sd else trace for entity in sorted(top[dim]): cur = len(nodes) - Q = FacetQuadratureRule(ref_el, dim, entity, Q_ref) - if trace == "normal": - Jdet = Q.jacobian_determinant() - n = ref_el.compute_scaled_normal(entity) / Jdet - phis = n[None, :, None] * Phis - elif trace == "tangential": - J = Q.jacobian() - piola_map = numpy.linalg.pinv(J.T) - phis = numpy.dot(piola_map, Phis).transpose((1, 0, 2)) - else: - Jdet = Q.jacobian_determinant() - phis = Phis / Jdet - nodes.extend(functional.FrobeniusIntegralMoment(ref_el, Q, phi) - for phi in phis) + Q, phis = map_duals(ref_el, dim, entity, mapping, Q_ref, Phis) + nodes.extend(FrobeniusIntegralMoment(ref_el, Q, phi) for phi in phis) entity_ids[dim][entity] = list(range(cur, len(nodes))) super(DemkowiczDual, self).__init__(nodes, ref_el, entity_ids) @@ -102,76 +90,78 @@ def __init__(self, ref_el, degree, sobolev_space): def _reference_duals(self, dim, degree, formdegree, sobolev_space): facet = symmetric_simplex(dim) Q = create_quadrature(facet, 2 * degree) - if dim == formdegree: - P = polynomial_set.ONPolynomialSet(facet, degree, (1,)) + if formdegree == dim: + shp = (dim,) if sobolev_space == "HCurl" else () + P = ONPolynomialSet(facet, degree, shp) duals = P.tabulate(Q.get_points())[(0,) * dim] return Q, duals Qpts, Qwts = Q.get_points(), Q.get_weights() shp = () if formdegree == 0 else (dim,) - P = polynomial_set.ONPolynomialSet(facet, degree, shp) - P_table = P.tabulate(Qpts, 1) - if sobolev_space == "H1": - dtrial = grad(P_table) - K = self._bubble_moments(facet, degree, formdegree, Qpts, Qwts, dtrial) - else: - rot = formdegree == 1 and sobolev_space == "HDiv" - trial = as_table(P_table[(0,) * dim]) - dtrial = div(P_table) if sobolev_space == "HDiv" else curl(P_table) - K1 = self._bubble_moments(facet, degree, formdegree, Qpts, Qwts, dtrial) - K2 = self._bubble_moments(facet, degree+1, formdegree-1, Qpts, Qwts, trial, rot=rot) - K = numpy.vstack((K1, K2)) - - duals = P_table[(0,) * dim] + P = ONPolynomialSet(facet, degree, shp) + P_at_qpts = P.tabulate(Qpts, 1) + + exterior_derivative = {"H1": grad, "HCurl": curl, "HDiv": div}[sobolev_space] + dtrial = exterior_derivative(P_at_qpts) + + K = self._bubble_derivative_moments(facet, degree, formdegree, Qpts, Qwts, dtrial) + if formdegree > 0: + trial = P_at_qpts[(0,) * dim] + if formdegree == 1 and sobolev_space == "HDiv": + rot = numpy.array([[0.0, 1.0], [-1.0, 0.0]], "d") + trial = numpy.dot(rot, trial).transpose((1, 0, 2)) + M = self._bubble_derivative_moments(facet, degree+1, formdegree-1, Qpts, Qwts, trial) + K = numpy.vstack((K, M)) + + duals = P_at_qpts[(0,) * dim] shp = (-1, ) + duals.shape[1:] duals = numpy.dot(K, duals.reshape((K.shape[1], -1))).reshape(shp) return Q, duals - def _bubble_moments(self, facet, degree, formdegree, Qpts, Qwts, trial, rot=False): - inner = lambda v, u: numpy.dot(numpy.multiply(v, Qwts), u.T) - galerkin = lambda V, U: sum(inner(V[k], U[k]) for k in V if sum(k) == 1) + def _bubble_derivative_moments(self, facet, degree, formdegree, Qpts, Qwts, trial): + """Integrate trial expressions against an orthonormal basis for + the exterior derivative of bubbles. + """ + inner = lambda v, u: numpy.tensordot(numpy.multiply(v, Qwts), u, axes=(range(1, v.ndim), range(1, u.ndim))) dim = facet.get_spatial_dimension() if formdegree >= dim - 1: - Pkm1 = polynomial_set.ONPolynomialSet(facet, degree-1, (1,)) + # We are at the end of the complex + # derivative of bubbles is P_k-1 minus constants + Pkm1 = ONPolynomialSet(facet, degree-1, trial.shape[1:-1]) P0 = Pkm1.take(list(range(1, Pkm1.get_num_members()))) - dtest = as_table(P0.tabulate(Qpts)[(0,) * dim]) - return galerkin(dtest, trial) + dtest = P0.tabulate(Qpts)[(0,) * dim] + return inner(dtest, trial) + # Get bubbles element = (None, Nedelec, RaviartThomas)[formdegree] if element is None: - B = polynomial_set.make_bubbles(facet, degree) + B = make_bubbles(facet, degree) else: fe = element(facet, degree) B = fe.get_nodal_basis().take(fe.entity_dofs()[dim][0]) - + # Tabulate the exterior derivate d = (grad, curl, div)[formdegree] dtest = d(B.tabulate(Qpts, 1)) - if rot: - assert dim == 2 - dtest[(1, 0)], dtest[(0, 1)] = dtest[(0, 1)], -dtest[(1, 0)] - - A = galerkin(dtest, dtest) + # Build an orthonormal basis, remove nullspace + A = inner(dtest, dtest) sig, S = scipy.linalg.eigh(A) nullspace_dim = len([s for s in sig if abs(s) <= 1.e-10]) S = S[:, nullspace_dim:] S *= numpy.sqrt(1 / sig[None, nullspace_dim:]) - return numpy.dot(S.T, galerkin(dtest, trial)) + + dtest = numpy.dot(S.T, dtest.reshape((dtest.shape[0], -1))).reshape((-1,) + dtest.shape[1:]) + return inner(dtest, trial) -class FDMDual(dual_set.DualSet): +class FDMDual(DualSet): def __init__(self, ref_el, degree, sobolev_space, element): nodes = [] entity_ids = {} - d = {"H1": grad, - "HCurl": curl, - "HDiv": div, - }[sobolev_space] - sd = ref_el.get_spatial_dimension() - Ref_el = symmetric_simplex(sd) + Ref_el = symmetric_simplex(sd) fe = element(Ref_el, degree, variant="demkowicz") ells = fe.dual_basis() entity_dofs = fe.entity_dofs() @@ -179,12 +169,14 @@ def __init__(self, ref_el, degree, sobolev_space, element): Q = create_quadrature(Ref_el, 2 * degree) X, W = Q.get_points(), Q.get_weights() - inner = lambda v: numpy.dot(numpy.multiply(v, W), v.T) - galerkin = lambda V, dofs: sum(inner(V[k][dofs]) for k in V if sum(k) == 1) + inner = lambda v: numpy.tensordot(numpy.multiply(v, W), v, axes=(range(1, v.ndim), range(1, v.ndim))) + dual_mapping = {"HCurl": "contravariant", "HDiv": "covariant"}.get(sobolev_space, None) + trace = {"HCurl": "contravariant", "HDiv": "normal"}.get(sobolev_space, None) + exterior_derivative = {"H1": grad, "HCurl": curl, "HDiv": div}[sobolev_space] - V = fe.tabulate(1, X) - V0 = as_table(V[(0,) * sd]) - V1 = d(V) + phi_at_qpts = fe.tabulate(1, X) + V0 = phi_at_qpts[(0,) * sd] + V1 = exterior_derivative(phi_at_qpts) for dim in sorted(entity_dofs): entity_ids[dim] = {} @@ -192,7 +184,7 @@ def __init__(self, ref_el, degree, sobolev_space, element): for entity in sorted(entity_dofs[dim]): cur = len(nodes) pts = ref_el.make_points(dim, entity, degree) - nodes.extend(functional.PointEvaluation(ref_el, pt) for pt in pts) + nodes.extend(PointEvaluation(ref_el, pt) for pt in pts) entity_ids[dim][entity] = list(range(cur, len(nodes))) continue dofs = entity_dofs[dim][0] @@ -201,53 +193,42 @@ def __init__(self, ref_el, degree, sobolev_space, element): entity_ids[dim][entity] = [] continue - A = galerkin(V1, dofs) - B = galerkin(V0, dofs) - nullspace = [i for i, a in enumerate(A.diagonal()) if a < 1E-10] - if len(nullspace) > 0: - A += B - _, S = scipy.linalg.eigh(B, A) - Sinv = numpy.dot(S.T, A) + B = inner(V0[dofs]) + if dim == sd: + _, S = scipy.linalg.eigh(B) + Sinv = S.T + else: + A = inner(V1[dofs]) + nullspace = [i for i, a in enumerate(A.diagonal()) if a < 1E-10] + if len(nullspace) > 0: + A += B + _, S = scipy.linalg.eigh(B, A) + Sinv = numpy.dot(S.T, A) + + phis = numpy.array([ells[i].f_at_qpts for i in dofs]) + phis = numpy.dot(Sinv, phis.reshape((Sinv.shape[0], -1))).reshape(phis.shape) Q_dof = ells[dofs[0]].Q Q_ref = Q_dof.reference_rule() - if dim == sd or formdegree == 0: - trace = None - else: - trace = "tangential" if sobolev_space == "HCurl" else "normal" - - # apply pushforward - Phis = numpy.array([ells[i].f_at_qpts for i in dofs]) - Jdet = Q_dof.jacobian_determinant() - if trace == "normal": - n = Ref_el.compute_scaled_normal(0) / Jdet - n *= 1 / numpy.dot(n, n) - Phis, = numpy.dot(n[None, :], Phis) - elif trace == "tangential": - J = Q_dof.jacobian() - Phis = numpy.dot(J.T, Phis).transpose((1, 0, 2)) + mapping = dual_mapping if dim == sd else trace + # map physical phis to reference values Phis + if mapping == "normal": + n = Ref_el.compute_normal(0) + Phis, = numpy.dot(n[None, :], phis) + elif mapping == "covariant": + piola_map = Q_dof.jacobian().T + Phis = numpy.dot(piola_map, phis).transpose((1, 0, 2)) + elif mapping == "contravariant": + piola_map = numpy.linalg.pinv(Q_dof.jacobian()) * Q_dof.jacobian_determinant() + Phis = numpy.dot(piola_map, phis).transpose((1, 0, 2)) else: - Phis *= Jdet - - Phis = numpy.dot(Sinv, Phis.reshape((Sinv.shape[0], -1))).reshape(Phis.shape) + Jdet = Q_dof.jacobian_determinant() + Phis = Jdet * phis for entity in sorted(entity_dofs[dim]): cur = len(nodes) - Q_facet = FacetQuadratureRule(ref_el, dim, entity, Q_ref) - # apply pullback - Jdet = Q_facet.jacobian_determinant() - if trace == "normal": - n = ref_el.compute_scaled_normal(entity) / Jdet - phis = n[None, :, None] * Phis[:, None, :] - elif trace == "tangential": - J = Q_facet.jacobian() - piola_map = numpy.linalg.pinv(J.T) - phis = numpy.dot(piola_map, Phis).transpose((1, 0, 2)) - else: - phis = (1 / Jdet) * Phis - - nodes.extend(functional.FrobeniusIntegralMoment(ref_el, Q_facet, phi) - for phi in phis) + Q_facet, phis = map_duals(ref_el, dim, entity, mapping, Q_ref, Phis) + nodes.extend(FrobeniusIntegralMoment(ref_el, Q_facet, phi) for phi in phis) entity_ids[dim][entity] = list(range(cur, len(nodes))) super(FDMDual, self).__init__(nodes, ref_el, entity_ids) @@ -264,35 +245,34 @@ def __init__(self, ref_el, degree, sobolev_space, element): ref_el = symmetric_simplex(dim) Q = create_quadrature(ref_el, 2 * degree) Qpts, Qwts = Q.get_points(), Q.get_weights() - inner = lambda v, u: numpy.dot(numpy.multiply(v, Qwts), u.T) - galerkin = lambda V, U: sum(inner(V[k], U[k]) for k in V if sum(k) == 1) + inner = lambda v, u: numpy.tensordot(numpy.multiply(v, Qwts), u, axes=(range(1, v.ndim), range(1, u.ndim))) variant = "fdm" # variant = "demkowicz" - spaces = ("H1", "HCurl", "HDiv") - + # variant = None space_dict = {"H1": (CG, grad), "HCurl": (N2Curl, curl), "HDiv": (N2Div, div), } + spaces = list(space_dict.keys()) fig, axes = plt.subplots(ncols=len(spaces), nrows=2, figsize=(6*len(spaces), 12)) axes = axes.T.flat for space in spaces: element, d = space_dict[space] fe = element(ref_el, degree, variant) - phi = fe.tabulate(1, Qpts) - dphi = d(phi) - stiff = galerkin(dphi, dphi) - - phi_table = as_table(phi[(0,) * dim]) - mass = galerkin(phi_table, phi_table) + phi_at_qpts = fe.tabulate(1, Qpts) + V0 = phi_at_qpts[(0,) * dim] + V1 = d(phi_at_qpts) + mass = inner(V0, V0) + stiff = inner(V1, V1) mats = (stiff, mass) title = f"{type(fe).__name__}({degree})" names = (f"{title} stiff", f"{title} mass") for name, A in zip(names, mats): A[abs(A) < 1E-10] = 0.0 + # print(A.diagonal()) nnz = numpy.count_nonzero(A) ax = next(axes) ax.spy(A, markersize=0) diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index ef3950f41..0e382859e 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -134,13 +134,13 @@ def _demkowicz_point_duals(self, ref_el, degree, variant="gll"): Qkm1 = create_quadrature(ref_el, 2 * (degree-1)) qpts, qwts = Qkm1.get_points(), Qkm1.get_weights() inner = lambda v, u: numpy.dot(numpy.multiply(v, qwts), u.T) - galerkin = lambda order, V, U: sum(inner(V[k], U[k]) for k in V if sum(k) == order) + galerkin = lambda V, U: sum(inner(V[k], U[k]) for k in V if sum(k) == 1) P = Lagrange(ref_el, degree, variant=variant) P_table = P.tabulate(1, qpts) B = make_bubbles(ref_el, degree) B_table = B.tabulate(qpts, 1) - phis = galerkin(1, B_table, P_table) + phis = galerkin(B_table, P_table) points = [] for node in P.dual_basis(): From e8f299326ee388d4cee932f733900f4eb3233272 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Tue, 2 Jan 2024 12:50:14 -0600 Subject: [PATCH 55/93] add a test --- FIAT/demkowicz.py | 34 ++++++++++---------- test/unit/test_demkowicz.py | 63 +++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 17 deletions(-) create mode 100644 test/unit/test_demkowicz.py diff --git a/FIAT/demkowicz.py b/FIAT/demkowicz.py index 0521c59d4..0db84d20b 100644 --- a/FIAT/demkowicz.py +++ b/FIAT/demkowicz.py @@ -37,6 +37,10 @@ def div(table_u): return div_u +def inner(v, u, Qwts): + return numpy.tensordot(numpy.multiply(v, Qwts), u, axes=(range(1, v.ndim), range(1, u.ndim))) + + def map_duals(ref_el, dim, entity, mapping, Q_ref, Phis): Q = FacetQuadratureRule(ref_el, dim, entity, Q_ref) if mapping == "normal": @@ -114,15 +118,13 @@ def _reference_duals(self, dim, degree, formdegree, sobolev_space): K = numpy.vstack((K, M)) duals = P_at_qpts[(0,) * dim] - shp = (-1, ) + duals.shape[1:] - duals = numpy.dot(K, duals.reshape((K.shape[1], -1))).reshape(shp) + duals = numpy.dot(K, duals.reshape((K.shape[1], -1))).reshape((-1,) + duals.shape[1:]) return Q, duals def _bubble_derivative_moments(self, facet, degree, formdegree, Qpts, Qwts, trial): """Integrate trial expressions against an orthonormal basis for the exterior derivative of bubbles. """ - inner = lambda v, u: numpy.tensordot(numpy.multiply(v, Qwts), u, axes=(range(1, v.ndim), range(1, u.ndim))) dim = facet.get_spatial_dimension() if formdegree >= dim - 1: @@ -131,7 +133,7 @@ def _bubble_derivative_moments(self, facet, degree, formdegree, Qpts, Qwts, tria Pkm1 = ONPolynomialSet(facet, degree-1, trial.shape[1:-1]) P0 = Pkm1.take(list(range(1, Pkm1.get_num_members()))) dtest = P0.tabulate(Qpts)[(0,) * dim] - return inner(dtest, trial) + return inner(dtest, trial, Qwts) # Get bubbles element = (None, Nedelec, RaviartThomas)[formdegree] @@ -144,14 +146,14 @@ def _bubble_derivative_moments(self, facet, degree, formdegree, Qpts, Qwts, tria d = (grad, curl, div)[formdegree] dtest = d(B.tabulate(Qpts, 1)) # Build an orthonormal basis, remove nullspace - A = inner(dtest, dtest) + A = inner(dtest, dtest, Qwts) sig, S = scipy.linalg.eigh(A) nullspace_dim = len([s for s in sig if abs(s) <= 1.e-10]) S = S[:, nullspace_dim:] S *= numpy.sqrt(1 / sig[None, nullspace_dim:]) - - dtest = numpy.dot(S.T, dtest.reshape((dtest.shape[0], -1))).reshape((-1,) + dtest.shape[1:]) - return inner(dtest, trial) + # Apply change of basis + dtest = numpy.dot(S.T, dtest.reshape((S.shape[0], -1))).reshape((-1,) + dtest.shape[1:]) + return inner(dtest, trial, Qwts) class FDMDual(DualSet): @@ -169,10 +171,9 @@ def __init__(self, ref_el, degree, sobolev_space, element): Q = create_quadrature(Ref_el, 2 * degree) X, W = Q.get_points(), Q.get_weights() - inner = lambda v: numpy.tensordot(numpy.multiply(v, W), v, axes=(range(1, v.ndim), range(1, v.ndim))) - dual_mapping = {"HCurl": "contravariant", "HDiv": "covariant"}.get(sobolev_space, None) - trace = {"HCurl": "contravariant", "HDiv": "normal"}.get(sobolev_space, None) exterior_derivative = {"H1": grad, "HCurl": curl, "HDiv": div}[sobolev_space] + trace = {"HCurl": "contravariant", "HDiv": "normal"}.get(sobolev_space, None) + dual_mapping = {"HCurl": "contravariant", "HDiv": "covariant"}.get(sobolev_space, None) phi_at_qpts = fe.tabulate(1, X) V0 = phi_at_qpts[(0,) * sd] @@ -193,12 +194,12 @@ def __init__(self, ref_el, degree, sobolev_space, element): entity_ids[dim][entity] = [] continue - B = inner(V0[dofs]) + B = inner(V0[dofs], V0[dofs], W) if dim == sd: _, S = scipy.linalg.eigh(B) Sinv = S.T else: - A = inner(V1[dofs]) + A = inner(V1[dofs], V1[dofs], W) nullspace = [i for i, a in enumerate(A.diagonal()) if a < 1E-10] if len(nullspace) > 0: A += B @@ -245,10 +246,9 @@ def __init__(self, ref_el, degree, sobolev_space, element): ref_el = symmetric_simplex(dim) Q = create_quadrature(ref_el, 2 * degree) Qpts, Qwts = Q.get_points(), Q.get_weights() - inner = lambda v, u: numpy.tensordot(numpy.multiply(v, Qwts), u, axes=(range(1, v.ndim), range(1, u.ndim))) variant = "fdm" - # variant = "demkowicz" + variant = "demkowicz" # variant = None space_dict = {"H1": (CG, grad), "HCurl": (N2Curl, curl), @@ -264,8 +264,8 @@ def __init__(self, ref_el, degree, sobolev_space, element): phi_at_qpts = fe.tabulate(1, Qpts) V0 = phi_at_qpts[(0,) * dim] V1 = d(phi_at_qpts) - mass = inner(V0, V0) - stiff = inner(V1, V1) + mass = inner(V0, V0, Qwts) + stiff = inner(V1, V1, Qwts) mats = (stiff, mass) title = f"{type(fe).__name__}({degree})" diff --git a/test/unit/test_demkowicz.py b/test/unit/test_demkowicz.py new file mode 100644 index 000000000..700c12897 --- /dev/null +++ b/test/unit/test_demkowicz.py @@ -0,0 +1,63 @@ +# Copyright (C) 2016 Imperial College London and others +# +# This file is part of FIAT. +# +# FIAT is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# FIAT is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with FIAT. If not, see . +# +# Authors: +# +# Pablo Brubeck + +import pytest +import numpy +from FIAT.hierarchical import IntegratedLegendre as CG +from FIAT.nedelec_second_kind import NedelecSecondKind as N2Curl +from FIAT.brezzi_douglas_marini import BrezziDouglasMarini as N2Div + + +@pytest.mark.parametrize("family, dim, degree, variant", + [(f, d, p, v) + for f in (CG, N2Curl, N2Div) + for v in ("demkowicz", "fdm") + for d in (2, 3) + for p in range(1, 7)]) +def test_galerkin_symmetry(dim, family, degree, variant): + from FIAT.quadrature_schemes import create_quadrature + from FIAT.reference_element import symmetric_simplex + from FIAT.demkowicz import grad, curl, div, inner + + s = symmetric_simplex(dim) + fe = family(s, degree, variant=variant) + exterior_derivative = {CG: grad, N2Curl: curl, N2Div: div}[family] + + Q = create_quadrature(s, 2 * degree) + Qpts, Qwts = Q.get_points(), Q.get_weights() + galerkin = lambda V: inner(V, V, Qwts) + + tab = fe.tabulate(1, Qpts) + phi = tab[(0,) * dim] + dphi = exterior_derivative(tab) + + entity_dofs = fe.entity_dofs() + for dim in sorted(entity_dofs): + for V in (phi, dphi): + A = [galerkin(V[entity_dofs[dim][entity]]) for entity in sorted(entity_dofs[dim])] + Aref = numpy.diag(A[0].diagonal()) if variant == "fdm" else A[0] + for A1 in A: + assert numpy.allclose(Aref, A1, rtol=1E-14) + + +if __name__ == '__main__': + import os + pytest.main(os.path.abspath(__file__)) From 129d6e6bc0d406d58bd635131552cbbc97212247 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Tue, 2 Jan 2024 17:28:52 -0600 Subject: [PATCH 56/93] clean up --- FIAT/demkowicz.py | 20 +++++++------------- FIAT/nedelec_second_kind.py | 2 +- test/unit/test_demkowicz.py | 29 +++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/FIAT/demkowicz.py b/FIAT/demkowicz.py index 0db84d20b..e87eef964 100644 --- a/FIAT/demkowicz.py +++ b/FIAT/demkowicz.py @@ -100,14 +100,12 @@ def _reference_duals(self, dim, degree, formdegree, sobolev_space): duals = P.tabulate(Q.get_points())[(0,) * dim] return Q, duals + exterior_derivative = {"H1": grad, "HCurl": curl, "HDiv": div}[sobolev_space] Qpts, Qwts = Q.get_points(), Q.get_weights() shp = () if formdegree == 0 else (dim,) P = ONPolynomialSet(facet, degree, shp) P_at_qpts = P.tabulate(Qpts, 1) - - exterior_derivative = {"H1": grad, "HCurl": curl, "HDiv": div}[sobolev_space] dtrial = exterior_derivative(P_at_qpts) - K = self._bubble_derivative_moments(facet, degree, formdegree, Qpts, Qwts, dtrial) if formdegree > 0: trial = P_at_qpts[(0,) * dim] @@ -117,15 +115,13 @@ def _reference_duals(self, dim, degree, formdegree, sobolev_space): M = self._bubble_derivative_moments(facet, degree+1, formdegree-1, Qpts, Qwts, trial) K = numpy.vstack((K, M)) - duals = P_at_qpts[(0,) * dim] - duals = numpy.dot(K, duals.reshape((K.shape[1], -1))).reshape((-1,) + duals.shape[1:]) + duals = numpy.tensordot(K, P_at_qpts[(0,) * dim], axes=(1, 0)) return Q, duals def _bubble_derivative_moments(self, facet, degree, formdegree, Qpts, Qwts, trial): """Integrate trial expressions against an orthonormal basis for the exterior derivative of bubbles. """ - dim = facet.get_spatial_dimension() if formdegree >= dim - 1: # We are at the end of the complex @@ -152,7 +148,7 @@ def _bubble_derivative_moments(self, facet, degree, formdegree, Qpts, Qwts, tria S = S[:, nullspace_dim:] S *= numpy.sqrt(1 / sig[None, nullspace_dim:]) # Apply change of basis - dtest = numpy.dot(S.T, dtest.reshape((S.shape[0], -1))).reshape((-1,) + dtest.shape[1:]) + dtest = numpy.tensordot(S.T, dtest, axes=(1, 0)) return inner(dtest, trial, Qwts) @@ -197,17 +193,15 @@ def __init__(self, ref_el, degree, sobolev_space, element): B = inner(V0[dofs], V0[dofs], W) if dim == sd: _, S = scipy.linalg.eigh(B) - Sinv = S.T else: A = inner(V1[dofs], V1[dofs], W) - nullspace = [i for i, a in enumerate(A.diagonal()) if a < 1E-10] - if len(nullspace) > 0: + if formdegree > 0: A += B _, S = scipy.linalg.eigh(B, A) - Sinv = numpy.dot(S.T, A) + S = numpy.dot(A, S) phis = numpy.array([ells[i].f_at_qpts for i in dofs]) - phis = numpy.dot(Sinv, phis.reshape((Sinv.shape[0], -1))).reshape(phis.shape) + phis = numpy.tensordot(S.T, phis, axes=(1, 0)) Q_dof = ells[dofs[0]].Q Q_ref = Q_dof.reference_rule() @@ -248,7 +242,7 @@ def __init__(self, ref_el, degree, sobolev_space, element): Qpts, Qwts = Q.get_points(), Q.get_weights() variant = "fdm" - variant = "demkowicz" + # variant = "demkowicz" # variant = None space_dict = {"H1": (CG, grad), "HCurl": (N2Curl, curl), diff --git a/FIAT/nedelec_second_kind.py b/FIAT/nedelec_second_kind.py index 561da64fb..5a2c39d29 100644 --- a/FIAT/nedelec_second_kind.py +++ b/FIAT/nedelec_second_kind.py @@ -197,7 +197,7 @@ def __init__(self, ref_el, degree, variant=None): raise ValueError(f"{type(self).__name__} elements only valid for k >= 1") sd = ref_el.get_spatial_dimension() - poly_set = ONPolynomialSet(ref_el, degree, (sd, )) + poly_set = ONPolynomialSet(ref_el, degree, (sd, ), variant="integral") if variant == "demkowicz": dual = demkowicz.DemkowiczDual(ref_el, degree, "HCurl") elif variant == "fdm": diff --git a/test/unit/test_demkowicz.py b/test/unit/test_demkowicz.py index 700c12897..39298ca87 100644 --- a/test/unit/test_demkowicz.py +++ b/test/unit/test_demkowicz.py @@ -58,6 +58,35 @@ def test_galerkin_symmetry(dim, family, degree, variant): assert numpy.allclose(Aref, A1, rtol=1E-14) +@pytest.mark.parametrize("family, dim, degree, variant", + [(f, d, p, v) + for f in (CG, ) + for v in (None, "demkowicz", "fdm") + for d in (1, 2, 3) + for p in range(1, 7)]) +def test_hierachical_interpolation(dim, family, degree, variant): + from FIAT.reference_element import symmetric_simplex + + s = symmetric_simplex(dim) + Vp = family(s, degree, variant=variant) + V1 = family(s, 1, variant=variant) + + primal = V1.get_nodal_basis() + dual = Vp.get_dual_set() + A = dual.to_riesz(primal) + B = primal.get_coeffs() + D = numpy.tensordot(A, B, axes=(range(1, A.ndim), range(1, B.ndim))) + + dim1 = V1.space_dimension() + dimp = Vp.space_dimension() + dofs_per_entity = len(V1.entity_dofs()[V1.formdegree][0]) + dofs = Vp.entity_dofs()[Vp.formdegree] + dof1 = sum((dofs[entity][:dofs_per_entity] for entity in sorted(dofs)), []) + dofp = numpy.setdiff1d(numpy.arange(dimp), dof1) + assert numpy.allclose(D[dofp], 0.0) + assert numpy.allclose(D[dof1], numpy.eye(dim1)) + + if __name__ == '__main__': import os pytest.main(os.path.abspath(__file__)) From 892d4dfb9084a00ef7dbf29dd919cc5cecb76f10 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Fri, 5 Jan 2024 10:39:56 -0600 Subject: [PATCH 57/93] add project_derivative --- FIAT/demkowicz.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/FIAT/demkowicz.py b/FIAT/demkowicz.py index e87eef964..04fb640ef 100644 --- a/FIAT/demkowicz.py +++ b/FIAT/demkowicz.py @@ -9,7 +9,7 @@ from FIAT.dual_set import DualSet from FIAT.functional import PointEvaluation, FrobeniusIntegralMoment -from FIAT.polynomial_set import ONPolynomialSet, make_bubbles +from FIAT.polynomial_set import make_bubbles, ONPolynomialSet, PolynomialSet from FIAT.quadrature import FacetQuadratureRule from FIAT.quadrature_schemes import create_quadrature from FIAT.reference_element import symmetric_simplex @@ -229,6 +229,26 @@ def __init__(self, ref_el, degree, sobolev_space, element): super(FDMDual, self).__init__(nodes, ref_el, entity_ids) +def project_derivative(fe, op): + """Return a PolynomialSet with the projection of the derivative of a FiniteElement fe. + The type of derivative is specified by op, must be either "grad", "curl", or "div". + """ + ref_el = fe.ref_el + degree = fe.degree() - 1 + + Q = create_quadrature(ref_el, 2 * degree) + Qpts, Qwts = Q.get_points(), Q.get_weights() + expr = {"grad": grad, "curl": curl, "div": div}[op](fe.tabulate(1, Qpts)) + + sd = ref_el.get_spatial_dimension() + P = ONPolynomialSet(ref_el, degree, expr.shape[1:-1]) + wts = P.tabulate(Qpts)[(0,) * sd] + numpy.multiply(wts, Qwts, out=wts) + wts *= 2 ** sd + coeffs = numpy.tensordot(expr, wts, axes=(range(1, expr.ndim), range(1, expr.ndim))) + return PolynomialSet(ref_el, degree, degree, P.get_expansion_set(), coeffs) + + if __name__ == "__main__": import matplotlib.pyplot as plt from FIAT import IntegratedLegendre as CG From 626b106634e71d67a1c209b847a4c9b0f48fc941 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Sat, 6 Jan 2024 11:02:40 -0600 Subject: [PATCH 58/93] FDM variants for N1curl, N1div --- FIAT/demkowicz.py | 50 +++++++++++++++++++++---------------- FIAT/nedelec.py | 12 ++++++--- FIAT/raviart_thomas.py | 11 +++++--- test/unit/test_demkowicz.py | 10 +++++--- 4 files changed, 51 insertions(+), 32 deletions(-) diff --git a/FIAT/demkowicz.py b/FIAT/demkowicz.py index 04fb640ef..7ffcba63f 100644 --- a/FIAT/demkowicz.py +++ b/FIAT/demkowicz.py @@ -13,8 +13,6 @@ from FIAT.quadrature import FacetQuadratureRule from FIAT.quadrature_schemes import create_quadrature from FIAT.reference_element import symmetric_simplex -from FIAT.nedelec import Nedelec -from FIAT.raviart_thomas import RaviartThomas def grad(table_u): @@ -60,7 +58,7 @@ def map_duals(ref_el, dim, entity, mapping, Q_ref, Phis): class DemkowiczDual(DualSet): - def __init__(self, ref_el, degree, sobolev_space): + def __init__(self, ref_el, degree, sobolev_space, kind=2): nodes = [] entity_ids = {} top = ref_el.get_topology() @@ -81,7 +79,7 @@ def __init__(self, ref_el, degree, sobolev_space): nodes.extend(PointEvaluation(ref_el, pt) for pt in pts) entity_ids[dim][entity] = list(range(cur, len(nodes))) else: - Q_ref, Phis = self._reference_duals(dim, degree, formdegree, sobolev_space) + Q_ref, Phis = self._reference_duals(dim, degree, formdegree, sobolev_space, kind) mapping = dual_mapping if dim == sd else trace for entity in sorted(top[dim]): cur = len(nodes) @@ -91,12 +89,13 @@ def __init__(self, ref_el, degree, sobolev_space): super(DemkowiczDual, self).__init__(nodes, ref_el, entity_ids) - def _reference_duals(self, dim, degree, formdegree, sobolev_space): + def _reference_duals(self, dim, degree, formdegree, sobolev_space, kind): + k = degree if kind == 2 else degree - 1 facet = symmetric_simplex(dim) Q = create_quadrature(facet, 2 * degree) if formdegree == dim: shp = (dim,) if sobolev_space == "HCurl" else () - P = ONPolynomialSet(facet, degree, shp) + P = ONPolynomialSet(facet, k, shp) duals = P.tabulate(Q.get_points())[(0,) * dim] return Q, duals @@ -106,13 +105,14 @@ def _reference_duals(self, dim, degree, formdegree, sobolev_space): P = ONPolynomialSet(facet, degree, shp) P_at_qpts = P.tabulate(Qpts, 1) dtrial = exterior_derivative(P_at_qpts) + K = self._bubble_derivative_moments(facet, degree, formdegree, Qpts, Qwts, dtrial) if formdegree > 0: trial = P_at_qpts[(0,) * dim] if formdegree == 1 and sobolev_space == "HDiv": rot = numpy.array([[0.0, 1.0], [-1.0, 0.0]], "d") trial = numpy.dot(rot, trial).transpose((1, 0, 2)) - M = self._bubble_derivative_moments(facet, degree+1, formdegree-1, Qpts, Qwts, trial) + M = self._bubble_derivative_moments(facet, k+1, formdegree-1, Qpts, Qwts, trial) K = numpy.vstack((K, M)) duals = numpy.tensordot(K, P_at_qpts[(0,) * dim], axes=(1, 0)) @@ -125,30 +125,33 @@ def _bubble_derivative_moments(self, facet, degree, formdegree, Qpts, Qwts, tria dim = facet.get_spatial_dimension() if formdegree >= dim - 1: # We are at the end of the complex - # derivative of bubbles is P_k-1 minus constants + # bubbles have derivative in P_k-1 minus constants Pkm1 = ONPolynomialSet(facet, degree-1, trial.shape[1:-1]) P0 = Pkm1.take(list(range(1, Pkm1.get_num_members()))) dtest = P0.tabulate(Qpts)[(0,) * dim] return inner(dtest, trial, Qwts) # Get bubbles - element = (None, Nedelec, RaviartThomas)[formdegree] - if element is None: + if formdegree == 0: B = make_bubbles(facet, degree) - else: - fe = element(facet, degree) + elif formdegree == 1: + from FIAT.nedelec import Nedelec + fe = Nedelec(facet, degree) B = fe.get_nodal_basis().take(fe.entity_dofs()[dim][0]) + else: + raise ValueError(f"{formdegree}-form bubbles not supported") # Tabulate the exterior derivate d = (grad, curl, div)[formdegree] dtest = d(B.tabulate(Qpts, 1)) - # Build an orthonormal basis, remove nullspace - A = inner(dtest, dtest, Qwts) - sig, S = scipy.linalg.eigh(A) - nullspace_dim = len([s for s in sig if abs(s) <= 1.e-10]) - S = S[:, nullspace_dim:] - S *= numpy.sqrt(1 / sig[None, nullspace_dim:]) - # Apply change of basis - dtest = numpy.tensordot(S.T, dtest, axes=(1, 0)) + if len(dtest) > 0: + # Build an orthonormal basis, remove nullspace + A = inner(dtest, dtest, Qwts) + sig, S = scipy.linalg.eigh(A) + nullspace_dim = len([s for s in sig if abs(s) <= 1.e-10]) + S = S[:, nullspace_dim:] + S *= numpy.sqrt(1 / sig[None, nullspace_dim:]) + # Apply change of basis + dtest = numpy.tensordot(S.T, dtest, axes=(1, 0)) return inner(dtest, trial, Qwts) @@ -252,6 +255,8 @@ def project_derivative(fe, op): if __name__ == "__main__": import matplotlib.pyplot as plt from FIAT import IntegratedLegendre as CG + from FIAT import Nedelec as N1Curl + from FIAT import RaviartThomas as N1Div from FIAT import NedelecSecondKind as N2Curl from FIAT import BrezziDouglasMarini as N2Div @@ -261,12 +266,13 @@ def project_derivative(fe, op): Q = create_quadrature(ref_el, 2 * degree) Qpts, Qwts = Q.get_points(), Q.get_weights() + kind = 1 variant = "fdm" # variant = "demkowicz" # variant = None space_dict = {"H1": (CG, grad), - "HCurl": (N2Curl, curl), - "HDiv": (N2Div, div), + "HCurl": (N1Curl if kind == 1 else N2Curl, curl), + "HDiv": (N1Div if kind == 1 else N2Div, div), } spaces = list(space_dict.keys()) diff --git a/FIAT/nedelec.py b/FIAT/nedelec.py index c05133022..9874e3c93 100644 --- a/FIAT/nedelec.py +++ b/FIAT/nedelec.py @@ -6,7 +6,7 @@ # SPDX-License-Identifier: LGPL-3.0-or-later from FIAT import (polynomial_set, expansions, dual_set, - finite_element, functional) + finite_element, functional, demkowicz) from itertools import chain import numpy from FIAT.check_format_variant import check_format_variant @@ -196,14 +196,20 @@ class Nedelec(finite_element.CiarletElement): def __init__(self, ref_el, degree, variant=None): - variant, interpolant_deg = check_format_variant(variant, degree) + if variant == "demkowicz": + dual = demkowicz.DemkowiczDual(ref_el, degree, "HCurl", kind=1) + elif variant == "fdm": + dual = demkowicz.FDMDual(ref_el, degree, "HCurl", type(self)) + else: + variant, interpolant_deg = check_format_variant(variant, degree) + dual = NedelecDual(ref_el, degree, variant, interpolant_deg) + if ref_el.get_spatial_dimension() == 3: poly_set = NedelecSpace3D(ref_el, degree) elif ref_el.get_spatial_dimension() == 2: poly_set = NedelecSpace2D(ref_el, degree) else: raise Exception("Not implemented") - dual = NedelecDual(ref_el, degree, variant, interpolant_deg) formdegree = 1 # 1-form super(Nedelec, self).__init__(poly_set, dual, degree, formdegree, mapping="covariant piola") diff --git a/FIAT/raviart_thomas.py b/FIAT/raviart_thomas.py index c66c4ff46..568b28410 100644 --- a/FIAT/raviart_thomas.py +++ b/FIAT/raviart_thomas.py @@ -6,7 +6,7 @@ # SPDX-License-Identifier: LGPL-3.0-or-later from FIAT import (expansions, polynomial_set, dual_set, - finite_element, functional) + finite_element, functional, demkowicz) import numpy from itertools import chain from FIAT.check_format_variant import check_format_variant @@ -141,10 +141,15 @@ class RaviartThomas(finite_element.CiarletElement): def __init__(self, ref_el, degree, variant=None): - variant, interpolant_deg = check_format_variant(variant, degree) + if variant == "demkowicz": + dual = demkowicz.DemkowiczDual(ref_el, degree, "HDiv", kind=1) + elif variant == "fdm": + dual = demkowicz.FDMDual(ref_el, degree, "HDiv", type(self)) + else: + variant, interpolant_deg = check_format_variant(variant, degree) + dual = RTDualSet(ref_el, degree, variant, interpolant_deg) poly_set = RTSpace(ref_el, degree) - dual = RTDualSet(ref_el, degree, variant, interpolant_deg) formdegree = ref_el.get_spatial_dimension() - 1 # (n-1)-form super(RaviartThomas, self).__init__(poly_set, dual, degree, formdegree, mapping="contravariant piola") diff --git a/test/unit/test_demkowicz.py b/test/unit/test_demkowicz.py index 39298ca87..4f1d611d3 100644 --- a/test/unit/test_demkowicz.py +++ b/test/unit/test_demkowicz.py @@ -22,16 +22,18 @@ import pytest import numpy from FIAT.hierarchical import IntegratedLegendre as CG +from FIAT.nedelec import Nedelec as N1Curl +from FIAT.raviart_thomas import RaviartThomas as N1Div from FIAT.nedelec_second_kind import NedelecSecondKind as N2Curl from FIAT.brezzi_douglas_marini import BrezziDouglasMarini as N2Div @pytest.mark.parametrize("family, dim, degree, variant", [(f, d, p, v) - for f in (CG, N2Curl, N2Div) + for f in (CG, N1Curl, N1Div, N2Curl, N2Div) for v in ("demkowicz", "fdm") for d in (2, 3) - for p in range(1, 7)]) + for p in range(1, 6)]) def test_galerkin_symmetry(dim, family, degree, variant): from FIAT.quadrature_schemes import create_quadrature from FIAT.reference_element import symmetric_simplex @@ -39,7 +41,7 @@ def test_galerkin_symmetry(dim, family, degree, variant): s = symmetric_simplex(dim) fe = family(s, degree, variant=variant) - exterior_derivative = {CG: grad, N2Curl: curl, N2Div: div}[family] + exterior_derivative = {CG: grad, N1Curl: curl, N2Curl: curl, N1Div: div, N2Div: div}[family] Q = create_quadrature(s, 2 * degree) Qpts, Qwts = Q.get_points(), Q.get_weights() @@ -60,7 +62,7 @@ def test_galerkin_symmetry(dim, family, degree, variant): @pytest.mark.parametrize("family, dim, degree, variant", [(f, d, p, v) - for f in (CG, ) + for f in (CG,) for v in (None, "demkowicz", "fdm") for d in (1, 2, 3) for p in range(1, 7)]) From b557a6ca3a5e065d5047c19c460728f640b9d720 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Mon, 8 Jan 2024 12:02:45 -0600 Subject: [PATCH 59/93] add perp() --- FIAT/demkowicz.py | 20 +++++++++++++++++--- test/unit/test_demkowicz.py | 10 +++++----- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/FIAT/demkowicz.py b/FIAT/demkowicz.py index 7ffcba63f..94219a79e 100644 --- a/FIAT/demkowicz.py +++ b/FIAT/demkowicz.py @@ -39,6 +39,13 @@ def inner(v, u, Qwts): return numpy.tensordot(numpy.multiply(v, Qwts), u, axes=(range(1, v.ndim), range(1, u.ndim))) +def perp(u): + u_perp = numpy.empty_like(u) + u_perp[:, 0, :] = u[:, 1, :] + u_perp[:, 1, :] = -u[:, 0, :] + return u_perp + + def map_duals(ref_el, dim, entity, mapping, Q_ref, Phis): Q = FacetQuadratureRule(ref_el, dim, entity, Q_ref) if mapping == "normal": @@ -110,8 +117,7 @@ def _reference_duals(self, dim, degree, formdegree, sobolev_space, kind): if formdegree > 0: trial = P_at_qpts[(0,) * dim] if formdegree == 1 and sobolev_space == "HDiv": - rot = numpy.array([[0.0, 1.0], [-1.0, 0.0]], "d") - trial = numpy.dot(rot, trial).transpose((1, 0, 2)) + trial = perp(trial) M = self._bubble_derivative_moments(facet, k+1, formdegree-1, Qpts, Qwts, trial) K = numpy.vstack((K, M)) @@ -129,6 +135,8 @@ def _bubble_derivative_moments(self, facet, degree, formdegree, Qpts, Qwts, tria Pkm1 = ONPolynomialSet(facet, degree-1, trial.shape[1:-1]) P0 = Pkm1.take(list(range(1, Pkm1.get_num_members()))) dtest = P0.tabulate(Qpts)[(0,) * dim] + if degree-1 == 0: + dtest *= numpy.sqrt(0.5) return inner(dtest, trial, Qwts) # Get bubbles @@ -238,10 +246,17 @@ def project_derivative(fe, op): """ ref_el = fe.ref_el degree = fe.degree() - 1 + rot = None + if fe.formdegree == 0: + if op == "curl": + rot = perp + op = "grad" Q = create_quadrature(ref_el, 2 * degree) Qpts, Qwts = Q.get_points(), Q.get_weights() expr = {"grad": grad, "curl": curl, "div": div}[op](fe.tabulate(1, Qpts)) + if rot is not None: + expr = rot(expr) sd = ref_el.get_spatial_dimension() P = ONPolynomialSet(ref_el, degree, expr.shape[1:-1]) @@ -292,7 +307,6 @@ def project_derivative(fe, op): names = (f"{title} stiff", f"{title} mass") for name, A in zip(names, mats): A[abs(A) < 1E-10] = 0.0 - # print(A.diagonal()) nnz = numpy.count_nonzero(A) ax = next(axes) ax.spy(A, markersize=0) diff --git a/test/unit/test_demkowicz.py b/test/unit/test_demkowicz.py index 4f1d611d3..d0e79ffce 100644 --- a/test/unit/test_demkowicz.py +++ b/test/unit/test_demkowicz.py @@ -62,11 +62,11 @@ def test_galerkin_symmetry(dim, family, degree, variant): @pytest.mark.parametrize("family, dim, degree, variant", [(f, d, p, v) - for f in (CG,) - for v in (None, "demkowicz", "fdm") - for d in (1, 2, 3) - for p in range(1, 7)]) -def test_hierachical_interpolation(dim, family, degree, variant): + for f in (CG, N1Curl,) + for v in ("demkowicz",) + for d in (2, 3) + for p in range(2, 7)]) +def test_hierarchical_interpolation(dim, family, degree, variant): from FIAT.reference_element import symmetric_simplex s = symmetric_simplex(dim) From 12c55794453e1e47d390996289230720eeabab5c Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Mon, 8 Jan 2024 12:55:20 -0600 Subject: [PATCH 60/93] Fix ONPolynomialSet normalization --- FIAT/expansions.py | 22 +++++++++++----------- test/unit/test_fiat.py | 25 +++++++++++++++---------- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/FIAT/expansions.py b/FIAT/expansions.py index c17cffa0d..e944d102d 100644 --- a/FIAT/expansions.py +++ b/FIAT/expansions.py @@ -9,8 +9,7 @@ import numpy import math -from FIAT import reference_element -from FIAT import jacobi +from FIAT import reference_element, jacobi def morton_index2(p, q=0): @@ -52,7 +51,7 @@ def jacobi_factors(x, y, z, dx, dy, dz): return fa, fb, fc, dfa, dfb, dfc -def dubiner_recurrence(dim, n, order, ref_pts, jacobian): +def dubiner_recurrence(dim, n, order, ref_pts, Jinv, scale): """Dubiner recurrence from (Kirby 2010)""" if order > 2: raise ValueError("Higher order derivatives not supported") @@ -65,8 +64,8 @@ def dubiner_recurrence(dim, n, order, ref_pts, jacobian): sym_outer = lambda x, y: outer(x, y) + outer(y, x) pad_dim = dim + 2 - dX = pad_jacobian(jacobian, pad_dim) - phi[0] = sum((ref_pts[i] - ref_pts[i] for i in range(dim)), 1.) + dX = pad_jacobian(Jinv, pad_dim) + phi[0] = sum((ref_pts[i] - ref_pts[i] for i in range(dim)), scale) if dphi is not None: dphi[0] = (phi[0] - phi[0]) * dX[0] if ddphi is not None: @@ -115,9 +114,10 @@ def dubiner_recurrence(dim, n, order, ref_pts, jacobian): c * (fc * ddphi[iprev] + sym_outer(dphi[iprev], dfc) + phi[iprev] * ddfc)) # normalize - for alpha in reference_element.lattice_iter(0, n+1, codim+1): - icur = idx(*alpha) - scale = math.sqrt(sum(alpha) + 0.5 * len(alpha)) + d = codim + 1 + for index in reference_element.lattice_iter(0, n+1, d): + icur = idx(*index) + scale = math.sqrt((2.0 * sum(index) + d) / d) for result in results: result[icur] *= scale return results @@ -165,7 +165,7 @@ def __init__(self, ref_el): v2 = self.base_ref_el.get_vertices() self.A, self.b = reference_element.make_affine_mapping(v1, v2) self.mapping = lambda x: numpy.dot(self.A, x) + self.b - self.scale = numpy.sqrt(numpy.linalg.det(self.A)) + self.scale = numpy.sqrt(1.0 / ref_el.volume()) self._dmats_cache = {} def get_num_members(self, n): @@ -184,7 +184,7 @@ def _tabulate(self, n, pts, order=0): """A version of tabulate() that also works for a single point. """ D = self.ref_el.get_spatial_dimension() - return dubiner_recurrence(D, n, order, self._mapping(pts), self.A) + return dubiner_recurrence(D, n, order, self._mapping(pts), self.A, self.scale) def get_dmats(self, degree): """Returns a numpy array with the expansion coefficients dmat[k, j, i] @@ -289,7 +289,7 @@ def _tabulate(self, n, pts, order=0): vals[i,j] = phi_i(pts[j]), derivs[i,j] = D vals[i,j].""" xs = self._mapping(pts).T results = [] - scale = numpy.sqrt(0.5 + numpy.arange(n+1)) + scale = self.scale * numpy.sqrt(2 * numpy.arange(n+1) + 1) for k in range(order+1): v = numpy.zeros((n + 1, len(xs)), xs.dtype) if n >= k: diff --git a/test/unit/test_fiat.py b/test/unit/test_fiat.py index 3a393c6b6..323751bfe 100644 --- a/test/unit/test_fiat.py +++ b/test/unit/test_fiat.py @@ -530,16 +530,21 @@ def test_error_point_high_order(element): eval(element) -@pytest.mark.parametrize('cell', [I, T, S]) -def test_expansion_orthonormality(cell): - from FIAT import expansions, quadrature - U = expansions.ExpansionSet(cell) - degree = 10 - rule = quadrature.make_quadrature(cell, degree + 1) - phi = U.tabulate(degree, rule.pts) - w = rule.get_weights() - scale = 0.5 ** -cell.get_spatial_dimension() - results = scale * np.dot(phi, w[:, None] * phi.T) +@pytest.mark.parametrize('degree', [0, 10]) +@pytest.mark.parametrize('dim', range(1, 4)) +@pytest.mark.parametrize('cell', ["default", "ufc"]) +def test_expansion_orthonormality(cell, dim, degree): + from FIAT.expansions import ExpansionSet + from FIAT.quadrature_schemes import create_quadrature + from FIAT import reference_element + make_cell = {"default": reference_element.default_simplex, + "ufc": reference_element.ufc_simplex}[cell] + ref_el = make_cell(dim) + U = ExpansionSet(ref_el) + Q = create_quadrature(ref_el, 2 * degree) + qpts, qwts = Q.get_points(), Q.get_weights() + phi = U.tabulate(degree, qpts) + results = np.dot(np.multiply(phi, qwts), phi.T) assert np.allclose(results, np.eye(results.shape[0])) From 9bff59eb03f0d063318f2e4a32250994e70d3de1 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Mon, 8 Jan 2024 21:56:13 -0600 Subject: [PATCH 61/93] More sparsity for variant=demkowicz --- FIAT/demkowicz.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/FIAT/demkowicz.py b/FIAT/demkowicz.py index 94219a79e..ad0caf5f7 100644 --- a/FIAT/demkowicz.py +++ b/FIAT/demkowicz.py @@ -135,8 +135,6 @@ def _bubble_derivative_moments(self, facet, degree, formdegree, Qpts, Qwts, tria Pkm1 = ONPolynomialSet(facet, degree-1, trial.shape[1:-1]) P0 = Pkm1.take(list(range(1, Pkm1.get_num_members()))) dtest = P0.tabulate(Qpts)[(0,) * dim] - if degree-1 == 0: - dtest *= numpy.sqrt(0.5) return inner(dtest, trial, Qwts) # Get bubbles @@ -149,12 +147,15 @@ def _bubble_derivative_moments(self, facet, degree, formdegree, Qpts, Qwts, tria else: raise ValueError(f"{formdegree}-form bubbles not supported") # Tabulate the exterior derivate + B_at_qpts = B.tabulate(Qpts, 1) d = (grad, curl, div)[formdegree] - dtest = d(B.tabulate(Qpts, 1)) + dtest = d(B_at_qpts) if len(dtest) > 0: # Build an orthonormal basis, remove nullspace + test = B_at_qpts[(0,) * dim] + B = inner(test, test, Qwts) A = inner(dtest, dtest, Qwts) - sig, S = scipy.linalg.eigh(A) + sig, S = scipy.linalg.eigh(A, B) nullspace_dim = len([s for s in sig if abs(s) <= 1.e-10]) S = S[:, nullspace_dim:] S *= numpy.sqrt(1 / sig[None, nullspace_dim:]) @@ -262,7 +263,6 @@ def project_derivative(fe, op): P = ONPolynomialSet(ref_el, degree, expr.shape[1:-1]) wts = P.tabulate(Qpts)[(0,) * sd] numpy.multiply(wts, Qwts, out=wts) - wts *= 2 ** sd coeffs = numpy.tensordot(expr, wts, axes=(range(1, expr.ndim), range(1, expr.ndim))) return PolynomialSet(ref_el, degree, degree, P.get_expansion_set(), coeffs) From 5cec49b66301801f4b71a85f0fb07833d4579848 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Tue, 9 Jan 2024 13:47:54 -0600 Subject: [PATCH 62/93] Rescale integral moment dofs to match lowest order point and integral variants --- FIAT/brezzi_douglas_marini.py | 5 ++--- FIAT/expansions.py | 35 ++++++++++++++++++++--------------- FIAT/nedelec.py | 4 ++-- FIAT/nedelec_second_kind.py | 6 +++--- FIAT/polynomial_set.py | 8 ++++---- FIAT/raviart_thomas.py | 7 +++---- 6 files changed, 34 insertions(+), 31 deletions(-) diff --git a/FIAT/brezzi_douglas_marini.py b/FIAT/brezzi_douglas_marini.py index 2c9aeb892..a3f1fbb6d 100644 --- a/FIAT/brezzi_douglas_marini.py +++ b/FIAT/brezzi_douglas_marini.py @@ -30,13 +30,12 @@ def __init__(self, ref_el, degree, variant, interpolant_deg): # Facet nodes are \int_F v\cdot n p ds where p \in P_{q} # degree is q Q_ref = create_quadrature(facet, interpolant_deg + degree) - Pq = polynomial_set.ONPolynomialSet(facet, degree) + Pq = polynomial_set.ONPolynomialSet(facet, degree, scale=1) Pq_at_qpts = Pq.tabulate(Q_ref.get_points())[(0,)*(sd - 1)] for f in top[sd - 1]: cur = len(nodes) Q = FacetQuadratureRule(ref_el, sd - 1, f, Q_ref) - Jdet = Q.jacobian_determinant() - n = ref_el.compute_scaled_normal(f) / Jdet + n = ref_el.compute_normal(f) phis = n[None, :, None] * Pq_at_qpts[:, None, :] nodes.extend(functional.FrobeniusIntegralMoment(ref_el, Q, phi) for phi in phis) diff --git a/FIAT/expansions.py b/FIAT/expansions.py index e944d102d..72280922c 100644 --- a/FIAT/expansions.py +++ b/FIAT/expansions.py @@ -141,31 +141,36 @@ def xi_tetrahedron(eta): class ExpansionSet(object): - def __new__(cls, ref_el, *args, **kwargs): + def __new__(cls, *args, **kwargs): """Returns an ExpansionSet instance appopriate for the given reference element.""" if cls is not ExpansionSet: return super(ExpansionSet, cls).__new__(cls) + ref_el = args[0] if ref_el.get_shape() == reference_element.POINT: - return PointExpansionSet(ref_el) + return PointExpansionSet(*args, **kwargs) elif ref_el.get_shape() == reference_element.LINE: - return LineExpansionSet(ref_el) + return LineExpansionSet(*args, **kwargs) elif ref_el.get_shape() == reference_element.TRIANGLE: - return TriangleExpansionSet(ref_el) + return TriangleExpansionSet(*args, **kwargs) elif ref_el.get_shape() == reference_element.TETRAHEDRON: - return TetrahedronExpansionSet(ref_el) + return TetrahedronExpansionSet(*args, **kwargs) else: raise ValueError("Invalid reference element type.") - def __init__(self, ref_el): + def __init__(self, ref_el, scale=None): + if isinstance(scale, str) and scale.lower() == "l2 piola": + scale = 1.0 / ref_el.volume() + elif scale is None: + scale = math.sqrt(1.0 / ref_el.volume()) self.ref_el = ref_el + self.scale = scale dim = ref_el.get_spatial_dimension() self.base_ref_el = reference_element.default_simplex(dim) v1 = ref_el.get_vertices() v2 = self.base_ref_el.get_vertices() self.A, self.b = reference_element.make_affine_mapping(v1, v2) self.mapping = lambda x: numpy.dot(self.A, x) + self.b - self.scale = numpy.sqrt(1.0 / ref_el.volume()) self._dmats_cache = {} def get_num_members(self, n): @@ -266,10 +271,10 @@ def tabulate_jet(self, n, pts, order=1): class PointExpansionSet(ExpansionSet): """Evaluates the point basis on a point reference element.""" - def __init__(self, ref_el): + def __init__(self, ref_el, **kwargs): if ref_el.get_spatial_dimension() != 0: raise ValueError("Must have a point") - super(PointExpansionSet, self).__init__(ref_el) + super(PointExpansionSet, self).__init__(ref_el, **kwargs) def tabulate(self, n, pts): """Returns a numpy array A[i,j] = phi_i(pts[j]) = 1.0.""" @@ -279,10 +284,10 @@ def tabulate(self, n, pts): class LineExpansionSet(ExpansionSet): """Evaluates the Legendre basis on a line reference element.""" - def __init__(self, ref_el): + def __init__(self, ref_el, **kwargs): if ref_el.get_spatial_dimension() != 1: raise Exception("Must have a line") - super(LineExpansionSet, self).__init__(ref_el) + super(LineExpansionSet, self).__init__(ref_el, **kwargs) def _tabulate(self, n, pts, order=0): """Returns a tuple of (vals, derivs) such that @@ -306,18 +311,18 @@ def _tabulate(self, n, pts, order=0): class TriangleExpansionSet(ExpansionSet): """Evaluates the orthonormal Dubiner basis on a triangular reference element.""" - def __init__(self, ref_el): + def __init__(self, ref_el, **kwargs): if ref_el.get_spatial_dimension() != 2: raise Exception("Must have a triangle") - super(TriangleExpansionSet, self).__init__(ref_el) + super(TriangleExpansionSet, self).__init__(ref_el, **kwargs) class TetrahedronExpansionSet(ExpansionSet): """Collapsed orthonormal polynomial expansion on a tetrahedron.""" - def __init__(self, ref_el): + def __init__(self, ref_el, **kwargs): if ref_el.get_spatial_dimension() != 3: raise Exception("Must be a tetrahedron") - super(TetrahedronExpansionSet, self).__init__(ref_el) + super(TetrahedronExpansionSet, self).__init__(ref_el, **kwargs) def polynomial_dimension(ref_el, degree): diff --git a/FIAT/nedelec.py b/FIAT/nedelec.py index c05133022..c36e2b15c 100644 --- a/FIAT/nedelec.py +++ b/FIAT/nedelec.py @@ -123,7 +123,7 @@ def __init__(self, ref_el, degree, variant, interpolant_deg): if phi_deg >= 0: facet = ref_el.construct_subelement(dim) Q_ref = create_quadrature(facet, interpolant_deg + phi_deg) - Pqmd = polynomial_set.ONPolynomialSet(facet, phi_deg, (dim,)) + Pqmd = polynomial_set.ONPolynomialSet(facet, phi_deg, (dim,), scale="L2 piola") Phis = Pqmd.tabulate(Q_ref.get_points())[(0,) * dim] Phis = numpy.transpose(Phis, (0, 2, 1)) @@ -165,7 +165,7 @@ def __init__(self, ref_el, degree, variant, interpolant_deg): interpolant_deg = degree cur = len(nodes) Q = create_quadrature(ref_el, interpolant_deg + phi_deg) - Pqmd = polynomial_set.ONPolynomialSet(ref_el, phi_deg) + Pqmd = polynomial_set.ONPolynomialSet(ref_el, phi_deg, scale="L2 piola") Phis = Pqmd.tabulate(Q.get_points())[(0,) * dim] nodes.extend(functional.IntegralMoment(ref_el, Q, phi, (d,), (dim,)) for d in range(dim) for phi in Phis) diff --git a/FIAT/nedelec_second_kind.py b/FIAT/nedelec_second_kind.py index 6e5269721..08e5c1379 100644 --- a/FIAT/nedelec_second_kind.py +++ b/FIAT/nedelec_second_kind.py @@ -129,7 +129,7 @@ def _generate_facet_dofs(self, codim, cell, degree, offset, variant, interpolant ref_facet = cell.construct_subelement(codim) Q_ref = create_quadrature(ref_facet, interpolant_deg + rt_degree) if codim == 1: - Phi = ONPolynomialSet(ref_facet, rt_degree, (codim,)) + Phi = ONPolynomialSet(ref_facet, rt_degree, (codim,), scale="L2 piola") else: # Construct Raviart-Thomas on the reference facet RT = RaviartThomas(ref_facet, rt_degree, variant) @@ -149,10 +149,10 @@ def _generate_facet_dofs(self, codim, cell, degree, offset, variant, interpolant # Get the quadrature and Jacobian on this facet Q_facet = FacetQuadratureRule(cell, codim, facet, Q_ref) J = Q_facet.jacobian() - detJ = Q_facet.jacobian_determinant() + Jdet = Q_facet.jacobian_determinant() # Map Phis -> phis (reference values to physical values) - piola_map = J / detJ + piola_map = J / Jdet phis = numpy.dot(Phis, piola_map.T) phis = numpy.transpose(phis, (0, 2, 1)) diff --git a/FIAT/polynomial_set.py b/FIAT/polynomial_set.py index 3a8d376be..9e191a341 100644 --- a/FIAT/polynomial_set.py +++ b/FIAT/polynomial_set.py @@ -120,7 +120,7 @@ class ONPolynomialSet(PolynomialSet): """ - def __init__(self, ref_el, degree, shape=tuple()): + def __init__(self, ref_el, degree, shape=tuple(), scale=None): if shape == tuple(): num_components = 1 @@ -130,7 +130,7 @@ def __init__(self, ref_el, degree, shape=tuple()): num_exp_functions = expansions.polynomial_dimension(ref_el, degree) num_members = num_components * num_exp_functions embedded_degree = degree - expansion_set = expansions.ExpansionSet(ref_el) + expansion_set = expansions.ExpansionSet(ref_el, scale=scale) # set up coefficients if shape == tuple(): @@ -211,7 +211,7 @@ class ONSymTensorPolynomialSet(PolynomialSet): """ - def __init__(self, ref_el, degree, size=None): + def __init__(self, ref_el, degree, size=None, scale=None): sd = ref_el.get_spatial_dimension() if size is None: @@ -222,7 +222,7 @@ def __init__(self, ref_el, degree, size=None): num_components = size * (size + 1) // 2 num_members = num_components * num_exp_functions embedded_degree = degree - expansion_set = expansions.ExpansionSet(ref_el) + expansion_set = expansions.ExpansionSet(ref_el, scale=scale) # set up coefficients for symmetric tensors coeffs_shape = (num_members, *shape, num_exp_functions) diff --git a/FIAT/raviart_thomas.py b/FIAT/raviart_thomas.py index c66c4ff46..1f190ce55 100644 --- a/FIAT/raviart_thomas.py +++ b/FIAT/raviart_thomas.py @@ -75,13 +75,12 @@ def __init__(self, ref_el, degree, variant, interpolant_deg): # Facet nodes are \int_F v\cdot n p ds where p \in P_{q-1} # degree is q - 1 Q_ref = create_quadrature(facet, interpolant_deg + degree - 1) - Pq = polynomial_set.ONPolynomialSet(facet, degree - 1) + Pq = polynomial_set.ONPolynomialSet(facet, degree - 1, scale=1) Pq_at_qpts = Pq.tabulate(Q_ref.get_points())[(0,)*(sd - 1)] for f in top[sd - 1]: cur = len(nodes) Q = FacetQuadratureRule(ref_el, sd-1, f, Q_ref) - Jdet = Q.jacobian_determinant() - n = ref_el.compute_scaled_normal(f) / Jdet + n = ref_el.compute_normal(f) phis = n[None, :, None] * Pq_at_qpts[:, None, :] nodes.extend(functional.FrobeniusIntegralMoment(ref_el, Q, phi) for phi in phis) @@ -91,7 +90,7 @@ def __init__(self, ref_el, degree, variant, interpolant_deg): if degree > 1: cur = len(nodes) Q = create_quadrature(ref_el, interpolant_deg + degree - 2) - Pkm1 = polynomial_set.ONPolynomialSet(ref_el, degree - 2) + Pkm1 = polynomial_set.ONPolynomialSet(ref_el, degree - 2, scale=1) Pkm1_at_qpts = Pkm1.tabulate(Q.get_points())[(0,) * sd] nodes.extend(functional.IntegralMoment(ref_el, Q, phi, (d,), (sd,)) for d in range(sd) From ce1f59588ae5759ac882f48fae8f3c1e23a2f74d Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 25 Jan 2024 17:11:40 +0000 Subject: [PATCH 63/93] add variant="dual" --- FIAT/expansions.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/FIAT/expansions.py b/FIAT/expansions.py index 9bc2fef70..32804ca6c 100644 --- a/FIAT/expansions.py +++ b/FIAT/expansions.py @@ -86,6 +86,7 @@ def dubiner_recurrence(dim, n, order, ref_pts, Jinv, scale, variant=None): if dim > 3 or dim < 0: raise ValueError("Invalid number of spatial dimensions") + beta = 1 if variant == "dual" else 0 coefficients = integrated_jrc if variant == "integral" else jrc X = pad_coordinates(ref_pts, pad_dim) idx = (lambda p: p, morton_index2, morton_index3)[dim-1] @@ -98,12 +99,13 @@ def dubiner_recurrence(dim, n, order, ref_pts, Jinv, scale, variant=None): icur = idx(*sub_index, 0) inext = idx(*sub_index, 1) - beta = 0 if variant == "integral": alpha = 2 * sum(sub_index) a = b = 1.0 else: alpha = 2 * sum(sub_index) + len(sub_index) + if variant == "dual": + alpha += 1 + len(sub_index) a = 0.5 * (alpha + beta) + 1.0 b = 0.5 * (alpha - beta) @@ -133,16 +135,17 @@ def dubiner_recurrence(dim, n, order, ref_pts, Jinv, scale, variant=None): # normalize d = codim + 1 + shift = 1 if variant == "dual" else 0 for index in reference_element.lattice_iter(0, n+1, d): - p = index[-1] - alpha = 2 * sum(index) - 1 - if variant == "integral" and p > (d == 1) and alpha > p: - norm2 = ((2*d+1)*(alpha - p)*alpha) / (2*d*p) - else: - norm2 = (2.0 * sum(index) + d) / d + icur = idx(*index) + norm2 = (2*sum(index) + d) / d + if variant is not None: + p = index[-1] + shift + alpha = 2 * (sum(index[:-1]) + d * shift) - 1 + if p > 0 and p + alpha > 0: + norm2 = (2*d+1) * (p + alpha) * (2*p + alpha) / (2*d*p) scale = math.sqrt(norm2) - icur = idx(*index) for result in results: result[icur] *= scale return results From ddeb3dd8e06b6ea3492a4b32bc6340bb004a5106 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Fri, 26 Jan 2024 11:53:01 +0000 Subject: [PATCH 64/93] local L2 duals --- FIAT/hierarchical.py | 47 +++++++++++--------------------------------- 1 file changed, 11 insertions(+), 36 deletions(-) diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index 0e382859e..926f652fb 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -60,9 +60,8 @@ def __init__(self, ref_el, degree, variant=None): variant = "beuchler" duals = { + "l2": self._L2_duals, "beuchler": self._beuchler_integral_duals, - "beuchler_point": self._beuchler_point_duals, - "demkowicz_point": self._demkowicz_point_duals, }[variant] nodes = [] @@ -71,19 +70,18 @@ def __init__(self, ref_el, degree, variant=None): top = ref_el.get_topology() for dim in sorted(top): + perms = make_entity_permutations_simplex(dim, degree - dim) entity_ids[dim] = {} entity_permutations[dim] = {} - if dim == 0: - perms = {0: [0]} + if dim == 0 or degree <= dim: for entity in sorted(top[dim]): cur = len(nodes) - pt, = ref_el.make_points(0, entity, degree) - nodes.append(functional.PointEvaluation(ref_el, pt)) + pts = ref_el.make_points(dim, entity, degree) + nodes.extend(functional.PointEvaluation(ref_el, pt) for pt in pts) entity_ids[dim][entity] = list(range(cur, len(nodes))) entity_permutations[dim][entity] = perms continue - perms = make_entity_permutations_simplex(dim, degree - dim) ref_facet = symmetric_simplex(dim) Q_ref, phis = duals(ref_facet, degree) for entity in sorted(top[dim]): @@ -100,15 +98,12 @@ def __init__(self, ref_el, degree, variant=None): super(IntegratedLegendreDual, self).__init__(nodes, ref_el, entity_ids, entity_permutations) - def _beuchler_point_duals(self, ref_el, degree, variant="gll"): - points = make_lattice(ref_el.vertices, degree, variant=variant) - weights = (1.0,) * len(points) - Q = QuadratureRule(ref_el, points, weights) - B = make_bubbles(ref_el, degree) - V = numpy.transpose(B.expansion_set.tabulate(degree, points)) - - PLU = scipy.linalg.lu_factor(V) - phis = scipy.linalg.lu_solve(PLU, B.get_coeffs().T, trans=1).T + def _L2_duals(self, ref_el, degree): + dim = ref_el.get_spatial_dimension() + phi_deg = degree - 1 - dim + Q = create_quadrature(ref_el, degree + phi_deg) + B = ONPolynomialSet(ref_el, phi_deg, variant="dual") + phis = B.tabulate(Q.get_points())[(0,) * dim] return Q, phis def _beuchler_integral_duals(self, ref_el, degree): @@ -130,26 +125,6 @@ def _beuchler_integral_duals(self, ref_el, degree): phis = numpy.dot(B.get_coeffs(), phis) return Q, phis - def _demkowicz_point_duals(self, ref_el, degree, variant="gll"): - Qkm1 = create_quadrature(ref_el, 2 * (degree-1)) - qpts, qwts = Qkm1.get_points(), Qkm1.get_weights() - inner = lambda v, u: numpy.dot(numpy.multiply(v, qwts), u.T) - galerkin = lambda V, U: sum(inner(V[k], U[k]) for k in V if sum(k) == 1) - - P = Lagrange(ref_el, degree, variant=variant) - P_table = P.tabulate(1, qpts) - B = make_bubbles(ref_el, degree) - B_table = B.tabulate(qpts, 1) - phis = galerkin(B_table, P_table) - - points = [] - for node in P.dual_basis(): - pt, = node.get_point_dict() - points.append(pt) - weights = (1.0,) * len(points) - Q = QuadratureRule(ref_el, points, weights) - return Q, phis - class IntegratedLegendre(finite_element.CiarletElement): """Simplicial continuous element with integrated Legendre polynomials.""" From 2100eef62ac5a591a0f22dbcd7116387f1f28dee Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Mon, 29 Jan 2024 20:45:30 +0000 Subject: [PATCH 65/93] Tabulate facet modes for integral variant --- FIAT/expansions.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/FIAT/expansions.py b/FIAT/expansions.py index 32804ca6c..a7b81cd52 100644 --- a/FIAT/expansions.py +++ b/FIAT/expansions.py @@ -101,7 +101,7 @@ def dubiner_recurrence(dim, n, order, ref_pts, Jinv, scale, variant=None): if variant == "integral": alpha = 2 * sum(sub_index) - a = b = 1.0 + a = b = 0.5 else: alpha = 2 * sum(sub_index) + len(sub_index) if variant == "dual": @@ -133,17 +133,25 @@ def dubiner_recurrence(dim, n, order, ref_pts, Jinv, scale, variant=None): ddphi[inext] = (factor * ddphi[icur] + sym_outer(dphi[icur], dfactor) - c * (fc * ddphi[iprev] + sym_outer(dphi[iprev], dfc) + phi[iprev] * ddfc)) + if variant == "integral": + icur = idx(*sub_index, 0) + inext = idx(*sub_index, 1) + for result in results: + result[icur] -= result[inext] + # normalize d = codim + 1 shift = 1 if variant == "dual" else 0 for index in reference_element.lattice_iter(0, n+1, d): icur = idx(*index) - norm2 = (2*sum(index) + d) / d if variant is not None: p = index[-1] + shift alpha = 2 * (sum(index[:-1]) + d * shift) - 1 + norm2 = 1.0 if p > 0 and p + alpha > 0: - norm2 = (2*d+1) * (p + alpha) * (2*p + alpha) / (2*d*p) + norm2 = (4*d+2) * (p + alpha) * (2*p + alpha) / (d*p) + else: + norm2 = (2*sum(index) + d) / d scale = math.sqrt(norm2) for result in results: From 9a76a25dbe42d4dbdbe9a09ea2b0fdd9f9a7686c Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Mon, 29 Jan 2024 20:45:30 +0000 Subject: [PATCH 66/93] Tabulate facet modes for integral variant --- FIAT/expansions.py | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/FIAT/expansions.py b/FIAT/expansions.py index 32804ca6c..7aaeafd5f 100644 --- a/FIAT/expansions.py +++ b/FIAT/expansions.py @@ -101,7 +101,7 @@ def dubiner_recurrence(dim, n, order, ref_pts, Jinv, scale, variant=None): if variant == "integral": alpha = 2 * sum(sub_index) - a = b = 1.0 + a = b = 0.5 else: alpha = 2 * sum(sub_index) + len(sub_index) if variant == "dual": @@ -138,16 +138,47 @@ def dubiner_recurrence(dim, n, order, ref_pts, Jinv, scale, variant=None): shift = 1 if variant == "dual" else 0 for index in reference_element.lattice_iter(0, n+1, d): icur = idx(*index) - norm2 = (2*sum(index) + d) / d if variant is not None: p = index[-1] + shift alpha = 2 * (sum(index[:-1]) + d * shift) - 1 + norm2 = 1.0 if p > 0 and p + alpha > 0: - norm2 = (2*d+1) * (p + alpha) * (2*p + alpha) / (2*d*p) + norm2 = (4*d+2) * (p + alpha) * (2*p + alpha) / (d*p) + else: + norm2 = (2*sum(index) + d) / d scale = math.sqrt(norm2) for result in results: result[icur] *= scale + + if variant == "integral": + icur = 0 + for inext in range(1, dim+1): + for result in results: + result[icur] -= result[inext] + + if dim == 2: + for i in range(2, n+1): + icur = idx(0, i) + iprev = idx(1, i-1) + for result in results: + result[icur] += result[iprev] + + elif dim == 3: + for i in range(2, n+1): + for j in range(0, n+1-i): + icur = idx(0, i, j) + iprev = idx(1, i-1, j) + for result in results: + result[icur] += result[iprev] + + icur = idx(0, 0, i) + iprev0 = idx(1, 0, i-1) + iprev1 = idx(0, 1, i-1) + for result in results: + result[icur] += result[iprev0] + result[icur] += result[iprev1] + return results From 3b8e1ba7fe2bad83d53ac0438d07be0134812e6a Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 1 Feb 2024 17:41:21 +0000 Subject: [PATCH 67/93] add scale kwarg to ONPolynomialSet and ExpansionSet, tabulate facet bubbles for integral variant and add the dual variant --- FIAT/expansions.py | 85 +++++++++++++++++++++++++++++++----------- FIAT/polynomial_set.py | 10 ++--- 2 files changed, 68 insertions(+), 27 deletions(-) diff --git a/FIAT/expansions.py b/FIAT/expansions.py index 3b2c07349..a8eaadb20 100644 --- a/FIAT/expansions.py +++ b/FIAT/expansions.py @@ -9,8 +9,7 @@ import numpy import math -from FIAT import reference_element -from FIAT import jacobi +from FIAT import reference_element, jacobi def morton_index2(p, q=0): @@ -63,7 +62,7 @@ def jacobi_factors(x, y, z, dx, dy, dz): return fa, fb, fc, dfa, dfb, dfc -def dubiner_recurrence(dim, n, order, ref_pts, jacobian, variant=None): +def dubiner_recurrence(dim, n, order, ref_pts, Jinv, scale, variant=None): """Dubiner recurrence from (Kirby 2010)""" if order > 2: raise ValueError("Higher order derivatives not supported") @@ -76,8 +75,11 @@ def dubiner_recurrence(dim, n, order, ref_pts, jacobian, variant=None): sym_outer = lambda x, y: outer(x, y) + outer(y, x) pad_dim = dim + 2 - dX = pad_jacobian(jacobian, pad_dim) - phi[0] = sum((ref_pts[i] - ref_pts[i] for i in range(dim)), 1.) + dX = pad_jacobian(Jinv, pad_dim) + if variant == "integral": + scale = -scale + + phi[0] = sum((ref_pts[i] - ref_pts[i] for i in range(dim)), scale) if dphi is not None: dphi[0] = (phi[0] - phi[0]) * dX[0] if ddphi is not None: @@ -87,6 +89,7 @@ def dubiner_recurrence(dim, n, order, ref_pts, jacobian, variant=None): if dim > 3 or dim < 0: raise ValueError("Invalid number of spatial dimensions") + beta = 1 if variant == "dual" else 0 coefficients = integrated_jrc if variant == "integral" else jrc X = pad_coordinates(ref_pts, pad_dim) idx = (lambda p: p, morton_index2, morton_index3)[dim-1] @@ -99,12 +102,13 @@ def dubiner_recurrence(dim, n, order, ref_pts, jacobian, variant=None): icur = idx(*sub_index, 0) inext = idx(*sub_index, 1) - beta = 0 if variant == "integral": alpha = 2 * sum(sub_index) - a = b = 1.0 + a = b = -0.5 else: alpha = 2 * sum(sub_index) + len(sub_index) + if variant == "dual": + alpha += 1 + len(sub_index) a = 0.5 * (alpha + beta) + 1.0 b = 0.5 * (alpha - beta) @@ -133,19 +137,53 @@ def dubiner_recurrence(dim, n, order, ref_pts, jacobian, variant=None): c * (fc * ddphi[iprev] + sym_outer(dphi[iprev], dfc) + phi[iprev] * ddfc)) # normalize - for index in reference_element.lattice_iter(0, n+1, codim+1): - p = index[-1] - d = len(index) - alpha = 2 * sum(index) - 1 - if variant == "integral" and p > (d == 1) and alpha > p: - norm2 = (d*(2*d+1)*(alpha - p)*alpha) / p - else: - norm2 = 2 * sum(index) + len(index) - - scale = math.sqrt(0.5 * norm2) + d = codim + 1 + shift = 1 if variant == "dual" else 0 + for index in reference_element.lattice_iter(0, n+1, d): icur = idx(*index) + if variant is not None: + p = index[-1] + shift + alpha = 2 * (sum(index[:-1]) + d * shift) - 1 + norm2 = 1.0 + if p > 0 and p + alpha > 0: + norm2 = (p + alpha) * (2*p + alpha) / p + norm2 /= 2 * math.sqrt((1, 1, 6, 18)[d]) + else: + norm2 = (2*sum(index) + d) / d + scale = math.sqrt(norm2) for result in results: result[icur] *= scale + + # recover facet modes + if variant == "integral": + icur = 0 + result[icur] *= -1 + for inext in range(1, dim+1): + for result in results: + result[icur] -= result[inext] + + if dim == 2: + for i in range(2, n+1): + icur = idx(0, i) + iprev = idx(1, i-1) + for result in results: + result[icur] -= result[iprev] + + elif dim == 3: + for i in range(2, n+1): + for j in range(0, n+1-i): + icur = idx(0, i, j) + iprev = idx(1, i-1, j) + for result in results: + result[icur] -= result[iprev] + + icur = idx(0, 0, i) + iprev0 = idx(1, 0, i-1) + iprev1 = idx(0, 1, i-1) + for result in results: + result[icur] -= result[iprev0] + result[icur] -= result[iprev1] + return results @@ -184,8 +222,13 @@ def __new__(cls, *args, **kwargs): except KeyError: raise ValueError("Invalid reference element type.") - def __init__(self, ref_el, variant=None): + def __init__(self, ref_el, scale=None, variant=None): + if scale is None: + scale = math.sqrt(1.0 / ref_el.volume()) + elif isinstance(scale, str) and scale.lower() == "l2 piola": + scale = 1.0 / ref_el.volume() self.ref_el = ref_el + self.scale = scale self.variant = variant dim = ref_el.get_spatial_dimension() self.base_ref_el = reference_element.default_simplex(dim) @@ -193,8 +236,6 @@ def __init__(self, ref_el, variant=None): v2 = self.base_ref_el.get_vertices() self.A, self.b = reference_element.make_affine_mapping(v1, v2) self.mapping = lambda x: numpy.dot(self.A, x) + self.b - detA = numpy.sqrt(numpy.linalg.det(numpy.dot(self.A.T, self.A))) - self.scale = numpy.sqrt(detA) self._dmats_cache = {} def get_num_members(self, n): @@ -213,7 +254,7 @@ def _tabulate(self, n, pts, order=0): """A version of tabulate() that also works for a single point. """ D = self.ref_el.get_spatial_dimension() - return dubiner_recurrence(D, n, order, self._mapping(pts), self.A, variant=self.variant) + return dubiner_recurrence(D, n, order, self._mapping(pts), self.A, self.scale, variant=self.variant) def get_dmats(self, degree): """Returns a numpy array with the expansion coefficients dmat[k, j, i] @@ -321,7 +362,7 @@ def _tabulate(self, n, pts, order=0): xs = self._mapping(pts).T results = [] - scale = numpy.sqrt(0.5 + numpy.arange(n+1)) + scale = self.scale * numpy.sqrt(2 * numpy.arange(n+1) + 1) for k in range(order+1): v = numpy.zeros((n + 1, len(xs)), xs.dtype) if n >= k: diff --git a/FIAT/polynomial_set.py b/FIAT/polynomial_set.py index 8997aad5a..64271edd5 100644 --- a/FIAT/polynomial_set.py +++ b/FIAT/polynomial_set.py @@ -121,7 +121,7 @@ class ONPolynomialSet(PolynomialSet): """ - def __init__(self, ref_el, degree, shape=tuple(), variant=None): + def __init__(self, ref_el, degree, shape=tuple(), scale=None, variant=None): if shape == tuple(): num_components = 1 @@ -131,7 +131,7 @@ def __init__(self, ref_el, degree, shape=tuple(), variant=None): num_exp_functions = expansions.polynomial_dimension(ref_el, degree) num_members = num_components * num_exp_functions embedded_degree = degree - expansion_set = expansions.ExpansionSet(ref_el, variant=variant) + expansion_set = expansions.ExpansionSet(ref_el, scale=scale, variant=variant) # set up coefficients if shape == tuple(): @@ -212,7 +212,7 @@ class ONSymTensorPolynomialSet(PolynomialSet): """ - def __init__(self, ref_el, degree, size=None): + def __init__(self, ref_el, degree, size=None, scale=None): sd = ref_el.get_spatial_dimension() if size is None: @@ -223,7 +223,7 @@ def __init__(self, ref_el, degree, size=None): num_components = size * (size + 1) // 2 num_members = num_components * num_exp_functions embedded_degree = degree - expansion_set = expansions.ExpansionSet(ref_el) + expansion_set = expansions.ExpansionSet(ref_el, scale=scale) # set up coefficients for symmetric tensors coeffs_shape = (num_members, *shape, num_exp_functions) @@ -249,7 +249,7 @@ def make_bubbles(ref_el, degree, shape=()): """ dim = ref_el.get_spatial_dimension() - poly_set = ONPolynomialSet(ref_el, degree, shape=shape, variant="integral") + poly_set = ONPolynomialSet(ref_el, degree, shape=shape, scale="L2 piola", variant="integral") degrees = chain(range(dim + 1, degree+1, 2), range(dim + 2, degree+1, 2)) if dim == 1: From ec123449a10a7deb705809327969976975557799 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 1 Feb 2024 17:46:21 +0000 Subject: [PATCH 68/93] remove SimplexFDMDualSet --- FIAT/fdm_element.py | 87 +++++++-------------------------------------- 1 file changed, 13 insertions(+), 74 deletions(-) diff --git a/FIAT/fdm_element.py b/FIAT/fdm_element.py index 1f7bd8f72..5f6408105 100644 --- a/FIAT/fdm_element.py +++ b/FIAT/fdm_element.py @@ -9,13 +9,12 @@ import abc import numpy -from FIAT import dual_set, finite_element, functional, polynomial_set, quadrature +from FIAT import dual_set, finite_element, functional, quadrature from FIAT.barycentric_interpolation import LagrangePolynomialSet from FIAT.hierarchical import IntegratedLegendre from FIAT.orientation_utils import make_entity_permutations_simplex from FIAT.P0 import P0Dual -from FIAT.quadrature_schemes import create_quadrature -from FIAT.reference_element import LINE, symmetric_simplex +from FIAT.reference_element import LINE def sym_eig(A, B): @@ -144,61 +143,6 @@ def __init__(self, ref_el, degree, bc_order=1, formdegree=0, orthogonalize=False super(FDMDual, self).__init__(nodes, ref_el, entity_ids, entity_permutations) -class SimplexFDMDualSet(dual_set.DualSet): - - def __init__(self, ref_el, degree): - sd = ref_el.get_spatial_dimension() - S = symmetric_simplex(sd) - CG = IntegratedLegendre(S, degree, variant="demkowicz") - - Q = create_quadrature(S, 2 * degree) - X, W = Q.get_points(), Q.get_weights() - - V = CG.tabulate(1, X) - inner = lambda v: numpy.dot(numpy.multiply(v, W), v.T) - galerkin = lambda order, dofs: sum(inner(V[k][dofs]) for k in V if sum(k) == order) - - nodes = [] - entity_ids = {} - - CG_nodes = CG.dual_basis() - entity_dofs = CG.entity_dofs() - for dim in sorted(entity_dofs): - entity_ids[dim] = {} - dofs = entity_dofs[dim][0] - if len(dofs) == 0 or dim == 0: - for entity in sorted(entity_dofs[dim]): - cur = len(nodes) - points = ref_el.make_points(dim, entity, degree) - nodes.extend(functional.PointEvaluation(ref_el, pt) for pt in points) - entity_ids[dim][entity] = list(range(cur, len(nodes))) - continue - - B = galerkin(0, dofs) - A = galerkin(1, dofs) - _, S = sym_eig(B, A) - Sinv = numpy.dot(S.T, A) - fs_at_qpts = numpy.array([CG_nodes[i].f_at_qpts for i in dofs]) - phis = numpy.dot(Sinv, fs_at_qpts) - - Q_dof = CG_nodes[dofs[0]].Q - Q_ref = Q_dof.reference_rule() - phis *= Q_dof.jacobian_determinant() - for entity in sorted(entity_dofs[dim]): - cur = len(nodes) - Q_facet = quadrature.FacetQuadratureRule(ref_el, dim, entity, Q_ref) - - # phis must transform like a d-form to undo the measure transformation - scale = 1 / Q_facet.jacobian_determinant() - Jphis = scale * phis - - nodes.extend(functional.IntegralMoment(ref_el, Q_facet, phi) for phi in Jphis) - entity_ids[dim][entity] = list(range(cur, len(nodes))) - - entity_permutations = CG.entity_permutations() - super(SimplexFDMDualSet, self).__init__(nodes, ref_el, entity_ids, entity_permutations) - - class FDMFiniteElement(finite_element.CiarletElement): """1D element that diagonalizes bilinear forms with BCs.""" @@ -215,23 +159,18 @@ def _formdegree(self): pass def __init__(self, ref_el, degree): - if ref_el.shape == LINE: - if degree == 0: - dual = P0Dual(ref_el) - else: - dual = FDMDual(ref_el, degree, bc_order=self._bc_order, - formdegree=self._formdegree, orthogonalize=self._orthogonalize) - if self._formdegree == 0: - poly_set = dual.embedded.poly_set - else: - lr = quadrature.GaussLegendreQuadratureLineRule(ref_el, degree+1) - poly_set = LagrangePolynomialSet(ref_el, lr.get_points()) + if ref_el.shape != LINE: + raise ValueError("%s is only defined in one dimension." % type(self)) + if degree == 0: + dual = P0Dual(ref_el) else: - assert self._formdegree == 0 - assert self._bc_order == 1 - dual = SimplexFDMDualSet(ref_el, degree) - poly_set = polynomial_set.ONPolynomialSet(ref_el, degree, variant="integral") - + dual = FDMDual(ref_el, degree, bc_order=self._bc_order, + formdegree=self._formdegree, orthogonalize=self._orthogonalize) + if self._formdegree == 0: + poly_set = dual.embedded.poly_set + else: + lr = quadrature.GaussLegendreQuadratureLineRule(ref_el, degree+1) + poly_set = LagrangePolynomialSet(ref_el, lr.get_points()) super(FDMFiniteElement, self).__init__(poly_set, dual, degree, self._formdegree) From 98648d0d81b34286d55b9d73131b7cce4e810b9d Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 1 Feb 2024 17:48:31 +0000 Subject: [PATCH 69/93] Only use integral duals for IntegratedLegendre --- FIAT/hierarchical.py | 115 ++++++------------------------------------- 1 file changed, 16 insertions(+), 99 deletions(-) diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index 7db3978ff..12d15aefb 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -9,11 +9,10 @@ import numpy import scipy -from FIAT import finite_element, dual_set, functional, Lagrange +from FIAT import finite_element, dual_set, functional, demkowicz, Lagrange from FIAT.reference_element import (POINT, LINE, TRIANGLE, TETRAHEDRON, make_lattice, symmetric_simplex) from FIAT.orientation_utils import make_entity_permutations_simplex -from FIAT.barycentric_interpolation import make_dmat from FIAT.quadrature import QuadratureRule, FacetQuadratureRule from FIAT.quadrature_schemes import create_quadrature from FIAT.polynomial_set import ONPolynomialSet, make_bubbles @@ -56,17 +55,8 @@ def __init__(self, ref_el, degree): class IntegratedLegendreDual(dual_set.DualSet): """The dual basis for integrated Legendre elements.""" - def __init__(self, ref_el, degree, variant=None): - if variant is None: - variant = "beuchler" - - duals = { - "beuchler": self._beuchler_integral_duals, - "beuchler_point": self._beuchler_point_duals, - "demkowicz": self._demkowicz_integral_duals, - "demkowicz_point": self._demkowicz_point_duals, - "orthonormal": self._orthonormal_duals, - }[variant] + def __init__(self, ref_el, degree): + duals = self._beuchler_integral_duals nodes = [] entity_ids = {} @@ -74,19 +64,18 @@ def __init__(self, ref_el, degree, variant=None): top = ref_el.get_topology() for dim in sorted(top): + perms = make_entity_permutations_simplex(dim, degree - dim) entity_ids[dim] = {} entity_permutations[dim] = {} - if dim == 0: - perms = {0: [0]} + if dim == 0 or degree <= dim: for entity in sorted(top[dim]): cur = len(nodes) - pt, = ref_el.make_points(0, entity, degree) - nodes.append(functional.PointEvaluation(ref_el, pt)) + pts = ref_el.make_points(dim, entity, degree) + nodes.extend(functional.PointEvaluation(ref_el, pt) for pt in pts) entity_ids[dim][entity] = list(range(cur, len(nodes))) entity_permutations[dim][entity] = perms continue - perms = make_entity_permutations_simplex(dim, degree - dim) ref_facet = symmetric_simplex(dim) Q_ref, phis = duals(ref_facet, degree) for entity in sorted(top[dim]): @@ -103,17 +92,6 @@ def __init__(self, ref_el, degree, variant=None): super(IntegratedLegendreDual, self).__init__(nodes, ref_el, entity_ids, entity_permutations) - def _beuchler_point_duals(self, ref_el, degree, variant="gll"): - points = make_lattice(ref_el.vertices, degree, variant=variant) - weights = (1.0,) * len(points) - Q = QuadratureRule(ref_el, points, weights) - B = make_bubbles(ref_el, degree) - V = numpy.transpose(B.expansion_set.tabulate(degree, points)) - - PLU = scipy.linalg.lu_factor(V) - phis = scipy.linalg.lu_solve(PLU, B.get_coeffs().T, trans=1).T - return Q, phis - def _beuchler_integral_duals(self, ref_el, degree): Q = create_quadrature(ref_el, 2 * degree) qpts, qwts = Q.get_points(), Q.get_weights() @@ -133,75 +111,6 @@ def _beuchler_integral_duals(self, ref_el, degree): phis = numpy.dot(B.get_coeffs(), phis) return Q, phis - def _demkowicz_point_duals(self, ref_el, degree, variant="gll"): - Qkm1 = create_quadrature(ref_el, 2 * (degree-1)) - qpts, qwts = Qkm1.get_points(), Qkm1.get_weights() - inner = lambda v, u: numpy.dot(numpy.multiply(v, qwts), u.T) - galerkin = lambda order, V, U: sum(inner(V[k], U[k]) for k in V if sum(k) == order) - - P = Lagrange(ref_el, degree, variant=variant) - P_table = P.tabulate(1, qpts) - B = make_bubbles(ref_el, degree) - B_table = B.tabulate(qpts, 1) - phis = galerkin(1, B_table, P_table) - - points = [] - for node in P.dual_basis(): - pt, = node.get_point_dict() - points.append(pt) - weights = (1.0,) * len(points) - Q = QuadratureRule(ref_el, points, weights) - return Q, phis - - def _demkowicz_integral_duals(self, ref_el, degree): - Q = create_quadrature(ref_el, 2 * degree) - qpts, qwts = Q.get_points(), Q.get_weights() - moments = lambda v: numpy.dot(numpy.multiply(v, qwts), v.T) - - dim = ref_el.get_spatial_dimension() - if dim == 1: - # Assemble a stiffness matrix in the Lagrange basis - dmat, _ = make_dmat(qpts.flatten()) - K = moments(dmat) - else: - # Get ON basis - P = ONPolynomialSet(ref_el, degree) - P_table = P.tabulate(qpts, 1) - # Assemble a stiffness matrix in the ON basis - K = sum(moments(P_table[alpha]) for alpha in P_table if sum(alpha) == 1) - # Change of basis to Lagrange polynomials at the quadrature nodes - V = numpy.multiply(P_table[(0, ) * dim], qwts) - K = numpy.dot(numpy.dot(V.T, K), V) - - B = make_bubbles(ref_el, degree) - phis = B.tabulate(qpts)[(0,) * dim] - phis = numpy.multiply(numpy.dot(phis, K), 1/qwts) - return Q, phis - - def _orthonormal_duals(self, ref_el, degree): - dim = ref_el.get_spatial_dimension() - - Q = create_quadrature(ref_el, 2 * degree) - qpts, qwts = Q.get_points(), Q.get_weights() - inner = lambda v, u: numpy.dot(numpy.multiply(v, qwts), u.T) - galerkin = lambda order, V, U: sum(inner(V[k], U[k]) for k in V if sum(k) == order) - - B = make_bubbles(ref_el, degree) - B_table = B.tabulate(qpts, 1) - - P = ONPolynomialSet(ref_el, degree) - P_table = P.tabulate(qpts, 1) - - KBP = galerkin(1, B_table, P_table) - phis = P_table[(0,) * dim] - phis = numpy.dot(KBP, phis) - - if len(phis) > 0: - KBB = galerkin(1, B_table, B_table) - V = numpy.linalg.cholesky(KBB) - phis = numpy.linalg.solve(V, phis) - return Q, phis - class IntegratedLegendre(finite_element.CiarletElement): """Simplicial continuous element with integrated Legendre polynomials.""" @@ -209,7 +118,15 @@ class IntegratedLegendre(finite_element.CiarletElement): def __init__(self, ref_el, degree, variant=None): if ref_el.shape not in {POINT, LINE, TRIANGLE, TETRAHEDRON}: raise ValueError("%s is only defined on simplices." % type(self)) + if degree < 1: + raise ValueError(f"{type(self).__name__} elements only valid for k >= 1") + poly_set = ONPolynomialSet(ref_el, degree, variant="integral") - dual = IntegratedLegendreDual(ref_el, degree, variant=variant) + if variant == "demkowicz": + dual = demkowicz.DemkowiczDual(ref_el, degree, "H1") + elif variant == "fdm": + dual = demkowicz.FDMDual(ref_el, degree, "H1", type(self)) + else: + dual = IntegratedLegendreDual(ref_el, degree) formdegree = 0 # 0-form super(IntegratedLegendre, self).__init__(poly_set, dual, degree, formdegree) From 13044a53a570179c1c29709cfd1110453d8f8322 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 1 Feb 2024 18:06:49 +0000 Subject: [PATCH 70/93] refactoring --- FIAT/expansions.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/FIAT/expansions.py b/FIAT/expansions.py index a8eaadb20..4aec16ab0 100644 --- a/FIAT/expansions.py +++ b/FIAT/expansions.py @@ -66,6 +66,8 @@ def dubiner_recurrence(dim, n, order, ref_pts, Jinv, scale, variant=None): """Dubiner recurrence from (Kirby 2010)""" if order > 2: raise ValueError("Higher order derivatives not supported") + if variant == "integral": + scale = -scale num_members = math.comb(n + dim, dim) results = tuple([None] * num_members for i in range(order+1)) @@ -76,9 +78,6 @@ def dubiner_recurrence(dim, n, order, ref_pts, Jinv, scale, variant=None): pad_dim = dim + 2 dX = pad_jacobian(Jinv, pad_dim) - if variant == "integral": - scale = -scale - phi[0] = sum((ref_pts[i] - ref_pts[i] for i in range(dim)), scale) if dphi is not None: dphi[0] = (phi[0] - phi[0]) * dX[0] From e87d80dff41d3d2bf7995c6bf5d485e57e2918e2 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 1 Feb 2024 18:09:28 +0000 Subject: [PATCH 71/93] flake8 --- FIAT/hierarchical.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index 12d15aefb..c9bc01d98 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -9,11 +9,11 @@ import numpy import scipy -from FIAT import finite_element, dual_set, functional, demkowicz, Lagrange +from FIAT import finite_element, dual_set, functional from FIAT.reference_element import (POINT, LINE, TRIANGLE, TETRAHEDRON, - make_lattice, symmetric_simplex) + symmetric_simplex) from FIAT.orientation_utils import make_entity_permutations_simplex -from FIAT.quadrature import QuadratureRule, FacetQuadratureRule +from FIAT.quadrature import FacetQuadratureRule from FIAT.quadrature_schemes import create_quadrature from FIAT.polynomial_set import ONPolynomialSet, make_bubbles @@ -122,11 +122,6 @@ def __init__(self, ref_el, degree, variant=None): raise ValueError(f"{type(self).__name__} elements only valid for k >= 1") poly_set = ONPolynomialSet(ref_el, degree, variant="integral") - if variant == "demkowicz": - dual = demkowicz.DemkowiczDual(ref_el, degree, "H1") - elif variant == "fdm": - dual = demkowicz.FDMDual(ref_el, degree, "H1", type(self)) - else: - dual = IntegratedLegendreDual(ref_el, degree) + dual = IntegratedLegendreDual(ref_el, degree) formdegree = 0 # 0-form super(IntegratedLegendre, self).__init__(poly_set, dual, degree, formdegree) From 7351d411cf82e29f4203b0422e221d0537fa3272 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 1 Feb 2024 18:36:45 +0000 Subject: [PATCH 72/93] set default normalization for orthonoramlity on the default simplex --- FIAT/expansions.py | 16 ++++++++++------ test/unit/test_fiat.py | 16 +++++++++------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/FIAT/expansions.py b/FIAT/expansions.py index 4aec16ab0..b64a7ef12 100644 --- a/FIAT/expansions.py +++ b/FIAT/expansions.py @@ -146,7 +146,7 @@ def dubiner_recurrence(dim, n, order, ref_pts, Jinv, scale, variant=None): norm2 = 1.0 if p > 0 and p + alpha > 0: norm2 = (p + alpha) * (2*p + alpha) / p - norm2 /= 2 * math.sqrt((1, 1, 6, 18)[d]) + norm2 *= (2*d+1) / (2*d) else: norm2 = (2*sum(index) + d) / d scale = math.sqrt(norm2) @@ -222,12 +222,7 @@ def __new__(cls, *args, **kwargs): raise ValueError("Invalid reference element type.") def __init__(self, ref_el, scale=None, variant=None): - if scale is None: - scale = math.sqrt(1.0 / ref_el.volume()) - elif isinstance(scale, str) and scale.lower() == "l2 piola": - scale = 1.0 / ref_el.volume() self.ref_el = ref_el - self.scale = scale self.variant = variant dim = ref_el.get_spatial_dimension() self.base_ref_el = reference_element.default_simplex(dim) @@ -236,6 +231,15 @@ def __init__(self, ref_el, scale=None, variant=None): self.A, self.b = reference_element.make_affine_mapping(v1, v2) self.mapping = lambda x: numpy.dot(self.A, x) + self.b self._dmats_cache = {} + if scale is None: + scale = math.sqrt(1.0 / self.base_ref_el.volume()) + elif isinstance(scale, str): + scale = scale.lower() + if scale == "orthonormal": + scale = math.sqrt(1.0 / ref_el.volume()) + elif scale == "l2 piola": + scale = 1.0 / ref_el.volume() + self.scale = scale def get_num_members(self, n): D = self.ref_el.get_spatial_dimension() diff --git a/test/unit/test_fiat.py b/test/unit/test_fiat.py index 201bc1f7d..bedf35daf 100644 --- a/test/unit/test_fiat.py +++ b/test/unit/test_fiat.py @@ -539,7 +539,7 @@ def test_expansion_orthonormality(cell): rule = create_quadrature(cell, 2*degree) phi = U.tabulate(degree, rule.pts) qwts = rule.get_weights() - scale = 0.5 ** -cell.get_spatial_dimension() + scale = 2 ** cell.get_spatial_dimension() results = scale * np.dot(np.multiply(phi, qwts), phi.T) assert np.allclose(results, np.diag(np.diag(results))) @@ -640,8 +640,7 @@ def test_make_bubbles(cell): P.get_num_members()))) Q = create_quadrature(cell, P.degree + B.degree) - qpts = Q.get_points() - qwts = Q.get_weights() + qpts, qwts = Q.get_points(), Q.get_weights() P_at_qpts = P.tabulate(qpts)[(0,) * sd] B_at_qpts = B.tabulate(qpts)[(0,) * sd] assert np.allclose(np.dot(np.multiply(P_at_qpts, qwts), B_at_qpts.T), 0.0) @@ -654,10 +653,13 @@ def test_bubble_duality(cell): degree = 10 sd = cell.get_spatial_dimension() B = make_bubbles(cell, degree) - rule = create_quadrature(cell, 2*degree) - phi = B.tabulate(rule.pts)[(0,) * sd] - qwts = rule.get_weights() / abs(phi[0]) - results = np.dot(np.multiply(phi, qwts), phi.T) + + Q = create_quadrature(cell, 2*B.degree - sd - 1) + qpts, qwts = Q.get_points(), Q.get_weights() + phi = B.tabulate(qpts)[(0,) * sd] + phi_dual = phi / abs(phi[0]) + scale = 2 ** sd + results = scale * np.dot(np.multiply(phi_dual, qwts), phi.T) assert np.allclose(results, np.diag(np.diag(results))) assert np.allclose(np.diag(results), 1.0) From e2b179aaad47ce94373ddd066a64bd2c78cf3d4e Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 1 Feb 2024 19:00:31 +0000 Subject: [PATCH 73/93] fix rescaling --- FIAT/expansions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/FIAT/expansions.py b/FIAT/expansions.py index b64a7ef12..3d0d01825 100644 --- a/FIAT/expansions.py +++ b/FIAT/expansions.py @@ -156,7 +156,8 @@ def dubiner_recurrence(dim, n, order, ref_pts, Jinv, scale, variant=None): # recover facet modes if variant == "integral": icur = 0 - result[icur] *= -1 + for result in results: + result[icur] *= -1 for inext in range(1, dim+1): for result in results: result[icur] -= result[inext] From 04f9f7f4b0dbb8bab5fb1db6c28bae517d0b2d44 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 1 Feb 2024 19:03:06 +0000 Subject: [PATCH 74/93] update expansions.py, polynomial_set.py --- FIAT/expansions.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/FIAT/expansions.py b/FIAT/expansions.py index a8eaadb20..3d0d01825 100644 --- a/FIAT/expansions.py +++ b/FIAT/expansions.py @@ -66,6 +66,8 @@ def dubiner_recurrence(dim, n, order, ref_pts, Jinv, scale, variant=None): """Dubiner recurrence from (Kirby 2010)""" if order > 2: raise ValueError("Higher order derivatives not supported") + if variant == "integral": + scale = -scale num_members = math.comb(n + dim, dim) results = tuple([None] * num_members for i in range(order+1)) @@ -76,9 +78,6 @@ def dubiner_recurrence(dim, n, order, ref_pts, Jinv, scale, variant=None): pad_dim = dim + 2 dX = pad_jacobian(Jinv, pad_dim) - if variant == "integral": - scale = -scale - phi[0] = sum((ref_pts[i] - ref_pts[i] for i in range(dim)), scale) if dphi is not None: dphi[0] = (phi[0] - phi[0]) * dX[0] @@ -147,7 +146,7 @@ def dubiner_recurrence(dim, n, order, ref_pts, Jinv, scale, variant=None): norm2 = 1.0 if p > 0 and p + alpha > 0: norm2 = (p + alpha) * (2*p + alpha) / p - norm2 /= 2 * math.sqrt((1, 1, 6, 18)[d]) + norm2 *= (2*d+1) / (2*d) else: norm2 = (2*sum(index) + d) / d scale = math.sqrt(norm2) @@ -157,7 +156,8 @@ def dubiner_recurrence(dim, n, order, ref_pts, Jinv, scale, variant=None): # recover facet modes if variant == "integral": icur = 0 - result[icur] *= -1 + for result in results: + result[icur] *= -1 for inext in range(1, dim+1): for result in results: result[icur] -= result[inext] @@ -223,12 +223,7 @@ def __new__(cls, *args, **kwargs): raise ValueError("Invalid reference element type.") def __init__(self, ref_el, scale=None, variant=None): - if scale is None: - scale = math.sqrt(1.0 / ref_el.volume()) - elif isinstance(scale, str) and scale.lower() == "l2 piola": - scale = 1.0 / ref_el.volume() self.ref_el = ref_el - self.scale = scale self.variant = variant dim = ref_el.get_spatial_dimension() self.base_ref_el = reference_element.default_simplex(dim) @@ -237,6 +232,15 @@ def __init__(self, ref_el, scale=None, variant=None): self.A, self.b = reference_element.make_affine_mapping(v1, v2) self.mapping = lambda x: numpy.dot(self.A, x) + self.b self._dmats_cache = {} + if scale is None: + scale = math.sqrt(1.0 / self.base_ref_el.volume()) + elif isinstance(scale, str): + scale = scale.lower() + if scale == "orthonormal": + scale = math.sqrt(1.0 / ref_el.volume()) + elif scale == "l2 piola": + scale = 1.0 / ref_el.volume() + self.scale = scale def get_num_members(self, n): D = self.ref_el.get_spatial_dimension() From d34e4e64fd93cb61870f2b597734b0e6c637f66b Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 1 Feb 2024 19:07:08 +0000 Subject: [PATCH 75/93] make regression tests pass --- FIAT/expansions.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/FIAT/expansions.py b/FIAT/expansions.py index 3d0d01825..3598017b0 100644 --- a/FIAT/expansions.py +++ b/FIAT/expansions.py @@ -68,6 +68,9 @@ def dubiner_recurrence(dim, n, order, ref_pts, Jinv, scale, variant=None): raise ValueError("Higher order derivatives not supported") if variant == "integral": scale = -scale + if n == 0: + # This is to make regression tests pass + scale = 1.0 num_members = math.comb(n + dim, dim) results = tuple([None] * num_members for i in range(order+1)) From 3b553510c32b083d685a76f826931563a02703a5 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Fri, 2 Feb 2024 10:18:36 +0000 Subject: [PATCH 76/93] add docstring to dubiner_recurrence --- FIAT/expansions.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/FIAT/expansions.py b/FIAT/expansions.py index 3598017b0..854855d8e 100644 --- a/FIAT/expansions.py +++ b/FIAT/expansions.py @@ -63,13 +63,29 @@ def jacobi_factors(x, y, z, dx, dy, dz): def dubiner_recurrence(dim, n, order, ref_pts, Jinv, scale, variant=None): - """Dubiner recurrence from (Kirby 2010)""" + """Tabulate a Dubiner expansion set using the recurrence from (Kirby 2010). + + :arg dim: The spatial dimension of the simplex. + :arg n: The polynomial degree. + :arg order: The maximum order of differenation. + :arg ref_pts: An ``ndarray`` with the coordinates on the default (-1, 1)^d simplex. + :arg Jinv: The inverse of the Jacobian of the coordinate mapping from the default simplex. + :arg scale: A scale factor that sets the first member of expansion set. + :arg variant: Choose between the default (None) orthogonal basis, + 'integral' for integrated Jacobi polynomials, + or 'dual' for the L2-duals of the integrated Jacobi polynomials. + + :returns: A tuple with tabulations of the expansion set and its derivatives. + """ if order > 2: raise ValueError("Higher order derivatives not supported") + if variant not in [None, "integral", "dual"]: + raise ValueError(f"Invalid variant {variant}") + if variant == "integral": scale = -scale if n == 0: - # This is to make regression tests pass + # Always return 1 for n=0 to make regression tests pass scale = 1.0 num_members = math.comb(n + dim, dim) @@ -156,7 +172,7 @@ def dubiner_recurrence(dim, n, order, ref_pts, Jinv, scale, variant=None): for result in results: result[icur] *= scale - # recover facet modes + # recover facet bubbles if variant == "integral": icur = 0 for result in results: From 8a7b6438124558f13e547e9b2f32328be8591c37 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Fri, 2 Feb 2024 10:31:45 +0000 Subject: [PATCH 77/93] flake --- FIAT/expansions.py | 23 +++++++++++++++++++++-- FIAT/hierarchical.py | 6 +++--- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/FIAT/expansions.py b/FIAT/expansions.py index 3d0d01825..854855d8e 100644 --- a/FIAT/expansions.py +++ b/FIAT/expansions.py @@ -63,11 +63,30 @@ def jacobi_factors(x, y, z, dx, dy, dz): def dubiner_recurrence(dim, n, order, ref_pts, Jinv, scale, variant=None): - """Dubiner recurrence from (Kirby 2010)""" + """Tabulate a Dubiner expansion set using the recurrence from (Kirby 2010). + + :arg dim: The spatial dimension of the simplex. + :arg n: The polynomial degree. + :arg order: The maximum order of differenation. + :arg ref_pts: An ``ndarray`` with the coordinates on the default (-1, 1)^d simplex. + :arg Jinv: The inverse of the Jacobian of the coordinate mapping from the default simplex. + :arg scale: A scale factor that sets the first member of expansion set. + :arg variant: Choose between the default (None) orthogonal basis, + 'integral' for integrated Jacobi polynomials, + or 'dual' for the L2-duals of the integrated Jacobi polynomials. + + :returns: A tuple with tabulations of the expansion set and its derivatives. + """ if order > 2: raise ValueError("Higher order derivatives not supported") + if variant not in [None, "integral", "dual"]: + raise ValueError(f"Invalid variant {variant}") + if variant == "integral": scale = -scale + if n == 0: + # Always return 1 for n=0 to make regression tests pass + scale = 1.0 num_members = math.comb(n + dim, dim) results = tuple([None] * num_members for i in range(order+1)) @@ -153,7 +172,7 @@ def dubiner_recurrence(dim, n, order, ref_pts, Jinv, scale, variant=None): for result in results: result[icur] *= scale - # recover facet modes + # recover facet bubbles if variant == "integral": icur = 0 for result in results: diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index 12d15aefb..5e1dc3d9f 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -9,11 +9,11 @@ import numpy import scipy -from FIAT import finite_element, dual_set, functional, demkowicz, Lagrange +from FIAT import finite_element, dual_set, functional, demkowicz from FIAT.reference_element import (POINT, LINE, TRIANGLE, TETRAHEDRON, - make_lattice, symmetric_simplex) + symmetric_simplex) from FIAT.orientation_utils import make_entity_permutations_simplex -from FIAT.quadrature import QuadratureRule, FacetQuadratureRule +from FIAT.quadrature import FacetQuadratureRule from FIAT.quadrature_schemes import create_quadrature from FIAT.polynomial_set import ONPolynomialSet, make_bubbles From 7f2c1c03168a9c01d6a2d2d72bd3a338bfeb1fd9 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Fri, 2 Feb 2024 10:52:17 +0000 Subject: [PATCH 78/93] fix test tolerance --- test/unit/test_fiat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/test_fiat.py b/test/unit/test_fiat.py index bedf35daf..5f0b0f28c 100644 --- a/test/unit/test_fiat.py +++ b/test/unit/test_fiat.py @@ -632,7 +632,7 @@ def test_make_bubbles(cell): points = cell.make_points(sd, 0, degree) values = B.tabulate(points)[(0,) * sd] assert values.shape == (m, m) - assert np.linalg.matrix_rank(values.T) == m + assert np.linalg.matrix_rank(values.T, tol=1E-12) == m # test that B does not have components in span(P_{degree+2} \ P_{degree}) P = ONPolynomialSet(cell, degree + 2) From 938c41b10ef16131fbb3ed7b0e7c96f2d81e8a9b Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Fri, 2 Feb 2024 10:57:53 +0000 Subject: [PATCH 79/93] revert scale behaviour to make regression tests pass --- FIAT/brezzi_douglas_marini.py | 2 +- FIAT/nedelec.py | 4 ++-- FIAT/nedelec_second_kind.py | 2 +- FIAT/raviart_thomas.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/FIAT/brezzi_douglas_marini.py b/FIAT/brezzi_douglas_marini.py index 66b5ff798..1f048924a 100644 --- a/FIAT/brezzi_douglas_marini.py +++ b/FIAT/brezzi_douglas_marini.py @@ -30,7 +30,7 @@ def __init__(self, ref_el, degree, variant, interpolant_deg): # Facet nodes are \int_F v\cdot n p ds where p \in P_{q} # degree is q Q_ref = create_quadrature(facet, interpolant_deg + degree) - Pq = polynomial_set.ONPolynomialSet(facet, degree, scale=1) + Pq = polynomial_set.ONPolynomialSet(facet, degree) Pq_at_qpts = Pq.tabulate(Q_ref.get_points())[(0,)*(sd - 1)] for f in top[sd - 1]: cur = len(nodes) diff --git a/FIAT/nedelec.py b/FIAT/nedelec.py index eeb37c2d7..9874e3c93 100644 --- a/FIAT/nedelec.py +++ b/FIAT/nedelec.py @@ -123,7 +123,7 @@ def __init__(self, ref_el, degree, variant, interpolant_deg): if phi_deg >= 0: facet = ref_el.construct_subelement(dim) Q_ref = create_quadrature(facet, interpolant_deg + phi_deg) - Pqmd = polynomial_set.ONPolynomialSet(facet, phi_deg, (dim,), scale="L2 piola") + Pqmd = polynomial_set.ONPolynomialSet(facet, phi_deg, (dim,)) Phis = Pqmd.tabulate(Q_ref.get_points())[(0,) * dim] Phis = numpy.transpose(Phis, (0, 2, 1)) @@ -165,7 +165,7 @@ def __init__(self, ref_el, degree, variant, interpolant_deg): interpolant_deg = degree cur = len(nodes) Q = create_quadrature(ref_el, interpolant_deg + phi_deg) - Pqmd = polynomial_set.ONPolynomialSet(ref_el, phi_deg, scale="L2 piola") + Pqmd = polynomial_set.ONPolynomialSet(ref_el, phi_deg) Phis = Pqmd.tabulate(Q.get_points())[(0,) * dim] nodes.extend(functional.IntegralMoment(ref_el, Q, phi, (d,), (dim,)) for d in range(dim) for phi in Phis) diff --git a/FIAT/nedelec_second_kind.py b/FIAT/nedelec_second_kind.py index 1271c2977..b379ecb04 100644 --- a/FIAT/nedelec_second_kind.py +++ b/FIAT/nedelec_second_kind.py @@ -130,7 +130,7 @@ def _generate_facet_dofs(self, codim, cell, degree, offset, variant, interpolant ref_facet = cell.construct_subelement(codim) Q_ref = create_quadrature(ref_facet, interpolant_deg + rt_degree) if codim == 1: - Phi = ONPolynomialSet(ref_facet, rt_degree, (codim,), scale="L2 piola") + Phi = ONPolynomialSet(ref_facet, rt_degree, (codim,)) else: # Construct Raviart-Thomas on the reference facet RT = RaviartThomas(ref_facet, rt_degree, variant) diff --git a/FIAT/raviart_thomas.py b/FIAT/raviart_thomas.py index cc6c4dace..8dd438949 100644 --- a/FIAT/raviart_thomas.py +++ b/FIAT/raviart_thomas.py @@ -75,7 +75,7 @@ def __init__(self, ref_el, degree, variant, interpolant_deg): # Facet nodes are \int_F v\cdot n p ds where p \in P_{q-1} # degree is q - 1 Q_ref = create_quadrature(facet, interpolant_deg + degree - 1) - Pq = polynomial_set.ONPolynomialSet(facet, degree - 1, scale=1) + Pq = polynomial_set.ONPolynomialSet(facet, degree - 1) Pq_at_qpts = Pq.tabulate(Q_ref.get_points())[(0,)*(sd - 1)] for f in top[sd - 1]: cur = len(nodes) @@ -90,7 +90,7 @@ def __init__(self, ref_el, degree, variant, interpolant_deg): if degree > 1: cur = len(nodes) Q = create_quadrature(ref_el, interpolant_deg + degree - 2) - Pkm1 = polynomial_set.ONPolynomialSet(ref_el, degree - 2, scale=1) + Pkm1 = polynomial_set.ONPolynomialSet(ref_el, degree - 2) Pkm1_at_qpts = Pkm1.tabulate(Q.get_points())[(0,) * sd] nodes.extend(functional.IntegralMoment(ref_el, Q, phi, (d,), (sd,)) for d in range(sd) From 71f7b17caec84d084551ff6690d919f07765b0ac Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Wed, 7 Feb 2024 16:28:54 +0000 Subject: [PATCH 80/93] fix scale --- FIAT/demkowicz.py | 53 ++++++++++++++++++++++++++++++------------ FIAT/polynomial_set.py | 1 + 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/FIAT/demkowicz.py b/FIAT/demkowicz.py index ad0caf5f7..a962e93db 100644 --- a/FIAT/demkowicz.py +++ b/FIAT/demkowicz.py @@ -102,14 +102,14 @@ def _reference_duals(self, dim, degree, formdegree, sobolev_space, kind): Q = create_quadrature(facet, 2 * degree) if formdegree == dim: shp = (dim,) if sobolev_space == "HCurl" else () - P = ONPolynomialSet(facet, k, shp) + P = ONPolynomialSet(facet, k, shp, scale="orthonormal") duals = P.tabulate(Q.get_points())[(0,) * dim] return Q, duals exterior_derivative = {"H1": grad, "HCurl": curl, "HDiv": div}[sobolev_space] Qpts, Qwts = Q.get_points(), Q.get_weights() shp = () if formdegree == 0 else (dim,) - P = ONPolynomialSet(facet, degree, shp) + P = ONPolynomialSet(facet, degree, shp, scale="orthonormal") P_at_qpts = P.tabulate(Qpts, 1) dtrial = exterior_derivative(P_at_qpts) @@ -132,7 +132,7 @@ def _bubble_derivative_moments(self, facet, degree, formdegree, Qpts, Qwts, tria if formdegree >= dim - 1: # We are at the end of the complex # bubbles have derivative in P_k-1 minus constants - Pkm1 = ONPolynomialSet(facet, degree-1, trial.shape[1:-1]) + Pkm1 = ONPolynomialSet(facet, degree-1, trial.shape[1:-1], scale="orthonormal") P0 = Pkm1.take(list(range(1, Pkm1.get_num_members()))) dtest = P0.tabulate(Qpts)[(0,) * dim] return inner(dtest, trial, Qwts) @@ -140,12 +140,12 @@ def _bubble_derivative_moments(self, facet, degree, formdegree, Qpts, Qwts, tria # Get bubbles if formdegree == 0: B = make_bubbles(facet, degree) - elif formdegree == 1: + else: from FIAT.nedelec import Nedelec - fe = Nedelec(facet, degree) + from FIAT.raviart_thomas import RaviartThomas + fe = (Nedelec, RaviartThomas)[formdegree-1](facet, degree) B = fe.get_nodal_basis().take(fe.entity_dofs()[dim][0]) - else: - raise ValueError(f"{formdegree}-form bubbles not supported") + # Tabulate the exterior derivate B_at_qpts = B.tabulate(Qpts, 1) d = (grad, curl, div)[formdegree] @@ -260,7 +260,7 @@ def project_derivative(fe, op): expr = rot(expr) sd = ref_el.get_spatial_dimension() - P = ONPolynomialSet(ref_el, degree, expr.shape[1:-1]) + P = ONPolynomialSet(ref_el, degree, expr.shape[1:-1], scale="orthonormal") wts = P.tabulate(Qpts)[(0,) * sd] numpy.multiply(wts, Qwts, out=wts) coeffs = numpy.tensordot(expr, wts, axes=(range(1, expr.ndim), range(1, expr.ndim))) @@ -269,14 +269,17 @@ def project_derivative(fe, op): if __name__ == "__main__": import matplotlib.pyplot as plt + from scipy.io import savemat, loadmat + from os.path import exists + from FIAT import IntegratedLegendre as CG from FIAT import Nedelec as N1Curl from FIAT import RaviartThomas as N1Div from FIAT import NedelecSecondKind as N2Curl from FIAT import BrezziDouglasMarini as N2Div - dim = 3 - degree = 7 + dim = 2 + degree = 10 ref_el = symmetric_simplex(dim) Q = create_quadrature(ref_el, 2 * degree) Qpts, Qwts = Q.get_points(), Q.get_weights() @@ -293,6 +296,11 @@ def project_derivative(fe, op): fig, axes = plt.subplots(ncols=len(spaces), nrows=2, figsize=(6*len(spaces), 12)) axes = axes.T.flat + fname = "fiat.mat" + mdict = dict() + if exists(fname): + loadmat(fname, mdict=mdict) + for space in spaces: element, d = space_dict[space] fe = element(ref_el, degree, variant) @@ -304,12 +312,27 @@ def project_derivative(fe, op): mats = (stiff, mass) title = f"{type(fe).__name__}({degree})" - names = (f"{title} stiff", f"{title} mass") + names = ("A", "B") for name, A in zip(names, mats): A[abs(A) < 1E-10] = 0.0 - nnz = numpy.count_nonzero(A) + scipy_mat = scipy.sparse.csr_matrix(A) + nnz = scipy_mat.count_nonzero() + ms = 0 ax = next(axes) - ax.spy(A, markersize=0) - ax.pcolor(numpy.log(abs(A))) - ax.set_title(f"{name} nnz {nnz}") + ax.spy(A, markersize=ms) + if ms == 0: + ax.pcolor(numpy.log(abs(A))) + ax.set_title(f"{title} {name} nnz {nnz}") + + if False: + family = {"H1": "Lagrange", "HCurl": "N1curl", "HDiv": "N1div"}[space] + if kind == 2: + family = family.replace("N1", "N2") + mat_name = "%s%dd_%s%d_%s" % (name, dim, family, degree, variant or "integral") + old_mat = mdict.get(mat_name, None) + old_mat = None + if old_mat is None or scipy_mat.shape[0] == old_mat.shape[0]: + mdict[mat_name] = scipy_mat + + savemat(fname, mdict) plt.show() diff --git a/FIAT/polynomial_set.py b/FIAT/polynomial_set.py index 64271edd5..20d4b89fc 100644 --- a/FIAT/polynomial_set.py +++ b/FIAT/polynomial_set.py @@ -261,6 +261,7 @@ def make_bubbles(ref_el, degree, shape=()): for alpha in mis(dim, p): if alpha[0] > 1 and min(alpha[1:]) > 0: indices.append(idx(*alpha)) + # indices = [idx(*alpha) for alpha in numpy.ndindex((degree,)*dim) if min(alpha)>0 and alpha[0]>1 and sum(alpha)<=degree] if shape != (): ncomp = numpy.prod(shape) From 26df18ebce2ea922c17a972589485b204225ab65 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Fri, 5 Jul 2024 15:42:36 +0100 Subject: [PATCH 81/93] Fix nullspace tolerance --- FIAT/demkowicz.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/FIAT/demkowicz.py b/FIAT/demkowicz.py index a962e93db..4b35b895a 100644 --- a/FIAT/demkowicz.py +++ b/FIAT/demkowicz.py @@ -156,7 +156,8 @@ def _bubble_derivative_moments(self, facet, degree, formdegree, Qpts, Qwts, tria B = inner(test, test, Qwts) A = inner(dtest, dtest, Qwts) sig, S = scipy.linalg.eigh(A, B) - nullspace_dim = len([s for s in sig if abs(s) <= 1.e-10]) + tol = sig[-1] * 1E-10 + nullspace_dim = len([s for s in sig if abs(s) <= tol]) S = S[:, nullspace_dim:] S *= numpy.sqrt(1 / sig[None, nullspace_dim:]) # Apply change of basis @@ -278,15 +279,15 @@ def project_derivative(fe, op): from FIAT import NedelecSecondKind as N2Curl from FIAT import BrezziDouglasMarini as N2Div - dim = 2 - degree = 10 + dim = 3 + degree = 7 ref_el = symmetric_simplex(dim) Q = create_quadrature(ref_el, 2 * degree) Qpts, Qwts = Q.get_points(), Q.get_weights() kind = 1 variant = "fdm" - # variant = "demkowicz" + variant = "demkowicz" # variant = None space_dict = {"H1": (CG, grad), "HCurl": (N1Curl if kind == 1 else N2Curl, curl), From 04edaa6c36039675b549aba4dbb48f4053eacfe7 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Mon, 15 Jul 2024 17:51:03 +0100 Subject: [PATCH 82/93] Demkowicz: split lowest-order/high-order DOFs --- FIAT/demkowicz.py | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/FIAT/demkowicz.py b/FIAT/demkowicz.py index 4b35b895a..9ea30aa39 100644 --- a/FIAT/demkowicz.py +++ b/FIAT/demkowicz.py @@ -50,7 +50,7 @@ def map_duals(ref_el, dim, entity, mapping, Q_ref, Phis): Q = FacetQuadratureRule(ref_el, dim, entity, Q_ref) if mapping == "normal": n = ref_el.compute_normal(entity) - phis = n[None, :, None] * Phis[:, None, :] + phis = n[None, :, None] * Phis elif mapping == "covariant": piola_map = numpy.linalg.pinv(Q.jacobian().T) phis = numpy.dot(piola_map, Phis).transpose((1, 0, 2)) @@ -100,23 +100,26 @@ def _reference_duals(self, dim, degree, formdegree, sobolev_space, kind): k = degree if kind == 2 else degree - 1 facet = symmetric_simplex(dim) Q = create_quadrature(facet, 2 * degree) - if formdegree == dim: - shp = (dim,) if sobolev_space == "HCurl" else () - P = ONPolynomialSet(facet, k, shp, scale="orthonormal") - duals = P.tabulate(Q.get_points())[(0,) * dim] - return Q, duals - - exterior_derivative = {"H1": grad, "HCurl": curl, "HDiv": div}[sobolev_space] Qpts, Qwts = Q.get_points(), Q.get_weights() + exterior_derivative = {"H1": grad, "HCurl": curl, "HDiv": div}[sobolev_space] + shp = () if formdegree == 0 else (dim,) + if formdegree == dim: + shp = (1,) + P = ONPolynomialSet(facet, degree, shp, scale="orthonormal") P_at_qpts = P.tabulate(Qpts, 1) - dtrial = exterior_derivative(P_at_qpts) + trial = P_at_qpts[(0,) * dim] + if formdegree == dim: + K = inner(trial[:1], trial, Qwts) + else: + dtrial = exterior_derivative(P_at_qpts) + if dim == 2 and formdegree == 1 and sobolev_space == "HDiv": + dtrial = dtrial[:, None, :] + K = self._bubble_derivative_moments(facet, degree, formdegree, Qpts, Qwts, dtrial) - K = self._bubble_derivative_moments(facet, degree, formdegree, Qpts, Qwts, dtrial) if formdegree > 0: - trial = P_at_qpts[(0,) * dim] - if formdegree == 1 and sobolev_space == "HDiv": + if dim == 2 and formdegree == 1 and sobolev_space == "HDiv": trial = perp(trial) M = self._bubble_derivative_moments(facet, k+1, formdegree-1, Qpts, Qwts, trial) K = numpy.vstack((K, M)) @@ -129,14 +132,6 @@ def _bubble_derivative_moments(self, facet, degree, formdegree, Qpts, Qwts, tria the exterior derivative of bubbles. """ dim = facet.get_spatial_dimension() - if formdegree >= dim - 1: - # We are at the end of the complex - # bubbles have derivative in P_k-1 minus constants - Pkm1 = ONPolynomialSet(facet, degree-1, trial.shape[1:-1], scale="orthonormal") - P0 = Pkm1.take(list(range(1, Pkm1.get_num_members()))) - dtest = P0.tabulate(Qpts)[(0,) * dim] - return inner(dtest, trial, Qwts) - # Get bubbles if formdegree == 0: B = make_bubbles(facet, degree) @@ -222,7 +217,7 @@ def __init__(self, ref_el, degree, sobolev_space, element): # map physical phis to reference values Phis if mapping == "normal": n = Ref_el.compute_normal(0) - Phis, = numpy.dot(n[None, :], phis) + Phis = numpy.dot(n[None, :], phis).transpose((1, 0, 2)) elif mapping == "covariant": piola_map = Q_dof.jacobian().T Phis = numpy.dot(piola_map, phis).transpose((1, 0, 2)) From eecfd047c4bd959c45d28de0f1e41a5f6b8b9e0b Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Tue, 16 Jul 2024 16:07:00 +0100 Subject: [PATCH 83/93] Construct bubbles of the right kind --- FIAT/demkowicz.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/FIAT/demkowicz.py b/FIAT/demkowicz.py index 9ea30aa39..7020fb780 100644 --- a/FIAT/demkowicz.py +++ b/FIAT/demkowicz.py @@ -97,7 +97,6 @@ def __init__(self, ref_el, degree, sobolev_space, kind=2): super(DemkowiczDual, self).__init__(nodes, ref_el, entity_ids) def _reference_duals(self, dim, degree, formdegree, sobolev_space, kind): - k = degree if kind == 2 else degree - 1 facet = symmetric_simplex(dim) Q = create_quadrature(facet, 2 * degree) Qpts, Qwts = Q.get_points(), Q.get_weights() @@ -116,18 +115,19 @@ def _reference_duals(self, dim, degree, formdegree, sobolev_space, kind): dtrial = exterior_derivative(P_at_qpts) if dim == 2 and formdegree == 1 and sobolev_space == "HDiv": dtrial = dtrial[:, None, :] - K = self._bubble_derivative_moments(facet, degree, formdegree, Qpts, Qwts, dtrial) + K = self._bubble_derivative_moments(facet, degree, formdegree, kind, Qpts, Qwts, dtrial) if formdegree > 0: if dim == 2 and formdegree == 1 and sobolev_space == "HDiv": trial = perp(trial) - M = self._bubble_derivative_moments(facet, k+1, formdegree-1, Qpts, Qwts, trial) + q = degree + 1 if kind == 2 else degree + M = self._bubble_derivative_moments(facet, q, formdegree-1, kind, Qpts, Qwts, trial) K = numpy.vstack((K, M)) duals = numpy.tensordot(K, P_at_qpts[(0,) * dim], axes=(1, 0)) return Q, duals - def _bubble_derivative_moments(self, facet, degree, formdegree, Qpts, Qwts, trial): + def _bubble_derivative_moments(self, facet, degree, formdegree, kind, Qpts, Qwts, trial): """Integrate trial expressions against an orthonormal basis for the exterior derivative of bubbles. """ @@ -135,10 +135,15 @@ def _bubble_derivative_moments(self, facet, degree, formdegree, Qpts, Qwts, tria # Get bubbles if formdegree == 0: B = make_bubbles(facet, degree) + elif kind == 1: + from FIAT.nedelec import Nedelec as N1curl + from FIAT.raviart_thomas import RaviartThomas as N1div + fe = (N1curl, N1div)[formdegree-1](facet, degree) + B = fe.get_nodal_basis().take(fe.entity_dofs()[dim][0]) else: - from FIAT.nedelec import Nedelec - from FIAT.raviart_thomas import RaviartThomas - fe = (Nedelec, RaviartThomas)[formdegree-1](facet, degree) + from FIAT.nedelec_second_kind import NedelecSecondKind as N2curl + from FIAT.brezzi_douglas_marini import BrezziDouglasMarini as N2div + fe = (N2curl, N2div)[formdegree-1](facet, degree) B = fe.get_nodal_basis().take(fe.entity_dofs()[dim][0]) # Tabulate the exterior derivate @@ -151,7 +156,7 @@ def _bubble_derivative_moments(self, facet, degree, formdegree, Qpts, Qwts, tria B = inner(test, test, Qwts) A = inner(dtest, dtest, Qwts) sig, S = scipy.linalg.eigh(A, B) - tol = sig[-1] * 1E-10 + tol = sig[-1] * 1E-12 nullspace_dim = len([s for s in sig if abs(s) <= tol]) S = S[:, nullspace_dim:] S *= numpy.sqrt(1 / sig[None, nullspace_dim:]) From b54dcc252afaef36c64f4becda6eb9ededee887d Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Tue, 16 Jul 2024 17:08:25 +0100 Subject: [PATCH 84/93] Add tetrahedral quadrature rules from gen-quad --- FIAT/gen_quad_data.py | 4650 ++++++++++++++++++++++++++++++++++++ FIAT/quadrature_schemes.py | 23 +- 2 files changed, 4667 insertions(+), 6 deletions(-) create mode 100644 FIAT/gen_quad_data.py diff --git a/FIAT/gen_quad_data.py b/FIAT/gen_quad_data.py new file mode 100644 index 000000000..a3efe6185 --- /dev/null +++ b/FIAT/gen_quad_data.py @@ -0,0 +1,4650 @@ +import numpy + +points = "points" +weights = "weights" + +tetrahedron_table = { # noqa + 16: { + points: [ + [1.4671830075200176e-02, 1.3388201316592577e-02, 7.2989709061924372e-03], + [1.0187412255249835e-01, 8.4202636301215925e-02, 1.7258887912772580e-02], + [9.9867758308143470e-02, 8.5210968239100598e-02, 6.6292283744394345e-02], + [8.8317316567763079e-02, 2.7323663622222518e-02, 1.1272821398661804e-02], + [1.8713294677825446e-01, 9.4088494843655099e-02, 8.9002831972985110e-03], + [1.2855927424378089e-01, 6.7492311233071756e-02, 6.0418268990586457e-02], + [1.9435956448067412e-01, 1.8393027282546867e-01, 1.1642624514268386e-02], + [2.0005347738247331e-01, 1.8877575625735551e-01, 8.6789219678324037e-02], + [1.9516685008610160e-01, 1.8648833062954931e-01, 1.7111000857658437e-01], + [2.0587472217781130e-01, 3.1119668967325136e-02, 1.5678492923033045e-02], + [2.0929161008170849e-01, 1.2192259960926730e-01, 6.0692052991170968e-02], + [2.4064530969474013e-01, 9.4940014880104701e-02, 8.1957655634224358e-02], + [2.8219604243673618e-01, 9.2739790589300625e-02, 1.6639135327214592e-02], + [2.4546857202318020e-01, 1.8693135185514267e-01, 6.6394853834234272e-03], + [2.6206000332304336e-01, 2.0349117253292162e-01, 4.9573006584285470e-02], + [2.3899739036992743e-01, 1.9492496742667331e-01, 1.3853684665096566e-01], + [2.4589507169551261e-01, 1.8059673310177912e-01, 1.7100181879734486e-01], + [3.6143989410321264e-01, 3.4838909831612813e-01, 1.3200292917440124e-02], + [2.8224562172952034e-01, 2.6897911675816927e-01, 4.5956237613429374e-02], + [2.9451975077872811e-01, 2.8510561965887826e-01, 2.1397595594238533e-01], + [3.4608355444332789e-01, 3.2815154549227810e-01, 3.1430205800048366e-01], + [3.5440811492828106e-01, 2.8565898456801707e-02, 1.4427639930515705e-02], + [3.7272529731445181e-01, 9.0058972550707175e-02, 7.6639919239422080e-02], + [4.0780798691108983e-01, 8.1365411790387296e-02, 1.4377341183881485e-02], + [3.9515153014166460e-01, 1.4861617067883465e-01, 1.2565461995137901e-02], + [3.6762264424379215e-01, 1.4803075964555559e-01, 7.8651237448419176e-02], + [3.6519353098908075e-01, 2.1628993688342610e-01, 1.4748184960671299e-02], + [3.8104284873068212e-01, 2.2908304747510258e-01, 7.1519264066283630e-02], + [5.3117784232086052e-01, 1.8203966957316481e-01, 1.8032619835641461e-01], + [3.5299669571407166e-01, 2.2214043316078522e-01, 1.6084635375841916e-01], + [3.8951301597917282e-01, 2.0187266040302795e-01, 1.9012354319894628e-01], + [4.9249099075508335e-01, 3.1954668912947209e-01, 1.7535049819630608e-01], + [3.8706793920218779e-01, 3.1955133790691581e-01, 1.1870452643736793e-02], + [4.2727859294504983e-01, 3.5498751031639397e-01, 6.6107505338488762e-02], + [3.7264066833923482e-01, 3.0361706419596429e-01, 1.4635761991167476e-01], + [3.9538978246850065e-01, 3.3220907147033490e-01, 2.6467749916147476e-01], + [4.0593894790881263e-01, 3.1491134489536549e-01, 3.0090385245520807e-01], + [5.3811895613132177e-01, 5.2399348836045456e-01, 1.3194566472944202e-02], + [4.7564195708505097e-01, 4.6072367527471836e-01, 6.4330419121182203e-02], + [3.6640517486805507e-01, 3.5333780112703533e-01, 1.1105323952503550e-01], + [3.7243562465494251e-01, 3.5802354336597486e-01, 2.0375510293308116e-01], + [4.3426609406922839e-01, 4.2169342806048238e-01, 3.4778447540976409e-01], + [5.2151142379971394e-01, 2.8283651102867173e-02, 1.5702613073095451e-02], + [5.1670774341243353e-01, 1.4951044282689496e-01, 6.8430458007101597e-02], + [5.3228465344057785e-01, 9.3616079478847397e-02, 7.6436997679325241e-02], + [5.6318189887246217e-01, 8.0826661092727972e-02, 1.2379086460647332e-02], + [5.3180775754940979e-01, 2.5432141771031463e-01, 6.7382639757694290e-02], + [5.2387641519343309e-01, 2.1908638929151686e-01, 1.7506763996470281e-01], + [5.4697574695992124e-01, 1.6904027985573686e-01, 1.4171662907226280e-02], + [5.4590701929498109e-01, 3.7539309526128722e-01, 7.6995027902807101e-02], + [5.6471810231085506e-01, 2.8179760466531717e-01, 1.5305446049691199e-01], + [4.9141214423189555e-01, 3.0402746739504566e-01, 2.3702807284198227e-01], + [5.9208498928095965e-01, 3.1827585182401635e-01, 3.0543233570923672e-01], + [5.1786079187292822e-01, 2.4219731321084531e-01, 1.2566928410205324e-02], + [5.6003577580531982e-01, 4.1287704767579503e-01, 1.6805572864895163e-01], + [5.4527975883320634e-01, 3.6977674658939680e-01, 3.5367252786759867e-01], + [5.3654578764574112e-01, 4.7448423339998264e-01, 1.7195987736003104e-01], + [6.0291081761455945e-01, 4.1705661315931658e-01, 2.4752037508056601e-01], + [5.6622372755807648e-01, 4.0629765197937195e-01, 3.2536540140275705e-01], + [5.2943384293325246e-01, 4.6463646567926481e-01, 4.5564925273961565e-01], + [5.3122204202328605e-01, 3.5985488357985795e-01, 1.5024120253038665e-02], + [6.0321841122625774e-01, 5.2869474606425559e-01, 7.7834177420922343e-02], + [5.2872345000164689e-01, 4.5349444812963430e-01, 2.8820732363109358e-01], + [5.5782654231599027e-01, 4.8814069812494498e-01, 4.2576043704332911e-01], + [5.0995714412696780e-01, 4.9857008526873403e-01, 4.8166187409359446e-01], + [5.4582723946658773e-01, 4.7160783159905384e-01, 1.4495811158772761e-02], + [6.5421482366028860e-01, 6.3911854986740557e-01, 7.0196568639281545e-02], + [5.5740408773862515e-01, 5.4580134944501280e-01, 1.4725951020048700e-01], + [5.1657316238069917e-01, 5.0419839040415837e-01, 2.3323915695271388e-01], + [5.3517468472194507e-01, 5.1998967327187495e-01, 3.4842378116153500e-01], + [5.7772782550801494e-01, 5.6348333861486122e-01, 4.8016859908619103e-01], + [6.8680319385376820e-01, 2.7913737822342032e-02, 1.2880307424522723e-02], + [6.9117167183267936e-01, 8.4502460807020507e-02, 6.6895985175806319e-02], + [7.1528829927957027e-01, 9.5672311465441501e-02, 1.3728063858258803e-02], + [6.7341219108618267e-01, 1.5642271815220840e-01, 6.9728602754689700e-02], + [6.7890099221350575e-01, 1.5060644565801243e-01, 1.5052057758122714e-01], + [6.7555873962989255e-01, 2.0824216344600979e-01, 1.6856477213573659e-01], + [6.8900154580303874e-01, 2.0715753705212486e-01, 1.4491737580511093e-02], + [6.8973547779826372e-01, 2.7293044243274062e-01, 7.4707796667369528e-02], + [6.9413553885641355e-01, 2.9520323006450633e-01, 1.7092137456432438e-01], + [7.1121553378900748e-01, 2.9704953934150707e-01, 2.8480870657479662e-01], + [6.7175847354310736e-01, 3.4763878020116418e-01, 1.4467854251998999e-02], + [6.9302260785618530e-01, 3.9900225687499452e-01, 7.2083968086625969e-02], + [6.8425943943196876e-01, 3.7386391367716143e-01, 3.0790857271521915e-01], + [6.9401481145804145e-01, 5.0522317579980291e-01, 8.0604165468641555e-03], + [6.9242559818148131e-01, 5.1758432084421602e-01, 5.3613209255660016e-02], + [6.9434352294011803e-01, 4.2040176144894309e-01, 1.7038424742549510e-01], + [6.9187118751778209e-01, 4.3020646963149800e-01, 2.7889209687936606e-01], + [7.0665063738808154e-01, 6.2776319029848948e-01, 1.5819547244527655e-02], + [7.0658383369180955e-01, 5.4325155582350138e-01, 1.4922848899994642e-01], + [6.9430381564278643e-01, 5.5636800481624626e-01, 2.5880422402944842e-01], + [6.9081490286864289e-01, 5.3268060907623482e-01, 3.4273750197814989e-01], + [7.1582454513639915e-01, 5.3802120289188471e-01, 4.6034360349292486e-01], + [6.7393604595291079e-01, 5.3485934804475876e-01, 5.1994013517555049e-01], + [7.0007519075357161e-01, 6.8454021142773602e-01, 1.3995961628149161e-02], + [7.7152430743474043e-01, 6.9926635021134864e-01, 8.0879474870566834e-02], + [7.0582564423050043e-01, 6.3750916158340665e-01, 1.8106248148504006e-01], + [6.7807963446633579e-01, 6.2480842612088461e-01, 3.1874243871112701e-01], + [6.8737196895756925e-01, 6.1496602049285398e-01, 4.5336536106916558e-01], + [7.1971999815012178e-01, 6.4628789653430585e-01, 5.7699817809035558e-01], + [6.7390071307486621e-01, 6.3580190377388401e-01, 6.1996382760842261e-01], + [7.4200455043107771e-01, 7.2965310406519956e-01, 1.2694670743571884e-01], + [6.9377472987791677e-01, 6.7905400147802963e-01, 2.0504916600235035e-01], + [6.7827380271801596e-01, 6.7084204487146482e-01, 3.3305885217459164e-01], + [6.9042937728521225e-01, 6.7678877797585502e-01, 4.7518467072075649e-01], + [6.9868086109904470e-01, 6.8531655529902824e-01, 6.0113401226185792e-01], + [6.8021984742647734e-01, 6.7863465688493574e-01, 6.6272358506413309e-01], + [8.2995763668946387e-01, 7.1242386012844924e-02, 5.7365550216741883e-02], + [8.2799498522816140e-01, 2.2608819133385402e-02, 7.0715795932874666e-03], + [8.1310490497317811e-01, 1.4533984083594165e-01, 7.6376293665242262e-02], + [8.1603914458922433e-01, 1.7345553493070504e-01, 1.6494420690000711e-01], + [8.3657166017134554e-01, 9.4198932736090243e-02, 1.6902518501610581e-02], + [8.2164382605116326e-01, 2.3523516035278677e-01, 7.4609159804322506e-02], + [8.1617005770102602e-01, 2.1659573462348927e-01, 1.6223286883157501e-01], + [8.2602263991374347e-01, 2.0346954402810297e-01, 1.3426204181055881e-02], + [8.2536287702929678e-01, 3.5573524888279529e-01, 6.2026520433495498e-02], + [8.2062700242658293e-01, 3.2732428255683815e-01, 1.7961599571547232e-01], + [8.1983515629783876e-01, 3.6281723578305819e-01, 2.9266461465790788e-01], + [8.3330211484350625e-01, 3.1883276001952299e-01, 3.0426438146330698e-01], + [8.1029258488154443e-01, 3.5289459875840146e-01, 1.1918366387840329e-02], + [8.2694910630164942e-01, 4.2606850013034103e-01, 1.5677229018248162e-01], + [8.2042878992319856e-01, 4.7518091158643216e-01, 3.1046732720793618e-01], + [8.4871994941703599e-01, 4.6759828591652514e-01, 4.5572414504132985e-01], + [8.1667406940321285e-01, 5.1758977840438047e-01, 1.5872492147015374e-02], + [8.2575285965529144e-01, 5.0457981649282779e-01, 7.5640771824404082e-02], + [8.2982818029414473e-01, 5.7484436671023675e-01, 1.7166703381112872e-01], + [8.2296856972498111e-01, 5.3815495846722627e-01, 2.6744952335100347e-01], + [8.2199790693173636e-01, 5.8828472416013122e-01, 4.1838857500271964e-01], + [8.3143127266093841e-01, 5.1371525558363962e-01, 4.4475488690744941e-01], + [7.3390943574690770e-01, 4.5724937397443421e-01, 4.4302783449505989e-01], + [8.3973371862523105e-01, 6.8327156161628988e-01, 1.5511721993906599e-02], + [8.2315899354995548e-01, 6.4481224825528760e-01, 7.8996074340734176e-02], + [8.3980379727842913e-01, 6.9540925361919081e-01, 1.8823693046808873e-01], + [8.2383995172064939e-01, 6.5422283315588303e-01, 3.1690213611861762e-01], + [8.2415955435847055e-01, 6.6682856440984284e-01, 4.6817970547257737e-01], + [8.4825533631779604e-01, 6.6439188132983462e-01, 5.7970544455304207e-01], + [8.3207474440722906e-01, 6.3341704246540931e-01, 6.1740437411555182e-01], + [8.5123889969210753e-01, 7.9398562179531118e-01, 1.5859673259397099e-02], + [8.4831440623572452e-01, 7.8047412063488186e-01, 1.9010332871107605e-01], + [8.1975903331062472e-01, 7.4739235713116237e-01, 3.1365812960283151e-01], + [8.1623685052035533e-01, 7.4412957932334067e-01, 4.5850878141697748e-01], + [8.3294430113720419e-01, 7.5558355988219794e-01, 6.0491965953288729e-01], + [8.3394372867847033e-01, 7.4474836036080860e-01, 6.9084951543478523e-01], + [7.9140905656090488e-01, 7.0615335530532264e-01, 6.9711307547014456e-01], + [8.0495957531419937e-01, 7.9263069175020173e-01, 1.5007473440264478e-02], + [8.2419915853756642e-01, 8.1175816806813950e-01, 6.9175840115592219e-02], + [8.3041805734150476e-01, 8.1683063204443263e-01, 1.8980163550011664e-01], + [8.1825449817418505e-01, 8.0374894395696250e-01, 3.3398638644862760e-01], + [8.2125756041934106e-01, 8.0696891391219971e-01, 4.9158621816216341e-01], + [8.2034773049298071e-01, 8.0439562184637359e-01, 6.3710106741328865e-01], + [8.2391114468382853e-01, 8.0673993372118025e-01, 7.4565166695496599e-01], + [8.2427073388482508e-01, 8.0723576338367298e-01, 7.9659250856489228e-01], + [9.3337200075476623e-01, 2.9215812499174831e-02, 1.1026293281327749e-02], + [9.3039627065744401e-01, 7.0231082603550224e-02, 5.7581298981431453e-02], + [9.2663825316590787e-01, 1.0378275143852893e-01, 1.8769829911926175e-02], + [9.2252527717044586e-01, 1.5996467364375577e-01, 8.8812816505319497e-02], + [9.2239131853997958e-01, 1.7131796897461721e-01, 1.5640346240425590e-01], + [9.2857412140541118e-01, 1.8659402767824287e-01, 7.8626535796961963e-03], + [9.2672592206945426e-01, 2.4805767802689835e-01, 5.6461415210139976e-02], + [9.2261868833008787e-01, 2.9805083483767036e-01, 2.2585334154189410e-01], + [9.2096818579948969e-01, 3.2680392165767269e-01, 1.3191297946458893e-02], + [9.2645693404382168e-01, 4.0734616869209850e-01, 7.5621472754654778e-02], + [9.2269318182646387e-01, 3.0541880248607267e-01, 1.4688139735169392e-01], + [9.2605162960038845e-01, 3.1498536836001584e-01, 3.0116277454200735e-01], + [9.1981235629486058e-01, 4.8275020435760618e-01, 1.4537381534657183e-02], + [9.2729823954166113e-01, 5.5656860210230585e-01, 6.5685811362579061e-02], + [9.2753900001246103e-01, 4.4667084918279620e-01, 1.7646121854045840e-01], + [9.2217687157087080e-01, 4.5944475744391322e-01, 2.9855068654684963e-01], + [9.2859706724366409e-01, 4.6696224926533181e-01, 4.0326744341526599e-01], + [9.4187417797619644e-01, 4.7674788261178930e-01, 4.6636109225948880e-01], + [9.2332035073889596e-01, 6.3655225761471335e-01, 3.1194931385474327e-03], + [9.2385158721010152e-01, 6.7844576545087920e-01, 4.7975905116127399e-02], + [9.2712976928512303e-01, 5.9235214663248670e-01, 1.6245244663898903e-01], + [9.2543907459249464e-01, 6.0219571681346795e-01, 3.0062218163948123e-01], + [9.2500437574632355e-01, 6.1930799089358357e-01, 4.5507228400070915e-01], + [9.3469046731545369e-01, 6.4433174565396756e-01, 5.7348755799449269e-01], + [9.1972157371855567e-01, 6.1194081293749503e-01, 5.9575919666171129e-01], + [9.4850374532176263e-01, 8.0423287839493096e-01, 1.2725521649331013e-02], + [9.1733540001488190e-01, 8.1181298010882030e-01, 7.3705419654588586e-02], + [9.2863710862273563e-01, 7.3320317597956752e-01, 1.3918575477217202e-01], + [9.2895047651751861e-01, 7.4208393983577026e-01, 2.8231292088030469e-01], + [9.2315069264322691e-01, 7.3607277038602514e-01, 4.4611533599655051e-01], + [9.3151134045206996e-01, 7.7724980297997515e-01, 6.1810219448447856e-01], + [9.3924292848751934e-01, 7.4417094041449683e-01, 7.3768782636205010e-01], + [9.4580319804743307e-01, 8.9657056410104252e-01, 1.4465103194366955e-02], + [8.8607534546198807e-01, 8.2782533142872550e-01, 9.2780412753623753e-02], + [9.4370863321575638e-01, 8.6237885136119052e-01, 2.0612654623125826e-01], + [9.2325848307677594e-01, 8.5124764574702250e-01, 3.3765899484864387e-01], + [9.2168379389319954e-01, 8.3365794088665757e-01, 4.3220053744947406e-01], + [9.2470660434884711e-01, 8.5433863074377947e-01, 5.7777013869611094e-01], + [9.2208277114001824e-01, 8.7261847951179450e-01, 8.1166434588124647e-01], + [9.0830992404474609e-01, 8.2417864304539612e-01, 8.1470462184319181e-01], + [8.9736014213273330e-01, 8.9467725860378677e-01, 5.1768310891017275e-03], + [9.3268242183780592e-01, 9.1811401582274410e-01, 5.6400451349932053e-02], + [9.2675575953871714e-01, 9.1154350919480942e-01, 1.5849063605765479e-01], + [9.2363111345142801e-01, 9.0940143061464684e-01, 3.1103628998396299e-01], + [9.2474876837171049e-01, 9.0889267269679541e-01, 4.8517144030168696e-01], + [9.2227997084657121e-01, 9.1023761300161354e-01, 6.5936394171729862e-01], + [9.2906206784258560e-01, 8.7921010619140705e-01, 7.3859683549599742e-01], + [9.2194898691508753e-01, 9.2146086622495105e-01, 8.1593987322731487e-01], + [9.3320087192229562e-01, 9.1955057727529133e-01, 9.0203544597914109e-01], + [9.8987645993677198e-01, 1.5599119453692196e-02, 9.9218458706850816e-03], + [9.8611006385584510e-01, 8.5149512093532179e-02, 6.4050666851479324e-02], + [9.8570328108228467e-01, 8.0482219926492543e-02, 1.3206860232136247e-02], + [9.8498255451677363e-01, 1.7365663238633164e-01, 7.3444223463943192e-02], + [9.8511391829029504e-01, 1.6741055951023123e-01, 1.5820710669234539e-01], + [9.8601935532750551e-01, 2.0150268189580034e-01, 1.4265692325186343e-02], + [9.8602935450804940e-01, 2.5670377119589677e-01, 7.1522919088228151e-02], + [9.8495157698499458e-01, 2.3428732625055856e-01, 1.7235009066221343e-01], + [9.8456348971685415e-01, 3.5799046664002643e-01, 1.7111585953106492e-02], + [9.8598011539576413e-01, 3.6188473563104212e-01, 8.7017441316709201e-02], + [9.8488752281559810e-01, 3.5320256441526365e-01, 1.8525590714335705e-01], + [9.8524720847985192e-01, 4.0303261173346344e-01, 3.2242585168806392e-01], + [9.8514965921380815e-01, 3.0871724670692513e-01, 2.9381783640235154e-01], + [9.8473399360187208e-01, 5.2708904517321364e-01, 1.5887755471972288e-02], + [9.8579985353972366e-01, 5.1344752925119930e-01, 8.4697365058504656e-02], + [9.8601741723911651e-01, 5.1798993872923782e-01, 2.0099413048989245e-01], + [9.8497161963516178e-01, 5.2282630444974232e-01, 3.3016707280948726e-01], + [9.8672921252506063e-01, 5.8427124699048627e-01, 4.9809378448982311e-01], + [9.8880534396914788e-01, 4.7447398988877448e-01, 4.5721192799092358e-01], + [9.8458044014196866e-01, 6.9018033770413456e-01, 1.6693499960862845e-02], + [9.8621576374764996e-01, 6.7355867422931215e-01, 8.2602254938652531e-02], + [9.8567215544829656e-01, 6.7201460691887371e-01, 1.9562876212990937e-01], + [9.8571395710987075e-01, 6.8293199133075089e-01, 3.4492423667738820e-01], + [9.8498066228850878e-01, 6.7152862603968488e-01, 4.7382955105468971e-01], + [9.8504574124580091e-01, 6.4829927033954582e-01, 6.3131794267858010e-01], + [9.9968858545341310e-01, 8.3552697071513382e-01, 1.2814750719180208e-03], + [9.8569093042228673e-01, 8.2213763292366415e-01, 5.6111028263940613e-02], + [9.8556289740124015e-01, 8.0818146070592012e-01, 1.5302560504515489e-01], + [9.8695638856006540e-01, 8.0984867585650255e-01, 2.9223001988592945e-01], + [9.8459850725709164e-01, 8.2391734840147024e-01, 4.5777802325748534e-01], + [9.8637631271329573e-01, 7.9336959068435942e-01, 5.8106837424842683e-01], + [9.8736610961782367e-01, 7.5693502686033409e-01, 6.6914586929305997e-01], + [9.3566236059455399e-01, 7.8493331849630399e-01, 7.3131064257657308e-01], + [9.8911706052636139e-01, 8.0724139341922341e-01, 7.8990222240595176e-01], + [9.9224566713837492e-01, 9.2994537171265157e-01, 2.1508989136475112e-02], + [9.8045435451834773e-01, 9.1733005997194395e-01, 9.5793195900745670e-02], + [9.9355101298448600e-01, 9.1617119125262192e-01, 2.0406067903567607e-01], + [9.8503843729687801e-01, 9.0917219287393169e-01, 3.4910550471012303e-01], + [9.8513527947290647e-01, 9.2585911217389982e-01, 5.0272938529342881e-01], + [9.8581926221328398e-01, 9.0640054471482323e-01, 6.4681548869600569e-01], + [9.8681799133657155e-01, 8.8921108201395693e-01, 7.6169484551393363e-01], + [9.8571045256771694e-01, 8.9744538057987489e-01, 8.4478800949843025e-01], + [9.8153117632842934e-01, 9.1535494943564444e-01, 9.0572828742983758e-01], + [9.8352793632323998e-01, 9.7476973549110923e-01, 1.1390426737426029e-02], + [9.9002346850809786e-01, 9.8050786954214886e-01, 7.6795938904564515e-02], + [9.8487897225671750e-01, 9.7109766353907412e-01, 1.8920686961470479e-01], + [9.8490227405694564e-01, 9.6773133439010217e-01, 3.1676038294586928e-01], + [9.8582487096669391e-01, 9.7587769824843140e-01, 4.6663241487764440e-01], + [9.8488635325612928e-01, 9.7144534164946117e-01, 6.4256865167223676e-01], + [9.8515601909829809e-01, 9.6713116503678886e-01, 7.9378421675936284e-01], + [9.8542716362958660e-01, 9.6748910280520461e-01, 9.0567109101498722e-01], + [9.9283050615217161e-01, 9.8476082018958633e-01, 9.7914918013914010e-01], + ], + weights: [ + 1.1224526958649615e-05, + 1.6634603879704229e-04, + 1.4231533326589221e-04, + 1.0838779383212657e-04, + 2.1191485448369989e-04, + 1.0460427657834838e-04, + 1.0751014703665844e-04, + 3.2095231473920768e-04, + 1.2550274523093091e-04, + 2.1360846374157488e-04, + 5.6132935964533918e-04, + 3.3636083478445815e-04, + 3.6646242390671763e-04, + 1.5984498383361088e-04, + 5.3943487144932545e-04, + 4.3678104623766714e-04, + 2.9136943028079069e-04, + 1.8800505082208104e-04, + 2.1381000063622186e-04, + 2.3822782555278505e-04, + 2.5639573453672862e-04, + 2.1367136134838721e-04, + 4.5676115525101948e-04, + 3.7170775553497192e-04, + 2.8870370047021890e-04, + 9.4045716829204752e-04, + 5.3124176603074730e-04, + 1.0524417410084565e-03, + 2.5766734217254106e-04, + 8.8003601344380342e-04, + 5.4662195278704420e-04, + 1.1850439739317308e-03, + 3.3703832745155140e-04, + 9.8112687554089122e-04, + 1.1449988581939598e-03, + 8.4177867173846623e-04, + 4.6043645785224651e-04, + 2.1408373489556736e-04, + 4.4863912165954417e-04, + 4.6221186558815836e-04, + 4.8133854423751198e-04, + 3.6574682660285904e-04, + 2.1872749330392226e-04, + 1.0466396277599513e-03, + 5.6813162957266162e-04, + 4.0129547947782883e-04, + 1.3441225271098466e-03, + 1.1976552617124574e-03, + 4.6739651125907167e-04, + 1.3972519468106017e-03, + 1.2097857192651027e-03, + 6.9982279509109302e-04, + 4.8244305067487563e-04, + 4.5846095050684998e-04, + 1.2804940146150530e-03, + 6.3471461021994118e-04, + 1.3636380618292788e-03, + 4.4077128373027778e-04, + 1.2380127075508017e-03, + 3.0617317768597479e-04, + 7.0532588611851247e-04, + 1.1853501909972828e-03, + 1.5513885185375936e-03, + 1.0073021434747328e-03, + 2.1713710741529482e-04, + 4.9506533926071982e-04, + 4.9742330149160803e-04, + 4.5531362759913932e-04, + 5.4423427721554880e-04, + 6.3400163167718509e-04, + 3.8847423427411105e-04, + 2.0002229324739713e-04, + 4.9842242903257042e-04, + 4.4444732207973977e-04, + 1.1210927085660285e-03, + 1.6889264892475570e-04, + 1.1461118870425458e-03, + 7.0814457229798631e-04, + 1.4861572156719784e-03, + 1.5988963911078404e-03, + 5.9983139656332829e-04, + 8.2206710199082638e-04, + 1.4577327721094241e-03, + 1.5907751323678213e-03, + 4.5967859982178917e-04, + 1.1828828134189580e-03, + 2.0666325862531681e-03, + 1.3723812716259943e-03, + 5.4489095133988059e-04, + 1.6910835696846936e-03, + 1.4514651824497889e-03, + 1.5810260341648644e-03, + 1.5845682997453327e-03, + 6.6776962172435063e-04, + 1.9849276690611832e-04, + 1.0944725637168694e-03, + 1.3477662531204710e-03, + 1.4886797115420960e-03, + 1.4691703235592077e-03, + 8.4287769309835986e-04, + 3.6844867811601799e-04, + 2.3662765074683776e-04, + 6.0544131230903895e-04, + 4.6744043068329530e-04, + 6.6160295941588795e-04, + 4.1613158311414793e-04, + 7.9925379140238239e-05, + 3.4705240539751404e-04, + 1.1122474236092073e-04, + 6.8140813691670344e-04, + 3.8107436069723257e-04, + 3.8534685424630413e-04, + 1.1826680137616489e-03, + 9.5866717119286605e-04, + 5.6485368791007291e-04, + 1.2014412802364362e-03, + 1.5204600926666699e-03, + 1.3564678947648877e-03, + 5.7508459123547899e-04, + 6.2846790206126690e-04, + 1.7482767087003590e-03, + 1.6567401459063524e-03, + 5.1754555220200320e-04, + 7.6997267038025571e-04, + 1.3877328836697080e-03, + 1.6013606055356368e-03, + 1.4499116245255430e-03, + 1.1557122887635562e-03, + 1.4116392819727205e-03, + 7.1065942333320713e-04, + 6.1685331814303871e-04, + 1.2705265108207324e-03, + 1.2910607554425808e-03, + 1.9426441064581306e-03, + 1.3261868955235592e-03, + 1.1228798391045075e-03, + 6.8311929285612004e-04, + 3.6991839489531166e-04, + 9.7425777354923418e-04, + 1.3186700083666853e-03, + 1.4318261797849978e-03, + 1.1235371283468724e-03, + 6.6623804963900795e-04, + 2.8715925332357890e-04, + 9.4278202219090642e-05, + 3.5199008496475880e-04, + 5.0320970349669808e-04, + 7.0732701875689133e-04, + 6.8687293929321248e-04, + 6.1934718877987955e-04, + 3.9669162909551560e-04, + 1.5924340958884661e-04, + 1.0439743534201859e-04, + 1.8207201827588802e-04, + 3.0570291022008472e-04, + 7.1816151235529972e-04, + 4.0554602737061962e-04, + 2.0629508900667646e-04, + 7.8916482187729644e-04, + 1.0312463386592510e-03, + 4.4913463211183484e-04, + 1.0613970761027161e-03, + 1.0360819364555296e-03, + 4.4236134435595440e-04, + 5.1355305617472940e-04, + 8.4732302722844311e-04, + 1.3073512245578216e-03, + 1.5333669764861836e-03, + 1.0943711468053296e-03, + 3.0842081719513996e-04, + 2.1160296387209211e-04, + 8.0752666252561372e-04, + 1.4817975191462336e-03, + 1.7629235057163304e-03, + 1.5345995163896832e-03, + 8.8936340852606183e-04, + 4.3145263916035369e-04, + 3.1325565126861881e-04, + 7.3956872508215167e-04, + 1.1882508332489669e-03, + 1.6375150556245230e-03, + 1.6205029466907109e-03, + 1.2327071405541449e-03, + 1.9882703510699503e-04, + 1.8311011970852174e-04, + 4.2601323151548971e-04, + 9.3460023159543733e-04, + 8.0519581178056885e-04, + 9.1091396907033663e-04, + 1.0213233418177470e-03, + 4.0083832097282404e-04, + 2.4493794417146142e-04, + 3.7702338094940552e-05, + 2.3690677274771106e-04, + 4.0068101772676820e-04, + 5.1283022100822185e-04, + 6.0344555127284452e-04, + 4.3076857358539852e-04, + 6.5283230907280424e-04, + 1.4422085551553854e-04, + 1.3806641275337689e-04, + 1.9170096532647887e-05, + 1.4353603874020930e-04, + 1.1659637613229677e-04, + 3.0695033998419738e-04, + 1.1513465090288338e-04, + 1.8495197929349849e-04, + 2.0472206187595755e-04, + 3.8886517651076435e-04, + 2.8260208713305654e-04, + 4.4585149131955550e-04, + 6.5040222720086415e-04, + 5.5941488315728823e-04, + 2.1866263950619290e-04, + 2.7244397677541905e-04, + 5.5438076418053335e-04, + 7.5533544289462277e-04, + 7.6044212509939119e-04, + 5.6781564136363760e-04, + 2.1814976909580348e-04, + 2.4829825304199316e-04, + 4.9723123971699929e-04, + 7.2026802129087221e-04, + 8.3103724922387512e-04, + 6.3678021483771173e-04, + 2.7943243900777716e-04, + 2.8941170534306064e-05, + 3.6396654188784554e-04, + 5.2863425338876651e-04, + 6.1497995165554515e-04, + 7.5086718838804226e-04, + 5.6863210409980247e-04, + 4.9319127669837420e-04, + 6.8883790673477481e-04, + 1.7234977317806468e-04, + 9.5951506324274449e-05, + 3.6872738964007737e-04, + 2.3062393580807267e-04, + 4.6330436004051145e-04, + 4.3587475297313078e-04, + 4.8153904625495886e-04, + 3.7429188787099809e-04, + 1.9386435276304742e-04, + 1.0566145762724310e-04, + 3.4951809526155953e-05, + 7.3077199802462815e-05, + 1.6894534029640094e-04, + 2.1944829768279111e-04, + 1.6821213366600344e-04, + 2.2803705663466310e-04, + 2.3677828764434523e-04, + 1.4537330700828142e-04, + 1.4550693611230334e-05, + ], + }, + 17: { + points: [ + [4.8365744989716505e-02, 2.2504332169471151e-02, 2.8040921664675665e-03], + [1.0197676595214786e-01, 9.7607765128972845e-02, 1.4335392127651531e-02], + [5.5807128497952163e-02, 4.2068147154011783e-02, 2.8140233057034680e-02], + [1.2658892837424982e-01, 3.0102156590719735e-02, 1.4751720519884633e-02], + [1.4899501098379128e-01, 9.7757363949624587e-02, 1.3936515833707091e-02], + [1.3213225361910241e-01, 6.8336514629320902e-02, 6.6737626965407679e-02], + [2.2330231925443428e-01, 2.0557064676086115e-01, 1.0238919549347575e-02], + [1.8001280492434160e-01, 1.1155836879961158e-01, 6.5982210784609294e-02], + [2.1814832497131273e-01, 2.0640885692288150e-01, 6.9811291330345845e-02], + [1.4445781066890195e-01, 1.2961428869625430e-01, 6.9433003920487318e-02], + [1.3778829517401750e-01, 1.2514263219005706e-01, 1.1095027181883677e-01], + [2.4682821574701735e-01, 2.9059885354261152e-02, 1.5056118582523745e-02], + [2.6330251760560652e-01, 9.2896806551720959e-02, 7.9515015280367446e-02], + [2.3249723653326573e-01, 8.0943947117334186e-02, 7.7017013533208431e-03], + [2.8344247816595386e-01, 1.2484327542754281e-01, 4.9527095157609197e-02], + [3.3699521066179716e-01, 2.0941151107339695e-01, 6.3937249122651474e-02], + [2.5196598092213002e-01, 1.7759605212311652e-01, 1.6417662778948194e-01], + [2.8556817711268967e-01, 1.8613051887997140e-01, 1.1106265034397600e-02], + [3.1003866399902025e-01, 2.4408247847790390e-01, 1.2246076506889263e-01], + [3.3861533264294258e-01, 2.0573054797549684e-01, 1.4588796155541844e-01], + [2.7479780704249485e-01, 2.1940352941604208e-01, 5.3854126764058312e-02], + [2.8286303553253694e-01, 2.3294003977412811e-01, 1.6803824040721208e-01], + [3.7315277965238880e-01, 3.0585563853484998e-01, 1.1544702432044328e-02], + [3.0631371592729467e-01, 3.0150262016342011e-01, 5.0404849874715350e-02], + [3.4776976375485386e-01, 3.3427906586088280e-01, 1.5032538007201598e-01], + [2.5510586651711165e-01, 2.4501441871957502e-01, 1.7059380374260077e-01], + [2.6015541451151969e-01, 2.4525598616557123e-01, 2.3215142607348366e-01], + [3.9392297031261869e-01, 2.6269952377405367e-02, 9.7523241368418119e-03], + [4.0236951313340702e-01, 6.3508609385061948e-02, 5.7662885496670750e-02], + [3.7382118676065768e-01, 9.0058408662639242e-02, 7.8462165449570426e-03], + [3.9062257199348682e-01, 1.0218935787834264e-01, 5.1335758350465659e-02], + [4.2997515394794095e-01, 1.4846764127765955e-01, 1.3286162384325675e-01], + [4.7027600960884192e-01, 1.8814497785375825e-01, 5.7582445443769469e-02], + [4.4117304137694219e-01, 2.0984696089116142e-01, 1.3741481368218475e-01], + [4.0488688363899611e-01, 2.0044039619381640e-01, 1.5651593158668299e-02], + [3.8744140431149443e-01, 2.1491985492995946e-01, 2.0472778851032297e-01], + [4.9163097571132880e-01, 3.3422709242844473e-01, 1.1643265101991783e-02], + [4.6104984588782233e-01, 3.4006672517396808e-01, 6.1845043368589064e-02], + [4.6346979870835364e-01, 3.3185848930270540e-01, 1.8733527213970133e-01], + [4.6395148161274119e-01, 3.2593498592748493e-01, 2.7178405805555350e-01], + [4.4893860580875516e-01, 2.7205178318794926e-01, 9.4956954025554610e-02], + [4.3947094863140895e-01, 3.8507088044327159e-01, 2.6350662562662486e-01], + [3.9952357395509325e-01, 3.2219182039527339e-01, 3.1272361413138527e-01], + [4.0310955975769219e-01, 3.6276569034633921e-01, 6.3297781257066990e-02], + [3.8640131550113049e-01, 3.3534081150457040e-01, 2.8603312710116391e-01], + [4.1185702492713111e-01, 3.9757736279954342e-01, 3.8649603515824921e-01], + [3.8285167157036454e-01, 3.7019844351392811e-01, 1.1837035960230420e-02], + [4.3931849568663678e-01, 4.3078786070006808e-01, 1.2296347239817765e-01], + [4.4347635355560944e-01, 3.8051792811506124e-01, 1.5245171759879342e-01], + [4.2908881785099823e-01, 4.1755891117045935e-01, 2.5965098635341960e-01], + [3.8718988127341636e-01, 3.8121829299680726e-01, 3.1629188602415609e-01], + [5.4470102817871890e-01, 2.8350402698841117e-02, 1.4247779333135568e-02], + [5.7879105778523887e-01, 8.1024587747031820e-02, 7.3510961273011391e-02], + [5.3132984277685713e-01, 9.3884426715299632e-02, 1.3868567622838337e-02], + [5.4008532781775820e-01, 1.0826903297230464e-01, 6.3797325018853906e-02], + [5.7372923187196934e-01, 1.8171847907840605e-01, 1.6961675650375355e-01], + [5.4706035460551672e-01, 2.0003187620621354e-01, 1.0503262291706249e-02], + [6.1219733676201160e-01, 2.0502623567673275e-01, 6.2632832670039496e-02], + [5.7772781047618105e-01, 2.0348492991871189e-01, 1.3672057110303784e-01], + [5.9323678223710063e-01, 3.3920675047356524e-01, 1.2284954680970482e-02], + [5.9165959679933167e-01, 3.2581575497473347e-01, 6.3021743602010766e-02], + [5.9105524113735142e-01, 3.1394589607654422e-01, 1.5436009091100564e-01], + [5.7864100450053768e-01, 3.1777680838149547e-01, 2.4965991975806054e-01], + [5.5259495073548426e-01, 2.8153064029098118e-01, 2.6714170887330829e-01], + [6.1648698421888903e-01, 4.6052092572344838e-01, 6.9177985763583166e-02], + [5.9677506678228365e-01, 4.3340540289635748e-01, 1.6349216772496641e-01], + [6.0009229458888624e-01, 4.2547063067690932e-01, 2.7459631707761145e-01], + [6.0204144581225216e-01, 4.6714538265039007e-01, 4.1138474912749712e-01], + [5.5130865532127260e-01, 3.7997846524274975e-01, 3.6982892498870262e-01], + [5.7923386956900846e-01, 4.7564967557347698e-01, 3.5765453884937259e-01], + [5.5911880517608192e-01, 4.8475863670112856e-01, 4.7245047775680760e-01], + [5.2490750767306960e-01, 4.5601315252761782e-01, 1.3939621287382742e-02], + [5.7745737183874035e-01, 5.1876453494610830e-01, 6.9576082778982376e-02], + [5.8095295586042228e-01, 5.0905366920196338e-01, 1.5640109183145640e-01], + [6.2567220023918579e-01, 5.6326807568205906e-01, 4.2168289285153104e-01], + [5.2400456344269120e-01, 4.8230574311710450e-01, 4.2409026368884140e-01], + [5.5254221796674263e-01, 5.3748423571032999e-01, 1.1215469436423141e-02], + [5.0930163427613750e-01, 4.9994810898685205e-01, 5.9417487670382331e-02], + [6.5547975013581816e-01, 6.4322975021175777e-01, 1.3562472476812937e-01], + [5.6040053297667891e-01, 5.4429279391346408e-01, 1.7150905837473904e-01], + [5.8503444660162207e-01, 5.1580056823632359e-01, 2.8578172627842829e-01], + [5.4425946012848159e-01, 5.3100445253816431e-01, 2.6138395554570926e-01], + [5.6161654321560606e-01, 5.4840437335029824e-01, 4.0158907235578523e-01], + [5.5063287060614940e-01, 5.4609226868527239e-01, 4.9059743112324483e-01], + [5.7398545682290414e-01, 5.5883082767450620e-01, 5.4853187142208226e-01], + [6.9295322800850068e-01, 2.5651981524037037e-02, 1.3874862152953523e-02], + [6.6114964316621760e-01, 6.4225464277132674e-02, 7.7312845256544073e-04], + [6.8639302499210197e-01, 1.0056145121029646e-01, 3.9992562432180190e-02], + [7.3139521360750792e-01, 8.3865178075784821e-02, 7.1860967060586131e-02], + [6.9368190774155603e-01, 1.7408893148258978e-01, 1.2870923540086791e-02], + [7.5216583313500329e-01, 2.0454702242599243e-01, 6.4934026933137087e-02], + [7.0771160243147546e-01, 1.7741496932410916e-01, 1.1760283532776573e-01], + [7.0915726436824045e-01, 1.8068781526247313e-01, 1.6728344330101430e-01], + [7.3449971235252609e-01, 3.2532297971459828e-01, 6.5012044813770917e-02], + [7.1983756918969155e-01, 3.0111060840214920e-01, 1.5060551042894799e-01], + [7.1557429679442563e-01, 3.0721125323903259e-01, 2.4225245243269553e-01], + [7.0417035595585309e-01, 3.0564407756000112e-01, 1.3433464570667308e-02], + [7.3268837640120177e-01, 4.3862684423885379e-01, 2.7374945532951206e-01], + [7.0211270330670139e-01, 3.1356785233116324e-01, 3.0204288880792168e-01], + [7.2700328159041805e-01, 4.5849598895880983e-01, 1.3852986424616244e-02], + [7.4757156655859747e-01, 4.7848516972325805e-01, 6.8877675779848727e-02], + [7.2329178431697672e-01, 4.3122083081027235e-01, 1.5182714232190347e-01], + [8.2411549790939564e-01, 5.4682146548182820e-01, 2.4504190602344905e-01], + [7.0229759777484191e-01, 4.5081799665985089e-01, 3.8124699321716382e-01], + [7.0244231601501672e-01, 4.3522729788708597e-01, 4.2095470206078461e-01], + [6.4827850587626901e-01, 5.0423765903819862e-01, 1.3984593982271427e-02], + [7.4155572952198745e-01, 6.0411812063233239e-01, 6.8092885411116702e-02], + [7.3537488414941476e-01, 5.7032709143363503e-01, 1.6101241439446839e-01], + [7.2012189760742973e-01, 5.5800181382402492e-01, 2.8785988778876098e-01], + [7.3565985066058281e-01, 5.7698027095652904e-01, 4.2121898105092853e-01], + [7.4566943257772411e-01, 5.9978471039519032e-01, 5.3817755803825962e-01], + [7.0674741392829243e-01, 5.4962965749408377e-01, 5.3877254594800772e-01], + [6.9398564622340397e-01, 6.3774758602632875e-01, 1.3174289877548711e-02], + [7.2806179995482989e-01, 6.6716809404584176e-01, 6.9064726473629517e-02], + [7.2873356655183485e-01, 6.5943685506565175e-01, 1.6495042326110348e-01], + [7.0763474039354701e-01, 6.3929070913327346e-01, 2.7873953941500612e-01], + [7.5666160385528236e-01, 6.8821390397579796e-01, 5.5650412086351464e-01], + [6.8133664711897302e-01, 6.2441034129685991e-01, 5.6225799421164346e-01], + [7.1044260830055472e-01, 6.4469603213880200e-01, 6.3163991061457592e-01], + [6.9511837351186623e-01, 6.9054696731811782e-01, 1.0074653905837288e-02], + [6.7863927458596318e-01, 6.6658360133948025e-01, 5.5751469628184315e-02], + [7.1072537329501551e-01, 6.9768781671249447e-01, 2.6675986901881943e-01], + [6.8027576036452420e-01, 6.6773757008426216e-01, 3.8602020005813870e-01], + [7.4431916964638400e-01, 6.7989513769667842e-01, 4.3397805533593425e-01], + [7.1290850762350255e-01, 7.0031984252450585e-01, 5.3130874171641684e-01], + [6.8967205375957774e-01, 6.7724433497024727e-01, 6.0338923901707253e-01], + [7.2142801898569320e-01, 7.0935951366138239e-01, 6.9378872786744850e-01], + [8.2394647380664732e-01, 3.1569692732499037e-02, 1.3303141678836629e-02], + [8.5710722572979281e-01, 6.7625167460262994e-02, 6.4243096761685270e-02], + [7.9035252995656724e-01, 9.2388945987193535e-02, 1.2224378947965965e-02], + [8.4169828378127842e-01, 1.2157933899362895e-01, 6.8429403551726012e-02], + [8.4257058220160852e-01, 1.6836726541610825e-01, 1.5355279855967177e-01], + [8.4612604143143177e-01, 1.3149254666940594e-01, 1.4366785029253952e-02], + [8.5169677682327294e-01, 2.2773937383491272e-01, 6.9611584812512298e-02], + [8.3456048359005186e-01, 3.6588809634621178e-01, 1.5139935621015965e-01], + [8.2765254418966938e-01, 2.4606214023764700e-01, 1.6311755435589936e-01], + [8.1304178876898003e-01, 2.6938326477205421e-01, 2.5533494279047586e-01], + [8.2569901956843916e-01, 2.3816732473821758e-01, 1.2259551459080321e-02], + [8.5565574058540395e-01, 3.5076561161491271e-01, 5.6621767794727496e-02], + [8.5617979530737187e-01, 4.9070466422095077e-01, 1.3197038050708931e-01], + [8.4126953024779594e-01, 4.0847489580918650e-01, 2.6901468678228430e-01], + [8.6684922839179857e-01, 3.4162481781499310e-01, 2.8443073930671475e-01], + [8.2651724240227686e-01, 3.8655751548666184e-01, 7.6522874487203229e-03], + [8.4312783323384743e-01, 4.6203967244442473e-01, 4.3599355198490695e-02], + [8.8303658791276596e-01, 5.0159939618802973e-01, 2.6282338040688724e-01], + [8.3466139686275054e-01, 5.4530888376251363e-01, 4.1345238901486553e-01], + [8.1757437364367092e-01, 4.3611395160401384e-01, 3.8031022882069515e-01], + [8.4266854880383435e-01, 3.9851169512938722e-01, 3.9032663612155599e-01], + [8.4971682608496257e-01, 5.6455897853367243e-01, 8.9498387828153177e-03], + [8.8052955985193448e-01, 5.7940444280482195e-01, 5.6679933150696964e-02], + [8.5345186946554885e-01, 6.1613107636637432e-01, 1.4313152432691464e-01], + [8.5941500466894682e-01, 6.1419576469460857e-01, 3.8430003675332775e-01], + [8.4387602223195390e-01, 5.9883367305175317e-01, 5.3322954842702330e-01], + [8.2728042519925737e-01, 5.2567920963012893e-01, 5.1223220460100993e-01], + [7.8695201130300241e-01, 6.3455606526779240e-01, 1.0077101109755925e-02], + [8.3699941388154853e-01, 6.5148270720097035e-01, 5.1477334121916692e-02], + [8.6011081722871108e-01, 7.2034563021231379e-01, 1.4222961056142494e-01], + [8.3986539989250453e-01, 6.8686865546276377e-01, 2.7063842007345812e-01], + [8.4128282907638974e-01, 6.9993339139361377e-01, 4.1430565386987794e-01], + [8.5591939890872293e-01, 7.1864081830962190e-01, 6.6199235541370427e-01], + [8.3992192565727142e-01, 6.6223877510143792e-01, 6.5018259432933478e-01], + [8.4389547314351465e-01, 7.6695165263016041e-01, 1.8386593355857152e-02], + [8.5378618174794163e-01, 7.8610307959318904e-01, 8.7736494062012893e-02], + [8.5023280580822347e-01, 7.8561970294748873e-01, 2.1328051177681109e-01], + [8.3135269134211198e-01, 7.6961431509876976e-01, 3.4737211436218224e-01], + [8.7151661948539250e-01, 8.0743559464422399e-01, 5.2573855751246756e-01], + [8.5673983446794133e-01, 7.9710160286367271e-01, 6.4899208450996704e-01], + [8.2794378126506407e-01, 7.6215465575289720e-01, 7.1428357415790633e-01], + [8.3388229306863915e-01, 7.5614200021860289e-01, 7.5272600140086166e-01], + [8.0753289199476463e-01, 7.8938649306693331e-01, 9.9842360921158172e-03], + [8.2220690004323238e-01, 8.0973394558238931e-01, 6.1653805918094988e-02], + [7.9319407601281222e-01, 7.7904398482172621e-01, 1.5701807737135118e-01], + [8.4244831972254863e-01, 8.3077084645973420e-01, 3.0071746284499057e-01], + [8.1911438099881761e-01, 8.0639389476524848e-01, 4.5219024530231894e-01], + [8.3048829027051552e-01, 8.1837259957750463e-01, 6.0615560467030960e-01], + [8.2271566090682435e-01, 8.1038380230337737e-01, 7.2591878230679474e-01], + [8.4571997356161299e-01, 8.3070920842392559e-01, 8.1560005628368137e-01], + [9.2774626787452119e-01, 1.7061544277968105e-02, 1.2738817547770254e-02], + [9.2190885011132018e-01, 6.8830915142969490e-02, 1.3099987950169008e-02], + [9.3813655954936570e-01, 9.1432928929922275e-02, 6.9135240528478789e-02], + [9.3687625690505816e-01, 1.6447108691412493e-01, 1.6280884278791946e-01], + [9.2784180745245093e-01, 1.8131728507774622e-01, 1.3139257947852173e-02], + [9.3675038217616458e-01, 1.7454124225953579e-01, 6.9075137445933452e-02], + [9.2099782802999475e-01, 3.0541071254004148e-01, 1.5722671464958687e-01], + [9.3086885901822158e-01, 2.1720568753088698e-01, 1.6840909831194370e-01], + [9.2131778196266612e-01, 2.9858188679497055e-01, 2.8798015338708316e-01], + [9.2651057693494032e-01, 3.2875570930980363e-01, 1.1496230511911468e-02], + [9.4092817974537180e-01, 3.0320727533675312e-01, 6.1941223245448797e-02], + [9.3234240056988038e-01, 4.1953650763935330e-01, 1.4113432750827565e-01], + [9.4059857326609198e-01, 3.9995470577668230e-01, 2.6344398371546562e-01], + [9.4917701332394411e-01, 3.7668724490270555e-01, 3.1989621566180854e-01], + [9.2740067696444162e-01, 4.8729099035336931e-01, 1.0998149836911813e-02], + [9.4121986141210290e-01, 4.7406009795988807e-01, 6.1083414744020084e-02], + [9.4191678632289588e-01, 5.6803808438521586e-01, 1.5803555336063466e-01], + [9.2647958014273235e-01, 5.3035665984582359e-01, 3.9612705883821320e-01], + [9.2170028916998681e-01, 5.1242864762050866e-01, 4.6399074774318799e-01], + [9.3785599684095389e-01, 4.5560137905073822e-01, 4.4613915464426990e-01], + [9.4707205711983244e-01, 6.4383338433052306e-01, 1.6272052995550932e-02], + [9.4316718274560363e-01, 6.4415439622710935e-01, 8.3233525529319799e-02], + [9.4326009482997752e-01, 7.1265473012457914e-01, 1.7118678123754946e-01], + [9.2291652885097375e-01, 6.5848751862566701e-01, 2.6633053055766043e-01], + [9.4455508614586747e-01, 6.6482144534269627e-01, 4.0525630624206099e-01], + [9.3708359370419181e-01, 6.7713817717173463e-01, 5.3968356994227207e-01], + [9.3553992025884758e-01, 6.6617896125537723e-01, 6.1379702926030522e-01], + [9.2692343040636649e-01, 6.1184271201157370e-01, 6.0449297121230050e-01], + [9.1424549260000920e-01, 7.3964042555879950e-01, 9.0046445120450097e-03], + [9.2771366241446185e-01, 7.6291429891292351e-01, 5.9493460576955148e-02], + [9.4863288489410791e-01, 8.0765075927678376e-01, 1.4719088110284401e-01], + [9.3360953755900522e-01, 7.9727992559006466e-01, 2.7240391926539498e-01], + [9.3325071616962352e-01, 7.7721326019638270e-01, 4.0812572773553923e-01], + [9.3680222104880273e-01, 7.8598600252154327e-01, 5.6088878648470497e-01], + [8.5683073872505944e-01, 7.0499197275628067e-01, 5.5328013188460068e-01], + [9.3451723296687639e-01, 7.9792899713810528e-01, 7.0916801868368951e-01], + [9.3475426245255500e-01, 7.6324167873014481e-01, 7.4769173550057455e-01], + [9.3933841589310874e-01, 8.6888674940942878e-01, 6.9531442963505349e-03], + [9.4087886670522858e-01, 8.7144157045758752e-01, 5.1952769280156165e-02], + [9.4091152627023811e-01, 8.8292401987783331e-01, 1.5900259266643618e-01], + [9.3322127993214365e-01, 8.7429191894251379e-01, 2.8881889905192354e-01], + [9.2833468223996540e-01, 8.6801687387356474e-01, 4.2466790011340644e-01], + [9.5936730250358604e-01, 8.9004459793120783e-01, 5.7281033289591965e-01], + [9.4535331456234162e-01, 8.8299513857012824e-01, 7.0058696971281664e-01], + [9.2872176719914235e-01, 8.8360250797404549e-01, 8.0848337607910525e-01], + [9.2881641733139719e-01, 8.5575689338592498e-01, 8.3859751327360044e-01], + [9.1181597224265321e-01, 8.9858725931800421e-01, 1.4971577133709073e-02], + [9.3052675514716809e-01, 9.1657305506982345e-01, 7.6381165849346996e-02], + [9.0257789774244790e-01, 8.9060282863194351e-01, 1.6983390019151173e-01], + [9.3971994230358735e-01, 9.2664370129717455e-01, 3.0029930473708200e-01], + [9.2380990126908746e-01, 9.1346074981774872e-01, 4.5566095133246937e-01], + [9.2808017084488792e-01, 9.1444269722198690e-01, 6.1210454932126868e-01], + [9.2294194330713242e-01, 9.1330966560959159e-01, 7.5747891561476832e-01], + [9.2846771190001065e-01, 9.2149524160034413e-01, 8.7267611316417881e-01], + [9.3985250367416917e-01, 9.2317493987883448e-01, 9.1728180344005605e-01], + [9.8560455282895387e-01, 3.4063315955585705e-02, 1.3956468954195644e-02], + [9.8806888321487762e-01, 7.6522499028367677e-02, 6.8890533238230675e-02], + [9.8467769478242761e-01, 1.1052406747171062e-01, 1.5092152729548006e-02], + [9.8859388540088600e-01, 1.4127785577801841e-01, 7.7547961283940314e-02], + [9.8726409913136881e-01, 1.8208460643376592e-01, 1.6576297448601787e-01], + [9.8560063041052337e-01, 2.2441213717174396e-01, 8.7103112149838589e-03], + [9.8913388376833233e-01, 2.4564801233472933e-01, 5.6690069631866319e-02], + [9.8435756030015542e-01, 2.8063931439226630e-01, 1.3851002989325564e-01], + [9.8671443103664547e-01, 2.7098369567794883e-01, 2.0025519843622508e-01], + [9.8529135156176295e-01, 3.1178635804079419e-01, 2.9900893477882767e-01], + [9.8598782579474997e-01, 3.6493028208599265e-01, 1.5738008593957502e-02], + [9.8896786038997286e-01, 4.1169220183373756e-01, 7.5592733000669793e-02], + [9.8597246485945778e-01, 4.0859699580172110e-01, 1.6336421168511561e-01], + [9.8890668025767603e-01, 4.2504545418835371e-01, 2.7120246723927932e-01], + [9.9254857769434335e-01, 4.0689293439128954e-01, 3.3894849950424644e-01], + [9.8620354346065342e-01, 5.1656485235468075e-01, 1.2593766150492260e-02], + [9.8922956626467018e-01, 5.6900613388299059e-01, 6.7605136376520894e-02], + [9.8796375864476205e-01, 5.6564015595977701e-01, 1.6054422818955297e-01], + [9.4900767810444908e-01, 5.2696893857379568e-01, 2.6412348452589957e-01], + [9.9274568376997263e-01, 5.5799126718280212e-01, 2.6712059190856174e-01], + [9.8565380674461767e-01, 5.6409692734806882e-01, 3.8661601745956520e-01], + [9.8437168355626625e-01, 5.5278016938707653e-01, 4.7639668655458356e-01], + [9.8791043584804050e-01, 4.7038640340648247e-01, 4.5619912319763051e-01], + [9.9181048777279690e-01, 6.8477025397019253e-01, 9.6037701690571096e-03], + [9.8776411648382201e-01, 6.8037526018794847e-01, 5.5539488982897241e-02], + [9.9022157561292845e-01, 7.1712669963502296e-01, 1.6054137348199810e-01], + [9.8402358540030532e-01, 6.9239003969794588e-01, 2.8392620093984050e-01], + [9.9031173417988183e-01, 6.9712208771988615e-01, 3.9762969422261418e-01], + [9.8918638058605690e-01, 7.0009884335007255e-01, 5.2753613880415828e-01], + [9.8795031378629639e-01, 7.0434194956598728e-01, 6.3056523625384486e-01], + [9.8608577768256023e-01, 6.2809329641393175e-01, 6.1244275107480151e-01], + [9.8329705417469515e-01, 8.1571271217460528e-01, 1.5538484397847689e-02], + [9.8745016634383320e-01, 7.9183589552653943e-01, 7.6571039017220732e-02], + [9.9118155836890465e-01, 8.6401563809530624e-01, 1.8230195649529982e-01], + [9.8706131713626888e-01, 8.0958703272450061e-01, 2.7846243408736188e-01], + [9.8670414658955563e-01, 8.2602048819873519e-01, 3.9403115448136233e-01], + [9.8796176697199167e-01, 8.1492001852337248e-01, 5.2073904967809825e-01], + [9.8605476724699870e-01, 8.2589298900655728e-01, 6.6774741168411156e-01], + [9.8760433538525438e-01, 8.2287868131728914e-01, 7.6624282045625913e-01], + [9.8708496173774340e-01, 7.7233441667595892e-01, 7.6209525385302401e-01], + [9.9084334790912043e-01, 9.2277532136399210e-01, 1.5757139980853596e-02], + [9.8819450958507349e-01, 9.0358175675044294e-01, 8.0521872346126844e-02], + [9.8751281177162353e-01, 9.5210828509620915e-01, 1.8367941968202842e-01], + [9.8586197665676134e-01, 9.1554703672059412e-01, 3.1051956451591728e-01], + [9.8813485676994584e-01, 9.1727414714926059e-01, 4.6508110109121431e-01], + [9.9442697267107805e-01, 9.2108584276891359e-01, 6.3824810977792845e-01], + [9.9014077978092685e-01, 9.2345245561297307e-01, 7.7607218106816700e-01], + [9.8524770755057645e-01, 9.1515867250327732e-01, 8.5613536413871583e-01], + [9.8698758683321042e-01, 8.9746137367946011e-01, 8.8847212027209799e-01], + [9.8213450481098841e-01, 9.6914168708970050e-01, 1.5349142792633181e-02], + [9.8767477821711114e-01, 9.7173210885380468e-01, 7.8775270148218945e-02], + [9.7738773889726516e-01, 9.7711164367423875e-01, 1.8218249713311049e-01], + [9.9071634266524555e-01, 9.7805729275067832e-01, 3.1563905962917471e-01], + [9.8434299830323269e-01, 9.7020970655233330e-01, 4.5354472933350709e-01], + [9.8684071686196873e-01, 9.7308641065355361e-01, 6.0626613348434688e-01], + [9.8506668135300035e-01, 9.7148560912268345e-01, 7.5468709313977511e-01], + [9.8539330391102986e-01, 9.7292625774148245e-01, 8.7869228709820346e-01], + [9.8769815706165498e-01, 9.7102425711600693e-01, 9.5223002373468579e-01], + ], + weights: [ + 2.7891645048924669e-05, + 5.9240500689929711e-05, + 6.5978960004489271e-05, + 1.4436350716490943e-04, + 2.3274099488849217e-04, + 7.9503156347022799e-05, + 1.6560077054520502e-04, + 4.0929853645439371e-04, + 2.2421688881744047e-04, + 1.6724859123569107e-04, + 1.2102246862800585e-04, + 1.9263193906584358e-04, + 3.9783180796362268e-04, + 2.1355828759641237e-04, + 5.8223166821553488e-04, + 4.7236856714447746e-04, + 3.4571235647222237e-04, + 3.1462787796355792e-04, + 5.6639592410931883e-04, + 6.8930988201383039e-04, + 4.9441509053464738e-04, + 3.8416209195427889e-04, + 3.0982173025635203e-04, + 1.4546217560819504e-04, + 4.1652323059428485e-04, + 2.6857622206009489e-04, + 1.7874718590020700e-04, + 1.6555763350557614e-04, + 2.1059612729525020e-04, + 2.9677102400535097e-04, + 5.8852212449639221e-04, + 4.7196954972515248e-04, + 8.6079998946401374e-04, + 9.8167037937063584e-04, + 6.0705337414128119e-04, + 4.2351922584344873e-04, + 4.5074295116736391e-04, + 9.8883169501795499e-04, + 1.1261739527780462e-03, + 9.5896324375412015e-04, + 9.3023717219299466e-04, + 8.3029654290866248e-04, + 3.3161364423282694e-04, + 6.1762224760577569e-04, + 4.6161788488350745e-04, + 1.7167048420878842e-04, + 1.6711496801992008e-04, + 2.7618974943992503e-04, + 1.0120286124733788e-03, + 3.6377283424653031e-04, + 2.2918355789165108e-04, + 1.9712704108924381e-04, + 2.7272311900836907e-04, + 4.6856261214378689e-04, + 6.1118768736360225e-04, + 4.8596197732398084e-04, + 5.3068837941582111e-04, + 1.1114292062138849e-03, + 1.0019518121756909e-03, + 4.8591080038365306e-04, + 1.4138836796500054e-03, + 1.5969838333126218e-03, + 1.2539645204344965e-03, + 5.6203555077390089e-04, + 1.1826665486993559e-03, + 1.6980903169518822e-03, + 1.4567860706870652e-03, + 9.3079009199262155e-04, + 4.4693543691857649e-04, + 6.4954758977829358e-04, + 3.9133810223944025e-04, + 4.0887722076827371e-04, + 8.5615252803438683e-04, + 1.1430853136792645e-03, + 7.9820053036786995e-04, + 6.1509998831974324e-04, + 1.7469540648215292e-04, + 2.9619024247777256e-04, + 4.3993049815372384e-04, + 4.7358115914980639e-04, + 1.3607110837399944e-03, + 5.6977762070215161e-04, + 5.4944704833447293e-04, + 1.6784788280145333e-04, + 1.6118232237749100e-04, + 1.5456103780224902e-04, + 1.1606265922172616e-04, + 6.2395424009789515e-04, + 3.4416459873136060e-04, + 5.5784444775304248e-04, + 8.2279803751786958e-04, + 8.4824789616938219e-04, + 4.9232741002454782e-04, + 1.2959013848550854e-03, + 1.4431149595967158e-03, + 1.3997638649269921e-03, + 6.0730953165911029e-04, + 1.9735688258290598e-03, + 5.6000365710911918e-04, + 6.0697578454514255e-04, + 1.3520656098434671e-03, + 1.8269323709606232e-03, + 1.6423848653281386e-03, + 1.3439356892405731e-03, + 5.7409511192635094e-04, + 5.5259958166247163e-04, + 8.9570379506093622e-04, + 1.6458223314355920e-03, + 1.9037533826305757e-03, + 1.6471647964810337e-03, + 9.4526312272877864e-04, + 4.4123571227350905e-04, + 3.8384387599829097e-04, + 6.7485882858371962e-04, + 1.1978706161865588e-03, + 1.2899773745980580e-03, + 8.1495764316158134e-04, + 6.2168963935779233e-04, + 3.3454935788992360e-04, + 6.1283463857843523e-05, + 3.1049110191478963e-04, + 6.5414243897892301e-04, + 6.0617344998615207e-04, + 1.3113772954315591e-03, + 4.8338682221420258e-04, + 3.2610750557389402e-04, + 1.7090651415117191e-04, + 1.8553123555158750e-04, + 1.2387176717432928e-04, + 2.6513351984552364e-04, + 7.0536492173482344e-04, + 4.4656962167665331e-04, + 2.6589442598524672e-04, + 9.0429493648986822e-04, + 1.4826083569930608e-03, + 1.1763829496307786e-03, + 4.5467980830286359e-04, + 5.1367208742729821e-04, + 9.6418733178286467e-04, + 1.4187767887255663e-03, + 1.3996942571964390e-03, + 8.4670429863214754e-04, + 3.8576299073731396e-04, + 6.8597991019407904e-04, + 1.0681564054912582e-03, + 1.2846700383571518e-03, + 1.0334019808655754e-03, + 3.6570538666546416e-04, + 3.9633725627159788e-04, + 6.5001734805585694e-04, + 1.2859584593034562e-03, + 1.3749352872110575e-03, + 9.9075383909226749e-04, + 5.1832210816757872e-04, + 3.7467540175869601e-04, + 7.9951049259854693e-04, + 1.0339682039359811e-03, + 1.6311601093040688e-03, + 1.3860812593978911e-03, + 5.1816981375234661e-04, + 4.0983168619035989e-04, + 4.2416678039582518e-04, + 7.9271315618702123e-04, + 1.0039509936764597e-03, + 1.1689955368234751e-03, + 1.0651785678409968e-03, + 7.8659247595178690e-04, + 5.8559039506361657e-04, + 1.4975666549771823e-04, + 1.3371881330177305e-04, + 3.1414353459502224e-04, + 5.2723029021729795e-04, + 5.2575384171012489e-04, + 6.2943368728614140e-04, + 4.7086853101370777e-04, + 3.7583560445044655e-04, + 1.6322552325927925e-04, + 5.1287342586510347e-05, + 2.3859213649297874e-04, + 2.9981209348189068e-04, + 1.1594040663079799e-04, + 3.4275508771761175e-04, + 6.2067414651884002e-04, + 9.9398878307963771e-04, + 7.1160657873016887e-04, + 3.4115843155293086e-04, + 3.8619313382447169e-04, + 7.2417143288839908e-04, + 9.9635921471528943e-04, + 8.5939811504243775e-04, + 6.2940626751072917e-04, + 3.4974153614389093e-04, + 8.2578577448028621e-04, + 9.4022953010034134e-04, + 1.1861972738661389e-03, + 7.9210826598394323e-04, + 2.7876908937320697e-04, + 3.8670719962095905e-04, + 7.4615829380926296e-04, + 7.6652545998783927e-04, + 1.3938030436269099e-03, + 1.1940827467710185e-03, + 1.0279124826764823e-03, + 6.5132432552961390e-04, + 2.7550596585891867e-04, + 2.8184602846598427e-04, + 6.8453582615058924e-04, + 6.3238795575942960e-04, + 1.0052246870398645e-03, + 1.2171105773861313e-03, + 1.0141059938261426e-03, + 1.2790131769592717e-03, + 7.8499085979366146e-04, + 3.4675726752107392e-04, + 1.3307754396200386e-04, + 3.8233123928165772e-04, + 5.8487134130646621e-04, + 5.4969998667491425e-04, + 8.4151655667072552e-04, + 5.8377562998757385e-04, + 6.2455938882142624e-04, + 4.0271220577590896e-04, + 2.6546856711806934e-04, + 1.2010921010969323e-04, + 2.3635746144184369e-04, + 3.3905114215108646e-04, + 3.4896798087820884e-04, + 3.6524361557371831e-04, + 4.3352940296186986e-04, + 2.7930518206587695e-04, + 1.1902040337869642e-04, + 5.5187929176263131e-05, + 6.5111506233509113e-05, + 5.7303144864862764e-05, + 1.4844959349619123e-04, + 2.2251917279257058e-04, + 1.5087637890626244e-04, + 1.1574661575965496e-04, + 2.7807885095500846e-04, + 3.8419155517856218e-04, + 3.0770764265640671e-04, + 1.8396390220744055e-04, + 2.0516816785559323e-04, + 3.6695330245219967e-04, + 5.0209270095397053e-04, + 3.8576684175618863e-04, + 2.2569151411133557e-04, + 1.8694792566324354e-04, + 3.1339556158439271e-04, + 4.9416223268806318e-04, + 1.0872824790544727e-03, + 3.3954189639145494e-04, + 5.5515629906578332e-04, + 5.0526339001360284e-04, + 1.7931252289388110e-04, + 9.5485194627693409e-05, + 2.1679946054377038e-04, + 4.4568741409383702e-04, + 6.3572990112158168e-04, + 4.0015091733127300e-04, + 4.4827451550172868e-04, + 3.4423671738090424e-04, + 2.1588805499872380e-04, + 2.0104669588563834e-04, + 3.2680804093902517e-04, + 3.1858069319270159e-04, + 4.1795343702095994e-04, + 4.4636635403360502e-04, + 5.0715406870529840e-04, + 4.8078492592526847e-04, + 2.5879021149378148e-04, + 1.2533292900244685e-04, + 8.0481491967152919e-05, + 2.4322835754548374e-04, + 2.3797015300726517e-04, + 4.1150944714294816e-04, + 3.8064056568357264e-04, + 2.2425201011759034e-04, + 2.1800491256895101e-04, + 2.0271046772829207e-04, + 8.6451518755914286e-05, + 5.8833721723477982e-05, + 1.1427782164636651e-04, + 6.8175388487184239e-05, + 1.1780439338578969e-04, + 2.0545718042093466e-04, + 1.8708986329546556e-04, + 1.8305059484146114e-04, + 1.2247146928311297e-04, + 6.3119697936266496e-05, + ], + }, + 18: { + points: [ + [7.8810288561015268e-02, 1.9616167112771279e-02, 1.3237206200457275e-02], + [3.6544978429271416e-02, 2.5593835735837293e-02, 3.4405131262404791e-03], + [5.5616982106167896e-02, 4.2912757254385245e-02, 3.8050714693034372e-02], + [1.7400489463643332e-01, 2.8742243672973476e-02, 1.5176745823345515e-02], + [1.2027208054971394e-01, 5.4228310615035004e-02, 4.7993097546051612e-03], + [1.5637300233079926e-01, 8.4903747004766286e-02, 7.4206945379888020e-02], + [1.2327966722546831e-01, 1.1014609977267455e-01, 1.0682183268896007e-02], + [1.4924818105407017e-01, 1.3493900282554097e-01, 1.2224802905714248e-01], + [1.3596112331639193e-01, 8.2368803027767057e-02, 3.6974579798707191e-02], + [1.0446964452744194e-01, 9.4073311674385859e-02, 4.7907821420740578e-02], + [2.9056946361171504e-01, 2.3551812935416999e-02, 1.1145719199034848e-02], + [2.4523350994270648e-01, 1.3400185232013345e-01, 6.5954609191984814e-02], + [2.5713337553729382e-01, 8.4885579771751443e-02, 7.2053557163533663e-02], + [2.4196191269318329e-01, 8.2351410940013234e-02, 1.2379043521369258e-02], + [2.6258881346729551e-01, 1.7866500275759800e-01, 1.6656732489437390e-01], + [2.2622066660640322e-01, 1.9302250641680366e-01, 6.3482170822916101e-02], + [2.1871241016539947e-01, 1.4761799241796850e-01, 1.3432949570148376e-02], + [2.3231291023980322e-01, 1.9176883441385634e-01, 1.3277455399368304e-01], + [2.6995042086712140e-01, 2.4553899184868827e-01, 2.3837643184588347e-01], + [2.4186274066628052e-01, 2.2713767240989388e-01, 1.2293164471474407e-02], + [2.0929478254115044e-01, 2.0877455646103429e-01, 5.6888016463244796e-02], + [2.1025088406461351e-01, 2.0651211081259235e-01, 1.2851008752911119e-01], + [2.6585553068938544e-01, 2.6446941717632666e-01, 2.3683631603352032e-01], + [3.6528893533049556e-01, 7.3051179263368085e-02, 9.5502934784227646e-03], + [3.7417250476038633e-01, 6.7047043474726037e-02, 5.7395502852798326e-02], + [3.5604403725017442e-01, 1.7439718258704803e-01, 6.4555281211459504e-03], + [3.4805879394798928e-01, 1.1380910592772034e-01, 5.6785808488811457e-02], + [4.0911806421532526e-01, 1.6476536078177789e-01, 3.8176777371088794e-02], + [3.6827606954302317e-01, 1.7203584835923261e-01, 1.6053901654622715e-01], + [3.4569608563036208e-01, 2.1399591259786646e-01, 1.5709606276971075e-01], + [3.4427339714649796e-01, 2.1321303572696074e-01, 4.5255656226302385e-02], + [4.1437019302421696e-01, 2.2874037385828164e-01, 1.0966747480508962e-01], + [3.8850557494209403e-01, 2.7930637926895729e-01, 2.7363835817524429e-01], + [3.3534929226782217e-01, 2.6085426989620442e-01, 8.3872732640466914e-03], + [3.4643986697446044e-01, 2.6646870095238900e-01, 1.0594388719770305e-01], + [3.8253703262195687e-01, 3.1301497109807724e-01, 1.8895864088167730e-01], + [3.5598124437599787e-01, 3.0999936924144472e-01, 5.3774503728684503e-02], + [3.4952344830691928e-01, 3.0415710508326704e-01, 2.5981716620015705e-01], + [3.7745087113316328e-01, 3.6601403131508115e-01, 1.3391745643079416e-02], + [3.4754143948503596e-01, 3.4072893536148086e-01, 7.2662541489791330e-02], + [3.4314522613666909e-01, 3.2776821958199498e-01, 1.5201009946055952e-01], + [3.4761885992252251e-01, 3.3519153609986924e-01, 2.4220874546006052e-01], + [4.0351913088445213e-01, 3.9157952024133302e-01, 3.8050811672031376e-01], + [4.5074057556429392e-01, 2.5127345384391087e-02, 1.2012799439708547e-02], + [5.0707132998659932e-01, 8.4994818018987214e-02, 1.2697566987188696e-02], + [4.8910200175774809e-01, 1.0046687761228983e-01, 5.8589208405296508e-02], + [4.8542278987888771e-01, 1.6893946918492231e-01, 1.0316493496312635e-02], + [5.2983213660874484e-01, 1.8269098102277348e-01, 6.3441221139514281e-02], + [4.5950529252732808e-01, 1.8572887756976639e-01, 1.3639299560589141e-01], + [4.8112817680300074e-01, 1.4580639638615772e-01, 1.3978014994762034e-01], + [5.4235215093650313e-01, 2.8663435777478391e-01, 1.3614224976447498e-01], + [5.1970861190378337e-01, 2.9821689117370292e-01, 6.8705036849453218e-03], + [5.0621073354033219e-01, 2.8176363008586491e-01, 5.3842757919169502e-02], + [5.2755941731911482e-01, 2.9161012006771186e-01, 2.3919950856484248e-01], + [5.0246097593201422e-01, 2.8710243997849638e-01, 2.7866719652410460e-01], + [4.5994386463948600e-01, 3.2272681339432802e-01, 1.6163474279407872e-02], + [4.7847561320292464e-01, 3.6611004693609478e-01, 6.0528425003687200e-02], + [4.9392814113959654e-01, 3.6293064094062205e-01, 1.3266397962463544e-01], + [5.1202968225291090e-01, 3.5165670664469661e-01, 2.2458980388849925e-01], + [4.5854752141048083e-01, 3.2998200495921259e-01, 2.8494863325767539e-01], + [4.6204558147686597e-01, 4.0973758816425560e-01, 1.1166145527140138e-02], + [5.1880986867235523e-01, 4.5664630989009913e-01, 2.4312331477899199e-01], + [4.4150142596975617e-01, 3.7869529008892505e-01, 3.6782987776388087e-01], + [5.2363266281526000e-01, 4.7324004548567317e-01, 6.3363630979714994e-02], + [4.7072473558063677e-01, 4.2531123824829514e-01, 1.4027938356918895e-01], + [4.9487870576083443e-01, 4.2740550556136886e-01, 3.2006513889995941e-01], + [5.1525851265766109e-01, 4.7060964832619517e-01, 4.2162698868463350e-01], + [5.3416986084502371e-01, 5.2925751399816934e-01, 1.0257951587328496e-02], + [4.8872040099460978e-01, 4.7860591944168307e-01, 6.1898481517803333e-02], + [4.9497411419984338e-01, 4.8720018474832194e-01, 1.5378058811184520e-01], + [4.7391838369341999e-01, 4.6636025686441485e-01, 2.4344434889144309e-01], + [4.9332437727996076e-01, 4.7755238438456560e-01, 3.2472613177984211e-01], + [4.5334575653997561e-01, 4.4554228787553063e-01, 3.8417828489724615e-01], + [5.5722284579716541e-01, 5.4872591737796528e-01, 5.3708372730351184e-01], + [6.1173379288004792e-01, 2.1958231040496032e-02, 1.1934880583278240e-02], + [5.5392350827857528e-01, 6.9761346085068163e-02, 6.5979323002975432e-02], + [6.1593281827522639e-01, 6.1701307751279216e-02, 7.3466397545498214e-03], + [6.3306600570025295e-01, 8.6020588837691495e-02, 4.8769630825354127e-02], + [6.5887444731054645e-01, 1.6316444450561521e-01, 5.7922089870870809e-02], + [6.3148641832248076e-01, 1.6229490436841029e-01, 1.1831976760666186e-02], + [6.5288409509906620e-01, 2.7823985051740230e-01, 6.2907104367773276e-02], + [6.0471835614659541e-01, 1.6819346340453117e-01, 1.3310239187825740e-01], + [6.6763342814118476e-01, 1.8134826989244163e-01, 1.7672067812374198e-01], + [6.0152876381813336e-01, 2.6263286789031376e-01, 1.4728858643177571e-02], + [6.1849226474929220e-01, 2.4197578192425356e-01, 1.4158289898949028e-01], + [6.0868917164046288e-01, 3.0116521538082053e-01, 2.1398305104521942e-01], + [6.0299496159653310e-01, 2.5694363602879178e-01, 2.4155143043806962e-01], + [6.6104654620186154e-01, 4.1961095998406189e-01, 1.1547349033922499e-02], + [6.3612616718896242e-01, 3.9507013797900298e-01, 5.1992513554075033e-02], + [6.7539507154107703e-01, 4.1037388304906713e-01, 2.5146279858783221e-01], + [6.4260354006813081e-01, 4.1432507785845812e-01, 3.4827568193068892e-01], + [6.5543087800898781e-01, 3.9481576233825760e-01, 3.8218409389588021e-01], + [5.8646295019855899e-01, 4.6445841932728854e-01, 6.6154757251070430e-03], + [6.1591010227060039e-01, 4.8766423530752329e-01, 4.2768229438148404e-02], + [6.2148361162812094e-01, 4.6996966082036551e-01, 1.1135236858010004e-01], + [6.3680514450669012e-01, 3.7881149058428237e-01, 1.3264272442629829e-01], + [6.3226891756971759e-01, 4.9331060197914117e-01, 3.4302576261847972e-01], + [5.7947029051090715e-01, 4.3956153219324751e-01, 4.2613176360488730e-01], + [6.0044706747224752e-01, 5.6151934791186697e-01, 1.4574382127562770e-02], + [6.2855797156946047e-01, 4.7783612289441357e-01, 2.2479412442372063e-01], + [6.1030493291024634e-01, 4.8876702134313660e-01, 4.2264840968617895e-01], + [6.7983474507443997e-01, 6.1818796247334351e-01, 7.2721293514197607e-02], + [6.2071534336250178e-01, 5.5076318658104295e-01, 1.5428913607057970e-01], + [6.4668471961910223e-01, 5.8256761949347213e-01, 2.6099515655039529e-01], + [6.5352485500326019e-01, 5.9154582130583933e-01, 3.7346065988845739e-01], + [6.3062205833758822e-01, 5.8502658967513410e-01, 4.5852982020451394e-01], + [6.5291951009384264e-01, 5.9115745711467294e-01, 5.4808919692997360e-01], + [5.7434579805091135e-01, 5.2153780085115020e-01, 5.1493081338569724e-01], + [6.4095902725257226e-01, 6.3125029012860256e-01, 6.1398578060902841e-02], + [6.3094070362709553e-01, 6.1645450296157900e-01, 1.5017275145598383e-01], + [6.1115877453845135e-01, 5.9624937212482276e-01, 2.7477711589452064e-01], + [6.4064005258076873e-01, 6.3093078880621012e-01, 4.1111537785606128e-01], + [5.9865393652540488e-01, 5.9165245922616427e-01, 4.8045002921910812e-01], + [6.2348192802026048e-01, 6.1083582091410726e-01, 5.6367588115243994e-01], + [7.3343924377691283e-01, 5.4255403469176872e-02, 4.8060524398850744e-02], + [7.5924301532780580e-01, 2.7140072916969259e-02, 9.4740861441610612e-03], + [7.7115088808780663e-01, 1.0945996633781170e-01, 5.4551627698391773e-02], + [7.4562399422253534e-01, 9.8430396666740749e-02, 1.1477682748869447e-02], + [7.8300045136432961e-01, 2.2746621671589648e-01, 6.6153857940304442e-02], + [7.1960106876415408e-01, 1.2315206167750366e-01, 1.0861279920365224e-01], + [7.6217626763763657e-01, 2.1471061226081681e-01, 1.2806773247470418e-02], + [7.4479890669868465e-01, 2.1324561360462987e-01, 1.3969922708945659e-01], + [7.7783798927400705e-01, 2.4974182024598240e-01, 2.3055694107943256e-01], + [7.1218991773136686e-01, 3.1929158858997542e-01, 9.0522197067648535e-03], + [7.6448073357664303e-01, 3.4516465201483870e-01, 5.9370982129355251e-02], + [7.3185625383952790e-01, 3.2132405245634837e-01, 1.4914624761416581e-01], + [7.1558686687426631e-01, 3.2231693091091507e-01, 2.6167485415960079e-01], + [7.4271339652316604e-01, 3.4561352967745057e-01, 3.3792704607281898e-01], + [7.8830150814182709e-01, 4.6988046991361471e-01, 1.5088246889989495e-02], + [7.6092373435890026e-01, 4.6800931994175771e-01, 7.2046523280620750e-02], + [8.2690578882843446e-01, 3.3713192034169670e-01, 2.3885695214606192e-01], + [7.8785132099297872e-01, 4.6497828771987820e-01, 3.4475693567729038e-01], + [7.6417861064433079e-01, 4.6221199787490896e-01, 4.2009511789705722e-01], + [7.5204468526652690e-01, 5.7040133310830876e-01, 9.8597532039699128e-03], + [7.5867832308926086e-01, 4.7199777321846648e-01, 1.6796034997061535e-01], + [7.6294472305863559e-01, 5.5609025675163248e-01, 4.3314476959727244e-01], + [7.4906696304753162e-01, 5.7885192428393761e-01, 5.3540888477294257e-01], + [7.3355648987402944e-01, 5.2329963083025921e-01, 5.1835814048913353e-01], + [7.2189698387101786e-01, 6.3950232100345250e-01, 1.3125896509357358e-02], + [7.5695734781719004e-01, 5.9145023813646058e-01, 5.9374961199540853e-02], + [7.4456136463523936e-01, 5.7525072715597680e-01, 1.4755906770704563e-01], + [7.4435670655680108e-01, 6.0015934097507773e-01, 2.5869660072935519e-01], + [7.7241397637259868e-01, 5.3974260318691225e-01, 2.9749284928431341e-01], + [7.5830295276580706e-01, 6.2175814043426858e-01, 4.0236504299288800e-01], + [7.4459936338838528e-01, 6.4923033352406589e-01, 5.3725557800174562e-01], + [7.1433412617964442e-01, 6.1474209855090900e-01, 6.0438907826448218e-01], + [7.1512632285630040e-01, 7.0278310636284036e-01, 1.1045624844589163e-02], + [8.0466530884667908e-01, 7.4444906509880771e-01, 5.4350754273909625e-02], + [7.6797137437825747e-01, 6.8852895566707240e-01, 1.4384980686161067e-01], + [7.5811754501821937e-01, 7.0254195719793544e-01, 2.4632609031000618e-01], + [7.7297274952994244e-01, 7.0451570354387760e-01, 3.7186932597837596e-01], + [7.7758104974841324e-01, 7.3402418382152934e-01, 5.2185434814799680e-01], + [7.5599571700216006e-01, 7.1059119504797352e-01, 6.6393989829974931e-01], + [7.1440070739114425e-01, 6.9425526997218312e-01, 6.8813571986410083e-01], + [7.7270745448406064e-01, 7.5897092290240498e-01, 5.9456122088615532e-02], + [7.6993607810134779e-01, 7.5573152889625073e-01, 1.4989803292770729e-01], + [7.2766550372982941e-01, 7.1918131150936615e-01, 2.6873711246191756e-01], + [7.5144295252779736e-01, 7.3740076952085309e-01, 4.1046262696820834e-01], + [7.6895766122046838e-01, 7.6508309526030516e-01, 5.6157576892959826e-01], + [7.3931559334367714e-01, 7.2287496384245464e-01, 6.1006794668458753e-01], + [7.4890882934016345e-01, 7.4826166721029330e-01, 7.1130212921489311e-01], + [8.7347093121153829e-01, 1.0450093933680200e-02, 9.8660869172979213e-03], + [8.5882675365259276e-01, 7.0873928988039514e-02, 5.4553338418018890e-02], + [8.6896381110646881e-01, 6.2433416761089065e-02, 1.0886102209124764e-02], + [8.6310550218116877e-01, 1.6251309192966479e-01, 1.2872115730089433e-02], + [8.6243865064612713e-01, 1.5879839002216098e-01, 6.2491150181705590e-02], + [8.4589396078004397e-01, 1.8377925823405547e-01, 1.3775687336469969e-01], + [8.3875615738125320e-01, 1.4466442219409417e-01, 1.3851753666858937e-01], + [8.8057875147586262e-01, 2.8773638730918971e-01, 4.3356495944907383e-03], + [8.8166507033535324e-01, 3.0360400094597884e-01, 5.1183095634300388e-02], + [8.6638045868831859e-01, 2.9532095804745873e-01, 1.4444338632544940e-01], + [8.9049071375361577e-01, 3.1189018441496091e-01, 2.5651686442363825e-01], + [8.7251737361670489e-01, 2.6377671789274987e-01, 2.5593675104614710e-01], + [8.2559210583517884e-01, 3.5155084896579963e-01, 1.6053397409012446e-02], + [8.8236567860518722e-01, 4.6415711968552997e-01, 5.7375687703068903e-02], + [8.4331032947036999e-01, 4.0883603716739947e-01, 1.2948363557382084e-01], + [8.0814941014636943e-01, 4.2236998515430957e-01, 2.4453255401016086e-01], + [8.9322897474285778e-01, 4.8892603093583820e-01, 2.4549796750338632e-01], + [8.5082073283862314e-01, 4.1709685331250707e-01, 3.7063883988721569e-01], + [8.6598573891514408e-01, 3.9001595471998995e-01, 3.8209750578740415e-01], + [8.8008274218799554e-01, 4.7061267322522632e-01, 6.8019325324193353e-03], + [8.9689541383617943e-01, 4.9830778155856675e-01, 3.6582117538934050e-01], + [8.3419919831713807e-01, 5.1377605289060735e-01, 5.0444263657984478e-01], + [8.6753155235912327e-01, 6.1444665686839550e-01, 1.5115618807156837e-02], + [8.6209539271651525e-01, 5.9220891146682908e-01, 6.9593175514923417e-02], + [8.6190081586523470e-01, 5.8014406596702750e-01, 1.5886278491957986e-01], + [8.5734450720856348e-01, 5.7764237855191314e-01, 2.7253471457183920e-01], + [8.7503573850925243e-01, 6.1786864905676919e-01, 4.1658393444757091e-01], + [8.6646108650388254e-01, 5.7595618520439040e-01, 4.9948001029553662e-01], + [8.6973758416966718e-01, 6.3951248303193076e-01, 6.1017038314764549e-01], + [8.6032883718831199e-01, 7.2707829870553065e-01, 1.0692690615692509e-02], + [8.5813656567593299e-01, 7.2499183634997644e-01, 5.9922649386437279e-02], + [8.6185334575387962e-01, 7.0119970877706128e-01, 1.4466147591215567e-01], + [8.4823039550389101e-01, 6.9228768714538780e-01, 2.5708006135215145e-01], + [8.6686419938356385e-01, 7.1370464036970749e-01, 3.8792876139957311e-01], + [8.5683557513413744e-01, 7.3813670446283319e-01, 5.2185179924171932e-01], + [8.6447721446363479e-01, 6.9045873292890414e-01, 5.7459418467739565e-01], + [8.4690945710907040e-01, 6.7569738270963908e-01, 6.7285261434361554e-01], + [8.2780036003944912e-01, 7.8241269525382917e-01, 8.4142175809567028e-03], + [9.0603278690096845e-01, 8.6806881826296134e-01, 6.2372445193699680e-02], + [8.7037225229525550e-01, 8.1096717901724613e-01, 1.4216163094441980e-01], + [8.7263600558014054e-01, 8.0227599947484274e-01, 2.7180538741837873e-01], + [8.7838199550865614e-01, 8.1552271922829123e-01, 4.1643643751128456e-01], + [8.7882506169678687e-01, 8.3190135285261901e-01, 5.5099860538490708e-01], + [8.5639159264431353e-01, 7.9363861282633852e-01, 6.7211723148026126e-01], + [8.4669420881196922e-01, 7.3167599420423379e-01, 6.8708229912550345e-01], + [8.3031797614927694e-01, 7.5835510434770748e-01, 7.5211162613742810e-01], + [8.5884798629918124e-01, 8.5173852190103316e-01, 1.6504512277456966e-02], + [8.6581361570439408e-01, 8.5967737696307789e-01, 8.7795551514144587e-02], + [8.9941249396146883e-01, 8.8799356043063826e-01, 1.6809373040052208e-01], + [8.5268355448180255e-01, 8.3993482349400572e-01, 2.5994520966728135e-01], + [8.3784167544009425e-01, 8.2341739306816109e-01, 3.7532111290681902e-01], + [8.6228862541979845e-01, 8.5478895970343827e-01, 5.2477287259561367e-01], + [8.8253015586683370e-01, 8.6963451654437995e-01, 6.7551281053093470e-01], + [8.4926791379721234e-01, 8.3812508570659061e-01, 7.4757221170206378e-01], + [8.4814237725312791e-01, 8.1472181530577481e-01, 7.8179364621472891e-01], + [8.5615276197719992e-01, 8.4613004552854432e-01, 8.3598349163613039e-01], + [9.4952307819146198e-01, 5.5435753057803010e-02, 5.0422487150252007e-02], + [9.4744816550310984e-01, 3.3868111030904056e-02, 1.0639656164639622e-02], + [9.3820802362902000e-01, 1.1368654986728356e-01, 5.8013207942564177e-02], + [9.4900152436835972e-01, 1.2445335614734794e-01, 1.1858376983776620e-02], + [9.3499833577766356e-01, 2.2764846883837411e-01, 6.6283571496479385e-02], + [9.2653702998936094e-01, 2.2052144709680385e-01, 1.4966399242853190e-01], + [9.3379050598116953e-01, 1.3922350831621033e-01, 1.2589834767784674e-01], + [9.4979356731765907e-01, 2.4989058133826184e-01, 1.3537845245003512e-02], + [9.5578267335588685e-01, 3.9802426861646928e-01, 6.1418349607851058e-02], + [9.6588069532250198e-01, 2.8666857378204047e-01, 1.4967161366571352e-01], + [9.7259489458336901e-01, 3.1467752489903361e-01, 2.4603043252196163e-01], + [9.4203625470221508e-01, 2.4923373014284261e-01, 2.3437180733517129e-01], + [9.4291177098229551e-01, 4.1100805841045351e-01, 1.3854656230890453e-02], + [9.3829577764832139e-01, 3.8980173535228912e-01, 1.3884372493726269e-01], + [9.3393965271786095e-01, 4.0175762979664925e-01, 2.5315962927327651e-01], + [9.5009384989992984e-01, 4.1376921050693083e-01, 3.5051580496409540e-01], + [9.4600886506571102e-01, 3.9894649676444149e-01, 3.8614109881269998e-01], + [9.4094289922487240e-01, 5.8668949521341707e-01, 5.2241069747967226e-03], + [9.4569635748642011e-01, 5.7594773305149205e-01, 4.0445629381689448e-02], + [9.2872767556642732e-01, 5.3109921420480444e-01, 1.3813422730338665e-01], + [9.5675978379029958e-01, 5.9513080687264808e-01, 3.7023403350903633e-01], + [9.7341668129595216e-01, 5.3766575923710047e-01, 4.0932374535178606e-01], + [9.3543249852644517e-01, 5.3359397039471068e-01, 4.7296903465011658e-01], + [9.2920389537362702e-01, 5.4690084932898908e-01, 5.3287185724969266e-01], + [9.4552774386475247e-01, 7.2994993821782184e-01, 1.4376030629098370e-02], + [9.3289438792170132e-01, 7.0548700959029376e-01, 7.3001427202928501e-02], + [9.5886802327353571e-01, 6.6051877008595472e-01, 1.2361683883021869e-01], + [9.4240033712790638e-01, 7.0444493807004660e-01, 1.8590727999516257e-01], + [9.5809146785886934e-01, 5.5507126639457094e-01, 2.3613852502580054e-01], + [9.4532057361442490e-01, 6.9621427399743385e-01, 4.0200320747325763e-01], + [9.4778178238492894e-01, 6.6776939831317794e-01, 5.3707283733603339e-01], + [9.4835648592179511e-01, 6.9313722358764263e-01, 6.4888933411353844e-01], + [9.4162186200631959e-01, 6.8303791118832125e-01, 6.8283084495573498e-01], + [9.5362220638408979e-01, 8.3680753356492521e-01, 1.0735010343037926e-02], + [9.3926776450122118e-01, 8.1800259181261026e-01, 6.7239031710224273e-02], + [9.3649069078643798e-01, 8.0389218605180346e-01, 1.7547485389006229e-01], + [9.4357915610601006e-01, 8.0161418804788598e-01, 3.0593032894383626e-01], + [9.4368941824968910e-01, 8.0570604322979000e-01, 4.4850484633899779e-01], + [9.2878267227065803e-01, 6.7072859229947479e-01, 2.8033366275835420e-01], + [9.4242042572742890e-01, 7.7239540800277706e-01, 5.5600284719789195e-01], + [9.3956122610985426e-01, 7.9509194813612960e-01, 6.9905660193979835e-01], + [9.3530300277628220e-01, 7.9510841749212857e-01, 7.7587816522594732e-01], + [9.2018030584919852e-01, 8.5751741686667815e-01, 1.6730253626133416e-02], + [9.5979959250106006e-01, 8.9735182437187955e-01, 1.3369850799651520e-01], + [9.4198938384761477e-01, 8.9466605209922057e-01, 2.5350860273689657e-01], + [9.5431854867510735e-01, 8.9907310027542797e-01, 3.9677457612348449e-01], + [9.5341573656742595e-01, 8.8805513475452003e-01, 5.4587449051429204e-01], + [9.3507984818633638e-01, 8.5806785350339587e-01, 6.4593548195044803e-01], + [9.4639608665061137e-01, 8.9081595947069403e-01, 7.5228225263443471e-01], + [9.3005196228067755e-01, 8.7431252072913246e-01, 8.2058447007322854e-01], + [9.2759431945340309e-01, 8.6601348573233283e-01, 8.6108603933532435e-01], + [9.4613053685536319e-01, 9.2898265890108078e-01, 6.6427521988656020e-03], + [9.5157088013635838e-01, 9.4557667453885152e-01, 5.2664464542030331e-02], + [9.6471731551469964e-01, 9.5238096588772791e-01, 1.4202966741782982e-01], + [9.4748208974946568e-01, 9.3980590070845749e-01, 2.8373416263151013e-01], + [9.2426435715706412e-01, 9.1295865616987515e-01, 4.0906187718912401e-01], + [9.4374561169386761e-01, 9.3022672922899274e-01, 5.6152151297013952e-01], + [9.5877105441236943e-01, 9.4646327866713165e-01, 6.9700142368796003e-01], + [9.5101525173437884e-01, 9.3913180422304854e-01, 8.1344057853605012e-01], + [9.2066543799482847e-01, 9.1366187505663776e-01, 8.5633201084991661e-01], + [9.5149483820746605e-01, 9.3905481946624081e-01, 9.2516125145616779e-01], + [9.9038924668062067e-01, 8.4035364467557085e-03, 5.9527724715070264e-03], + [9.9035361225849294e-01, 7.1506457818627495e-02, 5.2824471481039956e-02], + [9.8981404015856911e-01, 6.9914557395971327e-02, 1.1950874395109137e-02], + [9.8734549214712952e-01, 1.6420910248944229e-01, 6.1995401783785799e-02], + [9.9265442162246353e-01, 1.8326540322420787e-01, 1.1723011155588494e-02], + [9.8669064334088197e-01, 2.8655962600946622e-01, 6.2129432723305386e-02], + [9.8523072664776501e-01, 1.7417015570462063e-01, 1.3166455183210574e-01], + [9.8894782506171153e-01, 1.4728979534158843e-01, 1.4619999442443687e-01], + [9.8944834076140009e-01, 3.3463097269955044e-01, 1.0824349227365408e-02], + [9.9604961100122003e-01, 4.3405648453717705e-01, 5.6873383467094578e-02], + [9.9814988815066585e-01, 2.9162552143699005e-01, 1.5725400234045919e-01], + [9.9537902059999872e-01, 2.7751167311794817e-01, 2.3722348690905423e-01], + [9.8618066179015251e-01, 2.7916280454138642e-01, 2.6914532033868488e-01], + [9.8686735318858143e-01, 4.9914993067038854e-01, 1.1129489604761794e-02], + [9.8925611002132630e-01, 4.0788181633128456e-01, 1.4437021544848957e-01], + [9.9324654560646120e-01, 4.1610778588448677e-01, 3.0103848784346560e-01], + [9.8441263950730717e-01, 4.6281677928509030e-01, 2.5603832400525495e-01], + [9.9198043643949652e-01, 4.4765664622131024e-01, 4.0291492748355190e-01], + [9.9009704345281879e-01, 3.9813514596318128e-01, 3.8995450701672402e-01], + [9.8852349626973934e-01, 6.6212569412916222e-01, 8.2193498586133479e-03], + [9.9266437030288712e-01, 5.9674150627280342e-01, 4.9243774137084639e-02], + [9.7725518271441958e-01, 5.3529888860946873e-01, 9.1028781650776547e-02], + [9.9568454230656811e-01, 6.1125843240376776e-01, 3.2817269630848378e-01], + [9.9594976628972465e-01, 5.8996950338076570e-01, 4.4654498155384381e-01], + [9.8660377777722286e-01, 5.9078745354628137e-01, 5.3977002940010799e-01], + [9.8566001337939424e-01, 5.4686422515252975e-01, 5.4021437765387414e-01], + [9.9266189398224947e-01, 8.1049092378505705e-01, 7.7134621001988500e-03], + [9.8673994535039844e-01, 7.3310744597113198e-01, 5.0451837581655147e-02], + [9.9539740371611074e-01, 6.8986795261579159e-01, 1.3297404961717568e-01], + [9.8534887250709213e-01, 6.8498939417736238e-01, 2.4475854859426194e-01], + [9.9171474894755729e-01, 5.4865948373032369e-01, 1.6979857487681446e-01], + [9.8925696185383460e-01, 7.2512340428298061e-01, 5.0327239246587230e-01], + [9.9101892339982434e-01, 7.1785711649463724e-01, 6.1742656063478396e-01], + [9.8969856982206894e-01, 7.0358695215403178e-01, 6.8755070309445931e-01], + [9.8905181144920873e-01, 9.2327735768750796e-01, 8.8575472519693346e-03], + [9.8954366218035550e-01, 8.6100137183398961e-01, 4.9755142495026487e-02], + [9.8585762600142746e-01, 8.1625941765221488e-01, 1.3150635764132326e-01], + [9.9002185189595682e-01, 8.0957359048180755e-01, 2.5016587610632524e-01], + [9.8977937985239361e-01, 8.6425410243968881e-01, 4.0002086820522070e-01], + [9.8747727465427237e-01, 7.5325336761091177e-01, 3.8118567448214524e-01], + [9.9006444901206814e-01, 8.5139688438014027e-01, 5.4869653899421789e-01], + [9.8752061332215102e-01, 8.3756824143967545e-01, 6.7634777387893308e-01], + [9.8933259119231165e-01, 8.1620552933691914e-01, 7.5941864999866326e-01], + [9.8708130193283805e-01, 8.3339179867844881e-01, 8.2535453648091694e-01], + [9.9161341879614440e-01, 9.8141485322834532e-01, 1.6338627207108391e-02], + [9.9766144320533268e-01, 9.2764779055402879e-01, 1.3709494823880833e-01], + [9.8610688139608071e-01, 9.0524168303930086e-01, 2.5073007962144328e-01], + [9.9377531403242203e-01, 9.4089962803403193e-01, 3.7226938952083782e-01], + [9.9131917920058688e-01, 9.4044833597893762e-01, 5.3150040039998758e-01], + [9.8805439537053208e-01, 9.2963093083843429e-01, 6.8411075623191131e-01], + [9.9023006700477922e-01, 9.2691550159710179e-01, 8.0356793628137468e-01], + [9.8522814331911079e-01, 9.0114465433623447e-01, 8.4549425802929978e-01], + [9.8712380436766189e-01, 9.2959330391848027e-01, 9.1614511871949655e-01], + [9.8148462582348084e-01, 9.3635096516243554e-01, 5.3991536732073089e-02], + [9.9745523509174083e-01, 9.8771592144137776e-01, 9.4522093704650922e-02], + [9.9110206132098866e-01, 9.7559529554248015e-01, 2.2809769322587903e-01], + [9.8770324581026037e-01, 9.7577874752150395e-01, 3.7039333390206924e-01], + [9.8496650291159638e-01, 9.7629986209403175e-01, 5.0828475285903019e-01], + [9.9438938626112128e-01, 9.8408197337085879e-01, 6.6290339571111234e-01], + [9.9283769376714892e-01, 9.8167706046912806e-01, 8.1458662922250680e-01], + [9.8726821844528045e-01, 9.7250728286508559e-01, 9.1298906985488593e-01], + [9.9581284098725475e-01, 9.8834062474073425e-01, 9.8158892094136352e-01], + ], + weights: [ + 4.4676688382995928e-05, + 2.2386898677462645e-05, + 3.3363197875732576e-05, + 1.3752326038744353e-04, + 7.5362103494158611e-05, + 1.7863229043257799e-04, + 9.1287689064368832e-05, + 1.2344096885544791e-04, + 1.8712900834271144e-04, + 1.1487192573729186e-04, + 1.2972054622847003e-04, + 5.4140200791136109e-04, + 2.9808581593557384e-04, + 2.8003001026595141e-04, + 2.8707363851830908e-04, + 4.1230966454706882e-04, + 2.7804412105827665e-04, + 4.4429914116187792e-04, + 1.2462766321951206e-04, + 1.5129316170719529e-04, + 7.4086696739613902e-05, + 1.2262643908546005e-04, + 7.4685474226125593e-05, + 2.5582392636397611e-04, + 2.4789068928719987e-04, + 2.7756308479982676e-04, + 5.4986598066636438e-04, + 4.3257029716680550e-04, + 3.8085342366843476e-04, + 7.4368138271754434e-04, + 6.5395018611534854e-04, + 8.3474577570657592e-04, + 1.9107527442095026e-04, + 2.4043729837496599e-04, + 6.1439832671854420e-04, + 7.6854660434194319e-04, + 5.4355909462289469e-04, + 4.7915102696434567e-04, + 1.4784883781723791e-04, + 2.1083874644702477e-04, + 4.7650755526884597e-04, + 3.1969856781966042e-04, + 1.3375240526732339e-04, + 1.7108264330879989e-04, + 3.1303451250636749e-04, + 5.4769896004465543e-04, + 3.7115777183059461e-04, + 8.6790752655754844e-04, + 8.2509018736740783e-04, + 2.7795892203232123e-04, + 9.7635916195227385e-04, + 2.8708484120370625e-04, + 9.7177076043500972e-04, + 7.2906677368689721e-04, + 3.6548411605756656e-04, + 4.1048586069802775e-04, + 6.8712099795652790e-04, + 1.1638634172213408e-03, + 1.2711455485385614e-03, + 7.8746881839147583e-04, + 2.7033966455256378e-04, + 1.0063454327917428e-03, + 2.4189921766415429e-04, + 6.0531444726378745e-04, + 7.9142251647892263e-04, + 8.0683224809564684e-04, + 4.7363243849249522e-04, + 7.8494615421847975e-05, + 2.7036888215789079e-04, + 3.0628522219954701e-04, + 2.6508057334078707e-04, + 4.7058081656238792e-04, + 2.2933868994109351e-04, + 1.0564663559300660e-04, + 1.2357718174504313e-04, + 1.9070214984774012e-04, + 1.7425607471575671e-04, + 4.3713292233820360e-04, + 8.0257727168508086e-04, + 4.4654763234908818e-04, + 1.0738804763734194e-03, + 7.2516237424343453e-04, + 2.1991846373406771e-04, + 4.3798659220665770e-04, + 8.9085723439962905e-04, + 6.1461346744431412e-04, + 5.7540084967854801e-04, + 4.5304836179945851e-04, + 9.0740446973460614e-04, + 1.5859379124840375e-03, + 1.1031264239078134e-03, + 4.5639617249990241e-04, + 2.8015762206938825e-04, + 7.2392163650596998e-04, + 1.0709790626129013e-03, + 1.3634166098791358e-03, + 1.2793967957197940e-03, + 5.1451978120448203e-04, + 2.8249525581500748e-04, + 1.4868263055066377e-03, + 8.5654661952340200e-04, + 7.6639660347457578e-04, + 1.0771577753599148e-03, + 8.0901363181638396e-04, + 9.6994358648037015e-04, + 7.1285012619927890e-04, + 4.2606975479794670e-04, + 1.8390317739364016e-04, + 2.7762722466786419e-04, + 5.4377543632268743e-04, + 5.9804039846309259e-04, + 4.1667984544500501e-04, + 2.3777301570949863e-04, + 2.3052393048221802e-04, + 1.4590291181666785e-04, + 1.4353583474182124e-04, + 4.9570392700809258e-04, + 3.5192082178748130e-04, + 1.0018183956783224e-03, + 3.6181116542174864e-04, + 4.8118352334946895e-04, + 1.0383871529044224e-03, + 6.0663731204670872e-04, + 3.9601868286506299e-04, + 9.5280458142217627e-04, + 1.5015205087518923e-03, + 1.2006958343924077e-03, + 3.8269838931379594e-04, + 5.3354159727134047e-04, + 1.1829703043508295e-03, + 9.6128287540940896e-04, + 1.1395750256204906e-03, + 8.0203668813952367e-04, + 3.7574715156651488e-04, + 1.5737215554287066e-03, + 1.2610441575652282e-03, + 8.9167126649175024e-04, + 2.4136115513267314e-04, + 3.7636528232072499e-04, + 1.0249679324565910e-03, + 1.3552145862129840e-03, + 1.2830343721664448e-03, + 1.3616829617652216e-03, + 1.2942558265626042e-03, + 1.0884316999550203e-03, + 3.2888942349856024e-04, + 1.4183198978900247e-04, + 5.1086359819238892e-04, + 9.5459927112463466e-04, + 1.0131035353823739e-03, + 1.0756740482721835e-03, + 8.8286909974482773e-04, + 4.2923348793887057e-04, + 1.3305315444141333e-04, + 2.5898598637907758e-04, + 5.1179074362603556e-04, + 3.8827215515498191e-04, + 5.2501879067804358e-04, + 2.0989917100455480e-04, + 4.4383258853040856e-04, + 8.6064328506430901e-05, + 3.5525410147756181e-05, + 2.8492573855612652e-04, + 2.2237561828273341e-04, + 3.9259409949509366e-04, + 5.8895138894881510e-04, + 6.0533933307903027e-04, + 2.1524326601148318e-04, + 1.9363612850314103e-04, + 8.6596790774193526e-04, + 1.2297469087578189e-03, + 7.0859769380203074e-04, + 2.3912953726091502e-04, + 4.2633096594112981e-04, + 9.4558373301034291e-04, + 1.4211859205151955e-03, + 1.1659801938847631e-03, + 1.2410927620924626e-03, + 8.5649190441501849e-04, + 2.7823010171630907e-04, + 2.4314760698456982e-04, + 1.1343119588065878e-03, + 3.7647039967736982e-04, + 4.5803970786121591e-04, + 9.1558355214096682e-04, + 1.3747507056528110e-03, + 1.2054091161771009e-03, + 1.5395724549813559e-03, + 9.2336575184781805e-04, + 5.3969698901881963e-04, + 3.1524936614501050e-04, + 6.6241849994222153e-04, + 1.0821609412745429e-03, + 1.3312360160469727e-03, + 1.2690122749333759e-03, + 1.0895731777044728e-03, + 1.0581415571661664e-03, + 1.6858102757926133e-04, + 1.6480589035836347e-04, + 3.4821977353153628e-04, + 7.6508119460960266e-04, + 9.6968573488455108e-04, + 9.1295530720343144e-04, + 6.3752862226949879e-04, + 8.0132319969483494e-04, + 6.5322611991991940e-04, + 1.6328571091061441e-04, + 9.7236305808765723e-05, + 1.4287386550586740e-04, + 2.5727252878409373e-04, + 4.2431931081174910e-04, + 4.1423055643142147e-04, + 3.0027555725774258e-04, + 3.8917812473910002e-04, + 2.5289377081652917e-04, + 2.1332754113460685e-04, + 8.3443664292955232e-05, + 7.1023561736252661e-05, + 9.2349363330235411e-05, + 3.6493285110789038e-04, + 2.1620831847997903e-04, + 6.1023422978129498e-04, + 5.6400780254313107e-04, + 2.3989739303534281e-04, + 2.9186435310720463e-04, + 5.7902951073450731e-04, + 5.4584389440888123e-04, + 3.7331844940437773e-04, + 2.8817670755697461e-04, + 3.4111777478358320e-04, + 9.4910032545401566e-04, + 1.0025314565252910e-03, + 5.1457095686698658e-04, + 3.1050362147969448e-04, + 1.5060257230084352e-04, + 5.5282882976359399e-04, + 9.8356381767740974e-04, + 8.9986980954750377e-04, + 5.3085880047682647e-04, + 5.8911534099170240e-04, + 3.7595382721352874e-04, + 2.9475790729939505e-04, + 6.2688752137406210e-04, + 5.7485355221396754e-04, + 7.0344357044227208e-04, + 8.5531481071542408e-04, + 7.9371230834381393e-04, + 9.3497939106240919e-04, + 5.3304778577896623e-04, + 1.0909370040148775e-04, + 1.3754720765778156e-04, + 5.0885469732041028e-04, + 7.6613063899131674e-04, + 9.7893550235459825e-04, + 8.1561082017043312e-04, + 1.0796881193417414e-03, + 9.1292493054829647e-04, + 7.2495228247351762e-04, + 3.4005647129732385e-04, + 1.8561601826371218e-04, + 4.0471990210474496e-04, + 5.0366698158108708e-04, + 5.7896812419746776e-04, + 5.2082851546291745e-04, + 5.9293524098237148e-04, + 3.4158445920530402e-04, + 3.7857842747324470e-04, + 1.0025607931625220e-04, + 5.9052481610644055e-05, + 9.0093500810498777e-05, + 1.7591304474496715e-04, + 1.8600164623132814e-04, + 3.2866697855232942e-04, + 2.9344745721982869e-04, + 2.3569343934671559e-04, + 1.8086106107312948e-04, + 9.4691800117873139e-05, + 7.4109485450382453e-05, + 9.2734438292987380e-06, + 8.0844169918776410e-05, + 6.7528055428494512e-05, + 2.3613519977051396e-04, + 8.7539278827075418e-05, + 3.2429058025098899e-04, + 2.3657955101490360e-04, + 4.2743190677375480e-05, + 1.2361831848541817e-04, + 1.5456717182897166e-04, + 1.5044085822521524e-04, + 9.1743598531556219e-05, + 9.9555689865948903e-05, + 1.5110199151533349e-04, + 3.6610442435405983e-04, + 2.3692269589625531e-04, + 5.3314898641069921e-04, + 1.9925174800099218e-04, + 7.0357561242029323e-05, + 1.0162195472224659e-04, + 1.6957888117347908e-04, + 4.2353055268168534e-04, + 3.1728806131538695e-04, + 2.0378854894020167e-04, + 3.1137792794370202e-04, + 1.1468263157642022e-04, + 6.1555121946259584e-05, + 2.8864854558864037e-04, + 2.3885078585822157e-04, + 5.4858138676714178e-04, + 3.5558162207915213e-04, + 4.9257168393464299e-04, + 3.0561175826082984e-04, + 1.5608801761855245e-04, + 5.6933862455007041e-05, + 1.8721152857378954e-04, + 4.2659862204870084e-04, + 4.0853241230482193e-04, + 3.7479188533223127e-04, + 5.3195465857880733e-04, + 4.0344036615999553e-04, + 4.1584282635518836e-04, + 2.2181303744915745e-04, + 8.9271448675450451e-05, + 2.7103202459074453e-05, + 1.1615482916842642e-04, + 3.6142386755397287e-04, + 1.4621351628692428e-04, + 2.3068693993812028e-04, + 2.9034400550778493e-04, + 1.7957687464621418e-04, + 1.6507547503960204e-04, + 8.0747866371976321e-05, + 1.7506045777573496e-04, + 3.6979307057675238e-05, + 1.2638397262654147e-04, + 1.2928202907754892e-04, + 1.2475782764209925e-04, + 7.5051498248490640e-05, + 7.7523403704781121e-05, + 9.5854150137228747e-05, + 9.0166805321890557e-06, + ], + }, + 19: { + points: [ + [6.3120555913655410e-02, 1.9528818198912515e-02, 7.3091161505919461e-03], + [2.7159828605938564e-02, 2.2389638739317864e-02, 1.2527047109569048e-02], + [7.8438175446812669e-02, 6.5746706678627684e-02, 6.9628965352821603e-03], + [1.4359328235991858e-01, 1.9592451370337809e-02, 1.0693929698839493e-02], + [8.8438807712955037e-02, 4.9928958851359131e-02, 4.2315562154309289e-02], + [1.3475188368060689e-01, 6.7324633819085850e-02, 1.0209781275259218e-02], + [1.6236128233590699e-01, 1.5231210807451398e-01, 7.8066525522150661e-03], + [9.9745889221290074e-02, 8.6280174087481967e-02, 3.9775362209301147e-02], + [9.0987257314024392e-02, 8.2716422106457799e-02, 7.2293588996576980e-02], + [2.0333626265254337e-01, 5.6943739630321004e-02, 8.7946556649130050e-03], + [1.6484717686420369e-01, 5.9738642164256152e-02, 5.0608449758038507e-02], + [2.4719163278806774e-01, 1.0063690744840180e-01, 9.1609492414822302e-02], + [2.6748617399494512e-01, 1.4157630725800260e-01, 1.0256587615942425e-02], + [1.6109293394790625e-01, 9.1242201689884894e-02, 4.8714258892877248e-02], + [1.8137479850768876e-01, 1.2720036752035835e-01, 1.1861158729195700e-01], + [1.8920236047968772e-01, 1.3888975764852907e-01, 9.0014829024267218e-03], + [1.9207081424863104e-01, 1.5943256496762145e-01, 4.6463184310485399e-02], + [1.9390603162125059e-01, 1.5653777798241864e-01, 1.0801357393647423e-01], + [1.8202266201235240e-01, 1.7176344677686325e-01, 1.5982993890358968e-01], + [2.7243157741795770e-01, 2.6258307925708191e-01, 9.4737711239805608e-03], + [1.6042337075969004e-01, 1.6015632883352951e-01, 4.0082994879862659e-02], + [2.6656670848000846e-01, 2.5599303865342710e-01, 1.1296025517277311e-01], + [1.7204596119120585e-01, 1.6643501199323332e-01, 1.0461461803893071e-01], + [2.6980740575239814e-01, 4.6570447996301882e-02, 3.8998574578695114e-02], + [2.6528442582127409e-01, 9.1282503902025192e-02, 4.7341918736443070e-02], + [2.6262480905018171e-01, 1.9780649500527218e-02, 7.0662479312862481e-03], + [3.6196683559125786e-01, 1.1932244530546374e-01, 1.0951890352513037e-01], + [3.1337773632729587e-01, 8.6722320288791829e-02, 9.9999057689600570e-03], + [2.6432789463910250e-01, 1.5480694583564691e-01, 5.2375255712738579e-02], + [2.7890447530105045e-01, 1.6600707855962832e-01, 1.1842877211887419e-01], + [3.0689215530973113e-01, 1.8097179133518407e-01, 1.7112669179895848e-01], + [3.7793242105955754e-01, 2.5024979168339501e-01, 9.7818734939173636e-03], + [3.7712006483860022e-01, 2.2321720295189371e-01, 5.3758231861788013e-02], + [3.7824538395178847e-01, 2.3780626101727220e-01, 1.2841561991129846e-01], + [2.9122544080077239e-01, 2.3872049056460842e-01, 9.8961159702772809e-03], + [2.9193110976004438e-01, 2.3637995963760322e-01, 1.1793602401251688e-01], + [4.2121264127096475e-01, 2.6347141151719494e-01, 2.1534874585930625e-01], + [2.9107756417323344e-01, 2.4002738113913610e-01, 2.3114284053462414e-01], + [3.1677920237591561e-01, 2.4960849270861848e-01, 5.0029337175531888e-02], + [3.6037781556340032e-01, 3.2171440439304616e-01, 1.9706417730779030e-01], + [3.0850331819665433e-01, 2.4932458193611523e-01, 2.0163221464545886e-01], + [2.9577871097509600e-01, 2.8608477014107542e-01, 2.7440210994389314e-01], + [2.7830171224424177e-01, 2.6519787970706626e-01, 4.7897705262625628e-02], + [3.7955733836013134e-01, 3.6928601355410112e-01, 1.1462603961731975e-01], + [3.6459103301161072e-01, 3.5736601743547497e-01, 2.1147515145666554e-01], + [2.7647325274356971e-01, 2.6521772584053782e-01, 2.0409411786459175e-01], + [4.0153973286696193e-01, 1.8591787866397366e-02, 9.7858265854844327e-03], + [3.7801801610765218e-01, 5.7446773580237108e-02, 9.8526225351313196e-03], + [3.9023623848905814e-01, 8.8336848177008639e-02, 4.7458308506946753e-02], + [4.0564666198357469e-01, 5.7401183936728203e-02, 5.0178614962475498e-02], + [4.7121733068982713e-01, 1.1924224308474575e-01, 6.6341255885447861e-03], + [4.9892069212058238e-01, 1.3414490802547008e-01, 1.2412347320415922e-01], + [4.0440073051535774e-01, 1.7383115793947365e-01, 1.0601909779853676e-02], + [3.9088394957404415e-01, 1.6230072825659381e-01, 5.3194046761266343e-02], + [3.9065285991267668e-01, 1.6686226499204060e-01, 1.1603828385966768e-01], + [4.3987454717853658e-01, 2.1391195442376629e-01, 2.0442017082480152e-01], + [4.5176106656389109e-01, 2.9020616335000315e-01, 5.1426922804814569e-02], + [4.8555840288680086e-01, 2.4578506114261595e-01, 1.2138484891146514e-01], + [5.2699376365921269e-01, 2.3438438618946550e-01, 1.8332268935555524e-01], + [5.0419377737791238e-01, 3.7824533127555593e-01, 8.9142988124228904e-03], + [4.1257706515030407e-01, 3.5784468741876496e-01, 9.0991106130087346e-03], + [4.5311390179445687e-01, 3.6015805024320741e-01, 4.8548956731846553e-02], + [4.3197471946712801e-01, 3.1146596797277137e-01, 1.2359606535436296e-01], + [4.2696752868496962e-01, 3.3017008587735241e-01, 2.1894945947885774e-01], + [4.3889303916052830e-01, 3.4838431550072640e-01, 3.0350206416125403e-01], + [4.1714125804967200e-01, 2.9512267178286394e-01, 2.8659222917298577e-01], + [4.0630471745306546e-01, 3.7256812728347438e-01, 3.2522291625950106e-01], + [4.2253481574373775e-01, 3.7395854179177868e-01, 3.6517064424087126e-01], + [4.1643440367635115e-01, 3.8478952863837729e-01, 4.9763817621888508e-02], + [3.9850991167723643e-01, 3.4574887445914826e-01, 1.1590398138529084e-01], + [4.5941032488340494e-01, 4.1270684874765101e-01, 2.0779804938043772e-01], + [4.4177384028237598e-01, 4.0091054690485561e-01, 2.8865215338212497e-01], + [3.9375318043645063e-01, 3.8282956254778716e-01, 9.9550855329473675e-03], + [3.9475059633986809e-01, 3.9474705596933718e-01, 5.0655859947133036e-02], + [4.5828646157634106e-01, 4.4879624892707004e-01, 1.9840798612429977e-01], + [4.6682995896465290e-01, 4.5808423296104589e-01, 3.2525924487948948e-01], + [3.9297310325487578e-01, 3.8726308682036997e-01, 3.2940643871801178e-01], + [4.2433815334160696e-01, 4.1578799372420122e-01, 4.0612765281797569e-01], + [5.0430468763469916e-01, 5.6200470411714221e-02, 7.9244108449946019e-03], + [5.3245177233394558e-01, 8.0124282177654021e-02, 3.9508268671400282e-02], + [5.4875758620981718e-01, 1.8794792858758392e-02, 9.4909369159293377e-03], + [5.4465290185535475e-01, 6.1531058079087209e-02, 5.3216335417908017e-02], + [5.0550113479303749e-01, 1.4787238633698033e-01, 3.9813321903854178e-02], + [5.1541005605330614e-01, 1.4612875264277811e-01, 9.5578938872026745e-02], + [5.5884385128086711e-01, 2.1532059552853217e-01, 9.6956126308903740e-03], + [5.3489517363991823e-01, 2.5874668249489458e-01, 5.1896989343505694e-02], + [5.6223915879495712e-01, 2.2675456095680660e-01, 1.0875276527147683e-01], + [5.7185003473251428e-01, 2.3571807025132271e-01, 2.2577446679516847e-01], + [5.5157864268833545e-01, 3.4079705039497898e-01, 1.2376125596827070e-01], + [5.5095169129949906e-01, 3.3445685499250022e-01, 3.2417738098301563e-01], + [5.1405302289671306e-01, 2.9233761614014397e-01, 9.9136326157422908e-03], + [5.8958995207202003e-01, 3.8393090542856984e-01, 5.0588364581525221e-02], + [5.3562545867184419e-01, 3.3448037968917871e-01, 2.1318146685537562e-01], + [5.6477068381085782e-01, 3.4118597198176487e-01, 2.8889771978961820e-01], + [5.8578483451975361e-01, 4.6477601441790967e-01, 1.4056537661554477e-02], + [5.7052796994687816e-01, 4.5176665364006718e-01, 5.0625789317436359e-02], + [5.4509661405799315e-01, 4.2886095065188284e-01, 2.1732299627373075e-01], + [5.5976728885460147e-01, 4.2514052357136556e-01, 3.7481323839047831e-01], + [5.4593278122516831e-01, 4.2545821930867117e-01, 4.1603724461696417e-01], + [5.4411630356138951e-01, 4.9332252450784103e-01, 1.0689496831543745e-02], + [5.4371413529784896e-01, 4.2311603860422370e-01, 1.1983755169512592e-01], + [5.7032327539657890e-01, 5.1928748582600126e-01, 3.2919224433333394e-01], + [5.5646609792311730e-01, 4.3461453070352851e-01, 3.1462635624522128e-01], + [5.7055293907764981e-01, 5.1754098116855007e-01, 5.5091069223737708e-02], + [5.2889256285145170e-01, 4.7769552577036217e-01, 1.2463749526675606e-01], + [5.4680752053482062e-01, 4.9237224593376455e-01, 3.9629230230212398e-01], + [5.6197781126432800e-01, 5.1120775004449515e-01, 5.0594080049318635e-01], + [5.1630103240817637e-01, 5.0693928585753445e-01, 7.6895144486622798e-03], + [5.3214960722327231e-01, 5.2147344709482468e-01, 4.5144566626828475e-02], + [5.1365988413893882e-01, 5.0345416667827736e-01, 1.1645851526816549e-01], + [5.7493871029625798e-01, 5.2573243238070333e-01, 2.2512028671017409e-01], + [5.7485342791304495e-01, 5.6587443220681732e-01, 2.1458046749694748e-01], + [5.5056043745539240e-01, 5.4086319787225534e-01, 3.1102787115619840e-01], + [5.7241651523320303e-01, 5.6194595259852309e-01, 4.4322878114173109e-01], + [5.4627441932992893e-01, 4.9161692589960237e-01, 4.5481882284352015e-01], + [5.2480594814735571e-01, 5.1435399992575326e-01, 4.6629888009647580e-01], + [5.5606711796726416e-01, 5.4498987713656255e-01, 5.3561307595333152e-01], + [6.8009507335703190e-01, 2.1731587501017852e-02, 1.1598364006091514e-02], + [6.4675804963212735e-01, 5.9938362920509254e-02, 8.1424368699320390e-03], + [6.8310853591981857e-01, 8.1592099781982758e-02, 4.7971103219847398e-02], + [6.7590712110631423e-01, 5.9754618438923858e-02, 5.6336701808367445e-02], + [6.4357519300530985e-01, 1.4314563739372091e-01, 1.0308795393320005e-01], + [6.3930084420775968e-01, 1.3507844340521999e-01, 4.2244570169079010e-02], + [6.3536741104232852e-01, 1.4211700571256480e-01, 1.3414611223467557e-01], + [6.1130989099813826e-01, 1.2898012812794829e-01, 7.6703292771073163e-03], + [6.8222714192617895e-01, 2.5469957030798551e-01, 2.4524351923659291e-01], + [6.8206453027540914e-01, 2.2628267414106840e-01, 1.0243426106469337e-02], + [6.4692656134310345e-01, 2.3161924277831014e-01, 4.8925259098618439e-02], + [6.6346806661685676e-01, 2.1339214851761437e-01, 1.0662183945901885e-01], + [6.5435093232126673e-01, 2.3693695503079770e-01, 1.8779209176253320e-01], + [6.5485203194978758e-01, 3.2827830813133002e-01, 1.0008422360600639e-02], + [6.8736063344865128e-01, 3.5515409890543104e-01, 5.2041074094009217e-02], + [6.6806343050121875e-01, 3.3083072069549330e-01, 1.2264848776717480e-01], + [6.5580332001526664e-01, 3.2883342687680656e-01, 2.0773229941447177e-01], + [6.8021016146924673e-01, 3.5457092004355834e-01, 3.0334024910749458e-01], + [6.7897158763745280e-01, 3.6531132014352008e-01, 3.5546340937717302e-01], + [6.4503743704248617e-01, 4.2936170981075245e-01, 9.6100617761794519e-03], + [6.7177156194532872e-01, 4.4579562904713216e-01, 1.2302317892595266e-01], + [6.5858799138790636e-01, 4.3811475857231530e-01, 2.2052919459062123e-01], + [6.7447163164365131e-01, 4.5282982128691585e-01, 3.2841378765799600e-01], + [6.9023276241682885e-01, 4.7715863192585767e-01, 4.2482140855334105e-01], + [6.7429500615796445e-01, 4.7182527774799637e-01, 4.6180400687876172e-01], + [6.6505434265745444e-01, 5.4643504544826527e-01, 3.2034581873121625e-03], + [7.2181101543254023e-01, 4.9530676480278152e-01, 5.0638454957651097e-02], + [6.6755761703272465e-01, 5.4208451022292115e-01, 2.0705220119089943e-01], + [6.7562993351472789e-01, 5.4878441446193793e-01, 3.2449614316572145e-01], + [6.7141030619661290e-01, 5.6088652104681536e-01, 5.5056363052403878e-01], + [6.8866359599829996e-01, 5.6490581175181753e-01, 3.7924203659171131e-02], + [6.6984135852040894e-01, 5.4073209109164677e-01, 1.0793872221136667e-01], + [6.7904411731075409e-01, 5.5651492188207141e-01, 4.2964703220213829e-01], + [6.7365932112005578e-01, 5.6104936879210032e-01, 5.0776543331493240e-01], + [6.9533238577084266e-01, 6.5404433736749112e-01, 6.4631499123877301e-01], + [6.7386896289482079e-01, 6.2331302606078953e-01, 1.1872720904784464e-02], + [6.3870432931373944e-01, 6.0547256670362160e-01, 9.1976872804742704e-02], + [6.6760348937051595e-01, 6.1141065735994515e-01, 1.1590616191837892e-01], + [6.6917878460875857e-01, 6.1693615634439924e-01, 1.9063634357717807e-01], + [6.9036372192587248e-01, 6.3570535264620531e-01, 3.1682765505469296e-01], + [6.8481121923586785e-01, 6.3071392860258546e-01, 4.2801534990309775e-01], + [6.7014491915103680e-01, 6.2164645127921347e-01, 5.1311232281811436e-01], + [6.6386994776733410e-01, 6.2044927015000328e-01, 5.8110108563249752e-01], + [6.4651884181482422e-01, 6.3629087936623330e-01, 8.4434861374256855e-03], + [6.5811616707517218e-01, 6.4970692188017776e-01, 4.6651570744561922e-02], + [6.4581095759089546e-01, 6.3542036061066376e-01, 1.1790340662630849e-01], + [6.7764098788920168e-01, 6.6710012978151756e-01, 2.2270539317743235e-01], + [6.8085509155241097e-01, 6.7018780328064043e-01, 3.4095536035912033e-01], + [6.6205301556674989e-01, 6.5164618820930542e-01, 4.3969263836329142e-01], + [6.9790698254576800e-01, 6.8903984549626363e-01, 5.5380447400827748e-01], + [6.6660226829805325e-01, 6.5803028764940863e-01, 5.9796446305595818e-01], + [6.6235913235744659e-01, 6.5645560551739224e-01, 6.4399903482934051e-01], + [7.6949504665657342e-01, 7.8008851817337993e-02, 1.0998203564843434e-02], + [7.8481801867877998e-01, 2.1320591479804543e-02, 7.1068184601568623e-03], + [7.4467184495169580e-01, 1.5183059756662290e-01, 1.1168496230537383e-02], + [8.0344651346904006e-01, 9.2253865084123352e-02, 5.3493206207704459e-02], + [8.0571109474518521e-01, 4.7034346831197824e-02, 4.1068199048405106e-02], + [7.7355022857460165e-01, 1.8359289342691601e-01, 1.3327705080861732e-01], + [7.6797702508499388e-01, 1.6278763208408537e-01, 5.8864626457391427e-02], + [7.6330596873601786e-01, 1.2085047012333096e-01, 1.1144313557770900e-01], + [8.1421144606445439e-01, 2.1545171694400522e-01, 1.0984364845722336e-02], + [7.5633689530793013e-01, 2.6028603187013694e-01, 1.4369562388778553e-01], + [7.5807507733162083e-01, 2.1351498038465422e-01, 2.0291140257469811e-01], + [7.6887013130561843e-01, 3.0012915521071903e-01, 8.8862412429574142e-03], + [7.6271101466644187e-01, 2.5423246187167453e-01, 5.4563299079408135e-02], + [7.8428512005502260e-01, 3.2366931953929690e-01, 1.2273153079950948e-01], + [7.7895454557037491e-01, 3.4147148508296904e-01, 2.1867682800465266e-01], + [7.8071972329037220e-01, 3.0078205466887925e-01, 2.4871952833185121e-01], + [7.9068097467559073e-01, 3.4631203599205956e-01, 3.3632255753561641e-01], + [8.0317070743636743e-01, 3.5958723815756399e-01, 4.7394158315008858e-02], + [7.7456461070624871e-01, 4.4083109556200939e-01, 2.2021347404193159e-01], + [7.9261742882907749e-01, 4.3670249908709069e-01, 3.8491915266479287e-01], + [7.9124413994906040e-01, 4.8108896390358730e-01, 4.7086587120016948e-01], + [7.6927812147483765e-01, 4.3019539959624781e-01, 1.0242881503314809e-02], + [8.1250720679033595e-01, 4.8657795629594014e-01, 5.3007613287490589e-02], + [7.8457342758608839e-01, 4.3792155499682950e-01, 1.2175154821225373e-01], + [7.8007698557689897e-01, 4.3962085925284777e-01, 3.1515459362559667e-01], + [7.7146122775949211e-01, 5.5544307989709596e-01, 9.4311661683483712e-03], + [8.2427439442900630e-01, 6.2647307014089837e-01, 4.9722216467555647e-02], + [7.8827929550731324e-01, 5.5750188180805194e-01, 1.2289163645352782e-01], + [7.7481187421849307e-01, 5.5167293334110679e-01, 2.1705780422069906e-01], + [7.8081147813220342e-01, 5.6006714268491287e-01, 3.3610910219481543e-01], + [7.9587217043249492e-01, 5.5371685712655372e-01, 4.2775023855243510e-01], + [7.9648474348139942e-01, 5.6225506493003219e-01, 5.0766073511026333e-01], + [7.8929690545402198e-01, 6.0017054611150522e-01, 5.8924899013994791e-01], + [7.9054846110277877e-01, 6.7602472323308771e-01, 1.1577980360672055e-02], + [7.9087081221958710e-01, 6.8681856659001250e-01, 6.2132742174838794e-02], + [7.8206632589716563e-01, 6.4573956196429838e-01, 1.1370897409982057e-01], + [7.8336713576122530e-01, 6.6159737911328254e-01, 2.0062038971444096e-01], + [7.8654149158146847e-01, 6.5875882506704719e-01, 3.1359132515062349e-01], + [7.9428226551976455e-01, 6.7717666221769002e-01, 4.4276544115781080e-01], + [7.8611152095933823e-01, 6.3574056401671442e-01, 5.0329660381239061e-01], + [7.8921280997801491e-01, 6.6399125897641442e-01, 6.0674995821924227e-01], + [7.7885407855621414e-01, 6.8289398820364000e-01, 6.7163650196917590e-01], + [7.8202503149495373e-01, 7.3883662118870552e-01, 7.4313294833304603e-03], + [7.0416682022570276e-01, 6.5750334745564587e-01, 5.3108560659027845e-02], + [7.8722994073154762e-01, 7.3385093777796606e-01, 1.3217854944122884e-01], + [7.7678825684178066e-01, 7.2588323394299181e-01, 2.4996795823288093e-01], + [8.0293891489144931e-01, 7.4855398551397134e-01, 3.7898377451673199e-01], + [7.8241987745856523e-01, 7.3968290225271272e-01, 4.9093561210524989e-01], + [7.8728506106566021e-01, 7.2214048088633986e-01, 5.8463415200687019e-01], + [7.7794129782159327e-01, 7.3069755741356812e-01, 6.7388162193413037e-01], + [7.7435886739624027e-01, 7.6336855167725171e-01, 7.5141587648260955e-01], + [7.7316245855826049e-01, 7.6535214022820797e-01, 1.4100908680884991e-02], + [7.7383988703096052e-01, 7.6375922766608351e-01, 7.2980946138470129e-02], + [7.7107542693169995e-01, 7.6085098877359814e-01, 1.6280403430841925e-01], + [7.8529741662805230e-01, 7.7604568319639922e-01, 2.8529567972238618e-01], + [7.9490420963498942e-01, 7.8420772001784589e-01, 4.0582260038264339e-01], + [7.8120895620771391e-01, 7.7424685997750298e-01, 5.1532191571975194e-01], + [7.8691243627142848e-01, 7.7347470772156202e-01, 6.2962457560624507e-01], + [7.9431773718953358e-01, 7.8633051314818059e-01, 7.2567880054433720e-01], + [8.6706953210245918e-01, 6.0778693462006962e-02, 1.1664316708523831e-02], + [8.8596737416475690e-01, 1.4487351660083405e-02, 7.2558186105768129e-03], + [8.6831983014439806e-01, 1.3691051093618581e-01, 6.9363257911156592e-03], + [8.9428352947029821e-01, 1.2227377427732102e-01, 6.1033294618676501e-02], + [9.0456414259537543e-01, 5.8466248654583511e-02, 4.5962316987087956e-02], + [8.7913880782665066e-01, 1.7580965291760392e-01, 1.2780174457571572e-01], + [8.7078259525716639e-01, 1.6363565620885656e-01, 4.1635503682670195e-02], + [8.6922950308738178e-01, 1.2650355740208233e-01, 1.1727459518879550e-01], + [8.7681820764693308e-01, 2.7212855689721344e-01, 9.2852118202618097e-03], + [8.6261395854790079e-01, 2.5767667130018346e-01, 4.9660212225410456e-02], + [8.5706058064188262e-01, 2.3848956562927243e-01, 1.1964241358965316e-01], + [8.7759486783828655e-01, 2.7305331867075000e-01, 2.2324777361152334e-01], + [8.5295949294981743e-01, 2.2923664724799708e-01, 2.2002695998355376e-01], + [8.6688814576844875e-01, 4.0001886132049680e-01, 9.1048078913568628e-03], + [9.0020488625544326e-01, 3.7609675140717042e-01, 4.6986785539000056e-02], + [8.8573133964943018e-01, 3.2192935783731341e-01, 1.1110799863743200e-01], + [8.7403499940159080e-01, 4.2142187874162329e-01, 2.0269636164460492e-01], + [8.7267818163607658e-01, 3.2864126375593317e-01, 2.0860265830478492e-01], + [8.7839837606066917e-01, 4.0516517389854118e-01, 3.5356872931619948e-01], + [8.8469390418209859e-01, 3.4625015466713199e-01, 3.3615122562842209e-01], + [8.7642581065347225e-01, 4.2437526023642003e-01, 1.1407323424800930e-01], + [8.7868269408522426e-01, 4.2550103395498540e-01, 3.0081117341856684e-01], + [8.8548608617439573e-01, 4.7983058390976241e-01, 4.7053268810079157e-01], + [8.7037796599137685e-01, 5.4437916494217498e-01, 1.0402600756932131e-02], + [8.9974960769108014e-01, 5.0374124580179658e-01, 5.0395312175854884e-02], + [8.8551389680749470e-01, 5.4855611045547803e-01, 1.2295789784411967e-01], + [8.7393807528898659e-01, 5.5184573452590080e-01, 2.1549370248740871e-01], + [8.7535368699931615e-01, 5.3959012618691671e-01, 3.2087440539144818e-01], + [8.8219779206659976e-01, 5.3849375482961015e-01, 4.2014300045717307e-01], + [8.8529778438643247e-01, 5.5025760500078080e-01, 5.0295265868394567e-01], + [8.7944803980254260e-01, 6.7347702033322654e-01, 9.6545493896676039e-03], + [8.9094903997011832e-01, 6.2565748592508219e-01, 5.3594830025834185e-02], + [8.8004711313427175e-01, 6.4974914281641782e-01, 1.2774894488580071e-01], + [8.7422248865633612e-01, 6.7639307445114472e-01, 2.2380026927039387e-01], + [8.7577206336210645e-01, 6.6040812011854588e-01, 3.2989101508292873e-01], + [8.7991333317754639e-01, 6.5782866275962804e-01, 4.4406929305376536e-01], + [8.8825224771577382e-01, 6.6125736298497895e-01, 5.4437007019594508e-01], + [8.8234627082490480e-01, 6.7371433825066784e-01, 6.2751147913806171e-01], + [8.8492085557148414e-01, 6.1992479189402361e-01, 6.1164722350445588e-01], + [8.9346605046069372e-01, 7.7565845268626632e-01, 5.2489901669854049e-03], + [8.8749494487625169e-01, 7.6678175527563130e-01, 4.2023421188221124e-02], + [8.8788513442537265e-01, 7.4785330592582988e-01, 1.0933838785531429e-01], + [8.7674352785389387e-01, 7.6695531833043351e-01, 1.8437844132767403e-01], + [8.8205781097542701e-01, 7.7879203884250103e-01, 3.0649764269018065e-01], + [8.8649705378242882e-01, 7.6218270475988414e-01, 4.1764664579138533e-01], + [8.8649519161419787e-01, 7.5635525227371092e-01, 5.3035733303422472e-01], + [8.7840853417841869e-01, 7.5443807558083176e-01, 6.2957427858596304e-01], + [8.8431371106831991e-01, 7.6522592876393614e-01, 7.1398979927931128e-01], + [8.8029224085455027e-01, 7.4341289631531482e-01, 7.3460868540268287e-01], + [8.8225831432825164e-01, 8.2653667593407509e-01, 9.7346834548356700e-03], + [8.2407115768296346e-01, 7.7768808676118162e-01, 4.4786229400304745e-02], + [8.8573091452246377e-01, 8.2993165788895262e-01, 1.0508445388162281e-01], + [8.7000251752324442e-01, 8.2579529744190383e-01, 2.2175147941616763e-01], + [8.8094222164414981e-01, 8.4570613622724911e-01, 3.5416833388646080e-01], + [8.8198086219835292e-01, 8.2654155849945010e-01, 4.8826379385539032e-01], + [8.7729503683317411e-01, 8.2242048130277823e-01, 6.0507436788265878e-01], + [8.7906311176740581e-01, 8.3378641822757427e-01, 7.2476179762649140e-01], + [8.8071630927340860e-01, 8.1718819021420752e-01, 7.7389375770209445e-01], + [8.2967490436777547e-01, 7.8145902107377219e-01, 7.7056107892782133e-01], + [8.8051229248795337e-01, 8.6927594382494622e-01, 6.1916001701150924e-03], + [8.7594043133642385e-01, 8.6704459311777005e-01, 4.6108556581623224e-02], + [8.7490654217524388e-01, 8.6409294840681938e-01, 1.2875399108380678e-01], + [8.7318176926527147e-01, 8.6533379001317789e-01, 2.4192493468255483e-01], + [8.8672062583019307e-01, 8.8234812947674757e-01, 3.8581832561149854e-01], + [8.8662589121311441e-01, 8.7542644017691307e-01, 5.1760522651422469e-01], + [8.7149006502735216e-01, 8.6070074301198207e-01, 6.3357276175204902e-01], + [8.8304696910450109e-01, 8.7598736249903841e-01, 7.5183432738122835e-01], + [8.8683209049659895e-01, 8.7301334287194254e-01, 8.3077241427308035e-01], + [8.7677733103524957e-01, 8.6968000428819758e-01, 8.6267450474663154e-01], + [9.3591889589892296e-01, 5.9103262550494792e-02, 1.0934584955858558e-02], + [9.6207272898098262e-01, 2.0137999933765426e-02, 8.7107968196873508e-03], + [9.4842249235722165e-01, 1.2025303380904817e-01, 1.1377489178066337e-02], + [9.6051907702215622e-01, 1.0619662858921844e-01, 5.7459743383413725e-02], + [9.6758399306809706e-01, 4.8186555035933130e-02, 4.2594066830898419e-02], + [9.5110811064501277e-01, 1.9707593797818088e-01, 1.4370205503755748e-01], + [9.5093590026635022e-01, 1.8011351420284150e-01, 5.3934154322118469e-02], + [9.4974796294896136e-01, 1.2227840328487165e-01, 1.1218263701431039e-01], + [9.4146080862663128e-01, 2.1621825886341961e-01, 8.6333481289145447e-03], + [9.3910640207257923e-01, 2.5962693707510071e-01, 4.5395689450192733e-02], + [9.3921178364319413e-01, 2.4797607187190590e-01, 1.1984429808691012e-01], + [9.5619957430062019e-01, 2.6899405655453756e-01, 2.2137223532319270e-01], + [9.3677579175961567e-01, 2.2319754586828094e-01, 2.1307999331307151e-01], + [9.4798496693215406e-01, 3.5571669247301241e-01, 9.1014697990420661e-03], + [9.6347394518683416e-01, 3.6061141298424548e-01, 5.0265239148014930e-02], + [9.5371004360440159e-01, 3.5262305377988401e-01, 1.1709487776897462e-01], + [9.4752787741457523e-01, 4.0011115428258837e-01, 2.0925615913255832e-01], + [9.4564359206561222e-01, 3.3188386880365833e-01, 2.2446223363104462e-01], + [9.4850021473866886e-01, 3.5308457579377811e-01, 3.0648533293059504e-01], + [9.5152956727484506e-01, 3.4432760150848529e-01, 3.3627656705006204e-01], + [9.4254666747607307e-01, 4.9518178133849750e-01, 9.8479781376411349e-03], + [9.6382250908091005e-01, 4.8463886039833953e-01, 5.0596572453726292e-02], + [9.4820980874009120e-01, 4.7600486293349498e-01, 1.2031629311569636e-01], + [9.4527996838135320e-01, 5.1018683310995971e-01, 2.1365346301272528e-01], + [9.4949737066772621e-01, 5.2855902228823370e-01, 3.1905185348800019e-01], + [9.5017386642843138e-01, 4.6843243607020224e-01, 3.5083279895230030e-01], + [9.4862056131319683e-01, 4.9246327127101308e-01, 4.4194389717089327e-01], + [9.5347734252754934e-01, 4.6728717620024657e-01, 4.5670846967054302e-01], + [9.4746542427763869e-01, 6.3353991750945926e-01, 8.7446536540154128e-03], + [9.5657269993260940e-01, 6.2006497173494990e-01, 4.6543518087528310e-02], + [9.5309411629073981e-01, 6.0692907980921484e-01, 1.1204186051111968e-01], + [9.5081266876993431e-01, 6.2252269126593118e-01, 2.0434790632796201e-01], + [9.4740804024659542e-01, 6.4456214007356627e-01, 3.2104320475236325e-01], + [9.4726673536893813e-01, 6.3770994215064247e-01, 4.3425199481098398e-01], + [9.5269084487105593e-01, 6.1007465530518068e-01, 4.9746853826512832e-01], + [9.5200101925283032e-01, 6.2960358419909357e-01, 5.8607510479697356e-01], + [9.5440015930586852e-01, 5.9738769817865800e-01, 5.8936831085364205e-01], + [9.5456420855234025e-01, 7.5252239502337115e-01, 9.3298291040783878e-03], + [9.4152414734891787e-01, 7.3208139462717303e-01, 4.7582606129882576e-02], + [9.4504527889676326e-01, 7.1949980273677816e-01, 2.2395969750625017e-01], + [9.4775972373462880e-01, 7.5646679558124064e-01, 3.4523376890993784e-01], + [9.5115134718829042e-01, 7.4564725639099538e-01, 4.6635628320304656e-01], + [9.5381757968540537e-01, 7.4884721151041800e-01, 5.8694889960362540e-01], + [9.5150383630770219e-01, 7.3283486947284115e-01, 6.5852093892327579e-01], + [9.4962428381206676e-01, 7.2793395879444145e-01, 7.1518555704462561e-01], + [9.5885033862050018e-01, 8.4725650822250798e-01, 1.3755630982804055e-02], + [9.5588624045191928e-01, 8.4148811857967931e-01, 6.8295818866815503e-02], + [9.5128319069742373e-01, 7.2798212657839467e-01, 1.2256272284524643e-01], + [9.4782469548892423e-01, 8.1684806500137785e-01, 2.3709481320574216e-01], + [9.5613640850110637e-01, 8.4260924540622784e-01, 3.7012707532585998e-01], + [9.5328747215572318e-01, 8.4059330659214870e-01, 4.9815094275396038e-01], + [9.5002137806136766e-01, 8.3969680048524198e-01, 6.1897026250450993e-01], + [9.4910628826776966e-01, 8.4155140580835464e-01, 7.2820103933446090e-01], + [9.5086996908914911e-01, 8.1605874013850110e-01, 7.7368900268366303e-01], + [9.5526589480080515e-01, 8.3478495264633579e-01, 8.2915940177287006e-01], + [9.5846376136598610e-01, 9.1500767644958092e-01, 3.3478803258469567e-03], + [9.2924092233698052e-01, 8.8305775975052070e-01, 4.0357971525395539e-02], + [9.5612125068346787e-01, 8.3535264129270292e-01, 1.4478472607776760e-01], + [9.3957688650686078e-01, 8.8792504260323812e-01, 2.0131585360706256e-01], + [9.5173914331422738e-01, 8.9663775619680974e-01, 3.1270901836363729e-01], + [9.4300012951624701e-01, 8.9208424222347038e-01, 4.4883946654483597e-01], + [9.5231643103166919e-01, 9.0834531570597954e-01, 6.0143939006582847e-01], + [9.4420482142458628e-01, 9.0192673977253945e-01, 7.2374821179334670e-01], + [9.5484972474469454e-01, 9.1267421860327547e-01, 8.3416454321389288e-01], + [9.4844941826083906e-01, 8.8934681181484487e-01, 8.6249517030675404e-01], + [9.1159703428972894e-01, 8.6076877889270165e-01, 8.5606785519676742e-01], + [9.5341903647479354e-01, 9.4522320131686988e-01, 1.6771524294899524e-02], + [9.4904451072122176e-01, 9.3966930050660669e-01, 8.2078904651894075e-02], + [9.5698735468762841e-01, 9.0882051821473142e-01, 1.1432017880566897e-01], + [9.4838837828574374e-01, 9.3879163348662997e-01, 1.8548412404687645e-01], + [9.4742655511716778e-01, 9.3659142125259920e-01, 3.0880740414716168e-01], + [9.5524235848234396e-01, 9.4387908630004314e-01, 4.4524584374216253e-01], + [9.5609154766070281e-01, 9.4803668373862193e-01, 5.6304659944999302e-01], + [9.4364823772141382e-01, 9.3615076313508738e-01, 6.8183451924794658e-01], + [9.5463997442166193e-01, 9.4571906965597441e-01, 7.9939074717049041e-01], + [9.4698337128612042e-01, 9.4065671844887166e-01, 8.7581875292691092e-01], + [9.5590638335589440e-01, 9.4398997369568338e-01, 9.3197401101487765e-01], + [9.8853598750975591e-01, 6.0466979224627210e-02, 1.0663510059544119e-02], + [9.9910009981712244e-01, 9.4832066174296344e-03, 4.6849861249654975e-03], + [9.8998758488111138e-01, 1.3818545619361627e-01, 4.8881710430271081e-03], + [9.9334538742638867e-01, 1.2887589408044819e-01, 5.0718861204985401e-02], + [9.9346804116935594e-01, 6.1261876237343228e-02, 4.5907461516207060e-02], + [9.9151516063046230e-01, 1.6622155304136194e-01, 1.1821210355246654e-01], + [9.8953487224930825e-01, 1.7579099968069586e-01, 3.3915709160173987e-02], + [9.9050304555406965e-01, 1.2938314894263592e-01, 1.2062363901815222e-01], + [9.8827544943493484e-01, 2.5797513113179382e-01, 1.1293206213503795e-02], + [9.8974518323449523e-01, 2.7479700971195403e-01, 6.0772944553141930e-02], + [9.8827076835079164e-01, 2.4312323512657663e-01, 1.1788081872653930e-01], + [9.9114945039508329e-01, 2.7561264725138607e-01, 2.1897963255095385e-01], + [9.8785201113822274e-01, 2.3308557583390108e-01, 2.2241484609619108e-01], + [9.9085225234506724e-01, 3.8730510324922179e-01, 1.0510364678761257e-02], + [9.9426680716068305e-01, 3.9421998683580128e-01, 5.5367921330846448e-02], + [9.9146104390339074e-01, 3.6946377233708028e-01, 1.3645401780119543e-01], + [9.8936965609247496e-01, 4.4900028791487823e-01, 2.2293550808452028e-01], + [9.8929843603725998e-01, 3.4938640640439128e-01, 2.1644189892689419e-01], + [9.9033159708660734e-01, 3.7861354689575172e-01, 3.4145607294131292e-01], + [9.9093335516572856e-01, 3.5668260555097653e-01, 3.5032836056613481e-01], + [9.8833893194111733e-01, 5.1515158965883856e-01, 9.9398227284977197e-03], + [9.9403037209870304e-01, 5.3561470185321647e-01, 5.1799518867392327e-02], + [9.9004222299691491e-01, 4.8623661007848790e-01, 1.2709319752370896e-01], + [9.8919278334898941e-01, 5.4735739023250940e-01, 2.2734632082592182e-01], + [9.9092186563942031e-01, 5.1677628916779694e-01, 3.2126213976103857e-01], + [9.8992376457580089e-01, 4.3529562559397128e-01, 3.3448867985238057e-01], + [9.8988793654592033e-01, 5.0630790359532607e-01, 4.6094192304740250e-01], + [9.9125752238540710e-01, 5.0142526072834537e-01, 4.9310417462797157e-01], + [9.9022189594346477e-01, 6.4353227896092402e-01, 8.2501958554768353e-03], + [9.9110984518359591e-01, 6.6179216073159453e-01, 4.6338141363933792e-02], + [9.9033776423427600e-01, 6.1060919778783196e-01, 1.2082787082399435e-01], + [9.9094679250882289e-01, 6.5436385829893440e-01, 2.1568583495966542e-01], + [9.9005180012663196e-01, 6.5193231763565707e-01, 3.3908778960617536e-01], + [9.8933116039204583e-01, 6.3868188140632398e-01, 4.3406477836330914e-01], + [9.9102335344733028e-01, 5.7696953775208448e-01, 4.6297886049400211e-01], + [9.9043435590450046e-01, 6.3512622863782275e-01, 5.8639953010129542e-01], + [9.9195223814493982e-01, 6.4070014113165252e-01, 6.3091921152072972e-01], + [9.9104380240532908e-01, 7.6804459391551894e-01, 1.2655012914414985e-02], + [9.8749203733156576e-01, 7.7851425382571193e-01, 6.1265380984528113e-02], + [9.9195897023447754e-01, 7.2463568639981291e-01, 1.1457627815845838e-01], + [9.8915249713700393e-01, 7.6967649794687942e-01, 1.9443320346095372e-01], + [9.8942978904324774e-01, 7.5948886322069886e-01, 3.0504080660000066e-01], + [9.9023747179818900e-01, 7.7455110657369186e-01, 4.3079866993856875e-01], + [9.9104489754656722e-01, 7.5141649784656350e-01, 5.2818241368483021e-01], + [9.9099669362712528e-01, 7.1969009885282775e-01, 6.0197575110666091e-01], + [9.9069456647297582e-01, 7.6671196588552437e-01, 7.2328734333987377e-01], + [9.8908125869322960e-01, 7.5288902377290834e-01, 7.4654020815840971e-01], + [9.9358722148309708e-01, 8.7370732095655756e-01, 3.6766674228371249e-03], + [9.9264682780516900e-01, 8.7354313240831905e-01, 4.2324965204703809e-02], + [9.9236475361191512e-01, 8.5624632823627012e-01, 1.3032166260117620e-01], + [9.9053826617807883e-01, 8.6936644923007111e-01, 2.4332989431242708e-01], + [9.9048241532203751e-01, 8.5726759913015727e-01, 3.2153072741270128e-01], + [9.9224365906365508e-01, 8.7281126545920296e-01, 4.2398893518531861e-01], + [9.9028208990430167e-01, 8.6738474558587531e-01, 5.4271358792171576e-01], + [9.9090094539116080e-01, 8.4962876334307902e-01, 6.3793762558800204e-01], + [9.9063285956830294e-01, 8.3975250479305075e-01, 7.2662290993143441e-01], + [9.8938434456677360e-01, 8.7751138207447144e-01, 8.3249128477079848e-01], + [9.9291444447760768e-01, 8.5821869685727892e-01, 8.4891369172631925e-01], + [9.9407514952561615e-01, 9.4762438589125253e-01, 1.2213864911325359e-02], + [9.8211014341661373e-01, 9.3408156130977260e-01, 4.0868494230308569e-02], + [9.9286389628754690e-01, 9.3643485605086718e-01, 1.0174382307724848e-01], + [9.8868154045382017e-01, 9.3425339466889279e-01, 2.0937254229059185e-01], + [9.9105607214458813e-01, 9.4096228014076500e-01, 3.5079209856455718e-01], + [9.8822864190377380e-01, 9.3910738210205769e-01, 4.9133419663647643e-01], + [9.9215362838080490e-01, 9.3880704230892187e-01, 6.2512798344682974e-01], + [9.8852668645397734e-01, 9.2752565596302328e-01, 7.3362599047616284e-01], + [9.9133007831113606e-01, 9.3025126693670945e-01, 8.2658894785091486e-01], + [9.9244846654758645e-01, 9.4996608162147755e-01, 9.1253896793187961e-01], + [9.8681981353524406e-01, 9.3437301890352320e-01, 9.2814116957972315e-01], + [9.9224869165427010e-01, 9.8459288814088353e-01, 7.0887113185111057e-03], + [9.9086497428764642e-01, 9.8075773392647925e-01, 5.3929241801607880e-02], + [9.9035512628452083e-01, 9.7981528136635709e-01, 1.4288763724882739e-01], + [9.9004707352916121e-01, 9.7942530178653053e-01, 2.6437577137343365e-01], + [9.8986961860373257e-01, 9.8129441886860280e-01, 3.9968626389392581e-01], + [9.9439950283830358e-01, 9.8462398459390876e-01, 5.2432892308759860e-01], + [9.8969820447811041e-01, 9.7990839935638785e-01, 6.2988499395590936e-01], + [9.8965580859472513e-01, 9.7809399593818303e-01, 7.4448810982755753e-01], + [9.9163580512718352e-01, 9.7964097784759008e-01, 8.5470228238616652e-01], + [9.8862682881377906e-01, 9.7973489088685040e-01, 9.3117960230829477e-01], + [9.9525886868363334e-01, 9.8818498810471767e-01, 9.8173721333602082e-01], + ], + weights: [ + 3.6627731953110264e-05, + 1.3813350481119836e-05, + 4.1060712543472057e-05, + 6.3157431037203236e-05, + 5.4522046358802753e-05, + 1.0988358963821658e-04, + 5.0988204366063325e-05, + 8.7843747355704740e-05, + 4.2853149357286444e-05, + 1.1851226781839570e-04, + 9.6899074322087488e-05, + 1.3160532320345038e-04, + 2.3296062956315187e-04, + 2.0198801476043561e-04, + 1.3513196297130727e-04, + 1.1385043949989662e-04, + 2.4476033489085155e-04, + 2.2668383457624566e-04, + 8.2148089533865985e-05, + 7.1769190046831852e-05, + 4.0979711098614883e-05, + 2.2533090820369539e-04, + 9.1860216621301793e-05, + 1.1792864089854519e-04, + 3.2783098401980020e-04, + 7.7201548901777148e-05, + 2.3482550066836517e-04, + 1.8801574875190781e-04, + 4.1673448084100377e-04, + 4.1943184205054355e-04, + 2.2466647448751457e-04, + 2.6750862083403524e-04, + 3.7985074608340251e-04, + 5.8746339853108518e-04, + 1.6647959719532594e-04, + 4.6694941999237213e-04, + 6.2856714253495293e-04, + 1.6330466971486884e-04, + 4.3389886774783734e-04, + 2.8596530005401343e-04, + 3.9289457806718919e-04, + 9.1514524963193011e-05, + 2.0284889340011451e-04, + 2.4453139910459045e-04, + 2.0123162939730390e-04, + 2.1808284791009490e-04, + 8.4050216150637953e-05, + 1.4685969569821743e-04, + 3.4954364680993147e-04, + 1.4206459424449022e-04, + 1.7970709102366460e-04, + 2.9618290681978439e-04, + 3.1902264657609461e-04, + 5.7297480681267512e-04, + 5.6603779354751007e-04, + 2.9715467656436274e-04, + 4.9584118509940847e-04, + 6.5568345097968763e-04, + 7.0461185853572315e-04, + 2.3894338124948603e-04, + 1.7910217750054103e-04, + 4.7485489522379346e-04, + 6.5308443049170235e-04, + 6.4513882875095942e-04, + 4.0455411371000268e-04, + 2.4185991200161250e-04, + 2.8134988663897341e-04, + 1.7631884493880500e-04, + 3.7049315117825167e-04, + 5.5201809615505911e-04, + 5.2396278010541884e-04, + 3.6961271392398841e-04, + 8.5925711691391951e-05, + 6.2550996278978413e-05, + 2.4820766698043379e-04, + 2.2161330020651723e-04, + 1.3083426900092996e-04, + 7.5871344473228634e-05, + 1.5039122038039613e-04, + 2.9458943805564763e-04, + 8.4352040527360000e-05, + 1.7748529692123105e-04, + 5.4751591053938092e-04, + 5.3144887191403263e-04, + 3.1198854789287557e-04, + 7.9125362070494405e-04, + 6.7780210249140168e-04, + 3.2393040953987857e-04, + 9.4931245968212310e-04, + 3.4587439861047593e-04, + 3.4278450793459266e-04, + 7.4127318476415816e-04, + 1.0022526011498251e-03, + 6.8963831008395755e-04, + 1.5104710671266200e-04, + 4.3258361652015123e-04, + 1.0125279555645581e-03, + 5.9625774914348552e-04, + 2.6174053548172921e-04, + 2.1106696964804151e-04, + 8.6063411456901171e-04, + 7.1186582857527458e-04, + 8.3866645581433404e-04, + 4.6124866733948861e-04, + 6.4163028558407789e-04, + 5.2975999683768692e-04, + 1.1435445781179547e-04, + 6.3117310686181276e-05, + 1.9439916976641950e-04, + 2.8956884128063091e-04, + 6.5738727216016805e-04, + 2.6514732447997482e-04, + 3.2280277874358347e-04, + 2.7627454664285578e-04, + 3.5711686362405117e-04, + 2.0259990886773867e-04, + 7.9139486512662068e-05, + 8.8216028138436633e-05, + 1.7004759283536049e-04, + 3.3299015313610712e-04, + 9.4490795732599711e-05, + 4.4454362803141663e-04, + 4.8472195661526234e-04, + 2.5487871005957830e-04, + 2.3075189565268419e-04, + 2.5826238438379274e-04, + 2.7356211370267795e-04, + 6.8642381385043282e-04, + 6.9718583056048694e-04, + 7.1838315200900929e-04, + 3.3868763503376218e-04, + 8.8373474289711335e-04, + 1.1512873692607300e-03, + 1.1904849026173122e-03, + 7.9424293589239366e-04, + 3.4493239315466404e-04, + 3.4264042997219024e-04, + 1.1180857632027625e-03, + 1.3109815231944640e-03, + 1.1798570616042307e-03, + 7.5167865446008637e-04, + 3.2352076400483606e-04, + 1.3261416633577044e-04, + 7.5100728085612605e-04, + 1.1810090936528665e-03, + 1.1587706322240905e-03, + 2.5052685905103353e-04, + 5.6037668087074305e-04, + 8.8194398905954547e-04, + 8.7482332369551315e-04, + 5.9667890875517268e-04, + 1.3082111776512793e-04, + 1.9404760393466981e-04, + 1.4438434226369716e-05, + 5.3278874596096885e-04, + 6.0945462463048974e-04, + 8.3701696899609050e-04, + 6.4828321695850730e-04, + 5.7281485646739998e-04, + 2.9291147861304273e-04, + 7.4622256464837179e-05, + 1.4735294085906829e-04, + 3.0690349220688666e-04, + 3.0604468452046640e-04, + 3.8417570655509086e-04, + 3.2310831454917621e-04, + 1.8695664353406285e-04, + 1.9109552338057853e-04, + 5.7589335427703144e-05, + 2.1588880727848583e-04, + 6.9477788876077159e-05, + 2.9445599072568926e-04, + 3.3094410589305529e-04, + 1.0424979238136752e-04, + 6.4750984772626375e-04, + 6.3557818174037875e-04, + 2.2589899011716868e-04, + 1.9637061761077724e-04, + 6.1260445446643399e-04, + 2.7749151140782921e-04, + 2.9030414156312436e-04, + 6.5678941108893358e-04, + 9.1411563636937219e-04, + 7.5543081001654578e-04, + 7.8225557455539191e-04, + 3.5885750533333515e-04, + 6.9295311165351277e-04, + 1.3128641818495946e-03, + 7.4992097336932021e-04, + 3.5724161093214678e-04, + 3.7102045185104460e-04, + 7.0879061544255417e-04, + 1.1131454667218101e-03, + 1.0350534347843758e-03, + 3.2836543960004496e-04, + 5.7931645369481703e-04, + 9.6091851343146266e-04, + 1.2763421779994782e-03, + 1.3205735761891268e-03, + 8.5616039807830666e-04, + 6.6792417730745677e-04, + 3.2337903729629170e-04, + 2.9272232600311581e-04, + 4.5619509325654171e-04, + 6.7125712497201025e-04, + 9.5533458391008767e-04, + 1.1181413169397193e-03, + 1.0495143425906829e-03, + 8.2266078281456861e-04, + 6.3366421948023405e-04, + 2.1584751915740472e-04, + 1.1452145018355861e-04, + 3.9404389533671190e-04, + 6.5805431005285105e-04, + 7.4513381010654417e-04, + 6.9215069712595136e-04, + 6.2855023182980239e-04, + 6.9276789423871981e-04, + 4.1839770414073930e-04, + 9.0681949058785249e-05, + 8.7429845042745217e-05, + 2.2008770361077950e-04, + 3.0903305882550871e-04, + 3.0166530769229781e-04, + 3.2760331754502526e-04, + 2.4460034395735466e-04, + 3.1575459271867884e-04, + 1.6271188061136742e-04, + 1.4674789765998952e-04, + 3.8225846855809437e-05, + 1.7813009066235257e-04, + 3.0327979597254048e-04, + 1.4261528451406413e-04, + 3.9067829367228228e-04, + 3.9357984580405995e-04, + 2.0431917905280633e-04, + 2.3340086896041760e-04, + 4.8674315534698320e-04, + 6.9037750576674993e-04, + 5.6982510343981199e-04, + 2.4740101196692177e-04, + 2.9995708462681700e-04, + 5.3447597743663409e-04, + 6.1251896741458241e-04, + 1.0104263215598170e-03, + 6.3681592095710831e-04, + 6.6052628343516643e-04, + 2.5947179319746951e-04, + 7.7311640717769642e-04, + 7.6195310375661322e-04, + 2.7888746344105779e-04, + 3.1760982556367634e-04, + 5.4866236198355359e-04, + 7.8071861870843454e-04, + 1.1776032599789983e-03, + 1.1505147604597486e-03, + 8.5917047955880209e-04, + 5.9282113275884189e-04, + 2.4426820315414001e-04, + 4.8894153581990701e-04, + 7.2065358566198432e-04, + 9.9134557250015343e-04, + 1.0908083024846313e-03, + 9.6811385524671475e-04, + 7.5881102957338863e-04, + 4.8856337296904900e-04, + 2.4211071499176016e-04, + 9.7394381218531998e-05, + 4.1368605617296908e-04, + 5.9685914224139936e-04, + 6.4992007126367824e-04, + 9.0781471928684325e-04, + 8.1271146989413871e-04, + 7.1970703416652506e-04, + 7.0259916557106677e-04, + 3.1938972201038545e-04, + 2.1725416162299242e-04, + 1.1984202244678156e-04, + 3.2117544375662589e-04, + 4.7172356584548392e-04, + 5.2246887106194165e-04, + 5.3950692431919587e-04, + 5.9810627123512458e-04, + 5.9981377081386373e-04, + 4.3204387306329777e-04, + 2.4586504036789843e-04, + 1.4407617831694856e-04, + 4.8610019429001604e-05, + 1.3032392928342119e-04, + 2.4580526285270790e-04, + 2.2626244132187799e-04, + 1.5913800393461817e-04, + 3.2034155269391627e-04, + 2.6562516667787318e-04, + 1.6129561649083862e-04, + 1.5142038233093970e-04, + 3.7390540857959283e-05, + 8.7727168758466262e-05, + 3.5133384794595639e-05, + 1.3649037848356655e-04, + 2.0577540856901624e-04, + 3.6953760915517651e-05, + 3.2346095569007013e-04, + 2.7696949947090231e-04, + 1.3322903966386034e-04, + 1.6727919453890866e-04, + 3.3681463127781708e-04, + 4.9499358597782279e-04, + 1.4006088320458719e-04, + 2.0404453638331579e-04, + 2.0183133973418967e-04, + 2.8971096963805961e-04, + 5.2192753931023085e-04, + 5.8466359109748305e-04, + 4.9866825746479435e-04, + 3.8652096398370705e-04, + 1.4214779848422686e-04, + 2.1804931286774427e-04, + 3.5561386434650242e-04, + 6.1373970498793042e-04, + 6.9341194238119576e-04, + 6.7563376214812404e-04, + 6.2445345413950223e-04, + 4.3123963343303197e-04, + 1.7306826874631103e-04, + 1.7198452940459224e-04, + 3.4887864821307350e-04, + 5.2570462446952057e-04, + 6.4176537901388709e-04, + 8.2707972914974740e-04, + 6.5694838917212863e-04, + 6.0089259244907364e-04, + 3.3711568830717355e-04, + 1.5464522977646177e-04, + 1.2860726811773885e-04, + 3.6985284539132373e-04, + 6.3261476269366895e-04, + 6.7032960590025304e-04, + 6.9784810134926475e-04, + 6.1063112891648083e-04, + 4.3195130995575456e-04, + 2.0449996336409590e-04, + 1.4533821231229070e-04, + 3.0496415810788823e-04, + 5.7788605843651670e-04, + 5.8171122411607916e-04, + 4.6355193038105755e-04, + 5.2213603179058067e-04, + 5.2748236160488125e-04, + 4.5093715694244764e-04, + 2.5531414598393049e-04, + 8.1599568632411220e-05, + 4.2077336721760952e-05, + 2.1735325072805395e-04, + 3.9286705860318093e-04, + 3.6623816028935152e-04, + 4.2021822978993966e-04, + 4.3422789556661786e-04, + 4.1267068830918739e-04, + 3.3242922293416117e-04, + 2.2154368682131441e-04, + 1.4352091867921241e-04, + 6.9873347909145720e-05, + 5.0606102970636063e-05, + 1.2056557015416090e-04, + 2.3740286291176651e-04, + 1.6823434471499598e-04, + 2.0933548317294846e-04, + 1.8529044515612053e-04, + 1.3279664622093987e-04, + 1.5030505827979382e-04, + 1.1373187355480332e-04, + 6.7344954287670597e-05, + 5.4180018078836550e-05, + 5.0689296157213176e-05, + 3.5403705309109065e-06, + 3.8931819428163795e-05, + 7.3580118543664790e-05, + 3.7767361697647493e-05, + 1.1027560904926919e-04, + 9.2691629341560254e-05, + 4.9798065333055379e-05, + 1.0562835420621952e-04, + 1.7889499598104731e-04, + 2.4499183514300191e-04, + 1.6449851662007187e-04, + 9.7674981522132722e-05, + 8.4484868040069335e-05, + 1.3366482193938954e-04, + 2.2413806591314932e-04, + 2.1521847743962607e-04, + 2.5831538566456274e-04, + 1.4306702598127738e-04, + 5.4340336394164408e-05, + 9.6141326277422614e-05, + 1.3218151606587424e-04, + 2.8738040900308851e-04, + 3.1517360532151999e-04, + 2.8359995643675824e-04, + 2.5244111393066948e-04, + 1.6503531275605801e-04, + 7.0907836657495933e-05, + 7.0096993661656823e-05, + 1.5172793109206133e-04, + 2.5218375223773907e-04, + 2.9525541214867941e-04, + 3.6832148103893727e-04, + 3.0658328161925708e-04, + 2.5842058126420987e-04, + 1.7383031329160401e-04, + 6.6804398843762737e-05, + 8.4134410713215043e-05, + 2.0172447058179908e-04, + 1.8139432348717553e-04, + 3.0141088868952356e-04, + 3.4989403286195470e-04, + 3.2661988989518645e-04, + 2.8223414246182616e-04, + 2.7299397309456198e-04, + 1.6595210599053498e-04, + 5.2521115671144206e-05, + 2.4286489127653932e-05, + 1.0336182608282411e-04, + 1.9787673349071368e-04, + 2.1153591560193878e-04, + 1.7346873060930301e-04, + 2.1018700340825398e-04, + 2.4310808778347438e-04, + 2.3740297367189661e-04, + 2.2226266993490137e-04, + 1.2045998297576720e-04, + 4.3749309878572520e-05, + 2.7032795688361389e-05, + 9.1087205892697535e-05, + 1.0940930100057830e-04, + 2.1310836199122548e-04, + 1.9684652041328013e-04, + 2.2774576055881581e-04, + 1.5953912264453926e-04, + 2.0559773796670585e-04, + 1.2012923715288839e-04, + 5.1134902705837633e-05, + 3.6879170987733457e-05, + 9.5075683070815046e-06, + 4.2363811085347805e-05, + 7.2472546964253573e-05, + 9.0921408319481043e-05, + 7.6702519154667049e-05, + 4.7449702335084858e-05, + 7.1580199207354445e-05, + 9.1631400596487491e-05, + 6.3475221501040489e-05, + 3.7980794663614418e-05, + 6.8526526263901886e-06, + ], + }, + 20: { + points: [ + [6.3120555913655410e-02, 1.9528818198912515e-02, 7.3091161505919461e-03], + [2.7159828605938564e-02, 2.2389638739317864e-02, 1.2527047109569048e-02], + [7.8438175446812669e-02, 6.5746706678627684e-02, 6.9628965352821603e-03], + [1.4359328235991858e-01, 1.9592451370337809e-02, 1.0693929698839493e-02], + [8.8438807712955037e-02, 4.9928958851359131e-02, 4.2315562154309289e-02], + [1.3475188368060689e-01, 6.7324633819085850e-02, 1.0209781275259218e-02], + [1.6236128233590699e-01, 1.5231210807451398e-01, 7.8066525522150661e-03], + [9.9745889221290074e-02, 8.6280174087481967e-02, 3.9775362209301147e-02], + [9.0987257314024392e-02, 8.2716422106457799e-02, 7.2293588996576980e-02], + [2.0333626265254337e-01, 5.6943739630321004e-02, 8.7946556649130050e-03], + [1.6484717686420369e-01, 5.9738642164256152e-02, 5.0608449758038507e-02], + [2.4719163278806774e-01, 1.0063690744840180e-01, 9.1609492414822302e-02], + [2.6748617399494512e-01, 1.4157630725800260e-01, 1.0256587615942425e-02], + [1.6109293394790625e-01, 9.1242201689884894e-02, 4.8714258892877248e-02], + [1.8137479850768876e-01, 1.2720036752035835e-01, 1.1861158729195700e-01], + [1.8920236047968772e-01, 1.3888975764852907e-01, 9.0014829024267218e-03], + [1.9207081424863104e-01, 1.5943256496762145e-01, 4.6463184310485399e-02], + [1.9390603162125059e-01, 1.5653777798241864e-01, 1.0801357393647423e-01], + [1.8202266201235240e-01, 1.7176344677686325e-01, 1.5982993890358968e-01], + [2.7243157741795770e-01, 2.6258307925708191e-01, 9.4737711239805608e-03], + [1.6042337075969004e-01, 1.6015632883352951e-01, 4.0082994879862659e-02], + [2.6656670848000846e-01, 2.5599303865342710e-01, 1.1296025517277311e-01], + [1.7204596119120585e-01, 1.6643501199323332e-01, 1.0461461803893071e-01], + [2.6980740575239814e-01, 4.6570447996301882e-02, 3.8998574578695114e-02], + [2.6528442582127409e-01, 9.1282503902025192e-02, 4.7341918736443070e-02], + [2.6262480905018171e-01, 1.9780649500527218e-02, 7.0662479312862481e-03], + [3.6196683559125786e-01, 1.1932244530546374e-01, 1.0951890352513037e-01], + [3.1337773632729587e-01, 8.6722320288791829e-02, 9.9999057689600570e-03], + [2.6432789463910250e-01, 1.5480694583564691e-01, 5.2375255712738579e-02], + [2.7890447530105045e-01, 1.6600707855962832e-01, 1.1842877211887419e-01], + [3.0689215530973113e-01, 1.8097179133518407e-01, 1.7112669179895848e-01], + [3.7793242105955754e-01, 2.5024979168339501e-01, 9.7818734939173636e-03], + [3.7712006483860022e-01, 2.2321720295189371e-01, 5.3758231861788013e-02], + [3.7824538395178847e-01, 2.3780626101727220e-01, 1.2841561991129846e-01], + [2.9122544080077239e-01, 2.3872049056460842e-01, 9.8961159702772809e-03], + [2.9193110976004438e-01, 2.3637995963760322e-01, 1.1793602401251688e-01], + [4.2121264127096475e-01, 2.6347141151719494e-01, 2.1534874585930625e-01], + [2.9107756417323344e-01, 2.4002738113913610e-01, 2.3114284053462414e-01], + [3.1677920237591561e-01, 2.4960849270861848e-01, 5.0029337175531888e-02], + [3.6037781556340032e-01, 3.2171440439304616e-01, 1.9706417730779030e-01], + [3.0850331819665433e-01, 2.4932458193611523e-01, 2.0163221464545886e-01], + [2.9577871097509600e-01, 2.8608477014107542e-01, 2.7440210994389314e-01], + [2.7830171224424177e-01, 2.6519787970706626e-01, 4.7897705262625628e-02], + [3.7955733836013134e-01, 3.6928601355410112e-01, 1.1462603961731975e-01], + [3.6459103301161072e-01, 3.5736601743547497e-01, 2.1147515145666554e-01], + [2.7647325274356971e-01, 2.6521772584053782e-01, 2.0409411786459175e-01], + [4.0153973286696193e-01, 1.8591787866397366e-02, 9.7858265854844327e-03], + [3.7801801610765218e-01, 5.7446773580237108e-02, 9.8526225351313196e-03], + [3.9023623848905814e-01, 8.8336848177008639e-02, 4.7458308506946753e-02], + [4.0564666198357469e-01, 5.7401183936728203e-02, 5.0178614962475498e-02], + [4.7121733068982713e-01, 1.1924224308474575e-01, 6.6341255885447861e-03], + [4.9892069212058238e-01, 1.3414490802547008e-01, 1.2412347320415922e-01], + [4.0440073051535774e-01, 1.7383115793947365e-01, 1.0601909779853676e-02], + [3.9088394957404415e-01, 1.6230072825659381e-01, 5.3194046761266343e-02], + [3.9065285991267668e-01, 1.6686226499204060e-01, 1.1603828385966768e-01], + [4.3987454717853658e-01, 2.1391195442376629e-01, 2.0442017082480152e-01], + [4.5176106656389109e-01, 2.9020616335000315e-01, 5.1426922804814569e-02], + [4.8555840288680086e-01, 2.4578506114261595e-01, 1.2138484891146514e-01], + [5.2699376365921269e-01, 2.3438438618946550e-01, 1.8332268935555524e-01], + [5.0419377737791238e-01, 3.7824533127555593e-01, 8.9142988124228904e-03], + [4.1257706515030407e-01, 3.5784468741876496e-01, 9.0991106130087346e-03], + [4.5311390179445687e-01, 3.6015805024320741e-01, 4.8548956731846553e-02], + [4.3197471946712801e-01, 3.1146596797277137e-01, 1.2359606535436296e-01], + [4.2696752868496962e-01, 3.3017008587735241e-01, 2.1894945947885774e-01], + [4.3889303916052830e-01, 3.4838431550072640e-01, 3.0350206416125403e-01], + [4.1714125804967200e-01, 2.9512267178286394e-01, 2.8659222917298577e-01], + [4.0630471745306546e-01, 3.7256812728347438e-01, 3.2522291625950106e-01], + [4.2253481574373775e-01, 3.7395854179177868e-01, 3.6517064424087126e-01], + [4.1643440367635115e-01, 3.8478952863837729e-01, 4.9763817621888508e-02], + [3.9850991167723643e-01, 3.4574887445914826e-01, 1.1590398138529084e-01], + [4.5941032488340494e-01, 4.1270684874765101e-01, 2.0779804938043772e-01], + [4.4177384028237598e-01, 4.0091054690485561e-01, 2.8865215338212497e-01], + [3.9375318043645063e-01, 3.8282956254778716e-01, 9.9550855329473675e-03], + [3.9475059633986809e-01, 3.9474705596933718e-01, 5.0655859947133036e-02], + [4.5828646157634106e-01, 4.4879624892707004e-01, 1.9840798612429977e-01], + [4.6682995896465290e-01, 4.5808423296104589e-01, 3.2525924487948948e-01], + [3.9297310325487578e-01, 3.8726308682036997e-01, 3.2940643871801178e-01], + [4.2433815334160696e-01, 4.1578799372420122e-01, 4.0612765281797569e-01], + [5.0430468763469916e-01, 5.6200470411714221e-02, 7.9244108449946019e-03], + [5.3245177233394558e-01, 8.0124282177654021e-02, 3.9508268671400282e-02], + [5.4875758620981718e-01, 1.8794792858758392e-02, 9.4909369159293377e-03], + [5.4465290185535475e-01, 6.1531058079087209e-02, 5.3216335417908017e-02], + [5.0550113479303749e-01, 1.4787238633698033e-01, 3.9813321903854178e-02], + [5.1541005605330614e-01, 1.4612875264277811e-01, 9.5578938872026745e-02], + [5.5884385128086711e-01, 2.1532059552853217e-01, 9.6956126308903740e-03], + [5.3489517363991823e-01, 2.5874668249489458e-01, 5.1896989343505694e-02], + [5.6223915879495712e-01, 2.2675456095680660e-01, 1.0875276527147683e-01], + [5.7185003473251428e-01, 2.3571807025132271e-01, 2.2577446679516847e-01], + [5.5157864268833545e-01, 3.4079705039497898e-01, 1.2376125596827070e-01], + [5.5095169129949906e-01, 3.3445685499250022e-01, 3.2417738098301563e-01], + [5.1405302289671306e-01, 2.9233761614014397e-01, 9.9136326157422908e-03], + [5.8958995207202003e-01, 3.8393090542856984e-01, 5.0588364581525221e-02], + [5.3562545867184419e-01, 3.3448037968917871e-01, 2.1318146685537562e-01], + [5.6477068381085782e-01, 3.4118597198176487e-01, 2.8889771978961820e-01], + [5.8578483451975361e-01, 4.6477601441790967e-01, 1.4056537661554477e-02], + [5.7052796994687816e-01, 4.5176665364006718e-01, 5.0625789317436359e-02], + [5.4509661405799315e-01, 4.2886095065188284e-01, 2.1732299627373075e-01], + [5.5976728885460147e-01, 4.2514052357136556e-01, 3.7481323839047831e-01], + [5.4593278122516831e-01, 4.2545821930867117e-01, 4.1603724461696417e-01], + [5.4411630356138951e-01, 4.9332252450784103e-01, 1.0689496831543745e-02], + [5.4371413529784896e-01, 4.2311603860422370e-01, 1.1983755169512592e-01], + [5.7032327539657890e-01, 5.1928748582600126e-01, 3.2919224433333394e-01], + [5.5646609792311730e-01, 4.3461453070352851e-01, 3.1462635624522128e-01], + [5.7055293907764981e-01, 5.1754098116855007e-01, 5.5091069223737708e-02], + [5.2889256285145170e-01, 4.7769552577036217e-01, 1.2463749526675606e-01], + [5.4680752053482062e-01, 4.9237224593376455e-01, 3.9629230230212398e-01], + [5.6197781126432800e-01, 5.1120775004449515e-01, 5.0594080049318635e-01], + [5.1630103240817637e-01, 5.0693928585753445e-01, 7.6895144486622798e-03], + [5.3214960722327231e-01, 5.2147344709482468e-01, 4.5144566626828475e-02], + [5.1365988413893882e-01, 5.0345416667827736e-01, 1.1645851526816549e-01], + [5.7493871029625798e-01, 5.2573243238070333e-01, 2.2512028671017409e-01], + [5.7485342791304495e-01, 5.6587443220681732e-01, 2.1458046749694748e-01], + [5.5056043745539240e-01, 5.4086319787225534e-01, 3.1102787115619840e-01], + [5.7241651523320303e-01, 5.6194595259852309e-01, 4.4322878114173109e-01], + [5.4627441932992893e-01, 4.9161692589960237e-01, 4.5481882284352015e-01], + [5.2480594814735571e-01, 5.1435399992575326e-01, 4.6629888009647580e-01], + [5.5606711796726416e-01, 5.4498987713656255e-01, 5.3561307595333152e-01], + [6.8009507335703190e-01, 2.1731587501017852e-02, 1.1598364006091514e-02], + [6.4675804963212735e-01, 5.9938362920509254e-02, 8.1424368699320390e-03], + [6.8310853591981857e-01, 8.1592099781982758e-02, 4.7971103219847398e-02], + [6.7590712110631423e-01, 5.9754618438923858e-02, 5.6336701808367445e-02], + [6.4357519300530985e-01, 1.4314563739372091e-01, 1.0308795393320005e-01], + [6.3930084420775968e-01, 1.3507844340521999e-01, 4.2244570169079010e-02], + [6.3536741104232852e-01, 1.4211700571256480e-01, 1.3414611223467557e-01], + [6.1130989099813826e-01, 1.2898012812794829e-01, 7.6703292771073163e-03], + [6.8222714192617895e-01, 2.5469957030798551e-01, 2.4524351923659291e-01], + [6.8206453027540914e-01, 2.2628267414106840e-01, 1.0243426106469337e-02], + [6.4692656134310345e-01, 2.3161924277831014e-01, 4.8925259098618439e-02], + [6.6346806661685676e-01, 2.1339214851761437e-01, 1.0662183945901885e-01], + [6.5435093232126673e-01, 2.3693695503079770e-01, 1.8779209176253320e-01], + [6.5485203194978758e-01, 3.2827830813133002e-01, 1.0008422360600639e-02], + [6.8736063344865128e-01, 3.5515409890543104e-01, 5.2041074094009217e-02], + [6.6806343050121875e-01, 3.3083072069549330e-01, 1.2264848776717480e-01], + [6.5580332001526664e-01, 3.2883342687680656e-01, 2.0773229941447177e-01], + [6.8021016146924673e-01, 3.5457092004355834e-01, 3.0334024910749458e-01], + [6.7897158763745280e-01, 3.6531132014352008e-01, 3.5546340937717302e-01], + [6.4503743704248617e-01, 4.2936170981075245e-01, 9.6100617761794519e-03], + [6.7177156194532872e-01, 4.4579562904713216e-01, 1.2302317892595266e-01], + [6.5858799138790636e-01, 4.3811475857231530e-01, 2.2052919459062123e-01], + [6.7447163164365131e-01, 4.5282982128691585e-01, 3.2841378765799600e-01], + [6.9023276241682885e-01, 4.7715863192585767e-01, 4.2482140855334105e-01], + [6.7429500615796445e-01, 4.7182527774799637e-01, 4.6180400687876172e-01], + [6.6505434265745444e-01, 5.4643504544826527e-01, 3.2034581873121625e-03], + [7.2181101543254023e-01, 4.9530676480278152e-01, 5.0638454957651097e-02], + [6.6755761703272465e-01, 5.4208451022292115e-01, 2.0705220119089943e-01], + [6.7562993351472789e-01, 5.4878441446193793e-01, 3.2449614316572145e-01], + [6.7141030619661290e-01, 5.6088652104681536e-01, 5.5056363052403878e-01], + [6.8866359599829996e-01, 5.6490581175181753e-01, 3.7924203659171131e-02], + [6.6984135852040894e-01, 5.4073209109164677e-01, 1.0793872221136667e-01], + [6.7904411731075409e-01, 5.5651492188207141e-01, 4.2964703220213829e-01], + [6.7365932112005578e-01, 5.6104936879210032e-01, 5.0776543331493240e-01], + [6.9533238577084266e-01, 6.5404433736749112e-01, 6.4631499123877301e-01], + [6.7386896289482079e-01, 6.2331302606078953e-01, 1.1872720904784464e-02], + [6.3870432931373944e-01, 6.0547256670362160e-01, 9.1976872804742704e-02], + [6.6760348937051595e-01, 6.1141065735994515e-01, 1.1590616191837892e-01], + [6.6917878460875857e-01, 6.1693615634439924e-01, 1.9063634357717807e-01], + [6.9036372192587248e-01, 6.3570535264620531e-01, 3.1682765505469296e-01], + [6.8481121923586785e-01, 6.3071392860258546e-01, 4.2801534990309775e-01], + [6.7014491915103680e-01, 6.2164645127921347e-01, 5.1311232281811436e-01], + [6.6386994776733410e-01, 6.2044927015000328e-01, 5.8110108563249752e-01], + [6.4651884181482422e-01, 6.3629087936623330e-01, 8.4434861374256855e-03], + [6.5811616707517218e-01, 6.4970692188017776e-01, 4.6651570744561922e-02], + [6.4581095759089546e-01, 6.3542036061066376e-01, 1.1790340662630849e-01], + [6.7764098788920168e-01, 6.6710012978151756e-01, 2.2270539317743235e-01], + [6.8085509155241097e-01, 6.7018780328064043e-01, 3.4095536035912033e-01], + [6.6205301556674989e-01, 6.5164618820930542e-01, 4.3969263836329142e-01], + [6.9790698254576800e-01, 6.8903984549626363e-01, 5.5380447400827748e-01], + [6.6660226829805325e-01, 6.5803028764940863e-01, 5.9796446305595818e-01], + [6.6235913235744659e-01, 6.5645560551739224e-01, 6.4399903482934051e-01], + [7.6949504665657342e-01, 7.8008851817337993e-02, 1.0998203564843434e-02], + [7.8481801867877998e-01, 2.1320591479804543e-02, 7.1068184601568623e-03], + [7.4467184495169580e-01, 1.5183059756662290e-01, 1.1168496230537383e-02], + [8.0344651346904006e-01, 9.2253865084123352e-02, 5.3493206207704459e-02], + [8.0571109474518521e-01, 4.7034346831197824e-02, 4.1068199048405106e-02], + [7.7355022857460165e-01, 1.8359289342691601e-01, 1.3327705080861732e-01], + [7.6797702508499388e-01, 1.6278763208408537e-01, 5.8864626457391427e-02], + [7.6330596873601786e-01, 1.2085047012333096e-01, 1.1144313557770900e-01], + [8.1421144606445439e-01, 2.1545171694400522e-01, 1.0984364845722336e-02], + [7.5633689530793013e-01, 2.6028603187013694e-01, 1.4369562388778553e-01], + [7.5807507733162083e-01, 2.1351498038465422e-01, 2.0291140257469811e-01], + [7.6887013130561843e-01, 3.0012915521071903e-01, 8.8862412429574142e-03], + [7.6271101466644187e-01, 2.5423246187167453e-01, 5.4563299079408135e-02], + [7.8428512005502260e-01, 3.2366931953929690e-01, 1.2273153079950948e-01], + [7.7895454557037491e-01, 3.4147148508296904e-01, 2.1867682800465266e-01], + [7.8071972329037220e-01, 3.0078205466887925e-01, 2.4871952833185121e-01], + [7.9068097467559073e-01, 3.4631203599205956e-01, 3.3632255753561641e-01], + [8.0317070743636743e-01, 3.5958723815756399e-01, 4.7394158315008858e-02], + [7.7456461070624871e-01, 4.4083109556200939e-01, 2.2021347404193159e-01], + [7.9261742882907749e-01, 4.3670249908709069e-01, 3.8491915266479287e-01], + [7.9124413994906040e-01, 4.8108896390358730e-01, 4.7086587120016948e-01], + [7.6927812147483765e-01, 4.3019539959624781e-01, 1.0242881503314809e-02], + [8.1250720679033595e-01, 4.8657795629594014e-01, 5.3007613287490589e-02], + [7.8457342758608839e-01, 4.3792155499682950e-01, 1.2175154821225373e-01], + [7.8007698557689897e-01, 4.3962085925284777e-01, 3.1515459362559667e-01], + [7.7146122775949211e-01, 5.5544307989709596e-01, 9.4311661683483712e-03], + [8.2427439442900630e-01, 6.2647307014089837e-01, 4.9722216467555647e-02], + [7.8827929550731324e-01, 5.5750188180805194e-01, 1.2289163645352782e-01], + [7.7481187421849307e-01, 5.5167293334110679e-01, 2.1705780422069906e-01], + [7.8081147813220342e-01, 5.6006714268491287e-01, 3.3610910219481543e-01], + [7.9587217043249492e-01, 5.5371685712655372e-01, 4.2775023855243510e-01], + [7.9648474348139942e-01, 5.6225506493003219e-01, 5.0766073511026333e-01], + [7.8929690545402198e-01, 6.0017054611150522e-01, 5.8924899013994791e-01], + [7.9054846110277877e-01, 6.7602472323308771e-01, 1.1577980360672055e-02], + [7.9087081221958710e-01, 6.8681856659001250e-01, 6.2132742174838794e-02], + [7.8206632589716563e-01, 6.4573956196429838e-01, 1.1370897409982057e-01], + [7.8336713576122530e-01, 6.6159737911328254e-01, 2.0062038971444096e-01], + [7.8654149158146847e-01, 6.5875882506704719e-01, 3.1359132515062349e-01], + [7.9428226551976455e-01, 6.7717666221769002e-01, 4.4276544115781080e-01], + [7.8611152095933823e-01, 6.3574056401671442e-01, 5.0329660381239061e-01], + [7.8921280997801491e-01, 6.6399125897641442e-01, 6.0674995821924227e-01], + [7.7885407855621414e-01, 6.8289398820364000e-01, 6.7163650196917590e-01], + [7.8202503149495373e-01, 7.3883662118870552e-01, 7.4313294833304603e-03], + [7.0416682022570276e-01, 6.5750334745564587e-01, 5.3108560659027845e-02], + [7.8722994073154762e-01, 7.3385093777796606e-01, 1.3217854944122884e-01], + [7.7678825684178066e-01, 7.2588323394299181e-01, 2.4996795823288093e-01], + [8.0293891489144931e-01, 7.4855398551397134e-01, 3.7898377451673199e-01], + [7.8241987745856523e-01, 7.3968290225271272e-01, 4.9093561210524989e-01], + [7.8728506106566021e-01, 7.2214048088633986e-01, 5.8463415200687019e-01], + [7.7794129782159327e-01, 7.3069755741356812e-01, 6.7388162193413037e-01], + [7.7435886739624027e-01, 7.6336855167725171e-01, 7.5141587648260955e-01], + [7.7316245855826049e-01, 7.6535214022820797e-01, 1.4100908680884991e-02], + [7.7383988703096052e-01, 7.6375922766608351e-01, 7.2980946138470129e-02], + [7.7107542693169995e-01, 7.6085098877359814e-01, 1.6280403430841925e-01], + [7.8529741662805230e-01, 7.7604568319639922e-01, 2.8529567972238618e-01], + [7.9490420963498942e-01, 7.8420772001784589e-01, 4.0582260038264339e-01], + [7.8120895620771391e-01, 7.7424685997750298e-01, 5.1532191571975194e-01], + [7.8691243627142848e-01, 7.7347470772156202e-01, 6.2962457560624507e-01], + [7.9431773718953358e-01, 7.8633051314818059e-01, 7.2567880054433720e-01], + [8.6706953210245918e-01, 6.0778693462006962e-02, 1.1664316708523831e-02], + [8.8596737416475690e-01, 1.4487351660083405e-02, 7.2558186105768129e-03], + [8.6831983014439806e-01, 1.3691051093618581e-01, 6.9363257911156592e-03], + [8.9428352947029821e-01, 1.2227377427732102e-01, 6.1033294618676501e-02], + [9.0456414259537543e-01, 5.8466248654583511e-02, 4.5962316987087956e-02], + [8.7913880782665066e-01, 1.7580965291760392e-01, 1.2780174457571572e-01], + [8.7078259525716639e-01, 1.6363565620885656e-01, 4.1635503682670195e-02], + [8.6922950308738178e-01, 1.2650355740208233e-01, 1.1727459518879550e-01], + [8.7681820764693308e-01, 2.7212855689721344e-01, 9.2852118202618097e-03], + [8.6261395854790079e-01, 2.5767667130018346e-01, 4.9660212225410456e-02], + [8.5706058064188262e-01, 2.3848956562927243e-01, 1.1964241358965316e-01], + [8.7759486783828655e-01, 2.7305331867075000e-01, 2.2324777361152334e-01], + [8.5295949294981743e-01, 2.2923664724799708e-01, 2.2002695998355376e-01], + [8.6688814576844875e-01, 4.0001886132049680e-01, 9.1048078913568628e-03], + [9.0020488625544326e-01, 3.7609675140717042e-01, 4.6986785539000056e-02], + [8.8573133964943018e-01, 3.2192935783731341e-01, 1.1110799863743200e-01], + [8.7403499940159080e-01, 4.2142187874162329e-01, 2.0269636164460492e-01], + [8.7267818163607658e-01, 3.2864126375593317e-01, 2.0860265830478492e-01], + [8.7839837606066917e-01, 4.0516517389854118e-01, 3.5356872931619948e-01], + [8.8469390418209859e-01, 3.4625015466713199e-01, 3.3615122562842209e-01], + [8.7642581065347225e-01, 4.2437526023642003e-01, 1.1407323424800930e-01], + [8.7868269408522426e-01, 4.2550103395498540e-01, 3.0081117341856684e-01], + [8.8548608617439573e-01, 4.7983058390976241e-01, 4.7053268810079157e-01], + [8.7037796599137685e-01, 5.4437916494217498e-01, 1.0402600756932131e-02], + [8.9974960769108014e-01, 5.0374124580179658e-01, 5.0395312175854884e-02], + [8.8551389680749470e-01, 5.4855611045547803e-01, 1.2295789784411967e-01], + [8.7393807528898659e-01, 5.5184573452590080e-01, 2.1549370248740871e-01], + [8.7535368699931615e-01, 5.3959012618691671e-01, 3.2087440539144818e-01], + [8.8219779206659976e-01, 5.3849375482961015e-01, 4.2014300045717307e-01], + [8.8529778438643247e-01, 5.5025760500078080e-01, 5.0295265868394567e-01], + [8.7944803980254260e-01, 6.7347702033322654e-01, 9.6545493896676039e-03], + [8.9094903997011832e-01, 6.2565748592508219e-01, 5.3594830025834185e-02], + [8.8004711313427175e-01, 6.4974914281641782e-01, 1.2774894488580071e-01], + [8.7422248865633612e-01, 6.7639307445114472e-01, 2.2380026927039387e-01], + [8.7577206336210645e-01, 6.6040812011854588e-01, 3.2989101508292873e-01], + [8.7991333317754639e-01, 6.5782866275962804e-01, 4.4406929305376536e-01], + [8.8825224771577382e-01, 6.6125736298497895e-01, 5.4437007019594508e-01], + [8.8234627082490480e-01, 6.7371433825066784e-01, 6.2751147913806171e-01], + [8.8492085557148414e-01, 6.1992479189402361e-01, 6.1164722350445588e-01], + [8.9346605046069372e-01, 7.7565845268626632e-01, 5.2489901669854049e-03], + [8.8749494487625169e-01, 7.6678175527563130e-01, 4.2023421188221124e-02], + [8.8788513442537265e-01, 7.4785330592582988e-01, 1.0933838785531429e-01], + [8.7674352785389387e-01, 7.6695531833043351e-01, 1.8437844132767403e-01], + [8.8205781097542701e-01, 7.7879203884250103e-01, 3.0649764269018065e-01], + [8.8649705378242882e-01, 7.6218270475988414e-01, 4.1764664579138533e-01], + [8.8649519161419787e-01, 7.5635525227371092e-01, 5.3035733303422472e-01], + [8.7840853417841869e-01, 7.5443807558083176e-01, 6.2957427858596304e-01], + [8.8431371106831991e-01, 7.6522592876393614e-01, 7.1398979927931128e-01], + [8.8029224085455027e-01, 7.4341289631531482e-01, 7.3460868540268287e-01], + [8.8225831432825164e-01, 8.2653667593407509e-01, 9.7346834548356700e-03], + [8.2407115768296346e-01, 7.7768808676118162e-01, 4.4786229400304745e-02], + [8.8573091452246377e-01, 8.2993165788895262e-01, 1.0508445388162281e-01], + [8.7000251752324442e-01, 8.2579529744190383e-01, 2.2175147941616763e-01], + [8.8094222164414981e-01, 8.4570613622724911e-01, 3.5416833388646080e-01], + [8.8198086219835292e-01, 8.2654155849945010e-01, 4.8826379385539032e-01], + [8.7729503683317411e-01, 8.2242048130277823e-01, 6.0507436788265878e-01], + [8.7906311176740581e-01, 8.3378641822757427e-01, 7.2476179762649140e-01], + [8.8071630927340860e-01, 8.1718819021420752e-01, 7.7389375770209445e-01], + [8.2967490436777547e-01, 7.8145902107377219e-01, 7.7056107892782133e-01], + [8.8051229248795337e-01, 8.6927594382494622e-01, 6.1916001701150924e-03], + [8.7594043133642385e-01, 8.6704459311777005e-01, 4.6108556581623224e-02], + [8.7490654217524388e-01, 8.6409294840681938e-01, 1.2875399108380678e-01], + [8.7318176926527147e-01, 8.6533379001317789e-01, 2.4192493468255483e-01], + [8.8672062583019307e-01, 8.8234812947674757e-01, 3.8581832561149854e-01], + [8.8662589121311441e-01, 8.7542644017691307e-01, 5.1760522651422469e-01], + [8.7149006502735216e-01, 8.6070074301198207e-01, 6.3357276175204902e-01], + [8.8304696910450109e-01, 8.7598736249903841e-01, 7.5183432738122835e-01], + [8.8683209049659895e-01, 8.7301334287194254e-01, 8.3077241427308035e-01], + [8.7677733103524957e-01, 8.6968000428819758e-01, 8.6267450474663154e-01], + [9.3591889589892296e-01, 5.9103262550494792e-02, 1.0934584955858558e-02], + [9.6207272898098262e-01, 2.0137999933765426e-02, 8.7107968196873508e-03], + [9.4842249235722165e-01, 1.2025303380904817e-01, 1.1377489178066337e-02], + [9.6051907702215622e-01, 1.0619662858921844e-01, 5.7459743383413725e-02], + [9.6758399306809706e-01, 4.8186555035933130e-02, 4.2594066830898419e-02], + [9.5110811064501277e-01, 1.9707593797818088e-01, 1.4370205503755748e-01], + [9.5093590026635022e-01, 1.8011351420284150e-01, 5.3934154322118469e-02], + [9.4974796294896136e-01, 1.2227840328487165e-01, 1.1218263701431039e-01], + [9.4146080862663128e-01, 2.1621825886341961e-01, 8.6333481289145447e-03], + [9.3910640207257923e-01, 2.5962693707510071e-01, 4.5395689450192733e-02], + [9.3921178364319413e-01, 2.4797607187190590e-01, 1.1984429808691012e-01], + [9.5619957430062019e-01, 2.6899405655453756e-01, 2.2137223532319270e-01], + [9.3677579175961567e-01, 2.2319754586828094e-01, 2.1307999331307151e-01], + [9.4798496693215406e-01, 3.5571669247301241e-01, 9.1014697990420661e-03], + [9.6347394518683416e-01, 3.6061141298424548e-01, 5.0265239148014930e-02], + [9.5371004360440159e-01, 3.5262305377988401e-01, 1.1709487776897462e-01], + [9.4752787741457523e-01, 4.0011115428258837e-01, 2.0925615913255832e-01], + [9.4564359206561222e-01, 3.3188386880365833e-01, 2.2446223363104462e-01], + [9.4850021473866886e-01, 3.5308457579377811e-01, 3.0648533293059504e-01], + [9.5152956727484506e-01, 3.4432760150848529e-01, 3.3627656705006204e-01], + [9.4254666747607307e-01, 4.9518178133849750e-01, 9.8479781376411349e-03], + [9.6382250908091005e-01, 4.8463886039833953e-01, 5.0596572453726292e-02], + [9.4820980874009120e-01, 4.7600486293349498e-01, 1.2031629311569636e-01], + [9.4527996838135320e-01, 5.1018683310995971e-01, 2.1365346301272528e-01], + [9.4949737066772621e-01, 5.2855902228823370e-01, 3.1905185348800019e-01], + [9.5017386642843138e-01, 4.6843243607020224e-01, 3.5083279895230030e-01], + [9.4862056131319683e-01, 4.9246327127101308e-01, 4.4194389717089327e-01], + [9.5347734252754934e-01, 4.6728717620024657e-01, 4.5670846967054302e-01], + [9.4746542427763869e-01, 6.3353991750945926e-01, 8.7446536540154128e-03], + [9.5657269993260940e-01, 6.2006497173494990e-01, 4.6543518087528310e-02], + [9.5309411629073981e-01, 6.0692907980921484e-01, 1.1204186051111968e-01], + [9.5081266876993431e-01, 6.2252269126593118e-01, 2.0434790632796201e-01], + [9.4740804024659542e-01, 6.4456214007356627e-01, 3.2104320475236325e-01], + [9.4726673536893813e-01, 6.3770994215064247e-01, 4.3425199481098398e-01], + [9.5269084487105593e-01, 6.1007465530518068e-01, 4.9746853826512832e-01], + [9.5200101925283032e-01, 6.2960358419909357e-01, 5.8607510479697356e-01], + [9.5440015930586852e-01, 5.9738769817865800e-01, 5.8936831085364205e-01], + [9.5456420855234025e-01, 7.5252239502337115e-01, 9.3298291040783878e-03], + [9.4152414734891787e-01, 7.3208139462717303e-01, 4.7582606129882576e-02], + [9.4504527889676326e-01, 7.1949980273677816e-01, 2.2395969750625017e-01], + [9.4775972373462880e-01, 7.5646679558124064e-01, 3.4523376890993784e-01], + [9.5115134718829042e-01, 7.4564725639099538e-01, 4.6635628320304656e-01], + [9.5381757968540537e-01, 7.4884721151041800e-01, 5.8694889960362540e-01], + [9.5150383630770219e-01, 7.3283486947284115e-01, 6.5852093892327579e-01], + [9.4962428381206676e-01, 7.2793395879444145e-01, 7.1518555704462561e-01], + [9.5885033862050018e-01, 8.4725650822250798e-01, 1.3755630982804055e-02], + [9.5588624045191928e-01, 8.4148811857967931e-01, 6.8295818866815503e-02], + [9.5128319069742373e-01, 7.2798212657839467e-01, 1.2256272284524643e-01], + [9.4782469548892423e-01, 8.1684806500137785e-01, 2.3709481320574216e-01], + [9.5613640850110637e-01, 8.4260924540622784e-01, 3.7012707532585998e-01], + [9.5328747215572318e-01, 8.4059330659214870e-01, 4.9815094275396038e-01], + [9.5002137806136766e-01, 8.3969680048524198e-01, 6.1897026250450993e-01], + [9.4910628826776966e-01, 8.4155140580835464e-01, 7.2820103933446090e-01], + [9.5086996908914911e-01, 8.1605874013850110e-01, 7.7368900268366303e-01], + [9.5526589480080515e-01, 8.3478495264633579e-01, 8.2915940177287006e-01], + [9.5846376136598610e-01, 9.1500767644958092e-01, 3.3478803258469567e-03], + [9.2924092233698052e-01, 8.8305775975052070e-01, 4.0357971525395539e-02], + [9.5612125068346787e-01, 8.3535264129270292e-01, 1.4478472607776760e-01], + [9.3957688650686078e-01, 8.8792504260323812e-01, 2.0131585360706256e-01], + [9.5173914331422738e-01, 8.9663775619680974e-01, 3.1270901836363729e-01], + [9.4300012951624701e-01, 8.9208424222347038e-01, 4.4883946654483597e-01], + [9.5231643103166919e-01, 9.0834531570597954e-01, 6.0143939006582847e-01], + [9.4420482142458628e-01, 9.0192673977253945e-01, 7.2374821179334670e-01], + [9.5484972474469454e-01, 9.1267421860327547e-01, 8.3416454321389288e-01], + [9.4844941826083906e-01, 8.8934681181484487e-01, 8.6249517030675404e-01], + [9.1159703428972894e-01, 8.6076877889270165e-01, 8.5606785519676742e-01], + [9.5341903647479354e-01, 9.4522320131686988e-01, 1.6771524294899524e-02], + [9.4904451072122176e-01, 9.3966930050660669e-01, 8.2078904651894075e-02], + [9.5698735468762841e-01, 9.0882051821473142e-01, 1.1432017880566897e-01], + [9.4838837828574374e-01, 9.3879163348662997e-01, 1.8548412404687645e-01], + [9.4742655511716778e-01, 9.3659142125259920e-01, 3.0880740414716168e-01], + [9.5524235848234396e-01, 9.4387908630004314e-01, 4.4524584374216253e-01], + [9.5609154766070281e-01, 9.4803668373862193e-01, 5.6304659944999302e-01], + [9.4364823772141382e-01, 9.3615076313508738e-01, 6.8183451924794658e-01], + [9.5463997442166193e-01, 9.4571906965597441e-01, 7.9939074717049041e-01], + [9.4698337128612042e-01, 9.4065671844887166e-01, 8.7581875292691092e-01], + [9.5590638335589440e-01, 9.4398997369568338e-01, 9.3197401101487765e-01], + [9.8853598750975591e-01, 6.0466979224627210e-02, 1.0663510059544119e-02], + [9.9910009981712244e-01, 9.4832066174296344e-03, 4.6849861249654975e-03], + [9.8998758488111138e-01, 1.3818545619361627e-01, 4.8881710430271081e-03], + [9.9334538742638867e-01, 1.2887589408044819e-01, 5.0718861204985401e-02], + [9.9346804116935594e-01, 6.1261876237343228e-02, 4.5907461516207060e-02], + [9.9151516063046230e-01, 1.6622155304136194e-01, 1.1821210355246654e-01], + [9.8953487224930825e-01, 1.7579099968069586e-01, 3.3915709160173987e-02], + [9.9050304555406965e-01, 1.2938314894263592e-01, 1.2062363901815222e-01], + [9.8827544943493484e-01, 2.5797513113179382e-01, 1.1293206213503795e-02], + [9.8974518323449523e-01, 2.7479700971195403e-01, 6.0772944553141930e-02], + [9.8827076835079164e-01, 2.4312323512657663e-01, 1.1788081872653930e-01], + [9.9114945039508329e-01, 2.7561264725138607e-01, 2.1897963255095385e-01], + [9.8785201113822274e-01, 2.3308557583390108e-01, 2.2241484609619108e-01], + [9.9085225234506724e-01, 3.8730510324922179e-01, 1.0510364678761257e-02], + [9.9426680716068305e-01, 3.9421998683580128e-01, 5.5367921330846448e-02], + [9.9146104390339074e-01, 3.6946377233708028e-01, 1.3645401780119543e-01], + [9.8936965609247496e-01, 4.4900028791487823e-01, 2.2293550808452028e-01], + [9.8929843603725998e-01, 3.4938640640439128e-01, 2.1644189892689419e-01], + [9.9033159708660734e-01, 3.7861354689575172e-01, 3.4145607294131292e-01], + [9.9093335516572856e-01, 3.5668260555097653e-01, 3.5032836056613481e-01], + [9.8833893194111733e-01, 5.1515158965883856e-01, 9.9398227284977197e-03], + [9.9403037209870304e-01, 5.3561470185321647e-01, 5.1799518867392327e-02], + [9.9004222299691491e-01, 4.8623661007848790e-01, 1.2709319752370896e-01], + [9.8919278334898941e-01, 5.4735739023250940e-01, 2.2734632082592182e-01], + [9.9092186563942031e-01, 5.1677628916779694e-01, 3.2126213976103857e-01], + [9.8992376457580089e-01, 4.3529562559397128e-01, 3.3448867985238057e-01], + [9.8988793654592033e-01, 5.0630790359532607e-01, 4.6094192304740250e-01], + [9.9125752238540710e-01, 5.0142526072834537e-01, 4.9310417462797157e-01], + [9.9022189594346477e-01, 6.4353227896092402e-01, 8.2501958554768353e-03], + [9.9110984518359591e-01, 6.6179216073159453e-01, 4.6338141363933792e-02], + [9.9033776423427600e-01, 6.1060919778783196e-01, 1.2082787082399435e-01], + [9.9094679250882289e-01, 6.5436385829893440e-01, 2.1568583495966542e-01], + [9.9005180012663196e-01, 6.5193231763565707e-01, 3.3908778960617536e-01], + [9.8933116039204583e-01, 6.3868188140632398e-01, 4.3406477836330914e-01], + [9.9102335344733028e-01, 5.7696953775208448e-01, 4.6297886049400211e-01], + [9.9043435590450046e-01, 6.3512622863782275e-01, 5.8639953010129542e-01], + [9.9195223814493982e-01, 6.4070014113165252e-01, 6.3091921152072972e-01], + [9.9104380240532908e-01, 7.6804459391551894e-01, 1.2655012914414985e-02], + [9.8749203733156576e-01, 7.7851425382571193e-01, 6.1265380984528113e-02], + [9.9195897023447754e-01, 7.2463568639981291e-01, 1.1457627815845838e-01], + [9.8915249713700393e-01, 7.6967649794687942e-01, 1.9443320346095372e-01], + [9.8942978904324774e-01, 7.5948886322069886e-01, 3.0504080660000066e-01], + [9.9023747179818900e-01, 7.7455110657369186e-01, 4.3079866993856875e-01], + [9.9104489754656722e-01, 7.5141649784656350e-01, 5.2818241368483021e-01], + [9.9099669362712528e-01, 7.1969009885282775e-01, 6.0197575110666091e-01], + [9.9069456647297582e-01, 7.6671196588552437e-01, 7.2328734333987377e-01], + [9.8908125869322960e-01, 7.5288902377290834e-01, 7.4654020815840971e-01], + [9.9358722148309708e-01, 8.7370732095655756e-01, 3.6766674228371249e-03], + [9.9264682780516900e-01, 8.7354313240831905e-01, 4.2324965204703809e-02], + [9.9236475361191512e-01, 8.5624632823627012e-01, 1.3032166260117620e-01], + [9.9053826617807883e-01, 8.6936644923007111e-01, 2.4332989431242708e-01], + [9.9048241532203751e-01, 8.5726759913015727e-01, 3.2153072741270128e-01], + [9.9224365906365508e-01, 8.7281126545920296e-01, 4.2398893518531861e-01], + [9.9028208990430167e-01, 8.6738474558587531e-01, 5.4271358792171576e-01], + [9.9090094539116080e-01, 8.4962876334307902e-01, 6.3793762558800204e-01], + [9.9063285956830294e-01, 8.3975250479305075e-01, 7.2662290993143441e-01], + [9.8938434456677360e-01, 8.7751138207447144e-01, 8.3249128477079848e-01], + [9.9291444447760768e-01, 8.5821869685727892e-01, 8.4891369172631925e-01], + [9.9407514952561615e-01, 9.4762438589125253e-01, 1.2213864911325359e-02], + [9.8211014341661373e-01, 9.3408156130977260e-01, 4.0868494230308569e-02], + [9.9286389628754690e-01, 9.3643485605086718e-01, 1.0174382307724848e-01], + [9.8868154045382017e-01, 9.3425339466889279e-01, 2.0937254229059185e-01], + [9.9105607214458813e-01, 9.4096228014076500e-01, 3.5079209856455718e-01], + [9.8822864190377380e-01, 9.3910738210205769e-01, 4.9133419663647643e-01], + [9.9215362838080490e-01, 9.3880704230892187e-01, 6.2512798344682974e-01], + [9.8852668645397734e-01, 9.2752565596302328e-01, 7.3362599047616284e-01], + [9.9133007831113606e-01, 9.3025126693670945e-01, 8.2658894785091486e-01], + [9.9244846654758645e-01, 9.4996608162147755e-01, 9.1253896793187961e-01], + [9.8681981353524406e-01, 9.3437301890352320e-01, 9.2814116957972315e-01], + [9.9224869165427010e-01, 9.8459288814088353e-01, 7.0887113185111057e-03], + [9.9086497428764642e-01, 9.8075773392647925e-01, 5.3929241801607880e-02], + [9.9035512628452083e-01, 9.7981528136635709e-01, 1.4288763724882739e-01], + [9.9004707352916121e-01, 9.7942530178653053e-01, 2.6437577137343365e-01], + [9.8986961860373257e-01, 9.8129441886860280e-01, 3.9968626389392581e-01], + [9.9439950283830358e-01, 9.8462398459390876e-01, 5.2432892308759860e-01], + [9.8969820447811041e-01, 9.7990839935638785e-01, 6.2988499395590936e-01], + [9.8965580859472513e-01, 9.7809399593818303e-01, 7.4448810982755753e-01], + [9.9163580512718352e-01, 9.7964097784759008e-01, 8.5470228238616652e-01], + [9.8862682881377906e-01, 9.7973489088685040e-01, 9.3117960230829477e-01], + [9.9525886868363334e-01, 9.8818498810471767e-01, 9.8173721333602082e-01], + ], + weights: [ + 3.6627731953110264e-05, + 1.3813350481119836e-05, + 4.1060712543472057e-05, + 6.3157431037203236e-05, + 5.4522046358802753e-05, + 1.0988358963821658e-04, + 5.0988204366063325e-05, + 8.7843747355704740e-05, + 4.2853149357286444e-05, + 1.1851226781839570e-04, + 9.6899074322087488e-05, + 1.3160532320345038e-04, + 2.3296062956315187e-04, + 2.0198801476043561e-04, + 1.3513196297130727e-04, + 1.1385043949989662e-04, + 2.4476033489085155e-04, + 2.2668383457624566e-04, + 8.2148089533865985e-05, + 7.1769190046831852e-05, + 4.0979711098614883e-05, + 2.2533090820369539e-04, + 9.1860216621301793e-05, + 1.1792864089854519e-04, + 3.2783098401980020e-04, + 7.7201548901777148e-05, + 2.3482550066836517e-04, + 1.8801574875190781e-04, + 4.1673448084100377e-04, + 4.1943184205054355e-04, + 2.2466647448751457e-04, + 2.6750862083403524e-04, + 3.7985074608340251e-04, + 5.8746339853108518e-04, + 1.6647959719532594e-04, + 4.6694941999237213e-04, + 6.2856714253495293e-04, + 1.6330466971486884e-04, + 4.3389886774783734e-04, + 2.8596530005401343e-04, + 3.9289457806718919e-04, + 9.1514524963193011e-05, + 2.0284889340011451e-04, + 2.4453139910459045e-04, + 2.0123162939730390e-04, + 2.1808284791009490e-04, + 8.4050216150637953e-05, + 1.4685969569821743e-04, + 3.4954364680993147e-04, + 1.4206459424449022e-04, + 1.7970709102366460e-04, + 2.9618290681978439e-04, + 3.1902264657609461e-04, + 5.7297480681267512e-04, + 5.6603779354751007e-04, + 2.9715467656436274e-04, + 4.9584118509940847e-04, + 6.5568345097968763e-04, + 7.0461185853572315e-04, + 2.3894338124948603e-04, + 1.7910217750054103e-04, + 4.7485489522379346e-04, + 6.5308443049170235e-04, + 6.4513882875095942e-04, + 4.0455411371000268e-04, + 2.4185991200161250e-04, + 2.8134988663897341e-04, + 1.7631884493880500e-04, + 3.7049315117825167e-04, + 5.5201809615505911e-04, + 5.2396278010541884e-04, + 3.6961271392398841e-04, + 8.5925711691391951e-05, + 6.2550996278978413e-05, + 2.4820766698043379e-04, + 2.2161330020651723e-04, + 1.3083426900092996e-04, + 7.5871344473228634e-05, + 1.5039122038039613e-04, + 2.9458943805564763e-04, + 8.4352040527360000e-05, + 1.7748529692123105e-04, + 5.4751591053938092e-04, + 5.3144887191403263e-04, + 3.1198854789287557e-04, + 7.9125362070494405e-04, + 6.7780210249140168e-04, + 3.2393040953987857e-04, + 9.4931245968212310e-04, + 3.4587439861047593e-04, + 3.4278450793459266e-04, + 7.4127318476415816e-04, + 1.0022526011498251e-03, + 6.8963831008395755e-04, + 1.5104710671266200e-04, + 4.3258361652015123e-04, + 1.0125279555645581e-03, + 5.9625774914348552e-04, + 2.6174053548172921e-04, + 2.1106696964804151e-04, + 8.6063411456901171e-04, + 7.1186582857527458e-04, + 8.3866645581433404e-04, + 4.6124866733948861e-04, + 6.4163028558407789e-04, + 5.2975999683768692e-04, + 1.1435445781179547e-04, + 6.3117310686181276e-05, + 1.9439916976641950e-04, + 2.8956884128063091e-04, + 6.5738727216016805e-04, + 2.6514732447997482e-04, + 3.2280277874358347e-04, + 2.7627454664285578e-04, + 3.5711686362405117e-04, + 2.0259990886773867e-04, + 7.9139486512662068e-05, + 8.8216028138436633e-05, + 1.7004759283536049e-04, + 3.3299015313610712e-04, + 9.4490795732599711e-05, + 4.4454362803141663e-04, + 4.8472195661526234e-04, + 2.5487871005957830e-04, + 2.3075189565268419e-04, + 2.5826238438379274e-04, + 2.7356211370267795e-04, + 6.8642381385043282e-04, + 6.9718583056048694e-04, + 7.1838315200900929e-04, + 3.3868763503376218e-04, + 8.8373474289711335e-04, + 1.1512873692607300e-03, + 1.1904849026173122e-03, + 7.9424293589239366e-04, + 3.4493239315466404e-04, + 3.4264042997219024e-04, + 1.1180857632027625e-03, + 1.3109815231944640e-03, + 1.1798570616042307e-03, + 7.5167865446008637e-04, + 3.2352076400483606e-04, + 1.3261416633577044e-04, + 7.5100728085612605e-04, + 1.1810090936528665e-03, + 1.1587706322240905e-03, + 2.5052685905103353e-04, + 5.6037668087074305e-04, + 8.8194398905954547e-04, + 8.7482332369551315e-04, + 5.9667890875517268e-04, + 1.3082111776512793e-04, + 1.9404760393466981e-04, + 1.4438434226369716e-05, + 5.3278874596096885e-04, + 6.0945462463048974e-04, + 8.3701696899609050e-04, + 6.4828321695850730e-04, + 5.7281485646739998e-04, + 2.9291147861304273e-04, + 7.4622256464837179e-05, + 1.4735294085906829e-04, + 3.0690349220688666e-04, + 3.0604468452046640e-04, + 3.8417570655509086e-04, + 3.2310831454917621e-04, + 1.8695664353406285e-04, + 1.9109552338057853e-04, + 5.7589335427703144e-05, + 2.1588880727848583e-04, + 6.9477788876077159e-05, + 2.9445599072568926e-04, + 3.3094410589305529e-04, + 1.0424979238136752e-04, + 6.4750984772626375e-04, + 6.3557818174037875e-04, + 2.2589899011716868e-04, + 1.9637061761077724e-04, + 6.1260445446643399e-04, + 2.7749151140782921e-04, + 2.9030414156312436e-04, + 6.5678941108893358e-04, + 9.1411563636937219e-04, + 7.5543081001654578e-04, + 7.8225557455539191e-04, + 3.5885750533333515e-04, + 6.9295311165351277e-04, + 1.3128641818495946e-03, + 7.4992097336932021e-04, + 3.5724161093214678e-04, + 3.7102045185104460e-04, + 7.0879061544255417e-04, + 1.1131454667218101e-03, + 1.0350534347843758e-03, + 3.2836543960004496e-04, + 5.7931645369481703e-04, + 9.6091851343146266e-04, + 1.2763421779994782e-03, + 1.3205735761891268e-03, + 8.5616039807830666e-04, + 6.6792417730745677e-04, + 3.2337903729629170e-04, + 2.9272232600311581e-04, + 4.5619509325654171e-04, + 6.7125712497201025e-04, + 9.5533458391008767e-04, + 1.1181413169397193e-03, + 1.0495143425906829e-03, + 8.2266078281456861e-04, + 6.3366421948023405e-04, + 2.1584751915740472e-04, + 1.1452145018355861e-04, + 3.9404389533671190e-04, + 6.5805431005285105e-04, + 7.4513381010654417e-04, + 6.9215069712595136e-04, + 6.2855023182980239e-04, + 6.9276789423871981e-04, + 4.1839770414073930e-04, + 9.0681949058785249e-05, + 8.7429845042745217e-05, + 2.2008770361077950e-04, + 3.0903305882550871e-04, + 3.0166530769229781e-04, + 3.2760331754502526e-04, + 2.4460034395735466e-04, + 3.1575459271867884e-04, + 1.6271188061136742e-04, + 1.4674789765998952e-04, + 3.8225846855809437e-05, + 1.7813009066235257e-04, + 3.0327979597254048e-04, + 1.4261528451406413e-04, + 3.9067829367228228e-04, + 3.9357984580405995e-04, + 2.0431917905280633e-04, + 2.3340086896041760e-04, + 4.8674315534698320e-04, + 6.9037750576674993e-04, + 5.6982510343981199e-04, + 2.4740101196692177e-04, + 2.9995708462681700e-04, + 5.3447597743663409e-04, + 6.1251896741458241e-04, + 1.0104263215598170e-03, + 6.3681592095710831e-04, + 6.6052628343516643e-04, + 2.5947179319746951e-04, + 7.7311640717769642e-04, + 7.6195310375661322e-04, + 2.7888746344105779e-04, + 3.1760982556367634e-04, + 5.4866236198355359e-04, + 7.8071861870843454e-04, + 1.1776032599789983e-03, + 1.1505147604597486e-03, + 8.5917047955880209e-04, + 5.9282113275884189e-04, + 2.4426820315414001e-04, + 4.8894153581990701e-04, + 7.2065358566198432e-04, + 9.9134557250015343e-04, + 1.0908083024846313e-03, + 9.6811385524671475e-04, + 7.5881102957338863e-04, + 4.8856337296904900e-04, + 2.4211071499176016e-04, + 9.7394381218531998e-05, + 4.1368605617296908e-04, + 5.9685914224139936e-04, + 6.4992007126367824e-04, + 9.0781471928684325e-04, + 8.1271146989413871e-04, + 7.1970703416652506e-04, + 7.0259916557106677e-04, + 3.1938972201038545e-04, + 2.1725416162299242e-04, + 1.1984202244678156e-04, + 3.2117544375662589e-04, + 4.7172356584548392e-04, + 5.2246887106194165e-04, + 5.3950692431919587e-04, + 5.9810627123512458e-04, + 5.9981377081386373e-04, + 4.3204387306329777e-04, + 2.4586504036789843e-04, + 1.4407617831694856e-04, + 4.8610019429001604e-05, + 1.3032392928342119e-04, + 2.4580526285270790e-04, + 2.2626244132187799e-04, + 1.5913800393461817e-04, + 3.2034155269391627e-04, + 2.6562516667787318e-04, + 1.6129561649083862e-04, + 1.5142038233093970e-04, + 3.7390540857959283e-05, + 8.7727168758466262e-05, + 3.5133384794595639e-05, + 1.3649037848356655e-04, + 2.0577540856901624e-04, + 3.6953760915517651e-05, + 3.2346095569007013e-04, + 2.7696949947090231e-04, + 1.3322903966386034e-04, + 1.6727919453890866e-04, + 3.3681463127781708e-04, + 4.9499358597782279e-04, + 1.4006088320458719e-04, + 2.0404453638331579e-04, + 2.0183133973418967e-04, + 2.8971096963805961e-04, + 5.2192753931023085e-04, + 5.8466359109748305e-04, + 4.9866825746479435e-04, + 3.8652096398370705e-04, + 1.4214779848422686e-04, + 2.1804931286774427e-04, + 3.5561386434650242e-04, + 6.1373970498793042e-04, + 6.9341194238119576e-04, + 6.7563376214812404e-04, + 6.2445345413950223e-04, + 4.3123963343303197e-04, + 1.7306826874631103e-04, + 1.7198452940459224e-04, + 3.4887864821307350e-04, + 5.2570462446952057e-04, + 6.4176537901388709e-04, + 8.2707972914974740e-04, + 6.5694838917212863e-04, + 6.0089259244907364e-04, + 3.3711568830717355e-04, + 1.5464522977646177e-04, + 1.2860726811773885e-04, + 3.6985284539132373e-04, + 6.3261476269366895e-04, + 6.7032960590025304e-04, + 6.9784810134926475e-04, + 6.1063112891648083e-04, + 4.3195130995575456e-04, + 2.0449996336409590e-04, + 1.4533821231229070e-04, + 3.0496415810788823e-04, + 5.7788605843651670e-04, + 5.8171122411607916e-04, + 4.6355193038105755e-04, + 5.2213603179058067e-04, + 5.2748236160488125e-04, + 4.5093715694244764e-04, + 2.5531414598393049e-04, + 8.1599568632411220e-05, + 4.2077336721760952e-05, + 2.1735325072805395e-04, + 3.9286705860318093e-04, + 3.6623816028935152e-04, + 4.2021822978993966e-04, + 4.3422789556661786e-04, + 4.1267068830918739e-04, + 3.3242922293416117e-04, + 2.2154368682131441e-04, + 1.4352091867921241e-04, + 6.9873347909145720e-05, + 5.0606102970636063e-05, + 1.2056557015416090e-04, + 2.3740286291176651e-04, + 1.6823434471499598e-04, + 2.0933548317294846e-04, + 1.8529044515612053e-04, + 1.3279664622093987e-04, + 1.5030505827979382e-04, + 1.1373187355480332e-04, + 6.7344954287670597e-05, + 5.4180018078836550e-05, + 5.0689296157213176e-05, + 3.5403705309109065e-06, + 3.8931819428163795e-05, + 7.3580118543664790e-05, + 3.7767361697647493e-05, + 1.1027560904926919e-04, + 9.2691629341560254e-05, + 4.9798065333055379e-05, + 1.0562835420621952e-04, + 1.7889499598104731e-04, + 2.4499183514300191e-04, + 1.6449851662007187e-04, + 9.7674981522132722e-05, + 8.4484868040069335e-05, + 1.3366482193938954e-04, + 2.2413806591314932e-04, + 2.1521847743962607e-04, + 2.5831538566456274e-04, + 1.4306702598127738e-04, + 5.4340336394164408e-05, + 9.6141326277422614e-05, + 1.3218151606587424e-04, + 2.8738040900308851e-04, + 3.1517360532151999e-04, + 2.8359995643675824e-04, + 2.5244111393066948e-04, + 1.6503531275605801e-04, + 7.0907836657495933e-05, + 7.0096993661656823e-05, + 1.5172793109206133e-04, + 2.5218375223773907e-04, + 2.9525541214867941e-04, + 3.6832148103893727e-04, + 3.0658328161925708e-04, + 2.5842058126420987e-04, + 1.7383031329160401e-04, + 6.6804398843762737e-05, + 8.4134410713215043e-05, + 2.0172447058179908e-04, + 1.8139432348717553e-04, + 3.0141088868952356e-04, + 3.4989403286195470e-04, + 3.2661988989518645e-04, + 2.8223414246182616e-04, + 2.7299397309456198e-04, + 1.6595210599053498e-04, + 5.2521115671144206e-05, + 2.4286489127653932e-05, + 1.0336182608282411e-04, + 1.9787673349071368e-04, + 2.1153591560193878e-04, + 1.7346873060930301e-04, + 2.1018700340825398e-04, + 2.4310808778347438e-04, + 2.3740297367189661e-04, + 2.2226266993490137e-04, + 1.2045998297576720e-04, + 4.3749309878572520e-05, + 2.7032795688361389e-05, + 9.1087205892697535e-05, + 1.0940930100057830e-04, + 2.1310836199122548e-04, + 1.9684652041328013e-04, + 2.2774576055881581e-04, + 1.5953912264453926e-04, + 2.0559773796670585e-04, + 1.2012923715288839e-04, + 5.1134902705837633e-05, + 3.6879170987733457e-05, + 9.5075683070815046e-06, + 4.2363811085347805e-05, + 7.2472546964253573e-05, + 9.0921408319481043e-05, + 7.6702519154667049e-05, + 4.7449702335084858e-05, + 7.1580199207354445e-05, + 9.1631400596487491e-05, + 6.3475221501040489e-05, + 3.7980794663614418e-05, + 6.8526526263901886e-06, + ], + }, + 21: { + points: [ + [2.9353019938411256e-02, 1.6695843669939071e-02, 1.0978704249909330e-02], + [6.4705892286838276e-02, 6.2696099152863616e-02, 4.8236921669693747e-02], + [5.5550306719075154e-02, 4.3678499092716504e-02, 7.6391833586439764e-03], + [9.4984175670422930e-02, 6.2660557825509736e-02, 5.4947327753201297e-02], + [8.3122017903883266e-02, 1.9886607857334898e-02, 1.0481263078314170e-02], + [1.1350666307092830e-01, 4.9712542695698775e-02, 3.5388400198590477e-03], + [1.2070094849256398e-01, 7.8640846594837888e-02, 3.5201602075675950e-02], + [1.5381193734486867e-01, 1.0961446989826237e-01, 6.8367677586211664e-03], + [2.0335570754330795e-01, 1.4075027293048833e-01, 1.3179558138547232e-01], + [1.0894532502785453e-01, 1.0001985070294006e-01, 3.8876127530395779e-02], + [1.1948333932639998e-01, 1.1159134914187475e-01, 9.5401494461209834e-03], + [1.5172959216157966e-01, 1.4420578120533162e-01, 8.5904908374442326e-02], + [1.3247123439493161e-01, 1.2319062499464105e-01, 1.1404380823958961e-01], + [1.6963568117163866e-01, 1.9607373565044786e-02, 1.0401809584822778e-02], + [2.6933552935308847e-01, 5.8693366523420831e-02, 5.0660643268340202e-02], + [1.9943313364053161e-01, 5.4700833417082782e-02, 7.0753699853959946e-03], + [1.8772058437309261e-01, 8.4810544847637961e-02, 3.2744809427532211e-02], + [1.6584896496457713e-01, 6.3148081536975417e-02, 5.3713651861547279e-02], + [2.4277990341019648e-01, 1.2615061757862228e-01, 9.7431388374035425e-03], + [2.8996928438807207e-01, 1.7644755218246941e-01, 5.4340590854241949e-02], + [2.4110043527202296e-01, 1.4252971311505380e-01, 9.3230151919108994e-02], + [2.8252565802926732e-01, 1.3122405053468666e-01, 1.2172073505314072e-01], + [2.5273341046744874e-01, 2.0243276544903036e-01, 9.2881822044017671e-03], + [2.0728262099950440e-01, 1.5793808746530949e-01, 4.1206411104038630e-02], + [2.4070293476647944e-01, 1.9665303896780370e-01, 1.0558916765635629e-01], + [1.7282222276664530e-01, 1.3763996768425368e-01, 9.9862902476525725e-02], + [2.4882065657475824e-01, 2.4061619050979907e-01, 1.2135454897123399e-01], + [2.1377968757612351e-01, 2.0397350015771842e-01, 9.5919630710034221e-03], + [1.9881934353380365e-01, 1.8877577615237504e-01, 5.0363392744498164e-02], + [2.1382539143228313e-01, 2.0813023494100069e-01, 1.6541209703899290e-01], + [2.0440584323448921e-01, 1.8978106190727159e-01, 1.8355361956237345e-01], + [2.8554077465738825e-01, 1.9361602458447324e-02, 9.3972054667873541e-03], + [3.0126137040987139e-01, 6.1488692712404425e-02, 9.2173749229026991e-03], + [2.7409823693343160e-01, 8.8387387609066087e-02, 4.6734513959857268e-02], + [3.4531666173115583e-01, 1.4884972498800703e-01, 4.8921319603572599e-02], + [3.5884872240012267e-01, 1.6413216359209268e-01, 1.1612244905191223e-01], + [3.4574355531522644e-01, 1.3360503559610043e-01, 9.8346821779997148e-03], + [3.6531654773222877e-01, 2.5112632786623246e-01, 4.5159391047761059e-02], + [3.6475890380387177e-01, 2.3951050357178055e-01, 1.2463152614745530e-01], + [3.3052560186510216e-01, 2.2998960338455576e-01, 1.8412487422972587e-01], + [3.4032334218744725e-01, 2.2182538523526871e-01, 8.7553996662697153e-03], + [3.4873445037270839e-01, 2.2450620284466227e-01, 2.1605387153283115e-01], + [3.1962019543854975e-01, 2.7386222516072245e-01, 4.8492659566776092e-02], + [3.3086896066437238e-01, 2.7870119875054955e-01, 1.1086250825219297e-01], + [3.5840338673646271e-01, 3.0262104250455252e-01, 1.9533594307776883e-01], + [2.7677539885058944e-01, 2.4111062373764355e-01, 1.9672475357871988e-01], + [3.0516675403719123e-01, 2.5202405522141619e-01, 2.4300637265224906e-01], + [3.6805785504924338e-01, 3.1888126217741908e-01, 9.2075987469887547e-03], + [3.2634930221580621e-01, 3.1688496233615687e-01, 9.7367216185009087e-03], + [2.9666126350030447e-01, 2.8867086982585760e-01, 4.9845639806780480e-02], + [3.3771367224246623e-01, 3.2684805992157373e-01, 1.1883246166397292e-01], + [3.2720827348418119e-01, 3.1582869702487093e-01, 2.0756305345263584e-01], + [3.4194339871044688e-01, 3.3746037540483831e-01, 2.8529388644975529e-01], + [2.8429281312748500e-01, 2.7670672057506668e-01, 2.6548621406695105e-01], + [4.1903558471636643e-01, 1.9824046755443147e-02, 9.1308444611138111e-03], + [3.9625610447951032e-01, 5.4766445070637382e-02, 4.7066297621805531e-02], + [4.1802409826723202e-01, 6.2623516473003918e-02, 8.4166652394084465e-03], + [3.8852092458578513e-01, 8.8799827067505610e-02, 4.6711417858830213e-02], + [3.9132154338592157e-01, 1.2304639486086509e-01, 1.1410108731190099e-01], + [4.6438029016740306e-01, 1.3905570270647252e-01, 7.2582203086768576e-03], + [4.6544254951483233e-01, 1.4725868626014493e-01, 3.9375198995860360e-02], + [4.7096350826577454e-01, 1.5634021005480817e-01, 1.1318577021951490e-01], + [4.4491161461048834e-01, 2.3603417960351636e-01, 4.3389850047621914e-02], + [4.5487577162614512e-01, 2.0088243424397872e-01, 9.2140605764892469e-02], + [4.5240055069875040e-01, 2.6059169838090129e-01, 2.1630594227288386e-01], + [4.4525397526784133e-01, 2.3386482704427056e-01, 8.0777099353760949e-03], + [4.7459613799506484e-01, 2.8595584576971433e-01, 1.0399438279528583e-01], + [4.4032057961791798e-01, 2.1183648659338974e-01, 2.0316217109259357e-01], + [4.8376579797356184e-01, 2.6753823400696519e-01, 1.6279460030831602e-01], + [4.7041162438657885e-01, 3.2315601473544026e-01, 3.1444805871997045e-01], + [4.5877016113878510e-01, 3.4035687511622575e-01, 9.4191507987920337e-03], + [4.7134759386745695e-01, 3.5226072120009932e-01, 4.8553101595339536e-02], + [4.4602116452143059e-01, 3.3996297331425879e-01, 1.1346151635988773e-01], + [4.9714984445504018e-01, 3.8964846538260139e-01, 2.0076466153747813e-01], + [4.6511517686057069e-01, 3.3852342254255285e-01, 2.2961019153897763e-01], + [4.4284236797515125e-01, 3.4289543881253337e-01, 2.9666060964525903e-01], + [4.5222026102266782e-01, 4.0962713429610126e-01, 1.1811957031558847e-01], + [4.3116585942269614e-01, 3.6318726396296336e-01, 3.5382889651124150e-01], + [4.4633439372943312e-01, 3.9457120413385249e-01, 4.8199700561809208e-02], + [4.4663040594948500e-01, 4.0451764348741681e-01, 2.0571147568749149e-01], + [4.6390265694028532e-01, 4.1269726551851610e-01, 3.0418085278970169e-01], + [3.9524738034298773e-01, 3.6133429682812845e-01, 3.1650447848216523e-01], + [3.7863314028654593e-01, 3.5988828372118314e-01, 3.5428983733505154e-01], + [4.5245752909398057e-01, 4.4307129893834496e-01, 9.5440024858195957e-03], + [4.1352550345625227e-01, 4.0304364435067713e-01, 5.0400996655749174e-02], + [4.4048213892179117e-01, 4.3407116797428069e-01, 1.2086487917403194e-01], + [4.2469736374797962e-01, 4.1770148169785892e-01, 2.1527119207415876e-01], + [4.5436732665977220e-01, 4.4410427639344147e-01, 3.1924423763856102e-01], + [4.8394101041646803e-01, 4.7730814792247023e-01, 4.1770202781153409e-01], + [4.3303873099660206e-01, 4.2580538704931131e-01, 4.1179645758992439e-01], + [5.5418536373279303e-01, 2.0098557143693473e-02, 9.4669361941331336e-03], + [5.4406576492427994e-01, 6.5574687611566984e-02, 9.1325068594102204e-03], + [5.2001746300860408e-01, 9.1228813952465126e-02, 4.6690448126733591e-02], + [5.3308096935682581e-01, 5.6890794629141574e-02, 4.8417805796110193e-02], + [6.0028468225740816e-01, 1.6063571080727967e-01, 4.4384740398476306e-02], + [6.0259756913238927e-01, 1.5387473555251949e-01, 1.1511220112959941e-01], + [5.1879270464403804e-01, 1.2067023307776618e-01, 1.1266683408353338e-01], + [5.8839750525315448e-01, 1.4083627028089721e-01, 8.4195903040283921e-03], + [5.6577053942001576e-01, 1.9868884556686361e-01, 1.0256603817307225e-01], + [5.6567044786088838e-01, 2.4268695750219524e-01, 8.1950174072209295e-03], + [5.6239692457926738e-01, 2.4464068988014789e-01, 4.1943387886423045e-02], + [5.5371936475241468e-01, 2.4738996125832874e-01, 2.0241740896030130e-01], + [5.5923334605543185e-01, 2.0999377200606187e-01, 2.0172212791179417e-01], + [5.9747800349212221e-01, 2.8820446678456313e-01, 1.0398735323754577e-01], + [6.0647381239311193e-01, 2.9570120014544687e-01, 1.8447436495665112e-01], + [6.5564172414508926e-01, 3.4254086990030502e-01, 2.9794097478469789e-01], + [5.7266784640314883e-01, 3.1789969739486257e-01, 3.0904636136913421e-01], + [5.6854726419093116e-01, 3.5595137852559672e-01, 8.3922963444874980e-03], + [5.5818612619311025e-01, 3.4996147480176565e-01, 4.2713130307396791e-02], + [5.8465643102637932e-01, 3.8102329635147303e-01, 1.0466802558628563e-01], + [5.8376820056441681e-01, 3.7976525115251419e-01, 1.8714005574418408e-01], + [5.8715422030913555e-01, 3.7642527316918173e-01, 2.6903911495608440e-01], + [5.6720525704494107e-01, 4.1047318537369420e-01, 4.0021381306991277e-01], + [5.7889246446190679e-01, 4.6373714500024527e-01, 3.4977562270215506e-01], + [5.7286955357200620e-01, 3.7947055325700207e-01, 3.3316525182452006e-01], + [5.8564768566523562e-01, 4.7051142268017593e-01, 4.6265043145166229e-01], + [5.8883770777129807e-01, 4.7063226776108830e-01, 8.5392227890235210e-03], + [5.9044103967874517e-01, 4.7458373191043207e-01, 4.5341636094302473e-02], + [5.6671827218301785e-01, 4.5749049947626869e-01, 1.0976986197965194e-01], + [5.9477674892688792e-01, 4.7638750747968461e-01, 1.8941877712336178e-01], + [6.0990856650042879e-01, 4.8783685582884667e-01, 2.8784598168022107e-01], + [5.5915115143066130e-01, 4.6074260583536231e-01, 4.1295199601721289e-01], + [5.0276696079867311e-01, 4.5360382590393644e-01, 9.4020572621778768e-03], + [5.6576243522377556e-01, 5.1895510183307769e-01, 4.8884040095362145e-02], + [6.1871318071649428e-01, 5.6954516522265519e-01, 1.4275764128386317e-01], + [5.6878565150706861e-01, 5.1642528297283774e-01, 3.1586448538450262e-01], + [5.8127175805686171e-01, 5.3839801251505148e-01, 4.2751936107468475e-01], + [5.3304133164679723e-01, 4.9566351028788957e-01, 4.4998000738900396e-01], + [5.2262351036938981e-01, 4.7538183708422777e-01, 4.6614937808180268e-01], + [5.8848189467260326e-01, 5.7879173978637888e-01, 9.3780848280188619e-03], + [5.3834127326766545e-01, 5.2965214934750615e-01, 4.8994320142122585e-02], + [5.5008975471710420e-01, 5.4040098461665032e-01, 1.1896997600612476e-01], + [5.7359577572870857e-01, 5.2889363040908066e-01, 1.1278182570038192e-01], + [5.4072736876047678e-01, 5.3043778223928895e-01, 2.1496523696358602e-01], + [5.7524035252673600e-01, 5.2383210867380270e-01, 2.1326541923056200e-01], + [5.6244295501224228e-01, 5.5212825535113763e-01, 3.2909195673438835e-01], + [5.8919128089623141e-01, 5.8133898607005985e-01, 4.4629822984334128e-01], + [5.9423648247540894e-01, 5.8652633171373347e-01, 5.5713419600756442e-01], + [5.4942355254408071e-01, 5.3922978511214958e-01, 5.3254935312375995e-01], + [6.8523733859940206e-01, 1.8487157378793209e-02, 9.3080623208994396e-03], + [6.6001759650049030e-01, 5.9915121035652129e-02, 4.9820291054354655e-02], + [6.6176439343809912e-01, 5.9850304343488946e-02, 9.9967511309718947e-03], + [6.5917900965457188e-01, 1.0211651485902133e-01, 5.0760001981133201e-02], + [6.6034716231702706e-01, 1.2837632050560732e-01, 1.2126460508983161e-01], + [7.0612448586343257e-01, 1.3071818702378546e-01, 9.0403933393277316e-03], + [6.9122841590691320e-01, 2.1999481164946949e-01, 1.1386363374148691e-01], + [7.3451020848526782e-01, 1.5773063211680854e-01, 1.1577416962088626e-01], + [6.7814221822537113e-01, 2.2893262458079255e-01, 2.1887292099888744e-01], + [6.8193808875202877e-01, 2.3884799072571247e-01, 9.9371448654691583e-03], + [7.3100908340802573e-01, 1.6875690276063321e-01, 4.7226015761728066e-02], + [6.7619432513344124e-01, 2.4370393060503615e-01, 1.9372930270072261e-01], + [6.9475112550291784e-01, 2.5842839952670710e-01, 5.0081387307224702e-02], + [7.1820594985944186e-01, 3.1176780784176411e-01, 1.1928321936894401e-01], + [7.1235338270873116e-01, 3.2720930479926269e-01, 2.1139669470128983e-01], + [6.9623550246011656e-01, 3.3731810735595713e-01, 3.2903421774989339e-01], + [6.8250901307740142e-01, 3.6043399013376154e-01, 9.3967241541926469e-03], + [6.7641281781978191e-01, 3.6209771113683353e-01, 4.8728181920007066e-02], + [7.0704068344084703e-01, 4.1339386064035133e-01, 1.1706692337168199e-01], + [7.0465283570014026e-01, 4.0512872977043335e-01, 2.0053729123659358e-01], + [7.0645205246029996e-01, 4.1970047801902438e-01, 3.1157148145544811e-01], + [7.4940948153302922e-01, 3.2428740099601983e-01, 2.7518782327393759e-01], + [6.9451842003054975e-01, 4.8599223394113167e-01, 9.2072752792633494e-03], + [6.9072476063186061e-01, 5.0852049140038402e-01, 1.1206060272938860e-01], + [6.9879826259683742e-01, 5.0338051342535151e-01, 2.0019615779381439e-01], + [6.9379509466259870e-01, 4.9147274779001104e-01, 3.0146309175083319e-01], + [6.9880772341310837e-01, 5.1542905286392093e-01, 4.1197318045159947e-01], + [7.3066616147983310e-01, 4.4753687914148538e-01, 4.0469856429849099e-01], + [6.8115653348110794e-01, 4.7662145955302015e-01, 4.7207933545349016e-02], + [6.8889599157471215e-01, 5.9824146453041460e-01, 4.9637462614780076e-01], + [6.8552760006771019e-01, 5.0835372998295603e-01, 4.6513014768915950e-01], + [6.7745785488702859e-01, 4.5186740504291961e-01, 4.4361838647723473e-01], + [7.1820296306177978e-01, 6.0243237872030531e-01, 7.4244578908172591e-03], + [7.1078004809529871e-01, 6.0156891229286114e-01, 4.1679496587318295e-02], + [6.9189190185759275e-01, 5.9326254747361884e-01, 1.0682076557612701e-01], + [7.0785122040458104e-01, 6.0331642487637260e-01, 1.9955752534314702e-01], + [7.0245879093432717e-01, 5.9589990363392875e-01, 3.0265520097253545e-01], + [7.0944777072846488e-01, 5.9739756867654492e-01, 4.1051946569237879e-01], + [6.5867597051836657e-01, 5.6096678371907649e-01, 5.2257090494796410e-01], + [7.1568190050995772e-01, 5.8716045879374235e-01, 5.7954323512529171e-01], + [6.4209674517033710e-01, 5.9252993680816446e-01, 9.9198857167335976e-03], + [6.6324735643258614e-01, 6.1854778047497949e-01, 5.2872707192221223e-02], + [7.1466782226909686e-01, 6.7760648324216299e-01, 1.2056314275366685e-01], + [6.9762595652242365e-01, 6.5636219396312379e-01, 2.1674234900459241e-01], + [6.8274859400495480e-01, 6.4265002426272111e-01, 3.2466552557284367e-01], + [6.8424412505378507e-01, 6.3578709906822306e-01, 4.3422900471403048e-01], + [7.0107229617729483e-01, 6.7088778431396145e-01, 5.5161794070866299e-01], + [6.7432175101392966e-01, 6.3290193547458962e-01, 5.8592509864385267e-01], + [6.4610692484867482e-01, 5.9378865001113024e-01, 5.8532247150004935e-01], + [6.7060226587186089e-01, 6.6180535611037372e-01, 4.9271366741342949e-02], + [6.5844061122334108e-01, 6.5112546879179922e-01, 1.1842862024801924e-01], + [6.5747979546504165e-01, 6.4898664906397119e-01, 2.1439291815897149e-01], + [6.7296668371271706e-01, 6.6667800016101109e-01, 3.3181830512939681e-01], + [6.9535283767288025e-01, 6.8581795546169100e-01, 4.6037825616740341e-01], + [7.2797314529818857e-01, 7.2376679621325590e-01, 5.9229459692073771e-01], + [6.2840301268011600e-01, 6.2076929243782419e-01, 5.5307882391565499e-01], + [6.9143260534017392e-01, 6.8101935566560845e-01, 6.7159276051682337e-01], + [8.0316621730806914e-01, 1.8704883702925446e-02, 8.8425991952227438e-03], + [7.7237584654129066e-01, 5.6886409434062327e-02, 4.6572207389512670e-02], + [7.7257418309133796e-01, 5.5647552912059278e-02, 6.1352299154693460e-03], + [7.7996827620116294e-01, 8.6992550864167698e-02, 3.5615569873526924e-02], + [7.8787310819683831e-01, 1.2160086467707898e-01, 1.1275083351165056e-01], + [8.2116071760935938e-01, 1.2632438685864358e-01, 8.6078861660878175e-03], + [8.3253607307795952e-01, 1.7185112537207109e-01, 4.4027909318712824e-02], + [8.0588309030932059e-01, 2.2683859320712246e-01, 1.1406025998515952e-01], + [8.0550222340051369e-01, 1.3329432376122968e-01, 7.7255964913897301e-02], + [7.8216912112236969e-01, 2.1814247235557926e-01, 2.0764877033706416e-01], + [7.8454551473672651e-01, 2.2244320498644315e-01, 7.7708779559384133e-03], + [8.0927625963316963e-01, 2.6067062974218491e-01, 3.9452135208128965e-02], + [8.1233963251639929e-01, 3.0298724642378011e-01, 1.0016696582277375e-01], + [8.0520310278416174e-01, 3.3496125966098139e-01, 2.0205753294778225e-01], + [7.9729729948478612e-01, 2.5343505121876625e-01, 1.9708593474855135e-01], + [7.8574226971233874e-01, 3.4413039595857053e-01, 4.5716057233113920e-03], + [8.1816081127412432e-01, 4.1654050928705122e-01, 1.8316258718099046e-01], + [8.0077404311307998e-01, 3.6589511518176393e-01, 3.3050462180285917e-01], + [8.0869008380399932e-01, 3.3082160469178579e-01, 3.2380812526519265e-01], + [7.9513350258131743e-01, 4.8145356143781848e-01, 8.7628226171226932e-03], + [7.9049684876076343e-01, 3.6692803700999937e-01, 3.3543356087779565e-02], + [7.9812475127701976e-01, 3.9656724887715694e-01, 9.0638719852558253e-02], + [8.0567949162947106e-01, 4.1732128452317807e-01, 3.2136355478126988e-01], + [7.8829542817638032e-01, 4.9385962360128038e-01, 4.6530139329097768e-02], + [7.9678923512989464e-01, 5.2197951160773060e-01, 1.0958355185895136e-01], + [8.1162816642422986e-01, 5.1740845095570898e-01, 1.9684329409273546e-01], + [8.0297506764334659e-01, 4.8520665037413968e-01, 2.9489934700454118e-01], + [8.1047068170735281e-01, 5.3868123830190895e-01, 4.2869235226914165e-01], + [8.2600380632606607e-01, 4.9423357726758926e-01, 4.5010136566421172e-01], + [7.9087597034699175e-01, 4.5769849912018695e-01, 4.4994974391101883e-01], + [8.1231700563924858e-01, 6.1100009838739455e-01, 8.9283427174331551e-03], + [7.9299775465706901e-01, 6.1208412885404706e-01, 4.5175945274791483e-02], + [7.9486019442416234e-01, 6.2189775192194940e-01, 1.0561683069831074e-01], + [8.0396539201041806e-01, 6.1827180501533707e-01, 1.9013100520939230e-01], + [7.9112644160432954e-01, 5.8642491731996049e-01, 2.9590350076100735e-01], + [7.9883868931533497e-01, 6.0109957850046569e-01, 4.0694613882862818e-01], + [8.0341507936399925e-01, 6.4690785729001277e-01, 5.3318216507487770e-01], + [8.0673968081024106e-01, 5.9852554201530006e-01, 5.4821944164641256e-01], + [7.9608527601703738e-01, 5.7739405062121252e-01, 5.6727714921357886e-01], + [8.2466114682534564e-01, 7.2025664646745347e-01, 9.5505613092008931e-03], + [8.2694885919007366e-01, 7.3298800481352766e-01, 4.6390164653633638e-02], + [8.0263828919009794e-01, 7.1044982486996422e-01, 1.0447552588482677e-01], + [8.0881849825929975e-01, 7.1520626318199088e-01, 1.8615810782691583e-01], + [8.1462381121115246e-01, 6.8782061882569745e-01, 2.8946868224278605e-01], + [8.0483934918161093e-01, 6.9262041117636741e-01, 3.9898574581797153e-01], + [8.0807417496061884e-01, 7.0421439047119450e-01, 5.0750856102157893e-01], + [7.9974009790843381e-01, 7.2045775922849253e-01, 6.1279586821023135e-01], + [7.6972090029626494e-01, 6.5719594793917679e-01, 6.1288901282052144e-01], + [8.3127137149170649e-01, 7.0713169085372274e-01, 6.9827375527139923e-01], + [7.6590757298167034e-01, 7.2157932767300570e-01, 9.1391785733774301e-03], + [7.5855447724750347e-01, 7.1339507556382242e-01, 4.7043577914789352e-02], + [8.2838282024960075e-01, 7.9242985632336160e-01, 1.0895847641794708e-01], + [8.0415361881006253e-01, 7.6966798041599815e-01, 2.0619013458463237e-01], + [7.9737888125247214e-01, 7.3958021343128699e-01, 3.1091668963357444e-01], + [7.9007183823899496e-01, 7.4458647915096254e-01, 4.3834285157565572e-01], + [7.9649204003414098e-01, 7.5210376008553537e-01, 5.5755489790412216e-01], + [7.8477310928892441e-01, 7.5084835908486314e-01, 6.4425630855719140e-01], + [7.9908328197601441e-01, 7.5506851412119791e-01, 7.0919940275574700e-01], + [7.6688310597723663e-01, 7.1228312761386636e-01, 7.0316429810435788e-01], + [7.2072971001351649e-01, 7.1181293823952607e-01, 9.6049744104861265e-03], + [7.9291289354585615e-01, 7.8422953991163913e-01, 5.0988471883102356e-02], + [7.7352999611523943e-01, 7.6725030281556594e-01, 1.2085889836467029e-01], + [7.7001124498751938e-01, 7.6471959604465312e-01, 2.1599503370289586e-01], + [7.8358454841317027e-01, 7.7197823731510551e-01, 3.2759260464253542e-01], + [7.9409704006982151e-01, 7.8588003978110932e-01, 4.5490045228313553e-01], + [8.1361378816189633e-01, 8.0452569928183137e-01, 5.8807757808157513e-01], + [8.3676978047162798e-01, 8.2657519408947722e-01, 7.1174925720261251e-01], + [7.5031877943414604e-01, 7.4283270971364168e-01, 6.9257908701262982e-01], + [8.1376555115225968e-01, 8.0307583029709073e-01, 7.9356237915567684e-01], + [8.9554303670741464e-01, 1.8569826685501084e-02, 9.0395403332627425e-03], + [8.6670047166280106e-01, 4.8593036097233935e-02, 4.3911707411855919e-02], + [8.7625553502627151e-01, 6.0143129152423411e-02, 1.1326700659095058e-02], + [8.7968006658601883e-01, 8.5487020361035226e-02, 5.7065647720910037e-02], + [8.8904866042621045e-01, 1.2188812966208448e-01, 1.1602356377941540e-01], + [9.1007355180826821e-01, 1.3086006798058475e-01, 8.6774004191503690e-03], + [8.9584869307662163e-01, 1.3852546607957361e-01, 5.1293896928518289e-02], + [8.9470265536791072e-01, 2.2758054689458709e-01, 1.1875333139961379e-01], + [8.6920429926159826e-01, 1.7778211068263772e-01, 1.3564830731834643e-01], + [8.7765410730151439e-01, 2.1968916744337219e-01, 2.1040722760774627e-01], + [8.7864056656019696e-01, 2.2697204621686268e-01, 7.8314244169471973e-03], + [9.1019967692771697e-01, 2.2996799467390591e-01, 3.8507312601194672e-02], + [8.8680482318423637e-01, 2.7874610285461682e-01, 8.6877892811325491e-02], + [8.9436202462212844e-01, 3.1243742348491976e-01, 2.0063081595704216e-01], + [8.7617231412419760e-01, 3.4545516600169168e-01, 8.9053321481762857e-03], + [8.9159791125845023e-01, 3.5355477318318451e-01, 4.5759861137861554e-02], + [8.9504451350869885e-01, 3.9960177258798546e-01, 1.0434480861904566e-01], + [8.9327767845149120e-01, 3.7208809142553712e-01, 1.6643265611788985e-01], + [8.7897282476452376e-01, 4.1852604268719029e-01, 3.0060634206844933e-01], + [8.7632873323781457e-01, 3.0727234662282771e-01, 2.6011371639985231e-01], + [8.9852499646551520e-01, 3.3575094227941943e-01, 3.2622706907651405e-01], + [8.8147988604370697e-01, 4.7850916356823253e-01, 9.5787188478015321e-03], + [8.8414654323336894e-01, 4.8204159578638611e-01, 4.8800353426427662e-02], + [8.7230517862567192e-01, 4.8254705726758013e-01, 1.1943402384385715e-01], + [9.1051403006124076e-01, 5.2011822024113241e-01, 1.9996482320058129e-01], + [8.9554785366872991e-01, 4.8038719990994250e-01, 2.7271336092855586e-01], + [8.9833932343270195e-01, 5.3913232790750565e-01, 4.2354942008906893e-01], + [8.9303583184565916e-01, 4.2990417716323559e-01, 3.8005860378314943e-01], + [8.8445862128960728e-01, 4.6227746364007932e-01, 4.5274918159894162e-01], + [8.9464174783537120e-01, 6.1400208291136249e-01, 8.3922918376600798e-03], + [8.8129717474406621e-01, 6.1131167508703077e-01, 4.4659817954097507e-02], + [8.8780785422207742e-01, 5.9326113966946437e-01, 1.0831115346123252e-01], + [8.9466393335853323e-01, 6.2479985685167383e-01, 1.9390788098440498e-01], + [8.8383740059367388e-01, 6.0130712346416859e-01, 2.9855688851130441e-01], + [8.8717031154772941e-01, 5.8694718924567357e-01, 3.8786290176858357e-01], + [8.9893936492529058e-01, 5.7150958082690884e-01, 5.2155131480423289e-01], + [8.8522099173794389e-01, 5.8879219349026457e-01, 5.7954249123664381e-01], + [9.0350684923648394e-01, 7.3900952774894579e-01, 9.1019802388317000e-03], + [8.8862892065848909e-01, 7.2705255307033367e-01, 4.8287431969006110e-02], + [8.7982544482867187e-01, 6.9045782226193020e-01, 1.1152524542594801e-01], + [8.9360021148114310e-01, 7.2814387521863022e-01, 1.9354207256287337e-01], + [8.8993753157186462e-01, 6.9823542717837450e-01, 2.8478648874609147e-01], + [8.8395086410465906e-01, 6.9849135457972045e-01, 3.9616023190035726e-01], + [8.8841107327829560e-01, 6.9080185035752317e-01, 4.8812857101273521e-01], + [8.9451229104056673e-01, 6.6260965833771956e-01, 5.4884308322883990e-01], + [8.9211285733335444e-01, 6.8202254490631542e-01, 6.3790451596070985e-01], + [8.9288423222050672e-01, 6.9842479974779603e-01, 6.9082268248411716e-01], + [9.0859164030795603e-01, 8.3441860585535288e-01, 1.0179475075909170e-02], + [9.0262097171362210e-01, 8.2093396644900452e-01, 5.8648273094529767e-02], + [8.9626917367164582e-01, 7.9432187479614924e-01, 1.2575120687718347e-01], + [8.8935912837541342e-01, 7.9520362970781233e-01, 2.4857617237152330e-01], + [8.9978929855509671e-01, 7.9251150943272830e-01, 3.6217834439148744e-01], + [8.8889395080370859e-01, 7.8829078546171671e-01, 4.7752116873282369e-01], + [8.9096256132051144e-01, 7.7672821175878903e-01, 5.7711246376945180e-01], + [8.9045097570552956e-01, 7.6025131926402634e-01, 6.5169837508132811e-01], + [8.7102317619091185e-01, 7.5780311836857017e-01, 7.1185433925290920e-01], + [9.2554621670250647e-01, 8.1014908533502250e-01, 7.9938052692065054e-01], + [8.5853372863733213e-01, 8.2546605734069500e-01, 4.0353932512986381e-03], + [8.6339263499482544e-01, 8.2926367737100914e-01, 3.5639247090494472e-02], + [9.0892291793639657e-01, 8.7508800186630731e-01, 9.3088672136680262e-02], + [9.0065812481893981e-01, 8.5463178501261672e-01, 1.8331255052279993e-01], + [8.8611583101145397e-01, 8.4990350910135637e-01, 3.0171784585758382e-01], + [8.7744321320387608e-01, 8.3225555416060060e-01, 4.1191796806567821e-01], + [8.8392818462842759e-01, 8.4297817000083841e-01, 5.3834806492343856e-01], + [8.8961670963104089e-01, 8.4180641805989243e-01, 6.3906102483469285e-01], + [8.8469308726829143e-01, 8.3244985150207584e-01, 7.2011233885161696e-01], + [8.9565681215178161e-01, 8.5021136738834213e-01, 8.0648813300277589e-01], + [8.7433786293246107e-01, 8.2078641961869581e-01, 8.1300219078181635e-01], + [8.3683359940737578e-01, 8.3127061817096370e-01, 1.0360990014979390e-02], + [9.0136847986692636e-01, 8.9653293232122000e-01, 5.0941755822877813e-02], + [8.7446633951757247e-01, 8.6868887018468433e-01, 1.1539693071649972e-01], + [8.7371053311436719e-01, 8.6521429078818823e-01, 2.0425878776141651e-01], + [8.7528280557900529e-01, 8.6964926136287046e-01, 3.1997621527904813e-01], + [8.8409445715148682e-01, 8.7506014823907496e-01, 4.4596238013616912e-01], + [8.9127425364757484e-01, 8.8377816973501688e-01, 5.7829928976651368e-01], + [9.0378782032071303e-01, 8.9472789658480611e-01, 7.0764759173648384e-01], + [9.2769351232126351e-01, 9.1799890462934641e-01, 8.1772105294252595e-01], + [8.6734582824655737e-01, 8.5924789158576453e-01, 8.1171558027441759e-01], + [9.1204120576259806e-01, 9.0215864226362708e-01, 8.9310993055871635e-01], + [9.5397639764605757e-01, 2.0684563225379798e-02, 7.5223834669211741e-03], + [9.5106397573141555e-01, 8.0385264337099485e-02, 4.2191259438769071e-02], + [9.5217123314705876e-01, 6.9312369117526224e-02, 7.2522266990207966e-03], + [9.4653045545091852e-01, 4.9862539478013883e-02, 4.4866077603998300e-02], + [9.5573005888931029e-01, 1.2025378644948788e-01, 1.1351975614571957e-01], + [9.5610275577364046e-01, 1.8709085788981014e-01, 4.2011837196736694e-03], + [9.6028073298417949e-01, 1.5234494806352131e-01, 3.4997618829366729e-02], + [9.5168192265366269e-01, 1.9000946572659053e-01, 9.6787862227984245e-02], + [9.4737548023374718e-01, 1.5052694060897731e-01, 1.1450782482590960e-01], + [9.5124166328471549e-01, 2.2400104660139647e-01, 2.1446341465740337e-01], + [9.4817707841540089e-01, 2.9817005769923477e-01, 1.0398710429099765e-02], + [9.7027211015462911e-01, 2.4640809409000100e-01, 3.9764084511522052e-02], + [9.5024637604744144e-01, 2.9046919872867827e-01, 1.0283172670085730e-01], + [9.6523661175574582e-01, 2.9378729336396781e-01, 1.8349233106355337e-01], + [9.3879261758287624e-01, 2.5594531959297223e-01, 2.0593077780923832e-01], + [9.4684376237378620e-01, 4.2436900620284523e-01, 8.9846812971120515e-03], + [9.5794733947756738e-01, 3.6493188528313020e-01, 5.5123555506303205e-02], + [9.5406370652626182e-01, 4.2391257978682517e-01, 1.2932632327777605e-01], + [9.5323116004169350e-01, 3.9528237060999866e-01, 2.0849358206000057e-01], + [9.4701246828148988e-01, 4.1505515911133278e-01, 3.0505654978367014e-01], + [9.5576280732621688e-01, 3.6572350341685433e-01, 3.2139156794116469e-01], + [9.5896569447354074e-01, 3.4652704477982516e-01, 3.3852250283700441e-01], + [9.5438926401505764e-01, 4.7929012874195942e-01, 4.3616823708371985e-02], + [9.4773632712242573e-01, 5.3181372665019955e-01, 1.0740714062223183e-01], + [9.7276990454318146e-01, 5.3813540592463138e-01, 2.0114903710702284e-01], + [9.6112797577581077e-01, 5.0289694641654303e-01, 2.4661673284511959e-01], + [9.5483556161830951e-01, 5.3955389977550450e-01, 3.4659369666300532e-01], + [9.5922750544772506e-01, 5.3708146294610293e-01, 4.3163226589915066e-01], + [9.5332071551831143e-01, 4.9952135581376117e-01, 4.5665184768694084e-01], + [9.5215784131905856e-01, 4.7235509323840413e-01, 4.6414309269321413e-01], + [9.5266056222521900e-01, 5.6289841654422001e-01, 7.6439396576880756e-03], + [9.5074833350418708e-01, 6.1061775106520644e-01, 4.2589032574470290e-02], + [9.5337414624204608e-01, 6.6010788109186436e-01, 1.0690333966374604e-01], + [9.5835676242924295e-01, 6.4061718778644550e-01, 1.9728968391486096e-01], + [9.5280711955832897e-01, 6.3756406161018719e-01, 3.1424704632437966e-01], + [9.5169312228717651e-01, 6.6390476659487341e-01, 4.3631152928582490e-01], + [9.5677180218582913e-01, 6.5783717281906706e-01, 5.2491897136872345e-01], + [9.5794538416824060e-01, 6.3786022426402245e-01, 5.8387514068439728e-01], + [9.5163248123998534e-01, 6.0543741602624845e-01, 5.9522181765995708e-01], + [9.5919196958021347e-01, 7.0559365541425012e-01, 8.1614418589908543e-03], + [9.5301987640317176e-01, 7.3965626023149855e-01, 4.1715587338913847e-02], + [9.4704842245263610e-01, 7.5038968333180778e-01, 9.9738978963380770e-02], + [9.5504926324392780e-01, 7.5300867169624519e-01, 1.8922741873215246e-01], + [9.5386330670703323e-01, 7.4088050044462805e-01, 2.9894039523919758e-01], + [9.5065850684051767e-01, 7.6125543104606119e-01, 4.2529869924490377e-01], + [9.5442690788797091e-01, 7.7415938164031806e-01, 5.5739905322733252e-01], + [9.5708680463015749e-01, 7.5780342381696908e-01, 6.4377092758050991e-01], + [9.5326297588826259e-01, 7.4811517981246112e-01, 6.9986968044006759e-01], + [9.5659625766455159e-01, 7.3312272856773808e-01, 7.2344153877193496e-01], + [9.6562141122994360e-01, 8.3001674445787488e-01, 7.9054729515770604e-03], + [9.5330360897676347e-01, 8.4560605733737937e-01, 4.3294619621862651e-02], + [9.6105661730371172e-01, 8.4250146914728408e-01, 1.1310952329396048e-01], + [9.5438457833559442e-01, 8.4502509086925126e-01, 2.1038650077331830e-01], + [9.5461957626523908e-01, 8.3062521130044253e-01, 3.1453438454493593e-01], + [9.6678975455783811e-01, 8.6115229020013195e-01, 4.2787538270466818e-01], + [9.5045374385169379e-01, 8.4998766084827471e-01, 5.2050005600017735e-01], + [9.5480392663930092e-01, 8.6557526510652616e-01, 6.2293596743120416e-01], + [9.5237503170720827e-01, 8.4230618028197368e-01, 7.0297479935472107e-01], + [9.4917928728199097e-01, 8.3740298765431964e-01, 7.8163857831652239e-01], + [9.7198679501111074e-01, 8.5170930858382776e-01, 8.4203854989691274e-01], + [9.6618414934506014e-01, 9.1551679483574511e-01, 7.1498263792667566e-03], + [9.4781836505430139e-01, 9.1156663184463815e-01, 4.0227045792949653e-02], + [9.9213463101557220e-01, 9.5200870894981338e-01, 1.2835164874199564e-01], + [9.6116549764112369e-01, 9.1527810946652688e-01, 2.1038858626603255e-01], + [9.5277933078071209e-01, 9.0299353888778022e-01, 3.2464011301153500e-01], + [9.4673323703518053e-01, 8.9933672939782738e-01, 4.5048716071568934e-01], + [9.5268977293904733e-01, 9.1954658887626017e-01, 5.8843600466785217e-01], + [9.5454293646324362e-01, 9.1253001198351857e-01, 7.1619566900788101e-01], + [9.5408963892001908e-01, 9.0695147922320785e-01, 8.0598932547382951e-01], + [9.5995879593934008e-01, 9.1227358880030940e-01, 8.7738430636794384e-01], + [9.5259252016936491e-01, 9.0676004595526061e-01, 9.0047983837439072e-01], + [9.3026672176373693e-01, 9.1887390305499250e-01, 9.6332935179657929e-03], + [9.6794145845944080e-01, 9.5914938548963802e-01, 6.0736893936083135e-02], + [9.4780345386239118e-01, 9.3864465717861745e-01, 1.4063802339796164e-01], + [9.4683931597551130e-01, 9.3730600496219163e-01, 2.4924565672861568e-01], + [9.4960468247967500e-01, 9.4073256431178687e-01, 3.7114666893966314e-01], + [9.5273338428790177e-01, 9.4194590939682454e-01, 4.8580933780820784e-01], + [9.5506717387116735e-01, 9.5371231729459760e-01, 5.8297778658248789e-01], + [9.5601674156841066e-01, 9.4755010204817269e-01, 6.9344553697059996e-01], + [9.7288309444449939e-01, 9.6527712260129372e-01, 8.1441825839460402e-01], + [9.5276090908761568e-01, 9.4366610668886119e-01, 9.0193511415137184e-01], + [9.7655753105887488e-01, 9.6970253723320077e-01, 9.6198078543695176e-01], + [9.8966505577340613e-01, 2.0330840144340351e-02, 1.2517489716637300e-02], + [9.9047833661028706e-01, 7.3892670326521484e-02, 6.1940246324594078e-02], + [9.9215654263067865e-01, 5.5621350802066989e-02, 1.0374391154967034e-02], + [9.8992729962948434e-01, 1.1064665017820619e-01, 4.9697025040515187e-02], + [9.8808111189752523e-01, 1.2597478926728731e-01, 6.6460577355367629e-03], + [9.9710268569203497e-01, 1.7219395856243053e-01, 3.4030280233275499e-02], + [9.8948810538994070e-01, 2.2451189920761200e-01, 9.2736086000380302e-02], + [9.9056891786991896e-01, 1.7311720656951290e-01, 1.2075942118261983e-01], + [9.9106565928167623e-01, 1.5905802404104302e-01, 1.4970976993265162e-01], + [9.9194694639950654e-01, 2.4390943268904744e-01, 1.0128237174507216e-02], + [9.9419762178862003e-01, 2.9987227032721248e-01, 6.2039248652843763e-02], + [9.8905255052076857e-01, 3.5481528663139628e-01, 1.3588435560721784e-01], + [9.9647570778970318e-01, 3.0059085719877499e-01, 1.8223357499282408e-01], + [9.8750345776346549e-01, 2.5926695884064777e-01, 2.1316804006522774e-01], + [9.9145989972089710e-01, 2.7260982023752578e-01, 2.6366018996298773e-01], + [9.8898667842367305e-01, 3.6781066696870524e-01, 8.3640314940965129e-03], + [9.9225501375591529e-01, 3.8013495881284343e-01, 4.0709071146594882e-02], + [9.9114508300234994e-01, 4.4302586398295418e-01, 1.0932808598867483e-01], + [9.9131841470475979e-01, 4.2851152856958502e-01, 2.2383222194718580e-01], + [9.8897463511898331e-01, 4.0815719709833831e-01, 2.9436466159883945e-01], + [9.9313261728641722e-01, 3.5932046661005485e-01, 3.0929283007528741e-01], + [9.9186181389830730e-01, 4.0221972448118848e-01, 3.9204851880645586e-01], + [9.9033059350493235e-01, 5.0661498197042765e-01, 8.3874720098201256e-03], + [9.9149033799737740e-01, 4.8547436994794019e-01, 4.6263288382650584e-02], + [9.8965666329173618e-01, 5.5338499927418372e-01, 1.1066831399404636e-01], + [9.9639998178580058e-01, 5.1575281068698198e-01, 2.0561198270202002e-01], + [9.9147348009508152e-01, 6.2573144138891268e-01, 3.1994107612576228e-01], + [9.9145745872809388e-01, 5.2404254475474277e-01, 3.2588148549801410e-01], + [9.9237598814786199e-01, 5.3657070772417548e-01, 4.2466500902455068e-01], + [9.9030187791594082e-01, 4.7120945326848235e-01, 4.2320245873054935e-01], + [9.9044390846700225e-01, 5.3084654705433110e-01, 5.2286478071630360e-01], + [9.9125220344441933e-01, 6.5275718723042508e-01, 9.5847062181860926e-03], + [9.9076377047102637e-01, 5.9742310201595017e-01, 4.4075316833388452e-02], + [9.9045700827566185e-01, 6.6094005336866646e-01, 1.0103630425735122e-01], + [9.9350975229210481e-01, 6.3038196810514435e-01, 1.9632531091065283e-01], + [9.9095903231397475e-01, 7.1747706402110156e-01, 2.8856353292809017e-01], + [9.9027732386560374e-01, 7.4524124441149775e-01, 4.3134519607056648e-01], + [9.9084081422014014e-01, 6.4717235627544911e-01, 4.4696233609880243e-01], + [9.9203733393743287e-01, 6.6773809552084196e-01, 5.6090121279889593e-01], + [9.9163010811684649e-01, 5.9595960491127742e-01, 5.5330329299653935e-01], + [9.9085668810631544e-01, 6.6691458600174436e-01, 6.5844562955980579e-01], + [9.9324616863848347e-01, 7.8733001447653506e-01, 9.8861173935636017e-03], + [9.9075428274709632e-01, 7.2945363931784224e-01, 4.9991077839044842e-02], + [9.9064204832382741e-01, 7.9492301805525156e-01, 1.1766171897436054e-01], + [9.9156162381144397e-01, 7.2106971310009771e-01, 1.6741865725638047e-01], + [9.9125436657424382e-01, 8.0113256619321171e-01, 2.3690571848451220e-01], + [9.9126278636723142e-01, 8.1362922319449227e-01, 3.7049674837987850e-01], + [9.9147634760588166e-01, 7.6678512361130502e-01, 5.6496706406009312e-01], + [9.9179202519971910e-01, 7.9131858259376631e-01, 6.7929128284567197e-01], + [9.9135765563815448e-01, 7.3437530058633260e-01, 6.8994719282123163e-01], + [9.9264958087217547e-01, 7.9386472630755667e-01, 7.8571309624272512e-01], + [9.9488958947986217e-01, 9.0015758183994732e-01, 1.0881342805656306e-02], + [9.9018051359829606e-01, 8.4350250703228302e-01, 4.9430889330569404e-02], + [9.9508212623587311e-01, 9.0020307952870215e-01, 9.9427059064740642e-02], + [9.9167065347122429e-01, 8.7931827000903728e-01, 1.8288739681552557e-01], + [9.9101398982002820e-01, 8.8476493689124003e-01, 3.0367683614663199e-01], + [9.9488216539134156e-01, 8.9388020066759843e-01, 4.4044143001628600e-01], + [9.9078268646706791e-01, 8.5004465148621755e-01, 5.3192290867387437e-01], + [9.9113622828224879e-01, 8.7189819718849404e-01, 6.6544244505372951e-01], + [9.9036667305962101e-01, 8.8578120858734133e-01, 7.7258090395676504e-01], + [9.9038536745752326e-01, 8.4719288007560611e-01, 8.0062670376278922e-01], + [9.9397351093853359e-01, 8.9897520413414489e-01, 8.8897962758849214e-01], + [9.9365451128461346e-01, 9.6821582183950672e-01, 9.0205114815174441e-03], + [9.8814994788286814e-01, 9.3384074997024957e-01, 4.7186420887583500e-02], + [9.6187985409598431e-01, 9.0994600679570226e-01, 1.1265935505098397e-01], + [9.9268232296482883e-01, 9.4700595144201027e-01, 2.2960767126774398e-01], + [9.9075161371956100e-01, 9.4444773593621945e-01, 3.5047240825837717e-01], + [9.8948956829673329e-01, 9.4677238916689255e-01, 4.7235194343386006e-01], + [9.9069717832578230e-01, 9.3141318510614868e-01, 5.9036770524363735e-01], + [9.9147915969137834e-01, 9.4314445054810336e-01, 7.1390564885017094e-01], + [9.9157982768865482e-01, 9.5088738198987233e-01, 8.1862631171394129e-01], + [9.9164384441085252e-01, 9.3460015118422424e-01, 8.8334540607997525e-01], + [9.9080681453787611e-01, 9.5047016585266653e-01, 9.4245275366151837e-01], + [9.8247981784806593e-01, 9.7788728511426615e-01, 1.2899742223075860e-02], + [9.9785531086344503e-01, 9.8591888042020293e-01, 5.4966452577599224e-02], + [9.9010875569891010e-01, 9.8163645222065243e-01, 1.2810136896329641e-01], + [9.8955729120568836e-01, 9.8168860237873645e-01, 2.2232583104457235e-01], + [9.9004952851112360e-01, 9.8026727735151198e-01, 3.3721495346986158e-01], + [9.9099470224365449e-01, 9.8329458168849904e-01, 4.6816769834559957e-01], + [9.9133270184557643e-01, 9.7996474986733406e-01, 6.0129669421529253e-01], + [9.9112941843801794e-01, 9.8123162479846548e-01, 7.2258933633776012e-01], + [9.9957121676981464e-01, 9.9339804202514015e-01, 8.3573011965800348e-01], + [9.8885848168195489e-01, 9.7845186630520020e-01, 9.0805624156802478e-01], + [9.9662358338218093e-01, 9.8537340958887865e-01, 9.6545734931897831e-01], + ], + weights: [ + 1.3742415440655699e-05, + 1.9539779405334750e-05, + 2.8981865711423886e-05, + 6.2035155908342224e-05, + 4.4240985871374829e-05, + 4.1181392714584231e-05, + 1.2569137049742293e-04, + 7.3801351890770870e-05, + 1.4778456796390893e-04, + 5.3120840017148884e-05, + 3.9976892844729673e-05, + 7.0067424342527701e-05, + 4.2139858707242327e-05, + 6.4161380783615141e-05, + 1.3798447041387480e-04, + 8.7803181151633285e-05, + 1.7910769046351901e-04, + 1.2892953604848114e-04, + 1.7857081854847376e-04, + 3.6119107196291644e-04, + 3.3097501746639683e-04, + 2.0605302524107113e-04, + 1.3584940496355041e-04, + 2.4029003956743688e-04, + 2.7342543326493401e-04, + 1.4400287978482023e-04, + 1.3896970986623455e-04, + 6.3961821434018834e-05, + 1.3017909274860197e-04, + 8.3446282691835666e-05, + 4.4116900065049390e-05, + 7.8408249537259683e-05, + 1.4737844024536323e-04, + 2.4838950715920548e-04, + 3.9022375423095876e-04, + 4.9175892229016038e-04, + 2.2895228684456225e-04, + 3.8901109491888615e-04, + 6.4386690640046357e-04, + 4.2715346916746632e-04, + 1.9639714587049378e-04, + 1.9142607510315524e-04, + 3.3979485590029138e-04, + 4.3250351311545382e-04, + 4.5012481372002782e-04, + 2.5701498687343390e-04, + 1.5208284971050286e-04, + 1.6572209036373132e-04, + 7.2224565415503051e-05, + 1.2549923451087515e-04, + 2.2201405893808524e-04, + 2.2495786321965656e-04, + 9.7085631651826055e-05, + 5.9456594678584121e-05, + 8.6266982750775252e-05, + 1.4237135817567989e-04, + 1.6129534221139536e-04, + 3.2206986780603257e-04, + 2.1902455922966671e-04, + 2.0756906728853407e-04, + 4.4692386955389715e-04, + 4.7537182647415191e-04, + 5.1811022380506965e-04, + 4.9767269224788066e-04, + 5.6347802061958434e-04, + 2.4620876718719277e-04, + 6.1946546546297473e-04, + 2.4989734324514765e-04, + 6.2751972845373580e-04, + 2.2772024373900166e-04, + 2.5140968441444755e-04, + 4.6488447574963293e-04, + 6.6554418607301464e-04, + 6.6853460137394709e-04, + 6.8133790344163624e-04, + 4.8676681584271632e-04, + 5.3356573476308030e-04, + 1.8014201974188334e-04, + 3.7766458305125115e-04, + 5.5466073136475674e-04, + 5.1292434016399755e-04, + 3.1215613549718537e-04, + 5.4494724516787511e-05, + 7.7828558565978776e-05, + 1.8067473861043684e-04, + 1.6206286828506105e-04, + 2.1181972760248114e-04, + 2.6786464386389393e-04, + 1.4483473628405590e-04, + 6.9887472322242980e-05, + 8.8190597798392081e-05, + 1.8034506414898262e-04, + 3.6917761005645305e-04, + 1.5404361632204128e-04, + 5.3140717724339391e-04, + 4.6539703653378808e-04, + 2.1296517520494438e-04, + 2.3842633723415062e-04, + 6.0025339431521245e-04, + 2.7534339355751145e-04, + 5.8732281379188689e-04, + 6.1137430917758168e-04, + 2.5840796864794137e-04, + 8.8387663561056214e-04, + 7.7216501655122567e-04, + 5.9240796814406392e-04, + 3.0059640916666104e-04, + 2.8246888663071781e-04, + 5.8183496570325525e-04, + 8.3767764974497310e-04, + 9.7838726881954979e-04, + 8.1958810378319103e-04, + 1.2584712727293113e-04, + 7.9251376484319042e-04, + 6.7028944092199034e-04, + 1.8649410896459919e-04, + 2.3866188664434476e-04, + 5.2438472499530047e-04, + 7.2775161682622790e-04, + 7.4636488163588920e-04, + 7.1397357872565650e-04, + 4.7014714717360232e-04, + 1.8394949827851937e-04, + 3.2645347227632394e-04, + 2.2392202386292779e-04, + 6.9083115779164448e-04, + 4.9388035060590871e-04, + 3.1333301315993370e-04, + 1.4278864651005499e-04, + 8.1109304608193892e-05, + 1.6218333138443304e-04, + 2.2102809067612355e-04, + 3.9022128115794404e-04, + 3.2741996012225890e-04, + 6.9874602638690501e-04, + 3.4193010046490869e-04, + 2.3032323594668260e-04, + 7.6899963724880535e-05, + 5.8423264179706085e-05, + 7.1928563404944653e-05, + 1.7479065794971280e-04, + 1.6736040045241146e-04, + 4.1260667682645771e-04, + 2.1925148992590635e-04, + 2.4331380198109489e-04, + 7.5582329351884072e-04, + 4.6003942878325112e-04, + 2.9461241342218985e-04, + 3.2171807732576615e-04, + 5.1300618558504222e-04, + 5.8046164695648383e-04, + 6.8521279784787570e-04, + 7.9691823177034725e-04, + 7.4328972297284159e-04, + 2.9491102219789475e-04, + 3.3504136128569353e-04, + 7.3252851047134638e-04, + 9.3075127443160109e-04, + 1.0653388751987779e-03, + 8.6821007358602650e-04, + 3.7265270409791719e-04, + 3.0289402233100304e-04, + 8.3239216932392440e-04, + 1.0662838477165737e-03, + 1.0231675346035684e-03, + 8.5041897997195073e-04, + 5.4974974551711818e-04, + 6.4104003596179274e-04, + 6.7089968314651741e-04, + 5.3048531788625402e-04, + 2.9226261615228187e-04, + 1.9061337384268119e-04, + 4.5642062723184364e-04, + 6.8017501227127404e-04, + 8.7763279334171886e-04, + 9.5077057423826532e-04, + 7.8811213125169505e-04, + 2.9622519147536221e-04, + 2.1554572088574369e-04, + 1.8711464812801626e-04, + 2.5749339154968945e-04, + 4.5324473615513506e-04, + 5.5744369211137995e-04, + 6.7225684935665227e-04, + 6.2327013203667141e-04, + 3.5418099845598911e-04, + 3.4503806286689229e-04, + 1.5565714789863924e-04, + 1.6027272923803607e-04, + 1.7944125318479184e-04, + 2.7121755021028540e-04, + 2.4532246739426378e-04, + 3.2199530096928215e-04, + 1.3778097577989734e-04, + 1.3862730578812979e-04, + 8.3795251255725989e-05, + 6.0469686320081821e-05, + 1.4317330173904956e-04, + 9.7987021840805486e-05, + 2.6489144055959700e-04, + 2.1310370822952911e-04, + 1.9323755078752843e-04, + 3.6467536028250014e-04, + 6.4074528220050783e-04, + 2.7774101238018506e-04, + 2.9417584559015907e-04, + 2.2070781668403415e-04, + 4.9633440490771551e-04, + 6.4521175159704168e-04, + 8.6010675410300695e-04, + 6.0736935540472981e-04, + 1.7545655370180017e-04, + 9.2353741825851017e-04, + 4.2247460660657616e-04, + 2.3059115437361167e-04, + 2.8712725183280600e-04, + 5.6738721974026953e-04, + 7.5955898847488638e-04, + 7.3372293467599482e-04, + 6.5047285786065234e-04, + 7.8639225686207557e-04, + 1.0528591694020240e-03, + 1.1475815673507174e-03, + 8.7569680629714247e-04, + 4.9595079967594384e-04, + 2.5983059672890117e-04, + 2.5076937746171002e-04, + 5.1256251351231149e-04, + 6.4158280374570760e-04, + 9.4648693253064598e-04, + 1.0550664243245821e-03, + 9.6843644726414459e-04, + 7.2392590513447686e-04, + 5.7119377457938328e-04, + 2.8427696236030757e-04, + 1.8600609210514261e-04, + 3.2825460114952165e-04, + 5.0391540284388733e-04, + 7.0022018075424793e-04, + 7.8393243399925807e-04, + 7.9555135410000618e-04, + 6.2962280170742824e-04, + 4.6834649445866218e-04, + 4.5841464054534537e-04, + 1.9055253676359080e-04, + 1.3438939110615343e-04, + 2.7249698973501378e-04, + 3.5926674342773578e-04, + 4.7406772469306234e-04, + 7.1796517658259326e-04, + 6.4283127343341454e-04, + 5.0138107203849384e-04, + 2.0440490656826855e-04, + 2.9797000645822799e-04, + 1.6033348576484165e-04, + 7.0850443759920887e-05, + 1.4005965685231689e-04, + 1.5411660243995292e-04, + 1.7562121628269005e-04, + 3.6698834448604191e-04, + 2.7369267630696542e-04, + 2.5504575187790631e-04, + 2.0724396001585327e-04, + 1.4567260664024900e-04, + 7.4228025903957413e-05, + 4.1872253590992994e-05, + 5.7950099059727725e-05, + 1.4521835067461708e-04, + 2.1109280375324106e-04, + 1.1923212759475148e-04, + 1.3689131803309003e-04, + 3.2545608015825835e-04, + 4.4693297563019906e-04, + 4.4201394093256857e-04, + 2.1613746638371928e-04, + 1.8762081363874367e-04, + 3.5817844417974557e-04, + 4.1175914050807229e-04, + 6.0952387930113836e-04, + 2.3519694680229272e-04, + 5.0524031885759940e-04, + 4.9693255521855728e-04, + 7.0148059556303854e-04, + 6.5769050300354404e-04, + 5.0346058485144967e-04, + 2.2581412251515304e-04, + 2.5837987479197084e-04, + 5.9521913982783401e-04, + 6.0788241437357876e-04, + 8.3364667056487322e-04, + 8.4640218336172056e-04, + 7.2087565801736569e-04, + 5.3534308008851576e-04, + 2.5784637968963585e-04, + 2.0668262061036908e-04, + 5.1547693170938213e-04, + 6.5064985959099278e-04, + 7.8258139767785405e-04, + 9.0699022620204146e-04, + 7.9780766839478081e-04, + 4.7806210104499037e-04, + 2.2323096027762311e-04, + 1.8369902024634475e-04, + 4.0001716840356211e-04, + 5.6885982801885835e-04, + 6.0789752012477391e-04, + 7.2304915663631614e-04, + 7.7754772486513250e-04, + 7.0275046581923120e-04, + 7.1104235305063360e-04, + 4.1795187967259042e-04, + 1.4221220298540546e-04, + 1.3423593877768966e-04, + 2.3769420902055918e-04, + 5.2862048461577453e-04, + 5.9619101839225228e-04, + 6.8950056774695200e-04, + 6.1775381918311107e-04, + 5.9231603063362092e-04, + 5.2775976382807125e-04, + 3.8235490496226203e-04, + 1.4511902327645930e-04, + 4.7692213253705341e-05, + 1.9179200633090838e-04, + 2.0878606052302767e-04, + 4.0701158735192137e-04, + 4.3726097446825709e-04, + 4.7266487854688409e-04, + 4.3447165117144789e-04, + 4.0741629172779325e-04, + 4.0133204535803077e-04, + 2.2507198100513720e-04, + 1.1406921904763739e-04, + 4.3228456293201899e-05, + 7.2403639570695766e-05, + 1.0632700527411970e-04, + 1.9702139327152577e-04, + 1.6483725728383219e-04, + 2.4091217547146776e-04, + 1.8851469869919678e-04, + 1.8106261681674678e-04, + 1.2751307190485341e-04, + 1.1162624835783811e-04, + 4.7976059289896407e-05, + 3.0533951715427604e-05, + 1.4925428962818085e-04, + 7.3080503944080122e-05, + 5.0419210882000101e-05, + 7.1571567858632590e-05, + 7.2156355665715742e-05, + 2.0398740443407963e-04, + 2.7906246491027380e-04, + 2.2842688039566601e-04, + 1.5956487441513059e-04, + 1.8134131679153992e-04, + 1.9872728892268026e-04, + 4.5371710806488790e-04, + 3.8540169647688214e-04, + 3.5232253416699299e-04, + 1.7503758030096663e-04, + 3.6644983248492694e-04, + 5.0028520048167851e-04, + 5.0866787778061314e-04, + 5.5007924338425348e-04, + 3.4704385241991238e-04, + 1.2084089286858994e-04, + 3.3734669187992654e-04, + 5.8433381277102966e-04, + 3.8091510446865021e-04, + 3.7536161595526146e-04, + 6.0301545149262165e-04, + 4.3508907920570991e-04, + 3.2742144750730844e-04, + 1.4723503651519889e-04, + 1.4724424956775957e-04, + 3.7215515455977534e-04, + 4.5207239027388003e-04, + 6.0017864491906405e-04, + 7.3382978643941421e-04, + 6.3326013353576021e-04, + 4.9493135161863980e-04, + 3.5616454751121417e-04, + 1.8242704628668115e-04, + 1.2814813861039431e-04, + 2.8806774278715994e-04, + 3.6234427485706063e-04, + 5.2455889632252797e-04, + 5.9826562395726883e-04, + 6.8007493792054475e-04, + 5.8850490877065953e-04, + 3.9511079227227692e-04, + 2.7862532535618791e-04, + 1.4544483709684215e-04, + 9.4751780026562033e-05, + 2.2408716688307380e-04, + 3.1044132774009592e-04, + 4.2338842980809202e-04, + 4.5002779037751488e-04, + 3.4517767207335921e-04, + 4.1035174800683582e-04, + 3.4071876735042089e-04, + 3.8162388451099742e-04, + 2.9528536078975913e-04, + 7.1167924388320343e-05, + 5.4636677345915816e-05, + 1.2013347030867992e-04, + 7.9020392848101582e-05, + 2.4577184148332414e-04, + 3.7658801295158758e-04, + 3.6028638958033460e-04, + 3.1955345678184714e-04, + 2.6325530972451779e-04, + 2.2818454823971367e-04, + 1.1019941406116264e-04, + 5.2093108869020757e-05, + 5.1718443713524685e-05, + 6.9327801799047914e-05, + 1.3239245235764941e-04, + 1.6485050387242590e-04, + 1.6086726491015772e-04, + 1.4419504032668490e-04, + 5.1657313832475066e-05, + 1.1403332109064094e-04, + 8.3642065512105584e-05, + 6.6905238632249226e-05, + 1.6096063873731790e-05, + 1.6720590198021476e-05, + 5.1092504280635267e-05, + 3.1228909995774731e-05, + 8.9792612964982181e-05, + 4.7222691724646498e-05, + 4.7393484852957938e-05, + 1.7396715550599775e-04, + 1.2366257895291433e-04, + 5.6743645516212974e-05, + 6.6532725616914786e-05, + 1.0946211835209350e-04, + 2.3672257848091324e-04, + 1.1169087134547183e-04, + 1.6002816134207964e-04, + 6.2653933137453305e-05, + 7.7585919055238092e-05, + 9.8544729094088106e-05, + 2.0550719540811936e-04, + 1.9916611327815996e-04, + 2.5524632426238364e-04, + 1.0610608059746864e-04, + 6.9303880774088209e-05, + 7.7624822965386553e-05, + 1.2177629996644504e-04, + 2.2562420723219699e-04, + 1.4174199706478091e-04, + 3.0163288779917887e-04, + 2.3843579979736021e-04, + 2.0431373383352733e-04, + 1.5378867981271366e-04, + 6.7792024927497611e-05, + 7.5768165004268736e-05, + 1.3986455855404873e-04, + 1.8343642573363951e-04, + 1.9293170586049118e-04, + 2.7276857140509154e-04, + 3.3209048424732280e-04, + 2.9540672837967576e-04, + 2.1466855335580787e-04, + 1.4534522860895003e-04, + 6.7287776814680040e-05, + 5.5599249499248458e-05, + 1.6121021979058914e-04, + 2.1555067316600762e-04, + 1.7722035498271330e-04, + 2.3978260630693740e-04, + 2.4977576281092688e-04, + 2.6103941840667966e-04, + 1.8601786092592191e-04, + 1.4419489133315057e-04, + 4.7381207152391221e-05, + 3.6166021027221602e-05, + 1.3388416676983253e-04, + 7.7447352205927189e-05, + 1.7929332385397729e-04, + 2.1053854656261737e-04, + 1.2657718638175324e-04, + 2.7440008440792347e-04, + 2.1558016628182706e-04, + 1.5716142315273422e-04, + 1.2913670670918574e-04, + 2.8942461652265256e-05, + 1.6796436641167284e-05, + 9.7536371044740637e-05, + 2.1631777783229548e-04, + 1.1582585244332797e-04, + 1.3919110219772922e-04, + 1.6095029765286092e-04, + 1.9143267021329817e-04, + 1.2960861107554577e-04, + 9.8364386940614148e-05, + 8.3073247092942836e-05, + 2.0976391338709099e-05, + 1.6186457210113564e-05, + 1.8931642000132789e-05, + 4.3756050441915014e-05, + 5.8679960863719338e-05, + 7.7009310739707017e-05, + 6.3533243510149114e-05, + 8.2972497990217115e-05, + 6.3252122036756416e-05, + 1.3139658431468916e-05, + 5.1478998048092119e-05, + 1.1570116709505859e-05, + ], + }, +} + +tetrahedron_table = { + order: {name: numpy.array(ary) for name, ary in rule.items()} + for order, rule in tetrahedron_table.items() +} diff --git a/FIAT/quadrature_schemes.py b/FIAT/quadrature_schemes.py index 578c9800a..65297556f 100644 --- a/FIAT/quadrature_schemes.py +++ b/FIAT/quadrature_schemes.py @@ -36,7 +36,7 @@ # FIAT from FIAT.reference_element import (HEXAHEDRON, QUADRILATERAL, TENSORPRODUCT, TETRAHEDRON, TRIANGLE, UFCTetrahedron, - UFCTriangle, symmetric_simplex) + UFCTriangle, ufc_simplex, symmetric_simplex) def create_quadrature(ref_el, degree, scheme="default"): @@ -333,6 +333,12 @@ def _kmv_lump_scheme(ref_el, degree): return QuadratureRule(T, x, w) +def gen_quad_simplex(dim): + ref_el = ufc_simplex(dim) + ref_el.vertices = ((0., 0., 0.), (1., 0., 0.), (0., 1., 1.), (1., 1., 1.)) + return ref_el + + def xg_scheme(ref_el, degree): """A (nearly) Gaussian simplicial quadrature with very few quadrature nodes, available for low-to-moderate orders. @@ -347,10 +353,14 @@ def xg_scheme(ref_el, degree): http://dx.doi.org/10.1016/j.camwa.2009.10.027 """ dim = ref_el.get_spatial_dimension() + backend = "xg" if dim == 2: from FIAT.xg_quad_data import triangle_table as table - elif dim == 3: + elif dim == 3 and degree <= 15: from FIAT.xg_quad_data import tetrahedron_table as table + elif dim == 3: + from FIAT.gen_quad_data import tetrahedron_table as table + backend = "gen-quad" else: raise ValueError(f"Xiao-Gambutas rule not availale for {dim} dimensions.") try: @@ -358,10 +368,11 @@ def xg_scheme(ref_el, degree): except KeyError: raise ValueError(f"Xiao-Gambutas rule not availale for degree {degree}.") - pts_ref = order_table["points"] - wts_ref = order_table["weights"] - Ref1 = symmetric_simplex(dim) - pts, wts = map_quadrature(pts_ref, wts_ref, Ref1, ref_el) + ref_pts = order_table["points"] + ref_wts = order_table["weights"] + make_ref_el = {"xg": symmetric_simplex, "gen-quad": gen_quad_simplex} + Ref_el = make_ref_el[backend](dim) + pts, wts = map_quadrature(ref_pts, ref_wts, Ref_el, ref_el) return QuadratureRule(ref_el, pts, wts) From afc4bf9b5fe1763cd4d2af09cd1c8f2a73730746 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 18 Jul 2024 14:41:27 +0100 Subject: [PATCH 85/93] Add quadrature for degree 22 --- FIAT/gen_quad_data.py | 1172 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 1171 insertions(+), 1 deletion(-) diff --git a/FIAT/gen_quad_data.py b/FIAT/gen_quad_data.py index a3efe6185..558f57176 100644 --- a/FIAT/gen_quad_data.py +++ b/FIAT/gen_quad_data.py @@ -3,7 +3,7 @@ points = "points" weights = "weights" -tetrahedron_table = { # noqa +tetrahedron_table = { 16: { points: [ [1.4671830075200176e-02, 1.3388201316592577e-02, 7.2989709061924372e-03], @@ -4642,6 +4642,1176 @@ 1.1570116709505859e-05, ], }, + 22: { + points: [ + [5.1830936207085322e-02, 1.0518500515363042e-02, 4.2444645815583682e-03], + [4.1539247427756533e-02, 2.8369152615228384e-02, 2.7501302508077349e-02], + [2.4760757781539671e-02, 1.9728054207778439e-02, 6.7713122028286036e-03], + [1.1752465219014739e-01, 1.7307435878553327e-02, 1.3192346681559993e-02], + [7.2238677982256097e-02, 4.5249023021076341e-02, 3.8816515680717045e-03], + [8.2077477815237609e-02, 7.6365213605439047e-02, 1.1904719353898514e-02], + [1.1037212460452984e-01, 6.0677968431304574e-02, 5.8783740133236138e-02], + [8.8615810550792193e-02, 4.7341737508731968e-02, 2.5272831141081201e-02], + [7.6578685363536653e-02, 6.8984143919060700e-02, 4.3653389871995997e-02], + [1.0561411726757212e-01, 9.7452472342031735e-02, 9.0506408113573258e-02], + [1.9270520546419714e-01, 1.7343810133547171e-02, 5.8319970185689981e-03], + [1.6493722867504779e-01, 7.0056986296689827e-02, 5.1418165164440359e-02], + [2.1955058001378949e-01, 5.5586675618261430e-02, 4.8519744589610739e-02], + [1.3814755834620837e-01, 4.4038486075363023e-02, 7.3871491370148526e-03], + [2.0515687950991712e-01, 1.1018639839263325e-01, 4.0326958274973963e-02], + [2.2329402235879842e-01, 1.1744085192765601e-01, 1.1348162005203775e-01], + [1.5933240143210634e-01, 1.0131803407026665e-01, 4.2971417463269256e-03], + [1.4246472395134313e-01, 1.0661525003440717e-01, 2.6995736330426172e-02], + [2.3349590909660942e-01, 1.9145031656377962e-01, 9.2873511097984987e-02], + [1.5175529392005108e-01, 1.1379078973431245e-01, 7.6172752908977251e-02], + [1.8092343157293336e-01, 1.4174217236581546e-01, 1.3146005043194631e-01], + [1.5122706391863647e-01, 1.3895603090128675e-01, 3.3762438066632875e-03], + [1.7832855262256667e-01, 1.7089712755380426e-01, 2.6406985578626214e-02], + [1.5061071284300143e-01, 1.4333334154826780e-01, 6.2551944105016494e-02], + [1.7622510059800617e-01, 1.6880335600263721e-01, 1.2883499536454601e-01], + [2.0052116346062404e-01, 1.9406467687531667e-01, 1.8706290787406607e-01], + [2.7336695615045814e-01, 2.0292658559282660e-02, 1.5251213139736325e-02], + [3.0109877589266798e-01, 3.6959335327428941e-02, 1.4268590800511961e-03], + [2.4038979197758512e-01, 6.3081182401307986e-02, 2.4020687989214227e-02], + [3.3402032351032823e-01, 7.1862725558481153e-02, 6.4067653405777358e-02], + [2.4460859124912290e-01, 8.0499746437319344e-02, 5.5888726342445596e-03], + [2.9390990707939019e-01, 1.2153079192680387e-01, 7.9346984751908947e-02], + [3.2592120920979850e-01, 1.3617422551500472e-01, 1.2631483221278506e-01], + [2.5364752346098152e-01, 1.3993922759983418e-01, 1.1089702046666686e-02], + [2.4758533792290199e-01, 1.5472450726067968e-01, 1.1783426687042538e-01], + [3.1152217572167440e-01, 2.0530146084471787e-01, 4.6934822375930017e-02], + [2.9975092893785998e-01, 1.9529390138285602e-01, 9.6515822830585060e-02], + [3.0483131232054900e-01, 2.0719773641993350e-01, 1.9719368012461566e-01], + [2.5180367204133636e-01, 2.0138484161921219e-01, 7.2725086498408266e-03], + [2.3658578191170090e-01, 1.9344243469382094e-01, 3.7647776457668820e-02], + [2.6893758055176004e-01, 2.2881852326740820e-01, 1.7400664565974908e-01], + [2.7207934434945591e-01, 2.3322863360299445e-01, 2.2384386785883889e-01], + [2.6119892977074483e-01, 2.5185589661278751e-01, 7.5566195375628706e-03], + [2.6744063732827039e-01, 2.6111985051213610e-01, 3.9267083274574906e-02], + [2.5879030877805259e-01, 2.4928215473517917e-01, 8.6553192503421680e-02], + [2.6014209761925838e-01, 2.5235124131438347e-01, 1.5029839084555854e-01], + [2.8683155817824801e-01, 2.7765030996601425e-01, 2.3833068591191478e-01], + [3.2033345824101062e-01, 3.1356586088806399e-01, 3.0638721572325595e-01], + [3.6778600136751732e-01, 1.7199843201777688e-02, 8.7407529095772409e-03], + [3.4791920213047867e-01, 6.8816501473069655e-02, 3.3089163658578773e-02], + [4.3865361250312712e-01, 5.7349870670917680e-02, 4.9599335132140439e-02], + [3.6344622995956188e-01, 9.9392258852987914e-02, 1.0476911814594101e-02], + [3.3634096459137786e-01, 1.4575979308007583e-01, 4.4812926224007225e-02], + [4.1806547064184330e-01, 1.4342554226862528e-01, 9.6099884732482169e-02], + [3.6901954899343009e-01, 1.6777093435794216e-01, 7.9059604096999945e-03], + [4.3281628537545497e-01, 2.3664088152117363e-01, 4.1688664982358847e-02], + [4.0617162598843470e-01, 2.1871174876485075e-01, 1.1897153172129994e-01], + [3.7821106832766305e-01, 2.1625606083812848e-01, 1.7130405187143596e-01], + [4.1009133971267064e-01, 2.2324878697807637e-01, 2.1580736739111009e-01], + [3.5467418055977551e-01, 2.3728274808596275e-01, 8.2097402017645132e-03], + [4.2053823403075286e-01, 2.8679178474072503e-01, 1.0111412345197157e-01], + [3.6039551573753348e-01, 2.7087681344444675e-01, 2.2459710681494313e-01], + [3.9622000195073198e-01, 3.0331068817693679e-01, 4.1924515059293101e-02], + [3.7414509571560728e-01, 2.7791747571135977e-01, 1.7067640233436787e-01], + [3.8833352759402373e-01, 3.4273036913902044e-01, 3.0324777035588146e-01], + [3.9834166841009466e-01, 2.9311242641828361e-01, 2.8653259132019415e-01], + [3.6555859035227728e-01, 3.1702304692768984e-01, 7.9458554189068831e-03], + [3.4608796409383824e-01, 2.9395201784734926e-01, 1.0052589736181865e-01], + [3.7706270372547279e-01, 3.3809572152980866e-01, 3.3082014064580162e-01], + [3.3416335728078156e-01, 3.0071042290250322e-01, 3.9909416172165486e-02], + [3.5460355547845740e-01, 3.1653596702941067e-01, 1.8046797690399194e-01], + [3.9968186557188540e-01, 3.6917947951928587e-01, 2.7298805730352016e-01], + [3.8417438291468681e-01, 3.7446711090466567e-01, 7.5951647853360277e-03], + [3.7188484228867130e-01, 3.6746329256494875e-01, 4.0696503576664080e-02], + [3.6598544466865618e-01, 3.5585738200361344e-01, 9.6121540283757037e-02], + [3.7960687963989664e-01, 3.7139730299708934e-01, 1.7383051509129799e-01], + [3.5860734789204213e-01, 3.5457092641843135e-01, 2.5092722346120810e-01], + [4.1359024600324146e-01, 4.0544900280154611e-01, 3.5899116422238420e-01], + [4.2270195854873366e-01, 4.1369662177995487e-01, 4.0150851140004679e-01], + [4.8601470370203820e-01, 8.2486684170170660e-02, 3.8923407917092714e-02], + [4.9135001759942631e-01, 1.9124053681664137e-02, 1.0109995551154759e-02], + [4.3691518741986263e-01, 5.4079149606840009e-02, 8.0424158537233600e-03], + [5.2226680802600511e-01, 1.2718992184363040e-01, 9.1915651769577542e-02], + [5.4919543437412510e-01, 1.2110874103758282e-01, 1.1465895983869609e-01], + [4.7189731289874137e-01, 1.3085992987623030e-01, 7.7711587151778946e-03], + [5.2141203811680226e-01, 2.1324198842570302e-01, 3.7236765453040110e-02], + [4.5140573157024172e-01, 1.4233079508990573e-01, 4.6617811145058655e-02], + [4.4707623647460176e-01, 1.4126088458512276e-01, 1.3252308387517472e-01], + [4.8333679018984310e-01, 2.3159034263210529e-01, 9.3383215249335946e-02], + [5.4667599908590025e-01, 2.6464178626541235e-01, 1.6396385496148633e-01], + [4.8518932742002346e-01, 2.2826382133776363e-01, 1.8610089029739366e-01], + [4.9841883597900261e-01, 2.1790378237550687e-01, 7.2042277276154832e-03], + [4.9748717714744478e-01, 3.2218184755970070e-01, 2.2865297764620743e-01], + [4.7724266705706081e-01, 3.2025472571055247e-01, 2.8685844816491507e-01], + [5.1256245707832371e-01, 2.4195399114241545e-01, 2.3415569334940442e-01], + [4.8453111112200697e-01, 2.9736374329298126e-01, 8.6100376771797351e-03], + [5.1102868715563898e-01, 3.4719579881388041e-01, 4.4227612193662467e-02], + [5.4890085470380701e-01, 3.3265449089154281e-01, 1.0232762461759688e-01], + [5.1516634832872132e-01, 3.4155710798595523e-01, 1.7980130459390117e-01], + [5.3648632447573230e-01, 3.5283396367370168e-01, 3.4674596370983529e-01], + [4.7320571486828339e-01, 3.7221617465901990e-01, 7.8325343919412264e-03], + [5.1694653800225887e-01, 4.3257590519784317e-01, 4.1623505417547849e-02], + [5.0807176449836333e-01, 3.9824217645958032e-01, 1.0451426766987454e-01], + [4.6378445979492228e-01, 3.7383675956838230e-01, 1.7255744862603128e-01], + [5.3533429910102703e-01, 4.3649953757976578e-01, 2.4920090425259023e-01], + [4.7037642890073883e-01, 3.8043275987446623e-01, 2.7142071272447937e-01], + [4.7839722781672633e-01, 3.9143860399110120e-01, 3.8031065268466069e-01], + [4.5302887426943655e-01, 4.0700496500217426e-01, 9.5831910804175996e-02], + [4.9044014984680284e-01, 3.9317704281693955e-01, 3.4115908241000814e-01], + [4.9798733389770455e-01, 4.6154503887971704e-01, 4.5803441753321777e-01], + [4.9632276461774660e-01, 4.5849361477190542e-01, 8.5834557246553412e-03], + [4.5107302040775932e-01, 4.1738646923930062e-01, 4.0248498263611154e-02], + [4.7021877872848655e-01, 4.3159759008670606e-01, 1.8211422886464101e-01], + [4.9982138540914733e-01, 4.6103556754886116e-01, 2.7920789605357577e-01], + [5.2584046409814511e-01, 4.8280096967513136e-01, 3.8521815236862256e-01], + [5.1112547137391717e-01, 4.7366609106110108e-01, 4.3869321045299259e-01], + [5.0430340563362086e-01, 4.9989004125420938e-01, 1.1765772759566985e-02], + [4.9448471795149956e-01, 4.8526710383759764e-01, 5.4928957284074195e-02], + [4.7514297915170750e-01, 4.6786269042882112e-01, 1.2090972143658450e-01], + [5.1227040100837329e-01, 5.0533232608529766e-01, 2.1704345230001201e-01], + [4.7746464654711729e-01, 4.7025015967139994e-01, 2.9296638772869138e-01], + [5.0019624730592138e-01, 4.9240694145545200e-01, 3.9089707852436056e-01], + [5.5878189674454082e-01, 5.5197574157148621e-01, 5.0709449419079189e-01], + [4.9558070433985524e-01, 4.9003162582283821e-01, 4.8210867303175980e-01], + [5.9906053539683635e-01, 7.3657440272128893e-02, 3.0977977191136031e-02], + [6.2057682939652903e-01, 1.5748602714937297e-02, 8.1544152184809277e-03], + [5.6619749735175684e-01, 4.6303731755345315e-02, 4.1362817240134199e-03], + [5.8189846339523810e-01, 1.4643653702528778e-01, 4.0850737183159097e-02], + [5.7893753424296512e-01, 5.7104827395581308e-02, 4.8852605351728272e-02], + [5.4227994938304869e-01, 1.0183486150878129e-01, 7.8184872087203854e-03], + [6.4482186403557740e-01, 1.3665502877149527e-01, 8.9497221907900507e-02], + [6.4686955211474095e-01, 1.3747325204754787e-01, 1.2763634230937110e-01], + [6.1106024704583695e-01, 1.8504988014170193e-01, 7.7059987397285114e-03], + [6.4308137533532206e-01, 2.4020975425845834e-01, 4.2110761000190185e-02], + [6.1779432889800234e-01, 2.8065408654132734e-01, 9.6157025572063543e-02], + [5.6255715037782827e-01, 1.9530473112692823e-01, 1.0326681688169659e-01], + [5.9397509376843960e-01, 2.1579466903822370e-01, 1.7518428092606000e-01], + [6.0025775090571798e-01, 2.2218789666349514e-01, 2.1480210879174791e-01], + [6.1334896865082422e-01, 2.7952852199098877e-01, 7.9957442335692359e-03], + [6.3523850049363251e-01, 3.5164475409629581e-01, 2.5701642972167332e-01], + [6.1499205317114503e-01, 3.7197907918329687e-01, 8.3953291858287430e-03], + [5.9874136452505378e-01, 3.2700927550939213e-01, 4.2726701866263851e-02], + [6.3052964829389546e-01, 3.6339388917942783e-01, 1.8202750522427669e-01], + [5.8455668615476863e-01, 3.2624127539215303e-01, 2.8863603612740590e-01], + [6.4075535630443226e-01, 3.4683984373077836e-01, 3.3917743743560336e-01], + [5.9894350835808574e-01, 4.4840178612584286e-01, 9.3275761603468163e-03], + [6.1546136058704026e-01, 4.4763548305379514e-01, 4.6980313599953666e-02], + [6.3002105380383522e-01, 4.4247103118793096e-01, 1.0831304160593647e-01], + [6.0505816639616339e-01, 4.4487570340012783e-01, 1.8887384438268845e-01], + [6.3130260033353403e-01, 4.4914744493897746e-01, 2.8752012106472480e-01], + [5.9237859872654430e-01, 4.1470217450781949e-01, 3.3472532767259383e-01], + [6.2621202558089395e-01, 4.4657061734846965e-01, 4.1413136196798350e-01], + [6.0411659421415298e-01, 5.2658162148211585e-01, 6.2319710869045046e-03], + [6.2220834110059242e-01, 5.2184426927598104e-01, 1.0556527367254448e-01], + [5.9306894762074203e-01, 5.0840595138042977e-01, 1.8980752920134725e-01], + [6.2502588560341643e-01, 5.2903069764676880e-01, 2.9935743219519995e-01], + [6.9089425705004481e-01, 6.0864584317116732e-01, 4.9453081017014106e-01], + [5.8772141255397359e-01, 4.7575883764411375e-01, 4.6754099199523663e-01], + [6.3610536732859535e-01, 5.4283214563152549e-01, 4.0429078414059724e-02], + [5.9974062867931199e-01, 4.9540968951231273e-01, 3.6942879115249100e-01], + [5.9387136175361022e-01, 4.9834753162043105e-01, 4.4884182129052297e-01], + [6.0269053005642881e-01, 5.8740721515039007e-01, 4.9594261073525134e-03], + [6.0154865672249647e-01, 5.6434392197086036e-01, 3.5011822733883563e-02], + [5.7536583947777498e-01, 5.3305702336374161e-01, 8.9754656189803467e-02], + [5.6146321090255757e-01, 5.2692373765861211e-01, 1.5674291084474568e-01], + [6.1047721666574373e-01, 5.7305307655869531e-01, 2.7273974984359683e-01], + [6.1630507571832926e-01, 5.7370439736303258e-01, 3.8315886458855247e-01], + [6.3464171264824487e-01, 5.9614419329536905e-01, 4.9382374577442284e-01], + [6.4625826496566663e-01, 6.0925211009814062e-01, 5.6210621899459545e-01], + [6.1653873552317295e-01, 5.6513608160077267e-01, 5.5473745887628634e-01], + [6.1253410733983216e-01, 6.0532200739255926e-01, 4.3538730786188473e-02], + [5.9977082870316567e-01, 5.9296610220253598e-01, 1.1386751860048847e-01], + [6.1416867432689726e-01, 6.0648671341392113e-01, 2.0664338436576096e-01], + [6.0479433741380462e-01, 5.9736218606600411e-01, 3.2998504061746353e-01], + [6.0600881932851991e-01, 5.9730279679220166e-01, 4.2348312634690483e-01], + [6.3176367161214997e-01, 6.2408761781449706e-01, 5.2350847511329068e-01], + [6.1563275895946534e-01, 6.0518029375357429e-01, 5.9599526589518903e-01], + [7.5323144980379397e-01, 1.0861402435879351e-02, 3.1510766102748008e-03], + [6.9413172032298565e-01, 4.7881468129004984e-02, 7.1764917361245842e-03], + [7.0498573619533567e-01, 4.0436562262347694e-02, 3.1907504709316983e-02], + [6.6318232666820842e-01, 1.1178749041145158e-01, 8.6855616278748611e-03], + [7.1895503934363181e-01, 8.7018041516442393e-02, 4.0940597755849947e-02], + [7.0883107513874921e-01, 9.5259077448675719e-02, 8.5885894140530231e-02], + [7.1044027555996936e-01, 1.5539277227130613e-01, 4.6003304480287895e-02], + [6.7094570721454816e-01, 2.1540798293652241e-01, 9.9003402926264791e-02], + [7.3726372213103730e-01, 2.0120591138390628e-01, 6.3330029472260258e-03], + [7.2535990711185860e-01, 2.2624948624349434e-01, 3.4426128257172763e-02], + [7.4943170365824863e-01, 3.0157432495679670e-01, 9.2544400144772845e-02], + [6.8854840093630987e-01, 2.9043887112513372e-01, 1.8417073624914962e-01], + [7.1487202122467575e-01, 2.1692233279047562e-01, 1.6832202183936754e-01], + [7.1113980663229170e-01, 2.0580917062170548e-01, 1.9658258029640699e-01], + [7.0862581145088799e-01, 3.0051664725836746e-01, 9.0375577769021293e-03], + [7.2559099768893942e-01, 3.5086651645987121e-01, 4.4463528947794012e-02], + [6.9976966783234240e-01, 4.0235081392805194e-01, 1.0609013930920332e-01], + [7.3060799068591520e-01, 3.5874278455882780e-01, 1.6503495153218029e-01], + [7.7071153374020507e-01, 3.8994234360209029e-01, 2.9403408560313937e-01], + [6.9273527145495328e-01, 3.1684555407849985e-01, 2.7623041786634467e-01], + [7.2895448651084815e-01, 3.0489701941391251e-01, 2.9729252240863141e-01], + [7.3402085029592845e-01, 4.1826642586299084e-01, 8.2552421683435191e-03], + [7.1007493547977707e-01, 4.5604633264906536e-01, 4.3867016239872124e-02], + [7.1854679417973355e-01, 4.7634806687518627e-01, 1.9542618496532552e-01], + [7.3916133786081306e-01, 4.5344571678225276e-01, 2.7882703052214924e-01], + [7.2108175721780465e-01, 4.7041021811122835e-01, 3.7141036968275626e-01], + [7.1830258428432658e-01, 4.3777771897688744e-01, 3.9603406834479166e-01], + [6.8657553269637039e-01, 4.7913940499263702e-01, 4.7282874398006858e-01], + [7.2063884699830305e-01, 5.2240200384423563e-01, 8.5936151150462665e-03], + [7.2332259497827944e-01, 5.7311497037156933e-01, 4.7222028374995000e-02], + [7.2929185041480082e-01, 5.3805834685298926e-01, 1.0923185650562103e-01], + [7.1012405239465226e-01, 5.6184246496149159e-01, 1.9276819493979222e-01], + [7.2286075907297809e-01, 5.4874416262852876e-01, 2.9515010653911616e-01], + [7.4579326480417751e-01, 5.7065273356241364e-01, 4.0290763405487756e-01], + [6.9861187296201388e-01, 5.4017087948939668e-01, 4.4950387910698431e-01], + [7.5404451463130917e-01, 5.6709056244104306e-01, 5.2605044602020257e-01], + [7.2397512234763794e-01, 6.1491522630864681e-01, 9.3878757432205463e-03], + [7.3143502655399628e-01, 6.6845087073040754e-01, 4.9440044387374323e-02], + [7.3403434455022387e-01, 6.3329823096038984e-01, 1.0901896438990925e-01], + [7.1802679904437150e-01, 6.3339417232134865e-01, 1.9359283505121264e-01], + [7.1599225727118998e-01, 6.2020755930858706e-01, 2.9534549823022643e-01], + [7.1541570677741240e-01, 6.1722548275851474e-01, 4.1622834427508315e-01], + [7.9072742254948636e-01, 6.7347374643715907e-01, 5.5680622073769404e-01], + [7.0477754446916863e-01, 6.0773710058708541e-01, 5.6310602190192804e-01], + [7.0989523814436173e-01, 5.9039883078919209e-01, 5.8132542318156433e-01], + [7.0013568542386118e-01, 6.6120561627484697e-01, 9.6195321700066096e-03], + [7.2787048256603071e-01, 7.1376305084319080e-01, 4.0614449657564167e-02], + [6.8736789642473040e-01, 6.5114989928202671e-01, 9.9153203388406483e-02], + [6.7303466119934252e-01, 6.3235474456155472e-01, 1.8535393869101849e-01], + [7.3170741448498955e-01, 6.9300413166821695e-01, 2.8809230008889086e-01], + [7.2494037655166577e-01, 6.8249819510004894e-01, 3.9554065011790296e-01], + [7.3099998525978049e-01, 6.9494571871310573e-01, 5.1422796876874743e-01], + [7.6832311319550417e-01, 7.2275704150590159e-01, 6.2859356775319386e-01], + [7.5284123738854358e-01, 7.1286782228371370e-01, 6.8141989440662898e-01], + [7.2680264617712753e-01, 6.7594849521038625e-01, 6.7125401382420902e-01], + [6.8861720173515462e-01, 6.8751523736603670e-01, 1.0584765078133999e-02], + [7.1628010766417460e-01, 7.1006809803223425e-01, 9.7442624260901098e-02], + [7.2142676006795114e-01, 7.1267998562748591e-01, 1.8847232693358526e-01], + [7.0181350576402102e-01, 6.9474288279610574e-01, 3.0368555582703993e-01], + [7.1787219737907937e-01, 7.0927251145704040e-01, 4.2566263624966677e-01], + [7.2189464308181617e-01, 7.1646235169725458e-01, 5.2864789280027902e-01], + [7.3170618901660256e-01, 7.2216804332430906e-01, 6.2018754348123550e-01], + [7.0236915343958672e-01, 6.9575387094263674e-01, 6.5586651836891818e-01], + [7.3064558989808948e-01, 7.2123657045696477e-01, 7.1615895682997133e-01], + [8.6792400257108182e-01, 1.6886659307107761e-02, 6.5388021757727625e-03], + [8.0178092447900107e-01, 5.3712108070149880e-02, 8.1783681994283802e-03], + [8.2439823436250914e-01, 9.2082349848248063e-02, 4.3742663170159235e-02], + [8.0686830339120830e-01, 3.6682905853854739e-02, 2.7209328327232284e-02], + [7.7596070207692680e-01, 1.1491410654873234e-01, 9.4715685378725309e-03], + [8.2046999824751476e-01, 1.6615946964846942e-01, 4.8951806579657170e-02], + [8.7100272064249673e-01, 2.2120026812685262e-01, 1.1658505789682655e-01], + [7.7507014400974550e-01, 1.5003957710245586e-01, 1.0113677634421994e-01], + [8.1816809907944543e-01, 8.8973565869349666e-02, 7.9631397284792405e-02], + [8.3487246440969609e-01, 1.9368863585125407e-01, 9.0384699411045384e-03], + [8.1288544718791478e-01, 2.5333397624253284e-01, 4.1280918782274481e-02], + [8.5028212275536974e-01, 2.8925446423274864e-01, 9.4208079631191377e-02], + [7.7351076981571298e-01, 2.3032979161149530e-01, 1.1406296235844980e-01], + [8.3405045319682825e-01, 2.0834732403269596e-01, 1.6649160573700211e-01], + [7.9306233719643704e-01, 1.6686847770046159e-01, 1.5832143888150971e-01], + [8.0011225466004809e-01, 3.1602811693392369e-01, 8.0130563506995989e-03], + [8.4137803596893745e-01, 3.6009096469791785e-01, 1.5865738438916818e-01], + [8.1304489603104280e-01, 3.0872696674226452e-01, 2.0040978736266019e-01], + [7.9395486970242057e-01, 2.8517696175234442e-01, 2.4272272782693044e-01], + [8.3057966517914994e-01, 2.7712724434731334e-01, 2.6943515817229335e-01], + [8.2776507769236407e-01, 3.5877378855391623e-01, 4.1772770528085541e-02], + [8.1737018336722678e-01, 4.1754905846307111e-01, 1.0439423046242928e-01], + [8.1114371876867541e-01, 4.0734481790682153e-01, 2.3065740202203286e-01], + [8.1247639902762037e-01, 3.7652972857402234e-01, 3.3980157115212839e-01], + [8.3022479413953620e-01, 3.9587778809675489e-01, 3.8885970422347949e-01], + [8.3265102341927211e-01, 4.3561807442449707e-01, 7.6254051020547102e-03], + [8.1865199693584201e-01, 4.7518393177482682e-01, 4.1140355036914898e-02], + [8.0301383026609352e-01, 5.1554851552332037e-01, 1.0057027654611680e-01], + [8.1239839574027106e-01, 4.7283725290622780e-01, 1.8728387454335707e-01], + [8.6073625966515421e-01, 4.9628963520175373e-01, 3.3074142313942295e-01], + [8.2762145659768971e-01, 5.1822794126631333e-01, 4.1428979939374760e-01], + [8.1484265051344951e-01, 4.5665659538697539e-01, 4.1314258295126366e-01], + [7.5927402390291499e-01, 4.3101527708112836e-01, 4.2241976981231205e-01], + [8.2451876746006258e-01, 5.5572946604073947e-01, 8.3941573663200222e-03], + [8.1246104445464740e-01, 5.9026323981813811e-01, 4.2856420842698389e-02], + [8.0991298739011663e-01, 5.6979935573548257e-01, 1.8245179998214589e-01], + [8.2458113227723495e-01, 5.4833797592083910e-01, 2.9176837443895987e-01], + [8.2359968108966197e-01, 5.7575128669281961e-01, 3.9662075839303529e-01], + [8.3151565773996083e-01, 5.4949495612052779e-01, 5.0169472670434601e-01], + [8.0466794237331618e-01, 5.4724794679835176e-01, 5.3729711973170591e-01], + [8.2903755478925789e-01, 6.6714604049442572e-01, 7.6145341574274166e-03], + [8.2184347443014749e-01, 6.4676338846103287e-01, 1.0426011331076375e-01], + [8.1197167242572366e-01, 6.5941827836007116e-01, 1.8929238390090417e-01], + [8.1561902867383462e-01, 6.3222211981121557e-01, 2.7996232302317497e-01], + [8.2567324905735939e-01, 6.4542552997256886e-01, 3.8010273159423807e-01], + [8.5897714754219945e-01, 6.9432790583106407e-01, 5.0098635315594342e-01], + [8.2060648183047336e-01, 6.1565469292903752e-01, 5.1455589735903584e-01], + [8.6175559333046603e-01, 6.7701148606457462e-01, 6.3335564988928139e-01], + [8.0889402183667147e-01, 6.3526921958637350e-01, 6.2796331375894698e-01], + [8.1166731103682888e-01, 7.4170040758698708e-01, 7.7782199744186086e-03], + [8.1633455677317412e-01, 6.9632135404011875e-01, 4.1352816928385890e-02], + [8.3601121011800761e-01, 7.4540387088100513e-01, 9.5515020506588638e-02], + [8.2943223993801618e-01, 7.3521280753775775e-01, 1.7210372614155631e-01], + [8.1702944283086731e-01, 7.1828377307513225e-01, 2.7305273300937410e-01], + [8.1305444542316363e-01, 7.0914678222064009e-01, 3.7876498139795423e-01], + [7.9880928832571996e-01, 6.9668804771942516e-01, 4.5665541897540995e-01], + [8.2385102042313763e-01, 7.4311049861652023e-01, 5.6175356277115718e-01], + [8.1244876390225940e-01, 7.0693206208375425e-01, 6.5489707476852010e-01], + [8.1598503616585538e-01, 7.2027291754685863e-01, 7.0868536497477341e-01], + [7.8790791244431257e-01, 7.7317617056018351e-01, 4.0589042278413347e-03], + [8.1814423796705038e-01, 7.7572624862752582e-01, 3.5841448844349977e-02], + [8.1494145144577879e-01, 7.8121739603952589e-01, 9.7312715450045259e-02], + [7.7478276475413521e-01, 7.3179250452629974e-01, 1.5753256433755339e-01], + [8.1504804486264160e-01, 7.7376866293479318e-01, 2.4067545201521201e-01], + [8.2651493883729910e-01, 7.8328533808730583e-01, 3.6624560051540189e-01], + [8.3242032820787437e-01, 7.9648266224332898e-01, 5.9975728879468759e-01], + [8.6415224543266378e-01, 8.2118240463890813e-01, 7.1071671639403577e-01], + [8.5356211464816634e-01, 8.0994460337407992e-01, 7.6918341815320079e-01], + [8.4138863487177729e-01, 8.0338987004211793e-01, 7.9873510546318605e-01], + [8.1760351852212132e-01, 8.1118701969433837e-01, 2.5812826201621036e-02], + [8.1700755180626528e-01, 8.1177121265357144e-01, 7.8250732890298949e-02], + [8.2083056699696599e-01, 8.1298853963101558e-01, 1.6524476141339414e-01], + [8.0643909906324107e-01, 7.9850571056682285e-01, 2.8484953320840667e-01], + [8.0877354754181419e-01, 8.0036524038739176e-01, 4.1144853561560857e-01], + [8.1925388287646495e-01, 8.1106837055012893e-01, 5.4397282107203437e-01], + [8.2188172523945502e-01, 8.1406453190587658e-01, 6.6024819619243336e-01], + [8.2213315581107294e-01, 8.1363183988616383e-01, 7.4788973441815576e-01], + [8.1301253312931776e-01, 8.0480937777756256e-01, 7.8873278152429083e-01], + [9.4009442335893834e-01, 1.5030530623966571e-02, 8.8751476032489889e-03], + [8.8924744034997083e-01, 5.9768701810902672e-02, 8.5095294382352103e-03], + [9.0732408882458215e-01, 6.9597646621472609e-02, 4.1092929642223427e-02], + [8.9629278286061942e-01, 4.5289510545227236e-02, 4.2765906691527536e-02], + [8.7466164823005199e-01, 1.1933758703433686e-01, 9.0692633332533964e-03], + [9.0630197727698825e-01, 1.4220434542549973e-01, 4.5729244676267475e-02], + [9.4819669467393219e-01, 2.0516259598828623e-01, 1.0763855461001838e-01], + [8.8506831886914561e-01, 1.4477812091881692e-01, 9.9955451604756398e-02], + [8.9112041563977418e-01, 1.2340397009862644e-01, 1.1317173314805812e-01], + [9.1898530335548789e-01, 1.8907413337020362e-01, 7.8023319486825596e-03], + [9.0293156949593711e-01, 2.3504152489590607e-01, 4.2281916824789219e-02], + [9.2787796757421614e-01, 2.7704056062359800e-01, 1.0141524670184521e-01], + [9.0477319678436563e-01, 3.0080610110144901e-01, 1.9701347034253761e-01], + [8.9492248947211805e-01, 2.3117111050996980e-01, 2.0784550960830855e-01], + [8.8637253766966873e-01, 1.8211691749700207e-01, 1.7844269718005454e-01], + [8.7329903045987867e-01, 2.8078490707958614e-01, 7.8567371221223807e-03], + [9.0892745535344888e-01, 3.4502573328812719e-01, 3.8438744158716912e-02], + [8.9464428842834964e-01, 3.8228417871226039e-01, 9.2734831710080123e-02], + [9.1188480390397408e-01, 3.5917262469498129e-01, 1.7734771898741897e-01], + [8.7783256801876852e-01, 2.9610073000228332e-01, 2.2862553675557201e-01], + [8.9868286035506884e-01, 3.4580255828280498e-01, 3.1214669031904152e-01], + [9.2519247730835430e-01, 2.8752772740984278e-01, 2.8362497428552030e-01], + [9.0266887074260582e-01, 3.9961148992681822e-01, 7.2925018748770708e-03], + [9.0003467044492658e-01, 4.9970886649629898e-01, 8.8626747086569257e-02], + [9.0222224278099428e-01, 4.6828446558809816e-01, 1.7154184741167483e-01], + [9.0501033443610890e-01, 4.1794423504971828e-01, 2.6615469707570466e-01], + [8.7352511278893352e-01, 4.1279181055057990e-01, 3.2544145418887455e-01], + [9.0612011172585039e-01, 4.0081071487142711e-01, 3.9408107125122982e-01], + [9.0275109622928862e-01, 5.4511150506642969e-01, 5.4043846571779396e-03], + [9.0227467608963807e-01, 4.7070670416813082e-01, 3.5594948199721949e-02], + [8.9333535037506961e-01, 5.6872631917541139e-01, 1.6379951808702273e-01], + [8.9558985583451556e-01, 4.9868247474951138e-01, 2.5575207061408212e-01], + [9.0972504737105331e-01, 5.3460177260754616e-01, 4.4382830969191350e-01], + [8.9833046466477307e-01, 4.7780574361536166e-01, 4.4262369644136151e-01], + [8.7157534183989382e-01, 5.1661632757928566e-01, 5.0966779775928528e-01], + [9.0197037806054092e-01, 5.9585749188027526e-01, 3.2994343373189225e-02], + [8.8627478687335703e-01, 6.0763214249779951e-01, 8.8522165473662096e-02], + [8.9116683038757472e-01, 6.7353532254658488e-01, 1.7162229963256598e-01], + [8.9385362357136966e-01, 6.0663705352338504e-01, 2.5917695477269775e-01], + [9.1111129077787067e-01, 6.1926725969546692e-01, 3.6192541175716197e-01], + [9.0552659513735212e-01, 6.3249916911145920e-01, 4.5869254963804951e-01], + [9.1168609944782353e-01, 6.4516165101109435e-01, 5.5042334581206709e-01], + [8.9753719569010948e-01, 6.0022137104345574e-01, 5.6670953755114850e-01], + [9.0053947034420478e-01, 6.4090350395584472e-01, 6.3577865320889526e-01], + [9.0941131035546596e-01, 6.7430749629511788e-01, 4.8025364209491155e-03], + [8.9790426059544570e-01, 7.0785986425342806e-01, 3.4980337288732566e-02], + [9.0038573629422858e-01, 7.2691876340813066e-01, 9.2420128992236478e-02], + [9.0164176543782182e-01, 7.6028715158721449e-01, 1.9213446510444618e-01], + [8.9759556653831030e-01, 7.1320267064495635e-01, 2.8382255139151413e-01], + [9.0253382636061164e-01, 7.0987607101466155e-01, 4.0270993805661237e-01], + [9.3751945331973252e-01, 7.5021327737592869e-01, 5.4318034747601440e-01], + [8.9812863547019950e-01, 7.2511302096929853e-01, 6.0862565293123994e-01], + [9.3375287620253000e-01, 7.6324014457545342e-01, 7.0670066698113554e-01], + [8.9629450707205660e-01, 7.1930404579721829e-01, 7.0819640021655172e-01], + [9.0222830947708221e-01, 7.8892964440937963e-01, 6.9518988279782812e-03], + [8.9537473616301599e-01, 8.0530943084961992e-01, 3.8594953968566131e-02], + [9.1597696263415840e-01, 8.1298882179113996e-01, 9.7124900396605235e-02], + [9.0905337225377936e-01, 8.1568895642071715e-01, 1.6596550085584150e-01], + [9.0263673103728348e-01, 8.0784633494445690e-01, 2.7588175349810173e-01], + [8.9458449455733613e-01, 7.8700372738222246e-01, 3.7903753222386577e-01], + [8.9580303514007986e-01, 7.9287612205717939e-01, 5.0100871125134994e-01], + [9.0938439448366482e-01, 8.1269106261012802e-01, 6.2338159902407153e-01], + [8.8198392100929734e-01, 7.8050019533674386e-01, 6.8016711965171062e-01], + [9.0824827943146191e-01, 8.1128568538044898e-01, 7.7543876297151815e-01], + [8.9510607061079428e-01, 7.9318746258423489e-01, 7.8922929285761412e-01], + [8.8729678703707771e-01, 8.4801999059916477e-01, 4.4402297507171694e-03], + [8.8820022641529928e-01, 8.5672512619906749e-01, 3.1860034043813053e-02], + [9.0139093966900941e-01, 8.5893550162228738e-01, 9.0113751560928476e-02], + [8.8718847916401178e-01, 8.4629964200604824e-01, 1.8439668343447629e-01], + [8.9612211055172031e-01, 8.5636214431011048e-01, 3.1103856630436372e-01], + [9.0557417250875272e-01, 8.6095373594039037e-01, 4.4228264302516307e-01], + [8.2873648810832135e-01, 7.8471826887068286e-01, 4.8526826175639048e-01], + [9.1033725784297703e-01, 8.6872165457193629e-01, 5.7532866441822461e-01], + [9.0802087277671206e-01, 8.6997160514427707e-01, 6.7880628096797835e-01], + [9.2869023861484112e-01, 8.8722175450999718e-01, 8.1292251809223171e-01], + [9.1633584431034121e-01, 8.7991915896531225e-01, 8.6656174594048507e-01], + [8.9055057831634110e-01, 8.8471978801900031e-01, 8.9629082946340120e-03], + [8.9332312743571929e-01, 8.8540107710415517e-01, 6.2709618683477228e-02], + [9.0491825990615649e-01, 8.9663739014645050e-01, 1.3612656380563770e-01], + [8.9203386988665567e-01, 8.8435082246337682e-01, 2.5172216945124604e-01], + [8.9316417201929177e-01, 8.8514530858831020e-01, 3.8450270422784100e-01], + [8.9531183787490598e-01, 8.8667919484421820e-01, 5.2001093038861668e-01], + [9.0154669411404376e-01, 8.9457894523025894e-01, 6.5824416339156500e-01], + [9.0514298638545909e-01, 8.9720065845897057e-01, 7.7440669462714595e-01], + [9.0880005563350030e-01, 9.0034291260053589e-01, 8.5542329118109950e-01], + [8.8912024845809279e-01, 8.8427539420817802e-01, 8.7757254546680641e-01], + [9.8753972411830948e-01, 6.5465806902918772e-03, 4.7412849707490802e-03], + [9.5164793558218963e-01, 4.2839759051657975e-02, 7.3878611105921014e-03], + [9.5851923797956795e-01, 1.3488430970545148e-01, 9.6734550516636000e-02], + [9.6894881540958733e-01, 5.0249005261627164e-02, 4.0021486026065978e-02], + [9.5107225953588037e-01, 1.0894164268338927e-01, 8.3072796489022192e-03], + [9.6211877113763611e-01, 1.0294579634658149e-01, 4.0861763647665549e-02], + [9.8981653321017293e-01, 2.0329876857277626e-01, 9.6893142294440446e-02], + [9.3466097126649494e-01, 2.1700584244405743e-01, 1.7069540903327296e-01], + [9.4333849618442722e-01, 1.0299593404970848e-01, 9.6240419906433852e-02], + [9.6618801145572109e-01, 1.9980726772268637e-01, 9.9401044390412217e-03], + [9.6434372668116275e-01, 1.9395099052887310e-01, 4.4410452236515296e-02], + [9.7984254079953437e-01, 3.0042125654482565e-01, 9.9403470861134144e-02], + [9.5109854782064218e-01, 3.0858215587394716e-01, 2.3067932527203122e-01], + [9.6021727147680347e-01, 1.9449296433543264e-01, 1.8454336543693778e-01], + [9.4557268874989808e-01, 2.9301905441698672e-01, 7.1420984011219771e-03], + [9.6474125265886268e-01, 3.0633286866837778e-01, 4.2073099750314383e-02], + [9.5066848548154903e-01, 3.7480275449995898e-01, 1.0731793907682453e-01], + [9.6810599449753265e-01, 3.1705545700322280e-01, 1.8031044923168107e-01], + [9.4761108129624416e-01, 4.1521085912246791e-01, 3.3130435919908974e-01], + [9.6192178888700652e-01, 3.1827711810683096e-01, 2.9018310988673834e-01], + [9.8257887325703575e-01, 2.9919722182776443e-01, 2.9685644792278038e-01], + [9.6437037334441600e-01, 4.0894265877564201e-01, 9.7766636871094367e-03], + [9.5983481428078343e-01, 4.3884046931204729e-01, 5.0849647579443040e-02], + [9.5799017579576384e-01, 4.7529246216073251e-01, 1.1835901158132432e-01], + [9.5988520085186291e-01, 4.3445648883738769e-01, 1.9641179411161028e-01], + [9.6577481630624729e-01, 4.4698399231375752e-01, 2.8775880416909894e-01], + [9.6832108465297062e-01, 5.4795443053003945e-01, 4.4855550967562774e-01], + [9.5567066162518588e-01, 4.6236205895097865e-01, 4.2240437966850958e-01], + [9.6592577232827681e-01, 4.1989081610279611e-01, 4.1160653755435012e-01], + [9.5326381450093822e-01, 5.2078612391763346e-01, 8.5846434173267368e-03], + [9.6051001231392719e-01, 5.6356564513389307e-01, 4.4664368828459103e-02], + [9.5839644521071377e-01, 5.7117487683712964e-01, 1.1051229202323394e-01], + [9.5894279102465541e-01, 5.5919793196246537e-01, 1.9883635488691534e-01], + [9.5603876323229497e-01, 5.5453096431339322e-01, 2.9295912441713118e-01], + [9.4121974703330991e-01, 5.4569211331047762e-01, 3.7954945935895273e-01], + [9.5898488724310993e-01, 5.9080148886233519e-01, 5.4847357454419909e-01], + [9.4380036661067301e-01, 5.4055373052317346e-01, 5.3353834905106789e-01], + [9.6565583174008496e-01, 6.5292651334258589e-01, 6.1472379906096766e-03], + [9.6013668614991166e-01, 6.7650612837928137e-01, 2.9277993247492473e-02], + [9.5041262718648678e-01, 6.6444262418223299e-01, 8.9517049394522707e-02], + [9.5439031998909973e-01, 6.6750397092590619e-01, 1.8073976545738976e-01], + [9.5396544423948071e-01, 6.6529073205332601e-01, 2.8454003146388995e-01], + [9.6774515832237318e-01, 6.8148416925897348e-01, 4.0535446413749016e-01], + [9.6850025815730245e-01, 6.7340674365376540e-01, 4.9990843973746196e-01], + [9.6689212116385870e-01, 6.7489758984666615e-01, 5.7861201665810325e-01], + [9.4535478228901482e-01, 6.9363756214412242e-01, 6.5450990291461630e-01], + [9.6215548920741822e-01, 6.7580137826108522e-01, 6.6703064605976814e-01], + [9.5503028519919653e-01, 7.7560579150194819e-01, 1.0664305475904617e-02], + [9.6124086565418088e-01, 7.6176289820292153e-01, 5.9282604177369323e-02], + [9.5559310480666571e-01, 7.6463652631342283e-01, 1.4420126555149337e-01], + [9.5726195056748831e-01, 7.6854924344850728e-01, 2.5299057225730232e-01], + [9.5891163787592537e-01, 7.6830918356408351e-01, 3.7032543309161231e-01], + [9.5880529705398831e-01, 7.8098552415042022e-01, 4.8683940800774250e-01], + [9.8864864477907766e-01, 7.8880918866625849e-01, 5.7321753276751397e-01], + [9.6231580808374784e-01, 7.7963752666059682e-01, 6.5225265415365807e-01], + [9.7662010874497507e-01, 7.7884456827103321e-01, 7.2625279912359619e-01], + [9.5724648860667216e-01, 7.9052348310314025e-01, 7.8084038518006371e-01], + [9.6891483575823134e-01, 8.6937760105662765e-01, 6.6388031511092306e-03], + [9.5468552822955266e-01, 8.3799540407255957e-01, 4.3232763111877667e-02], + [9.6719704267527495e-01, 8.5704132080032858e-01, 1.0811692460447433e-01], + [9.6328890211557183e-01, 8.5637500550799506e-01, 2.0546442946716120e-01], + [9.6001769857830066e-01, 8.5262683321846899e-01, 3.1779813973856796e-01], + [9.5402070950575524e-01, 8.4825790044918437e-01, 4.2743095343363186e-01], + [9.5957807124119543e-01, 8.5943496316113244e-01, 5.4777437255689143e-01], + [9.6620403338293881e-01, 8.6136460426989581e-01, 6.5921970840648325e-01], + [9.5114759930358028e-01, 8.5392619467516240e-01, 7.5006404551517092e-01], + [9.7152349276499317e-01, 8.6854932053453349e-01, 8.2711574843865099e-01], + [9.5554474616641527e-01, 8.8101664808740654e-01, 8.7402993763459369e-01], + [9.4670616084020942e-01, 8.9742944496051003e-01, 1.2558669107728104e-02], + [9.5860261052818108e-01, 9.1262020571870850e-01, 5.5706474900651004e-02], + [9.5620537827897789e-01, 9.1108694068233564e-01, 1.4231332077414610e-01], + [9.5295846681244756e-01, 9.0848601956875497e-01, 2.5158620722475206e-01], + [9.5897055866117409e-01, 9.1439584762638404e-01, 3.8440758774988826e-01], + [9.6155866470826723e-01, 9.1819136053681416e-01, 5.2295848624451990e-01], + [9.6517885669684866e-01, 9.2345727918747378e-01, 6.5790069843248322e-01], + [9.5471339872834315e-01, 9.1227028361057150e-01, 7.6176299727303731e-01], + [9.8932724643879988e-01, 9.4435578224259764e-01, 8.4562004715268979e-01], + [9.6749359456851658e-01, 9.2872643078679884e-01, 8.8699953283125377e-01], + [9.5698655674554645e-01, 9.4495446254751703e-01, 9.3878707666138195e-01], + [9.6039420978489121e-01, 9.4977519290400048e-01, 6.6358780379204285e-03], + [9.4411391615507168e-01, 9.3600102382274619e-01, 3.9210744431767024e-02], + [9.6464507039068637e-01, 9.5571192696553664e-01, 1.0283250410633811e-01], + [9.5507922743094653e-01, 9.4630661913318781e-01, 2.0972145725106242e-01], + [9.5516882561813854e-01, 9.4663404694114184e-01, 3.3566093147732445e-01], + [9.5612858925483979e-01, 9.4759264461208426e-01, 4.7577480286919088e-01], + [9.5748953143433713e-01, 9.4940326808851561e-01, 6.1669326942583813e-01], + [9.6068729754438265e-01, 9.5268033787577877e-01, 7.5043558215702022e-01], + [9.6693828562111361e-01, 9.5844580242348409e-01, 8.5915863293109762e-01], + [9.6718090297140868e-01, 9.6312361577949035e-01, 9.2892078555653435e-01], + [9.9067580646083198e-01, 4.1080798732005541e-02, 1.0101076319727384e-02], + [9.9309652467987852e-01, 9.6380157630629779e-02, 5.6055710284199853e-02], + [9.9808236709166420e-01, 4.4647237955014844e-02, 3.8474381721136611e-02], + [9.9049094553336647e-01, 9.9308544603751117e-02, 7.0820832938658634e-03], + [9.9308579864561008e-01, 1.3243244461131706e-01, 3.6029163833791525e-02], + [9.9372395975123440e-01, 1.6522354029997605e-01, 1.2475302344861017e-01], + [9.8888475449840674e-01, 1.1257282141702979e-01, 1.0532469488648138e-01], + [9.9176894882886513e-01, 1.8837290283297856e-01, 6.1987799508690863e-03], + [9.9514190511468104e-01, 2.1907616338866112e-01, 3.7870527748198747e-02], + [9.9980393500715736e-01, 2.9918461417474396e-01, 9.9445693751694472e-02], + [9.9643391890430288e-01, 2.9737454060093271e-01, 1.8729830322034732e-01], + [9.8298591825468273e-01, 2.3453258063631247e-01, 1.8079522977901830e-01], + [9.9449254657885944e-01, 2.0872593636812245e-01, 1.9967664006815833e-01], + [9.9020606775005060e-01, 3.0416491234680881e-01, 8.5828668157099351e-03], + [9.9446870848808888e-01, 3.2256768502528771e-01, 4.3910558187206870e-02], + [9.9126356033250629e-01, 4.2075946037280998e-01, 9.4557068642113551e-02], + [9.9246720227736718e-01, 3.7355749350805756e-01, 1.7230661827590882e-01], + [9.8946322623351290e-01, 3.7931619495734098e-01, 2.9168578775383408e-01], + [9.9412394472863030e-01, 2.9626159096214144e-01, 2.6069653731886344e-01], + [9.9596341708681324e-01, 3.6190282196995943e-01, 3.4969031652181903e-01], + [9.9456455500527141e-01, 4.3069649582140324e-01, 3.5098372809999729e-03], + [9.9259242353248600e-01, 4.3367848979556517e-01, 3.3272603044257311e-02], + [9.9220195913510512e-01, 5.4882675080807530e-01, 9.9284701202435099e-02], + [9.9128924422819253e-01, 4.6378065854050082e-01, 1.7500617509873076e-01], + [9.9452300116743242e-01, 4.4986427629254672e-01, 2.7229937404184013e-01], + [9.9331333401029487e-01, 5.1071736582209537e-01, 3.9878929378771855e-01], + [9.9062975031090494e-01, 4.3791551207272816e-01, 3.9407175372434444e-01], + [9.9545514440693583e-01, 4.6955751177361554e-01, 4.6294671706900287e-01], + [9.9011984422017818e-01, 5.5343495916524721e-01, 9.2756759318662566e-03], + [9.9295335855354139e-01, 5.5729696685778618e-01, 4.3984138134562638e-02], + [9.9077837311693140e-01, 6.7771104044813257e-01, 1.2198388525536065e-01], + [9.9252120105012964e-01, 5.7736729200107706e-01, 1.8449940886309041e-01], + [9.9201447764866180e-01, 5.5901030355201597e-01, 2.7951163002740997e-01], + [9.8910975308015048e-01, 5.8637162076147009e-01, 3.8983074184957434e-01], + [9.9466041391252047e-01, 6.4581647699538491e-01, 5.3672879118218542e-01], + [9.9315366413718165e-01, 5.6374510360351560e-01, 5.1820719535020743e-01], + [9.8601414320859193e-01, 5.5423133050051332e-01, 5.4411677849951146e-01], + [9.9539064261338983e-01, 6.7663342998374010e-01, 1.0153418915056592e-02], + [9.9144480546376379e-01, 6.7847763297045782e-01, 5.2307165889770788e-02], + [9.9152869287212042e-01, 6.9165205509336558e-01, 2.1272015418202098e-01], + [9.9062042723256494e-01, 6.7372818971729087e-01, 3.0454909305813943e-01], + [9.9622830326499712e-01, 6.9771052702185610e-01, 4.0471336227422394e-01], + [9.9760665322970876e-01, 7.0113785598957978e-01, 5.0465304585990545e-01], + [9.9429663254536649e-01, 7.7479717250963320e-01, 6.6062639380672794e-01], + [9.9157880062828563e-01, 6.9727544136267672e-01, 6.5317097779324662e-01], + [9.9368119572890445e-01, 6.5224749650000624e-01, 6.4456805583441468e-01], + [9.8948030509115370e-01, 7.8071511695732432e-01, 8.5114522055793253e-03], + [9.9338339557215194e-01, 7.9265429001442222e-01, 4.4908786591230682e-02], + [9.9119504055984919e-01, 7.8720891322498565e-01, 1.1598680923423910e-01], + [9.9165682537732902e-01, 7.9585105066378248e-01, 2.1339316719459339e-01], + [9.8985459548306931e-01, 7.5948573689619148e-01, 2.9805083313757513e-01], + [9.9249572296064792e-01, 7.9506453134417188e-01, 3.4229642751589101e-01], + [9.9203972742346658e-01, 7.9708150593002536e-01, 4.6329548808581372e-01], + [9.9560982093389716e-01, 8.3026253587788312e-01, 7.8585558751133677e-01], + [9.9209100546241658e-01, 7.7389099951055518e-01, 7.6484829924675390e-01], + [9.9683890133391739e-01, 8.7988009713694293e-01, 5.6267156411194605e-03], + [9.9017897317734604e-01, 8.7993079588361589e-01, 3.6031364568121220e-02], + [9.9442528571623678e-01, 8.7878135291944837e-01, 9.7431416668394610e-02], + [9.9349698036885015e-01, 8.8541112313524273e-01, 1.9055460314346068e-01], + [9.9261299443226725e-01, 8.7917727253495415e-01, 3.1196511741561833e-01], + [9.9105365859281858e-01, 8.8303300259074824e-01, 4.4466642446272187e-01], + [9.9288046610605163e-01, 8.8243544010898856e-01, 5.7424932784011073e-01], + [9.9488468270530583e-01, 8.7399960065482873e-01, 6.8095278040959351e-01], + [9.9068215232979406e-01, 8.7540250855551194e-01, 7.6915760887848117e-01], + [9.9165226857246069e-01, 8.7793650245520682e-01, 8.7004491058964228e-01], + [9.9032916183946684e-01, 9.4894630749797138e-01, 7.8320144206236487e-03], + [9.9280928237897659e-01, 9.4540658153545643e-01, 3.9014857390543396e-02], + [9.9136942792075033e-01, 9.4230568426062489e-01, 9.8538608513239215e-02], + [9.9144978562187791e-01, 9.4846072148024974e-01, 1.8575874667339443e-01], + [9.9082328336189640e-01, 9.4282668894531774e-01, 2.9336503819272852e-01], + [9.9271032617299693e-01, 9.4731553609138808e-01, 4.1653686169669157e-01], + [9.9192150489023001e-01, 9.4708805428856868e-01, 5.3869807120286539e-01], + [9.9411618273984492e-01, 9.4691483650826269e-01, 6.4927514326036440e-01], + [9.9195212833332269e-01, 9.4114685469830706e-01, 7.5261254372137221e-01], + [9.9781670287533586e-01, 9.2709809894626527e-01, 8.8531754422566089e-01], + [9.8984365442145161e-01, 9.5084473789146795e-01, 9.4235564859467780e-01], + [9.9602116658361539e-01, 9.9105577341042739e-01, 5.1365133497498052e-03], + [9.8824282512260653e-01, 9.7908153019822231e-01, 3.6315582288868963e-02], + [9.9554962678393166e-01, 9.8572658722538786e-01, 9.4734322758639092e-02], + [9.9110261058910032e-01, 9.8321608222229551e-01, 1.8259753372755477e-01], + [9.9140476207368022e-01, 9.8210628616088524e-01, 2.9555193733759894e-01], + [9.9141441718486412e-01, 9.8273065370264179e-01, 4.2586363729876409e-01], + [9.9184639199754565e-01, 9.8322610077197059e-01, 5.6324791383135975e-01], + [9.9208806090975876e-01, 9.8304545387596454e-01, 6.9498072718449333e-01], + [9.9281826542181373e-01, 9.8277503267339106e-01, 8.0720286664309826e-01], + [9.9696912839751572e-01, 9.8969335390048252e-01, 8.9602002203315922e-01], + [9.9395102522489565e-01, 9.8036824425840485e-01, 9.4028464607450557e-01], + [9.9396555130485253e-01, 9.9025704527181180e-01, 9.8368841577688193e-01], + ], + weights: [ + 1.1895723741300887e-05, + 9.1812012837114365e-06, + 8.3274779278013334e-06, + 3.0840957560744458e-05, + 2.2499146630275578e-05, + 2.8512525739053109e-05, + 3.3828186807863343e-05, + 6.4461722408591977e-05, + 4.1454672575655082e-05, + 2.9831947129170658e-05, + 4.1317769451363259e-05, + 1.2507965838166272e-04, + 9.7409414406401530e-05, + 7.6289675960234605e-05, + 2.5337178665913492e-04, + 8.2100364713452354e-05, + 6.0248554352000430e-05, + 1.1984965063449574e-04, + 2.8455484954079092e-04, + 1.4804386492602839e-04, + 1.0178867597693862e-04, + 3.0121437484799369e-05, + 6.2247896678565710e-05, + 8.8176272979214595e-05, + 8.8526542301735364e-05, + 3.5737179463331647e-05, + 4.7277384021020104e-05, + 4.7116024714698781e-05, + 1.5994862380321859e-04, + 1.3327348077687781e-04, + 9.4010207701886997e-05, + 3.1778798500533791e-04, + 2.0016986911324309e-04, + 1.6528844845689633e-04, + 2.8175283870443313e-04, + 3.4589783236642797e-04, + 3.6803345597557249e-04, + 1.6162555693979779e-04, + 1.1031065730828597e-04, + 2.1881358552159610e-04, + 2.8642324524894973e-04, + 1.1416225093438786e-04, + 5.4780744522224648e-05, + 6.0188821650036848e-05, + 1.5448337418294748e-04, + 1.4259207242917952e-04, + 1.2845920570679102e-04, + 4.0995962134675048e-05, + 5.6140194240350212e-05, + 2.3850934861800933e-04, + 1.4946158698946746e-04, + 1.7486701013834393e-04, + 4.2390227790898834e-04, + 4.6517427246703066e-04, + 1.9368344264756614e-04, + 5.0663494559220962e-04, + 5.3400389997870529e-04, + 4.2454966032393386e-04, + 1.8478546410842788e-04, + 1.9116538948530604e-04, + 5.9879088719067117e-04, + 3.3473374608937055e-04, + 4.0641793814775198e-04, + 4.9993854693414989e-04, + 2.7099522789360633e-04, + 1.3241359046147031e-04, + 1.3200946757513195e-04, + 4.6286485489498467e-04, + 9.5315486527404957e-05, + 2.2155542472478251e-04, + 3.8055140482244713e-04, + 3.1713193322044966e-04, + 6.0169138787760377e-05, + 7.5373426599106570e-05, + 1.7556553724800626e-04, + 2.1647305637167966e-04, + 1.1032004244451222e-04, + 1.3278808915738894e-04, + 4.6682522599900113e-05, + 2.5389970076262103e-04, + 7.5227004903943764e-05, + 1.3101264288424937e-04, + 3.2686919216422467e-04, + 1.5256555285122369e-04, + 1.6731933484135869e-04, + 4.3998264160144206e-04, + 4.2247062469726927e-04, + 2.3440766040611652e-04, + 5.3077251437308589e-04, + 6.4445375891383782e-04, + 4.9132167440031204e-04, + 1.9780534649196021e-04, + 6.6376782362536225e-04, + 4.1293044021199418e-04, + 1.9595823348441061e-04, + 2.4634496869876607e-04, + 4.6121770676953389e-04, + 7.6733372456271661e-04, + 6.7327177992146468e-04, + 2.0767515415426858e-04, + 1.9089917274641487e-04, + 3.9163947156643558e-04, + 6.6718318606523973e-04, + 5.3432903201607203e-04, + 4.6991003236324715e-04, + 5.5550202187470022e-04, + 1.7841414461268999e-04, + 4.1875078039596472e-04, + 3.6525941144008555e-04, + 6.5735341784207632e-05, + 1.3321132820549820e-04, + 2.1174168221002994e-04, + 4.5485464436643595e-04, + 4.7490273476295624e-04, + 4.2970902944137215e-04, + 2.7037534701946492e-04, + 4.8241117671557388e-05, + 1.5019620489429818e-04, + 1.8325185037650033e-04, + 2.0567741843901540e-04, + 2.0578378574772350e-04, + 1.8089083735078331e-04, + 1.2547568548103329e-04, + 3.3867771453842403e-05, + 2.4119843915213954e-04, + 5.4230098174437751e-05, + 7.5893650512450967e-05, + 4.5321109168901621e-04, + 1.5875379016089389e-04, + 1.2854592058282098e-04, + 4.6108606398060075e-04, + 1.6442599224031641e-04, + 2.2566925489440450e-04, + 4.5927062920161958e-04, + 7.6481951555307790e-04, + 5.5265265088468159e-04, + 5.5987557184956004e-04, + 2.2960279871283066e-04, + 1.9837184220469108e-04, + 7.8851424522849477e-04, + 2.4822837125463930e-04, + 6.3354092056962794e-04, + 9.1111161791969061e-04, + 5.1200103494205304e-04, + 2.5548311208552899e-04, + 2.3821140979027837e-04, + 4.7933716860870023e-04, + 6.9675341831235452e-04, + 8.4502779399674730e-04, + 9.2740529259710341e-04, + 5.9184528045807573e-04, + 4.5678821384306513e-04, + 1.4219721893849331e-04, + 6.6142060580418440e-04, + 7.2196795096132835e-04, + 6.5382789601621364e-04, + 4.8052716150144548e-04, + 1.9305819917036699e-04, + 3.3893147399147378e-04, + 6.8082556063380558e-04, + 4.4614361799819187e-04, + 5.5202176397441451e-05, + 2.4296410757426853e-04, + 3.7090655141478975e-04, + 3.5393852011508868e-04, + 5.7901786424220886e-04, + 5.3516595039507871e-04, + 3.2684982538428657e-04, + 2.6184082386550246e-04, + 1.6073174040266011e-04, + 1.1600104538230512e-04, + 1.8158337760031834e-04, + 2.1204381334047245e-04, + 2.2764497828078219e-04, + 2.2863881958690084e-04, + 1.4983306346972396e-04, + 7.4631695402784860e-05, + 2.7787684662026367e-05, + 1.0565235211833075e-04, + 1.0315758152013101e-04, + 1.9922598539932085e-04, + 2.6653693326229237e-04, + 1.8281268092954620e-04, + 4.4542954598620065e-04, + 5.3564381849593686e-04, + 1.8990496758834139e-04, + 3.3965027522105465e-04, + 7.5012757084343042e-04, + 8.3955667445133981e-04, + 5.2870908117768928e-04, + 2.2544427515274379e-04, + 2.2457884606454784e-04, + 6.1638837063480350e-04, + 9.5349431228981514e-04, + 8.0268855340883468e-04, + 8.2072767508141312e-04, + 5.9073753859023017e-04, + 2.3477559111043206e-04, + 2.6539302678385335e-04, + 6.1750601334835560e-04, + 9.5063824494890303e-04, + 1.0174914297170141e-03, + 7.3412919551747529e-04, + 5.6236716872562226e-04, + 2.1479938065204033e-04, + 2.5792742920556895e-04, + 4.6924033030962373e-04, + 7.2166907561499439e-04, + 8.0629112435778405e-04, + 9.1253069593778629e-04, + 7.8954318542422304e-04, + 6.0024761509844780e-04, + 5.2196284468625351e-04, + 2.1132359388899158e-04, + 3.5466709382739271e-04, + 6.0984729787494211e-04, + 5.4961458236212134e-04, + 7.7763997244237619e-04, + 6.9869034534258327e-04, + 5.9268073428251321e-04, + 4.0667298068690171e-04, + 2.0851604426195508e-04, + 1.3948207579517883e-04, + 1.5329427836609913e-04, + 3.7311326081207658e-04, + 4.3583280103490950e-04, + 5.0511064560794354e-04, + 5.8251895057243215e-04, + 4.9254109496791871e-04, + 4.2578299758342611e-04, + 2.2420200059074523e-04, + 8.7671015105526159e-05, + 2.7546618780582729e-05, + 1.4007940767360826e-04, + 2.4391849297969171e-04, + 2.3410989450838390e-04, + 2.4117162212673060e-04, + 1.5204061513232720e-04, + 1.6803567660755706e-04, + 1.1154212832833393e-04, + 3.8582520278644458e-05, + 4.0187288104788568e-05, + 1.0229636290974128e-04, + 2.5444515132394497e-04, + 8.6269155621694049e-05, + 1.8940081184290185e-04, + 4.3954562888469021e-04, + 5.2632137989332790e-04, + 4.3271059089383207e-04, + 1.5547884852191228e-04, + 1.7819011243381925e-04, + 4.1655883526100688e-04, + 5.2075851263021331e-04, + 6.3397707592888046e-04, + 3.3435790277260491e-04, + 2.0290596676874012e-04, + 2.1094493501771103e-04, + 6.3557935652584947e-04, + 7.2738521867311422e-04, + 4.4155285032594903e-04, + 2.2938994360549445e-04, + 4.9445099800766827e-04, + 7.8106186469498499e-04, + 6.6516276346197264e-04, + 3.7033346083100024e-04, + 1.7059660268413097e-04, + 2.0429590466378454e-04, + 5.3614525625459363e-04, + 7.5333898020212540e-04, + 8.7564177278046790e-04, + 7.2688816018465262e-04, + 6.6511156892509933e-04, + 3.4575158090003618e-04, + 2.3065283452043358e-04, + 2.2836163802840699e-04, + 5.0446514453819592e-04, + 7.9339917324398033e-04, + 8.5659688102087501e-04, + 6.6531430120682019e-04, + 3.9187922916085244e-04, + 2.2551472201206703e-04, + 1.8808543335858898e-04, + 6.2548646220547211e-04, + 6.0105711564279786e-04, + 8.2265722663199742e-04, + 6.2114149783364775e-04, + 7.2476892442928647e-04, + 6.7055047329232992e-04, + 4.2202784883590635e-04, + 1.6543334414982782e-04, + 1.3544030641504551e-04, + 3.8220784109764452e-04, + 4.2988733745908964e-04, + 4.7920346699420870e-04, + 6.9347510776974640e-04, + 6.6942775329592715e-04, + 4.7435842914283984e-04, + 4.7853245686483995e-04, + 4.0617737810474009e-04, + 1.8315288766055583e-04, + 4.6576575181802912e-05, + 1.9492321382744363e-04, + 3.1026216715650593e-04, + 3.5191327727465476e-04, + 4.3867702718872615e-04, + 5.0374439360688903e-04, + 3.1237591613833576e-04, + 3.2337114943649809e-04, + 2.2183565491382806e-04, + 6.5994963636016623e-05, + 5.9254526713083344e-05, + 9.0100343823126255e-05, + 1.9248391626300437e-04, + 2.4196208168542879e-04, + 2.5831607080345779e-04, + 2.2485443317339694e-04, + 1.8376426302443849e-04, + 1.4671521483713545e-04, + 6.4231209822480275e-05, + 2.2280833291007966e-05, + 8.6420131221501414e-05, + 1.5798394326647106e-04, + 4.1866416090113057e-05, + 1.3716808011037321e-04, + 3.0480356426426251e-04, + 3.1825557659421815e-04, + 2.9798398597708929e-04, + 1.0879636623603214e-04, + 1.1631315920811343e-04, + 3.8655592813643518e-04, + 4.0740921113472555e-04, + 3.7300849576189148e-04, + 2.1273520090877887e-04, + 7.9290159988570374e-05, + 1.6874526982074990e-04, + 3.6069264246153370e-04, + 5.1552403140149540e-04, + 5.1471879267856228e-04, + 2.9066686206900610e-04, + 3.7822170952886696e-04, + 1.0010014231276951e-04, + 1.6965380598017295e-04, + 5.7962715379723893e-04, + 6.7651093945995272e-04, + 5.6313245306301943e-04, + 6.2824298176584949e-04, + 1.5099613889348065e-04, + 1.2832016458351077e-04, + 3.6387118190413459e-04, + 7.0866530875250753e-04, + 6.5498679802809226e-04, + 5.5835162082212451e-04, + 3.6942531276923146e-04, + 1.6581989211655643e-04, + 3.7224369241559569e-04, + 5.7605231630452898e-04, + 6.6509337018958998e-04, + 7.5058052513084868e-04, + 6.6062088979804384e-04, + 6.8184908792230995e-04, + 5.1418792026796398e-04, + 3.1935385020443103e-04, + 1.1422897727996892e-04, + 1.1646308590405381e-04, + 3.5452743282871638e-04, + 4.6509404120909396e-04, + 4.1523055005622224e-04, + 7.7874820058999232e-04, + 7.7633318956461632e-04, + 5.5610658655825531e-04, + 4.6963563548087196e-04, + 2.7709358801338241e-04, + 1.5379175984179359e-04, + 1.2306132690105049e-04, + 2.5324047814773875e-04, + 2.7197964059212301e-04, + 3.1791248349158396e-04, + 5.3088310398263197e-04, + 6.1837625312236185e-04, + 6.5252639120038226e-04, + 4.9886523476647430e-04, + 4.1160732805127211e-04, + 2.7038021582227799e-04, + 7.1694102255140656e-05, + 5.2146947940707611e-05, + 1.1196247646614793e-04, + 2.5412522418663438e-04, + 3.9803654879343403e-04, + 3.9915738942768251e-04, + 4.3898002406698566e-04, + 5.2316981286623264e-04, + 3.7518881991999365e-04, + 2.6126412609302663e-04, + 1.9713186101744543e-04, + 9.0406681565695756e-05, + 3.2110137830763535e-05, + 8.1422276331337174e-05, + 1.4286409645097433e-04, + 1.8440965713939077e-04, + 2.0880208489579421e-04, + 2.1617398718884161e-04, + 1.6113912455627678e-04, + 1.4485967481486886e-04, + 9.2303172112279805e-05, + 2.2472518929320770e-05, + 4.8192061655150907e-06, + 4.8256057123429434e-05, + 1.6733575041294745e-04, + 5.8951155295912691e-05, + 9.2834956988651639e-05, + 1.4122702998409738e-04, + 1.5804353860784012e-04, + 2.4325242042274938e-04, + 7.1120996730111014e-05, + 7.8084712763311554e-05, + 2.2296482523548428e-04, + 2.4472647639988950e-04, + 2.6810460244411577e-04, + 1.3097566017770531e-04, + 1.2166214810166070e-04, + 2.6183166998710732e-04, + 3.6094558153693324e-04, + 3.6460675622393537e-04, + 4.3055272004916868e-04, + 2.3881178125140131e-04, + 3.8730281334982719e-05, + 1.3138884105518976e-04, + 3.2146368104315435e-04, + 3.3784763850269284e-04, + 4.7353593916348269e-04, + 4.2612814472876999e-04, + 3.5038126149291400e-04, + 2.6060570492674227e-04, + 1.2053724086300770e-04, + 1.2794075254328639e-04, + 2.9131833803300089e-04, + 3.7107418056749595e-04, + 5.1083285570339520e-04, + 5.6150675763850459e-04, + 5.9803812514601070e-04, + 2.8650508271613466e-04, + 1.4247297799298937e-04, + 8.8099169693153976e-05, + 1.8220385425323315e-04, + 4.0837935287269656e-04, + 5.3480083904684588e-04, + 5.6717087302384352e-04, + 5.2856736890626282e-04, + 4.5018143651095132e-04, + 3.2153710058439205e-04, + 2.1109687473597677e-04, + 1.2580312590907940e-04, + 1.2244446475934517e-04, + 2.6978712249421427e-04, + 4.3887759356971303e-04, + 5.0077390147568613e-04, + 5.0809631440652283e-04, + 3.8631105814628940e-04, + 2.8282578329920187e-04, + 3.8468906777018607e-04, + 1.6075897558576131e-04, + 1.2722644542664650e-04, + 5.6659961695752419e-05, + 1.8894700193662414e-04, + 2.5433200394957354e-04, + 3.4895195306677184e-04, + 3.8342922839320232e-04, + 4.0670525839578506e-04, + 3.7895664668115731e-04, + 3.4158613414708928e-04, + 3.0223521050790178e-04, + 1.6234890364096692e-04, + 6.5898409042625846e-05, + 8.2217748894497836e-05, + 1.6464772145930102e-04, + 2.4155036261452371e-04, + 3.0903584244651097e-04, + 3.1703723504274242e-04, + 2.7194091156002877e-04, + 2.4158203309758238e-04, + 2.2516345109394043e-04, + 1.0749262770117499e-04, + 1.0697044890178262e-04, + 2.5756777473760019e-05, + 2.5252659577488030e-05, + 5.5677417556894920e-05, + 9.0847481788766115e-05, + 1.2762685245519735e-04, + 1.4770713626051414e-04, + 1.5190024584067535e-04, + 1.3686025955647810e-04, + 1.0981217723866244e-04, + 8.3761785367205107e-05, + 2.6024454398859902e-05, + 2.8470064791195087e-05, + 5.2251919880583366e-05, + 8.9396638325969094e-06, + 3.3473186471669428e-05, + 5.5296070399758797e-05, + 6.2776275036193890e-05, + 4.3482390864714253e-05, + 3.4396890802562608e-05, + 6.4197281418522622e-05, + 4.2599301000094014e-05, + 9.6598560720876928e-05, + 1.5549819591550735e-04, + 3.8142648974544360e-05, + 6.6432546391660260e-05, + 7.4364668078837228e-05, + 1.9691726256225651e-04, + 1.3899198841461398e-04, + 2.0103549043925479e-04, + 7.4830673514678138e-05, + 3.4554997472046422e-05, + 2.4217383287858506e-05, + 1.0835406104879331e-04, + 1.8378232623036592e-04, + 1.9800222854511723e-04, + 1.6738707830412793e-04, + 1.7177057070642596e-04, + 1.4132471338917119e-04, + 2.8857457900799460e-05, + 7.0513553651599997e-05, + 1.0160190227985899e-04, + 2.2782336890316427e-04, + 2.1123982088989073e-04, + 2.4716850608009762e-04, + 3.0757687357405700e-04, + 1.4913358961557267e-04, + 1.2359887121319378e-04, + 7.3124937736127355e-05, + 4.1897414549981469e-05, + 1.4980075896161617e-04, + 2.2218107710804183e-04, + 2.5940571346693819e-04, + 1.2778247148199562e-04, + 1.0093914019363669e-04, + 1.5500240821870247e-04, + 1.4269326145013761e-04, + 4.1210519085600946e-05, + 5.8119149094707924e-05, + 9.7217131271988804e-05, + 1.9130573283390639e-04, + 2.2856068841718696e-04, + 7.1992001276570020e-05, + 2.1170304275730805e-04, + 2.3654628043792605e-04, + 6.5906334902104681e-05, + 5.3917590094538546e-05, + 1.6403993475855773e-05, + 8.6518037437564171e-05, + 9.1016707758571636e-05, + 1.4419151726800334e-04, + 1.9183822982179384e-04, + 2.3003529447678415e-04, + 1.7722151443313479e-04, + 1.1534383262351058e-04, + 1.5575044860833938e-04, + 4.0237215577704865e-05, + 2.6472031124117413e-05, + 4.2855656394951511e-05, + 8.6994337589164374e-05, + 1.0855456972230857e-04, + 1.4345038048254528e-04, + 1.2165565129174600e-04, + 1.1760988740403488e-04, + 9.0558651012020126e-05, + 1.1728933338305691e-04, + 3.6383296741211417e-05, + 2.9869074218036097e-05, + 3.5471989170721027e-06, + 3.1784987762372058e-05, + 2.5091248320569711e-05, + 4.6717035122732813e-05, + 6.4449423023337055e-05, + 6.6064614609335766e-05, + 6.3352153718095123e-05, + 5.8606291682085021e-05, + 4.6438228450754785e-05, + 1.5090367750652258e-05, + 2.4023192605788964e-05, + 4.4915509359161821e-06, + ], + }, } tetrahedron_table = { From 14a59484c1fbb8fae035f61a1a6d6e6785a484de Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Fri, 19 Jul 2024 17:00:10 +0100 Subject: [PATCH 86/93] Fix gen-quad reference simplex --- FIAT/quadrature_schemes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/FIAT/quadrature_schemes.py b/FIAT/quadrature_schemes.py index 65297556f..a88a97f4b 100644 --- a/FIAT/quadrature_schemes.py +++ b/FIAT/quadrature_schemes.py @@ -36,7 +36,7 @@ # FIAT from FIAT.reference_element import (HEXAHEDRON, QUADRILATERAL, TENSORPRODUCT, TETRAHEDRON, TRIANGLE, UFCTetrahedron, - UFCTriangle, ufc_simplex, symmetric_simplex) + UFCTriangle, symmetric_simplex) def create_quadrature(ref_el, degree, scheme="default"): @@ -334,8 +334,8 @@ def _kmv_lump_scheme(ref_el, degree): def gen_quad_simplex(dim): - ref_el = ufc_simplex(dim) - ref_el.vertices = ((0., 0., 0.), (1., 0., 0.), (0., 1., 1.), (1., 1., 1.)) + ref_el = symmetric_simplex(dim) + ref_el.vertices = tuple(tuple(float(i > j) for j in range(dim)) for i in range(dim+1)) return ref_el From a3c0e8dc03b9e894ee0e928b8784a6ebd37df51e Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Mon, 12 Aug 2024 11:55:53 +0100 Subject: [PATCH 87/93] Reduced Demkowicz elements --- FIAT/demkowicz.py | 30 ++++++++++++++++++++++++++++-- FIAT/restricted.py | 4 ++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/FIAT/demkowicz.py b/FIAT/demkowicz.py index 7020fb780..6be839463 100644 --- a/FIAT/demkowicz.py +++ b/FIAT/demkowicz.py @@ -68,6 +68,7 @@ class DemkowiczDual(DualSet): def __init__(self, ref_el, degree, sobolev_space, kind=2): nodes = [] entity_ids = {} + reduced_dofs = {} top = ref_el.get_topology() sd = ref_el.get_spatial_dimension() formdegree = {"H1": 0, "HCurl": 1, "HDiv": sd-1, "L2": sd}[sobolev_space] @@ -79,14 +80,17 @@ def __init__(self, ref_el, degree, sobolev_space, kind=2): if dim < formdegree or degree <= dim - formdegree: for entity in top[dim]: entity_ids[dim][entity] = [] + reduced_dofs[dim] = 0 elif dim == 0 and formdegree == 0: for entity in sorted(top[dim]): cur = len(nodes) pts = ref_el.make_points(dim, entity, degree) nodes.extend(PointEvaluation(ref_el, pt) for pt in pts) entity_ids[dim][entity] = list(range(cur, len(nodes))) + reduced_dofs[dim] = len(nodes) else: - Q_ref, Phis = self._reference_duals(dim, degree, formdegree, sobolev_space, kind) + Q_ref, Phis, rdofs = self._reference_duals(dim, degree, formdegree, sobolev_space, kind) + reduced_dofs[dim] = rdofs mapping = dual_mapping if dim == sd else trace for entity in sorted(top[dim]): cur = len(nodes) @@ -94,6 +98,7 @@ def __init__(self, ref_el, degree, sobolev_space, kind=2): nodes.extend(FrobeniusIntegralMoment(ref_el, Q, phi) for phi in phis) entity_ids[dim][entity] = list(range(cur, len(nodes))) + self._reduced_dofs = reduced_dofs super(DemkowiczDual, self).__init__(nodes, ref_el, entity_ids) def _reference_duals(self, dim, degree, formdegree, sobolev_space, kind): @@ -117,6 +122,7 @@ def _reference_duals(self, dim, degree, formdegree, sobolev_space, kind): dtrial = dtrial[:, None, :] K = self._bubble_derivative_moments(facet, degree, formdegree, kind, Qpts, Qwts, dtrial) + reduced_dofs = K.shape[0] if formdegree > 0: if dim == 2 and formdegree == 1 and sobolev_space == "HDiv": trial = perp(trial) @@ -125,7 +131,7 @@ def _reference_duals(self, dim, degree, formdegree, sobolev_space, kind): K = numpy.vstack((K, M)) duals = numpy.tensordot(K, P_at_qpts[(0,) * dim], axes=(1, 0)) - return Q, duals + return Q, duals, reduced_dofs def _bubble_derivative_moments(self, facet, degree, formdegree, kind, Qpts, Qwts, trial): """Integrate trial expressions against an orthonormal basis for @@ -164,6 +170,26 @@ def _bubble_derivative_moments(self, facet, degree, formdegree, kind, Qpts, Qwts dtest = numpy.tensordot(S.T, dtest, axes=(1, 0)) return inner(dtest, trial, Qwts) + def get_indices(self, restriction_domain, take_closure=True): + """Return the list of dofs with support on the given restriction domain. + Allows for reduced Demkowicz elements, excluding the exterior + derivative of the previous space in the de Rham complex. + + :arg restriction_domain: can be 'reduced', 'interior', 'vertex', + 'edge', 'face' or 'facet' + :kwarg take_closure: Are we taking the closure of the restriction domain? + """ + if restriction_domain == "reduced": + indices = [] + entity_ids = self.get_entity_ids() + for dim in entity_ids: + reduced_dofs = self._reduced_dofs[dim] + for entity, ids in entity_ids[dim].items(): + indices.extend(ids[:reduced_dofs]) + return indices + else: + return super(DemkowiczDual, self).get_indices(restriction_domain, take_closure=take_closure) + class FDMDual(DualSet): diff --git a/FIAT/restricted.py b/FIAT/restricted.py index 26c82d913..6f964b39e 100644 --- a/FIAT/restricted.py +++ b/FIAT/restricted.py @@ -40,6 +40,10 @@ def get_indices(self, restriction_domain, take_closure=True): # Call get_indices on the parent class to support multiple restriction domains return type(self._dual).get_indices(self, restriction_domain, take_closure=take_closure) + def __getattr__(self, name): + val = getattr(self._dual, name) + return val + class RestrictedElement(CiarletElement): """Restrict the given element to the specified list of dofs.""" From af0463a56a3ffcfb97e959f3143b017190294f87 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 15 Aug 2024 12:34:30 +0100 Subject: [PATCH 88/93] Demkowicz L2 element --- FIAT/demkowicz.py | 24 ++++++++++++++++++------ FIAT/hierarchical.py | 7 ++++++- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/FIAT/demkowicz.py b/FIAT/demkowicz.py index 6be839463..7b9545af4 100644 --- a/FIAT/demkowicz.py +++ b/FIAT/demkowicz.py @@ -65,7 +65,7 @@ def map_duals(ref_el, dim, entity, mapping, Q_ref, Phis): class DemkowiczDual(DualSet): - def __init__(self, ref_el, degree, sobolev_space, kind=2): + def __init__(self, ref_el, degree, sobolev_space, kind=None): nodes = [] entity_ids = {} reduced_dofs = {} @@ -74,6 +74,8 @@ def __init__(self, ref_el, degree, sobolev_space, kind=2): formdegree = {"H1": 0, "HCurl": 1, "HDiv": sd-1, "L2": sd}[sobolev_space] trace = {"HCurl": "contravariant", "HDiv": "normal"}.get(sobolev_space, None) dual_mapping = {"HCurl": "contravariant", "HDiv": "covariant"}.get(sobolev_space, None) + if kind is None: + kind = 1 if formdegree == 0 else 2 for dim in sorted(top): entity_ids[dim] = {} @@ -105,28 +107,37 @@ def _reference_duals(self, dim, degree, formdegree, sobolev_space, kind): facet = symmetric_simplex(dim) Q = create_quadrature(facet, 2 * degree) Qpts, Qwts = Q.get_points(), Q.get_weights() - exterior_derivative = {"H1": grad, "HCurl": curl, "HDiv": div}[sobolev_space] + exterior_derivative = {"H1": grad, "HCurl": curl, "HDiv": div, "L2": None}[sobolev_space] shp = () if formdegree == 0 else (dim,) - if formdegree == dim: + if sobolev_space == "L2" and dim > 2: + shp = () + elif formdegree == dim: shp = (1,) P = ONPolynomialSet(facet, degree, shp, scale="orthonormal") P_at_qpts = P.tabulate(Qpts, 1) trial = P_at_qpts[(0,) * dim] - if formdegree == dim: + # Evaluate type-I degrees of freedom on P + if formdegree >= dim: K = inner(trial[:1], trial, Qwts) else: dtrial = exterior_derivative(P_at_qpts) if dim == 2 and formdegree == 1 and sobolev_space == "HDiv": dtrial = dtrial[:, None, :] K = self._bubble_derivative_moments(facet, degree, formdegree, kind, Qpts, Qwts, dtrial) - reduced_dofs = K.shape[0] + + # Evaluate type-II degrees of freedom on P if formdegree > 0: + q = degree + 1 if kind == 2 else degree + if q > degree: + Q2 = create_quadrature(facet, 2 * q) + Qpts, Qwts = Q2.get_points(), Q2.get_weights() + trial = P.tabulate(Qpts, 0)[(0,) * dim] + if dim == 2 and formdegree == 1 and sobolev_space == "HDiv": trial = perp(trial) - q = degree + 1 if kind == 2 else degree M = self._bubble_derivative_moments(facet, q, formdegree-1, kind, Qpts, Qwts, trial) K = numpy.vstack((K, M)) @@ -168,6 +179,7 @@ def _bubble_derivative_moments(self, facet, degree, formdegree, kind, Qpts, Qwts S *= numpy.sqrt(1 / sig[None, nullspace_dim:]) # Apply change of basis dtest = numpy.tensordot(S.T, dtest, axes=(1, 0)) + return inner(dtest, trial, Qwts) def get_indices(self, restriction_domain, take_closure=True): diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index 3c3600cd3..900f1c76d 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -67,7 +67,12 @@ def __init__(self, ref_el, degree, variant=None): if splitting is not None: ref_el = splitting(ref_el) poly_set = ONPolynomialSet(ref_el, degree) - dual = LegendreDual(ref_el, degree) + if variant == "demkowicz": + dual = demkowicz.DemkowiczDual(ref_el, degree, "L2") + elif variant == "fdm": + dual = demkowicz.FDMDual(ref_el, degree, "L2", type(self)) + else: + dual = LegendreDual(ref_el, degree) formdegree = ref_el.get_spatial_dimension() # n-form super(Legendre, self).__init__(poly_set, dual, degree, formdegree) From 5580a9ebba39d109c7323ddc491d10f9c0cd0510 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 15 Aug 2024 15:59:45 +0100 Subject: [PATCH 89/93] Refactor FDMDual to inherit from DemkowiczDual --- FIAT/demkowicz.py | 149 +++++++++++++++++----------------------------- 1 file changed, 54 insertions(+), 95 deletions(-) diff --git a/FIAT/demkowicz.py b/FIAT/demkowicz.py index 7b9545af4..4a84505fb 100644 --- a/FIAT/demkowicz.py +++ b/FIAT/demkowicz.py @@ -9,7 +9,7 @@ from FIAT.dual_set import DualSet from FIAT.functional import PointEvaluation, FrobeniusIntegralMoment -from FIAT.polynomial_set import make_bubbles, ONPolynomialSet, PolynomialSet +from FIAT.polynomial_set import make_bubbles, ONPolynomialSet from FIAT.quadrature import FacetQuadratureRule from FIAT.quadrature_schemes import create_quadrature from FIAT.reference_element import symmetric_simplex @@ -203,107 +203,66 @@ def get_indices(self, restriction_domain, take_closure=True): return super(DemkowiczDual, self).get_indices(restriction_domain, take_closure=take_closure) -class FDMDual(DualSet): +class FDMDual(DemkowiczDual): def __init__(self, ref_el, degree, sobolev_space, element): - nodes = [] - entity_ids = {} - sd = ref_el.get_spatial_dimension() - - Ref_el = symmetric_simplex(sd) - fe = element(Ref_el, degree, variant="demkowicz") - ells = fe.dual_basis() - entity_dofs = fe.entity_dofs() - formdegree = fe.formdegree - - Q = create_quadrature(Ref_el, 2 * degree) - X, W = Q.get_points(), Q.get_weights() exterior_derivative = {"H1": grad, "HCurl": curl, "HDiv": div}[sobolev_space] - trace = {"HCurl": "contravariant", "HDiv": "normal"}.get(sobolev_space, None) - dual_mapping = {"HCurl": "contravariant", "HDiv": "covariant"}.get(sobolev_space, None) + self.trace = {"HCurl": "contravariant", "HDiv": "normal"}.get(sobolev_space, None) + self.dual_mapping = {"HCurl": "contravariant", "HDiv": "covariant"}.get(sobolev_space, None) - phi_at_qpts = fe.tabulate(1, X) - V0 = phi_at_qpts[(0,) * sd] - V1 = exterior_derivative(phi_at_qpts) + sd = ref_el.get_spatial_dimension() + base_ref_el = symmetric_simplex(sd) + self.fe = element(base_ref_el, degree, variant="demkowicz") - for dim in sorted(entity_dofs): - entity_ids[dim] = {} - if dim == 0 and formdegree == 0: - for entity in sorted(entity_dofs[dim]): - cur = len(nodes) - pts = ref_el.make_points(dim, entity, degree) - nodes.extend(PointEvaluation(ref_el, pt) for pt in pts) - entity_ids[dim][entity] = list(range(cur, len(nodes))) - continue - dofs = entity_dofs[dim][0] - if len(dofs) == 0: - for entity in sorted(entity_dofs[dim]): - entity_ids[dim][entity] = [] - continue + Q = create_quadrature(base_ref_el, 2 * degree) + phis = self.fe.tabulate(1, Q.get_points()) + self.Q = Q + self.V0 = phis[(0,) * sd] + self.V1 = exterior_derivative(phis) + super(FDMDual, self).__init__(ref_el, degree, sobolev_space, kind=None) - B = inner(V0[dofs], V0[dofs], W) - if dim == sd: - _, S = scipy.linalg.eigh(B) - else: - A = inner(V1[dofs], V1[dofs], W) - if formdegree > 0: - A += B - _, S = scipy.linalg.eigh(B, A) - S = numpy.dot(A, S) - - phis = numpy.array([ells[i].f_at_qpts for i in dofs]) - phis = numpy.tensordot(S.T, phis, axes=(1, 0)) - - Q_dof = ells[dofs[0]].Q - Q_ref = Q_dof.reference_rule() - mapping = dual_mapping if dim == sd else trace - # map physical phis to reference values Phis - if mapping == "normal": - n = Ref_el.compute_normal(0) - Phis = numpy.dot(n[None, :], phis).transpose((1, 0, 2)) - elif mapping == "covariant": - piola_map = Q_dof.jacobian().T - Phis = numpy.dot(piola_map, phis).transpose((1, 0, 2)) - elif mapping == "contravariant": - piola_map = numpy.linalg.pinv(Q_dof.jacobian()) * Q_dof.jacobian_determinant() - Phis = numpy.dot(piola_map, phis).transpose((1, 0, 2)) - else: - Jdet = Q_dof.jacobian_determinant() - Phis = Jdet * phis - - for entity in sorted(entity_dofs[dim]): - cur = len(nodes) - Q_facet, phis = map_duals(ref_el, dim, entity, mapping, Q_ref, Phis) - nodes.extend(FrobeniusIntegralMoment(ref_el, Q_facet, phi) for phi in phis) - entity_ids[dim][entity] = list(range(cur, len(nodes))) - - super(FDMDual, self).__init__(nodes, ref_el, entity_ids) - - -def project_derivative(fe, op): - """Return a PolynomialSet with the projection of the derivative of a FiniteElement fe. - The type of derivative is specified by op, must be either "grad", "curl", or "div". - """ - ref_el = fe.ref_el - degree = fe.degree() - 1 - rot = None - if fe.formdegree == 0: - if op == "curl": - rot = perp - op = "grad" + def _reference_duals(self, dim, degree, formdegree, sobolev_space, kind): + entity_dofs = self.fe.entity_dofs() + ells = self.fe.dual_basis() + Ref_el = self.fe.get_reference_element() + sd = Ref_el.get_spatial_dimension() + + dofs = entity_dofs[dim][0] + V0 = self.V0[dofs] + W = self.Q.get_weights() + B = inner(V0, V0, W) + if dim == sd: + _, S = scipy.linalg.eigh(B) + else: + V1 = self.V1[dofs] + A = inner(V1, V1, W) + if formdegree > 0: + A += B + _, S = scipy.linalg.eigh(B, A) + S = numpy.dot(A, S) + + phis = numpy.array([ells[i].f_at_qpts for i in dofs]) + phis = numpy.tensordot(S.T, phis, axes=(1, 0)) + + Q_dof = ells[dofs[0]].Q + Q_ref = Q_dof.reference_rule() + mapping = self.dual_mapping if dim == sd else self.trace + # map physical phis to reference values Phis + if mapping == "normal": + n = Ref_el.compute_normal(0) + Phis = numpy.dot(n[None, :], phis).transpose((1, 0, 2)) + elif mapping == "covariant": + piola_map = Q_dof.jacobian().T + Phis = numpy.dot(piola_map, phis).transpose((1, 0, 2)) + elif mapping == "contravariant": + piola_map = numpy.linalg.pinv(Q_dof.jacobian()) * Q_dof.jacobian_determinant() + Phis = numpy.dot(piola_map, phis).transpose((1, 0, 2)) + else: + Jdet = Q_dof.jacobian_determinant() + Phis = Jdet * phis - Q = create_quadrature(ref_el, 2 * degree) - Qpts, Qwts = Q.get_points(), Q.get_weights() - expr = {"grad": grad, "curl": curl, "div": div}[op](fe.tabulate(1, Qpts)) - if rot is not None: - expr = rot(expr) - - sd = ref_el.get_spatial_dimension() - P = ONPolynomialSet(ref_el, degree, expr.shape[1:-1], scale="orthonormal") - wts = P.tabulate(Qpts)[(0,) * sd] - numpy.multiply(wts, Qwts, out=wts) - coeffs = numpy.tensordot(expr, wts, axes=(range(1, expr.ndim), range(1, expr.ndim))) - return PolynomialSet(ref_el, degree, degree, P.get_expansion_set(), coeffs) + reduced_dofs = self.fe.dual._reduced_dofs[dim] + return Q_ref, Phis, reduced_dofs if __name__ == "__main__": From dd0ddd794258358e4513059b691249184aa4f89d Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Mon, 19 Aug 2024 11:39:42 +0100 Subject: [PATCH 90/93] update tests --- test/unit/test_demkowicz.py | 83 +++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 40 deletions(-) diff --git a/test/unit/test_demkowicz.py b/test/unit/test_demkowicz.py index d0e79ffce..57805090a 100644 --- a/test/unit/test_demkowicz.py +++ b/test/unit/test_demkowicz.py @@ -26,67 +26,70 @@ from FIAT.raviart_thomas import RaviartThomas as N1Div from FIAT.nedelec_second_kind import NedelecSecondKind as N2Curl from FIAT.brezzi_douglas_marini import BrezziDouglasMarini as N2Div +from FIAT.quadrature_schemes import create_quadrature +from FIAT.reference_element import symmetric_simplex, ufc_simplex +from FIAT.demkowicz import grad, curl, div, inner +from FIAT.restricted import RestrictedElement -@pytest.mark.parametrize("family, dim, degree, variant", - [(f, d, p, v) - for f in (CG, N1Curl, N1Div, N2Curl, N2Div) - for v in ("demkowicz", "fdm") - for d in (2, 3) - for p in range(1, 6)]) -def test_galerkin_symmetry(dim, family, degree, variant): - from FIAT.quadrature_schemes import create_quadrature - from FIAT.reference_element import symmetric_simplex - from FIAT.demkowicz import grad, curl, div, inner +@pytest.fixture(params=(2, 3), ids=("T", "S")) +def ref_el(request): + return symmetric_simplex(request.param) - s = symmetric_simplex(dim) - fe = family(s, degree, variant=variant) + +@pytest.fixture(params=(2, 3), ids=("T", "S")) +def cell(request): + return ufc_simplex(request.param) + + +@pytest.mark.parametrize("degree", (2, 3, 5)) +@pytest.mark.parametrize("variant", ("demkowicz", "fdm")) +@pytest.mark.parametrize("family", (CG, N1Curl, N1Div, N2Curl, N2Div)) +def test_galerkin_symmetry(ref_el, family, degree, variant): + sd = ref_el.get_spatial_dimension() + fe = family(ref_el, degree, variant=variant) exterior_derivative = {CG: grad, N1Curl: curl, N2Curl: curl, N1Div: div, N2Div: div}[family] - Q = create_quadrature(s, 2 * degree) + Q = create_quadrature(ref_el, 2 * degree) Qpts, Qwts = Q.get_points(), Q.get_weights() galerkin = lambda V: inner(V, V, Qwts) tab = fe.tabulate(1, Qpts) - phi = tab[(0,) * dim] + phi = tab[(0,) * sd] dphi = exterior_derivative(tab) entity_dofs = fe.entity_dofs() for dim in sorted(entity_dofs): for V in (phi, dphi): A = [galerkin(V[entity_dofs[dim][entity]]) for entity in sorted(entity_dofs[dim])] - Aref = numpy.diag(A[0].diagonal()) if variant == "fdm" else A[0] + Aref = numpy.diag(A[0].diagonal()) if variant == "fdm" or dim == sd else A[0] for A1 in A: assert numpy.allclose(Aref, A1, rtol=1E-14) -@pytest.mark.parametrize("family, dim, degree, variant", - [(f, d, p, v) - for f in (CG, N1Curl,) - for v in ("demkowicz",) - for d in (2, 3) - for p in range(2, 7)]) -def test_hierarchical_interpolation(dim, family, degree, variant): - from FIAT.reference_element import symmetric_simplex +@pytest.mark.parametrize("degree", (2, 3, 7)) +@pytest.mark.parametrize("variant", ("demkowicz",)) +@pytest.mark.parametrize("family", (CG, N1Curl, N1Div)) +def test_hierarchical_interpolation(cell, family, degree, variant): + V1 = family(cell, 1, variant=variant) + Vp = family(cell, degree, variant=variant) + Vf = RestrictedElement(Vp, restriction_domain="facet") + primal = V1.get_nodal_basis() - s = symmetric_simplex(dim) - Vp = family(s, degree, variant=variant) - V1 = family(s, 1, variant=variant) + for V in (Vp, Vf): + dual = V.get_dual_set() + A = dual.to_riesz(primal) + B = primal.get_coeffs() + D = numpy.tensordot(A, B, axes=(range(1, A.ndim), range(1, B.ndim))) - primal = V1.get_nodal_basis() - dual = Vp.get_dual_set() - A = dual.to_riesz(primal) - B = primal.get_coeffs() - D = numpy.tensordot(A, B, axes=(range(1, A.ndim), range(1, B.ndim))) - - dim1 = V1.space_dimension() - dimp = Vp.space_dimension() - dofs_per_entity = len(V1.entity_dofs()[V1.formdegree][0]) - dofs = Vp.entity_dofs()[Vp.formdegree] - dof1 = sum((dofs[entity][:dofs_per_entity] for entity in sorted(dofs)), []) - dofp = numpy.setdiff1d(numpy.arange(dimp), dof1) - assert numpy.allclose(D[dofp], 0.0) - assert numpy.allclose(D[dof1], numpy.eye(dim1)) + dim1 = V1.space_dimension() + dimp = V.space_dimension() + dofs_per_entity = len(V1.entity_dofs()[V1.formdegree][0]) + dofs = V.entity_dofs()[V.formdegree] + dof1 = sum((dofs[entity][:dofs_per_entity] for entity in sorted(dofs)), []) + dofp = numpy.setdiff1d(numpy.arange(dimp), dof1) + assert numpy.allclose(D[dofp], 0.0) + assert numpy.allclose(D[dof1], numpy.eye(dim1)) if __name__ == '__main__': From 28e172c818c3fcde16ca9caee53595c37ff35d7b Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Mon, 19 Aug 2024 18:49:20 +0100 Subject: [PATCH 91/93] Test exterior derivative sparsity --- FIAT/demkowicz.py | 4 +-- test/unit/test_demkowicz.py | 66 ++++++++++++++++++++++++++++++++----- 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/FIAT/demkowicz.py b/FIAT/demkowicz.py index 4a84505fb..c9d92737a 100644 --- a/FIAT/demkowicz.py +++ b/FIAT/demkowicz.py @@ -178,7 +178,7 @@ def _bubble_derivative_moments(self, facet, degree, formdegree, kind, Qpts, Qwts S = S[:, nullspace_dim:] S *= numpy.sqrt(1 / sig[None, nullspace_dim:]) # Apply change of basis - dtest = numpy.tensordot(S.T, dtest, axes=(1, 0)) + dtest = numpy.tensordot(S, dtest, axes=(0, 0)) return inner(dtest, trial, Qwts) @@ -200,7 +200,7 @@ def get_indices(self, restriction_domain, take_closure=True): indices.extend(ids[:reduced_dofs]) return indices else: - return super(DemkowiczDual, self).get_indices(restriction_domain, take_closure=take_closure) + return DualSet.get_indices(self, restriction_domain, take_closure=take_closure) class FDMDual(DemkowiczDual): diff --git a/test/unit/test_demkowicz.py b/test/unit/test_demkowicz.py index 57805090a..14644f5a5 100644 --- a/test/unit/test_demkowicz.py +++ b/test/unit/test_demkowicz.py @@ -22,10 +22,11 @@ import pytest import numpy from FIAT.hierarchical import IntegratedLegendre as CG -from FIAT.nedelec import Nedelec as N1Curl -from FIAT.raviart_thomas import RaviartThomas as N1Div -from FIAT.nedelec_second_kind import NedelecSecondKind as N2Curl -from FIAT.brezzi_douglas_marini import BrezziDouglasMarini as N2Div +from FIAT.hierarchical import Legendre as DG +from FIAT.nedelec import Nedelec as N1curl +from FIAT.raviart_thomas import RaviartThomas as N1div +from FIAT.nedelec_second_kind import NedelecSecondKind as N2curl +from FIAT.brezzi_douglas_marini import BrezziDouglasMarini as N2div from FIAT.quadrature_schemes import create_quadrature from FIAT.reference_element import symmetric_simplex, ufc_simplex from FIAT.demkowicz import grad, curl, div, inner @@ -44,11 +45,11 @@ def cell(request): @pytest.mark.parametrize("degree", (2, 3, 5)) @pytest.mark.parametrize("variant", ("demkowicz", "fdm")) -@pytest.mark.parametrize("family", (CG, N1Curl, N1Div, N2Curl, N2Div)) +@pytest.mark.parametrize("family", (CG, N1curl, N1div, N2curl, N2div)) def test_galerkin_symmetry(ref_el, family, degree, variant): sd = ref_el.get_spatial_dimension() fe = family(ref_el, degree, variant=variant) - exterior_derivative = {CG: grad, N1Curl: curl, N2Curl: curl, N1Div: div, N2Div: div}[family] + exterior_derivative = {CG: grad, N1curl: curl, N2curl: curl, N1div: div, N2div: div}[family] Q = create_quadrature(ref_el, 2 * degree) Qpts, Qwts = Q.get_points(), Q.get_weights() @@ -67,9 +68,9 @@ def test_galerkin_symmetry(ref_el, family, degree, variant): assert numpy.allclose(Aref, A1, rtol=1E-14) -@pytest.mark.parametrize("degree", (2, 3, 7)) +@pytest.mark.parametrize("degree", (2, 3, 5)) @pytest.mark.parametrize("variant", ("demkowicz",)) -@pytest.mark.parametrize("family", (CG, N1Curl, N1Div)) +@pytest.mark.parametrize("family", (CG, N1curl, N1div)) def test_hierarchical_interpolation(cell, family, degree, variant): V1 = family(cell, 1, variant=variant) Vp = family(cell, degree, variant=variant) @@ -92,6 +93,55 @@ def test_hierarchical_interpolation(cell, family, degree, variant): assert numpy.allclose(D[dof1], numpy.eye(dim1)) +@pytest.mark.parametrize("kind", (2,)) +@pytest.mark.parametrize("degree", (2, 3, 5)) +@pytest.mark.parametrize("variant", ("demkowicz",)) +@pytest.mark.parametrize("formdegree", (0, 1, 2), ids=("grad", "curl", "div")) +def test_exterior_derivative_sparsity(cell, formdegree, kind, degree, variant): + sd = cell.get_spatial_dimension() + if kind == 1: + Ncurl, Ndiv = N1curl, N1div + elif kind == 2: + Ncurl, Ndiv = N2curl, N2div + else: + raise ValueError("Invalid kind") + op = ("grad", "curl", "div")[formdegree] + family = [CG, Ncurl, Ndiv, DG] + if sd == 2 and formdegree > 0: + family = [CG, family[formdegree], DG] + formdegree = 1 + + Xfamily, Yfamily = family[formdegree:formdegree+2] + Ydegree = degree - (kind == 2 or formdegree == sd-1) + X = Xfamily(cell, degree, variant=variant) + Y = Yfamily(cell, Ydegree, variant=variant) + Yred = RestrictedElement(Y, restriction_domain="reduced") + restriction_domain = {"grad": "edge", "curl": "face", "div": "interior"}[op] + Ylow = RestrictedElement(Yred, restriction_domain=restriction_domain) + + primal = X.get_nodal_basis() + dual = Y.get_dual_set() + A = dual.to_riesz(primal) + B = primal.get_coeffs() + dmats = primal.get_dmats() + + B = numpy.tensordot(B, dmats, axes=(-1, -1)) + if op == "curl": + d = B.shape[1] + perm = ((i, j) for i in reversed(range(d)) for j in reversed(range(i+1, d))) + B = numpy.stack([((-1)**k) * (B[:, i, j, :] - B[:, j, i, :]) + for k, (i, j) in enumerate(perm)], axis=1) + elif op == "div": + B = numpy.trace(B, axis1=1, axis2=2) + B = B.reshape(-1, *A.shape[1:]) + D = numpy.tensordot(A, B, axes=(range(1, A.ndim), range(1, B.ndim))) + D[abs(D) < 1E-10] = 0 + + nnz = len(numpy.flatnonzero(D)) + expected = (Y.space_dimension() - Yred.space_dimension()) + (Y.formdegree+1)*Ylow.space_dimension() + assert nnz == expected + + if __name__ == '__main__': import os pytest.main(os.path.abspath(__file__)) From 3044611fd88c7adef057ec573df3c7eafecabad9 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Wed, 21 Aug 2024 21:09:34 +0100 Subject: [PATCH 92/93] add a switch to condense the mass matrix instead of the stiffness matrix --- FIAT/demkowicz.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/FIAT/demkowicz.py b/FIAT/demkowicz.py index c9d92737a..54516d5df 100644 --- a/FIAT/demkowicz.py +++ b/FIAT/demkowicz.py @@ -144,7 +144,7 @@ def _reference_duals(self, dim, degree, formdegree, sobolev_space, kind): duals = numpy.tensordot(K, P_at_qpts[(0,) * dim], axes=(1, 0)) return Q, duals, reduced_dofs - def _bubble_derivative_moments(self, facet, degree, formdegree, kind, Qpts, Qwts, trial): + def _bubble_derivative_moments(self, facet, degree, formdegree, kind, Qpts, Qwts, trial, deriv=True): """Integrate trial expressions against an orthonormal basis for the exterior derivative of bubbles. """ @@ -167,20 +167,21 @@ def _bubble_derivative_moments(self, facet, degree, formdegree, kind, Qpts, Qwts B_at_qpts = B.tabulate(Qpts, 1) d = (grad, curl, div)[formdegree] dtest = d(B_at_qpts) + test = B_at_qpts[(0,) * dim] + expr = dtest if deriv else test if len(dtest) > 0: # Build an orthonormal basis, remove nullspace - test = B_at_qpts[(0,) * dim] B = inner(test, test, Qwts) A = inner(dtest, dtest, Qwts) sig, S = scipy.linalg.eigh(A, B) tol = sig[-1] * 1E-12 nullspace_dim = len([s for s in sig if abs(s) <= tol]) S = S[:, nullspace_dim:] - S *= numpy.sqrt(1 / sig[None, nullspace_dim:]) + if deriv: + S *= numpy.sqrt(1 / sig[None, nullspace_dim:]) # Apply change of basis - dtest = numpy.tensordot(S, dtest, axes=(0, 0)) - - return inner(dtest, trial, Qwts) + expr = numpy.tensordot(S, expr, axes=(0, 0)) + return inner(expr, trial, Qwts) def get_indices(self, restriction_domain, take_closure=True): """Return the list of dofs with support on the given restriction domain. From a95bf3a7be738e6255622cf7c1d8712935ee2452 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Wed, 20 Nov 2024 17:06:45 +0000 Subject: [PATCH 93/93] mass-decoupling variant --- FIAT/brezzi_douglas_marini.py | 4 ++-- FIAT/check_format_variant.py | 8 +++----- FIAT/demkowicz.py | 28 ++++++++++++++++++---------- FIAT/hierarchical.py | 4 ++-- FIAT/nedelec.py | 4 ++-- FIAT/nedelec_second_kind.py | 4 ++-- FIAT/raviart_thomas.py | 4 ++-- test/unit/test_hct.py | 22 ++++++++++++++++++++++ 8 files changed, 53 insertions(+), 25 deletions(-) diff --git a/FIAT/brezzi_douglas_marini.py b/FIAT/brezzi_douglas_marini.py index d4181de18..5f5451a85 100644 --- a/FIAT/brezzi_douglas_marini.py +++ b/FIAT/brezzi_douglas_marini.py @@ -94,8 +94,8 @@ def __init__(self, ref_el, degree, variant=None): sd = ref_el.get_spatial_dimension() poly_set = polynomial_set.ONPolynomialSet(ref_el, degree, (sd, )) - if variant == "demkowicz": - dual = demkowicz.DemkowiczDual(ref_el, degree, "HDiv") + if variant and variant.startswith("demkowicz"): + dual = demkowicz.DemkowiczDual(ref_el, degree, "HDiv", variant=variant) elif variant == "fdm": dual = demkowicz.FDMDual(ref_el, degree, "HDiv", type(self)) else: diff --git a/FIAT/check_format_variant.py b/FIAT/check_format_variant.py index 30ff58f21..c9c59ce89 100644 --- a/FIAT/check_format_variant.py +++ b/FIAT/check_format_variant.py @@ -30,10 +30,9 @@ def check_format_variant(variant, degree): if variant is None: variant = "integral" - match = re.match(r"^integral(?:\((\d+)\))?$", variant) + match = re.match(r"^(\w+)(?:\((\d+)\))?$", variant) if match: - variant = "integral" - extra_degree, = match.groups() + variant, extra_degree = match.groups() extra_degree = int(extra_degree) if extra_degree is not None else 0 interpolant_degree = degree + extra_degree if interpolant_degree < degree: @@ -61,7 +60,7 @@ def parse_lagrange_variant(variant, discontinuous=False, integral=False): default = "integral" if integral else "spectral" if integral: - supported_point_variants = {"integral": None, "fdm": "fdm", "demkowicz": "demkowicz"} + supported_point_variants = {"integral": None, "fdm": "fdm", "demkowicz": "demkowicz", "demkowiczmass": "demkowiczmass"} elif discontinuous: supported_point_variants = supported_dg_variants else: @@ -71,7 +70,6 @@ def parse_lagrange_variant(variant, discontinuous=False, integral=False): splitting = None splitting_args = tuple() point_variant = supported_point_variants[default] - for pre_opt in options: opt = pre_opt.lower() if opt in supported_splits: diff --git a/FIAT/demkowicz.py b/FIAT/demkowicz.py index 54516d5df..ec2d0be92 100644 --- a/FIAT/demkowicz.py +++ b/FIAT/demkowicz.py @@ -65,7 +65,7 @@ def map_duals(ref_el, dim, entity, mapping, Q_ref, Phis): class DemkowiczDual(DualSet): - def __init__(self, ref_el, degree, sobolev_space, kind=None): + def __init__(self, ref_el, degree, sobolev_space, kind=None, variant=None): nodes = [] entity_ids = {} reduced_dofs = {} @@ -76,6 +76,8 @@ def __init__(self, ref_el, degree, sobolev_space, kind=None): dual_mapping = {"HCurl": "contravariant", "HDiv": "covariant"}.get(sobolev_space, None) if kind is None: kind = 1 if formdegree == 0 else 2 + if variant is None: + variant = "demkowicz" for dim in sorted(top): entity_ids[dim] = {} @@ -91,7 +93,7 @@ def __init__(self, ref_el, degree, sobolev_space, kind=None): entity_ids[dim][entity] = list(range(cur, len(nodes))) reduced_dofs[dim] = len(nodes) else: - Q_ref, Phis, rdofs = self._reference_duals(dim, degree, formdegree, sobolev_space, kind) + Q_ref, Phis, rdofs = self._reference_duals(dim, degree, formdegree, sobolev_space, kind, variant) reduced_dofs[dim] = rdofs mapping = dual_mapping if dim == sd else trace for entity in sorted(top[dim]): @@ -101,13 +103,12 @@ def __init__(self, ref_el, degree, sobolev_space, kind=None): entity_ids[dim][entity] = list(range(cur, len(nodes))) self._reduced_dofs = reduced_dofs - super(DemkowiczDual, self).__init__(nodes, ref_el, entity_ids) + super().__init__(nodes, ref_el, entity_ids) - def _reference_duals(self, dim, degree, formdegree, sobolev_space, kind): + def _reference_duals(self, dim, degree, formdegree, sobolev_space, kind, variant): facet = symmetric_simplex(dim) Q = create_quadrature(facet, 2 * degree) Qpts, Qwts = Q.get_points(), Q.get_weights() - exterior_derivative = {"H1": grad, "HCurl": curl, "HDiv": div, "L2": None}[sobolev_space] shp = () if formdegree == 0 else (dim,) if sobolev_space == "L2" and dim > 2: @@ -122,10 +123,16 @@ def _reference_duals(self, dim, degree, formdegree, sobolev_space, kind): if formdegree >= dim: K = inner(trial[:1], trial, Qwts) else: - dtrial = exterior_derivative(P_at_qpts) - if dim == 2 and formdegree == 1 and sobolev_space == "HDiv": - dtrial = dtrial[:, None, :] - K = self._bubble_derivative_moments(facet, degree, formdegree, kind, Qpts, Qwts, dtrial) + if variant == "demkowicz": + deriv = True + exterior_derivative = {"H1": grad, "HCurl": curl, "HDiv": div, "L2": None}[sobolev_space] + dtrial = exterior_derivative(P_at_qpts) + if dim == 2 and formdegree == 1 and sobolev_space == "HDiv": + dtrial = dtrial[:, None, :] + else: + deriv = False + dtrial = trial + K = self._bubble_derivative_moments(facet, degree, formdegree, kind, Qpts, Qwts, dtrial, deriv=deriv) reduced_dofs = K.shape[0] # Evaluate type-II degrees of freedom on P @@ -220,7 +227,7 @@ def __init__(self, ref_el, degree, sobolev_space, element): self.Q = Q self.V0 = phis[(0,) * sd] self.V1 = exterior_derivative(phis) - super(FDMDual, self).__init__(ref_el, degree, sobolev_space, kind=None) + super().__init__(ref_el, degree, sobolev_space, kind=None) def _reference_duals(self, dim, degree, formdegree, sobolev_space, kind): entity_dofs = self.fe.entity_dofs() @@ -286,6 +293,7 @@ def _reference_duals(self, dim, degree, formdegree, sobolev_space, kind): kind = 1 variant = "fdm" variant = "demkowicz" + # variant = "demkowicz-mass" # variant = None space_dict = {"H1": (CG, grad), "HCurl": (N1Curl if kind == 1 else N2Curl, curl), diff --git a/FIAT/hierarchical.py b/FIAT/hierarchical.py index 945acde6c..f129839e3 100644 --- a/FIAT/hierarchical.py +++ b/FIAT/hierarchical.py @@ -78,7 +78,7 @@ def __init__(self, ref_el, degree, variant=None): if splitting is not None: ref_el = splitting(ref_el) poly_set = ONPolynomialSet(ref_el, degree) - if variant == "demkowicz": + if variant and variant.startswith("demkowicz"): dual = demkowicz.DemkowiczDual(ref_el, degree, "L2") elif variant == "fdm": dual = demkowicz.FDMDual(ref_el, degree, "L2", type(self)) @@ -135,7 +135,7 @@ def __init__(self, ref_el, degree, variant=None): if degree < 1: raise ValueError(f"{type(self).__name__} elements only valid for k >= 1") poly_set = ONPolynomialSet(ref_el, degree, variant="bubble") - if variant == "demkowicz": + if variant and variant.startswith("demkowicz"): dual = demkowicz.DemkowiczDual(ref_el, degree, "H1") elif variant == "fdm": dual = demkowicz.FDMDual(ref_el, degree, "H1", type(self)) diff --git a/FIAT/nedelec.py b/FIAT/nedelec.py index d61712c6d..6471eca58 100644 --- a/FIAT/nedelec.py +++ b/FIAT/nedelec.py @@ -196,8 +196,8 @@ class Nedelec(finite_element.CiarletElement): def __init__(self, ref_el, degree, variant=None): - if variant == "demkowicz": - dual = demkowicz.DemkowiczDual(ref_el, degree, "HCurl", kind=1) + if variant and variant.startswith("demkowicz"): + dual = demkowicz.DemkowiczDual(ref_el, degree, "HCurl", kind=1, variant=variant) elif variant == "fdm": dual = demkowicz.FDMDual(ref_el, degree, "HCurl", type(self)) else: diff --git a/FIAT/nedelec_second_kind.py b/FIAT/nedelec_second_kind.py index c43d05969..0b6e92618 100644 --- a/FIAT/nedelec_second_kind.py +++ b/FIAT/nedelec_second_kind.py @@ -198,8 +198,8 @@ def __init__(self, ref_el, degree, variant=None): sd = ref_el.get_spatial_dimension() poly_set = ONPolynomialSet(ref_el, degree, (sd, ), variant="bubble") - if variant == "demkowicz": - dual = demkowicz.DemkowiczDual(ref_el, degree, "HCurl") + if variant and variant.startswith("demkowicz"): + dual = demkowicz.DemkowiczDual(ref_el, degree, "HCurl", variant=variant) elif variant == "fdm": dual = demkowicz.FDMDual(ref_el, degree, "HCurl", type(self)) else: diff --git a/FIAT/raviart_thomas.py b/FIAT/raviart_thomas.py index a450e3326..2438cdca9 100644 --- a/FIAT/raviart_thomas.py +++ b/FIAT/raviart_thomas.py @@ -141,8 +141,8 @@ class RaviartThomas(finite_element.CiarletElement): def __init__(self, ref_el, degree, variant=None): - if variant == "demkowicz": - dual = demkowicz.DemkowiczDual(ref_el, degree, "HDiv", kind=1) + if variant and variant.startswith("demkowicz"): + dual = demkowicz.DemkowiczDual(ref_el, degree, "HDiv", kind=1, variant=variant) elif variant == "fdm": dual = demkowicz.FDMDual(ref_el, degree, "HDiv", type(self)) else: diff --git a/test/unit/test_hct.py b/test/unit/test_hct.py index 84e0a2379..7e462a65c 100644 --- a/test/unit/test_hct.py +++ b/test/unit/test_hct.py @@ -5,6 +5,8 @@ from FIAT.reference_element import ufc_simplex from FIAT.functional import PointEvaluation from FIAT.macro import CkPolynomialSet +from FIAT.quadrature_schemes import create_quadrature +from FIAT.jacobi import eval_jacobi @pytest.fixture @@ -74,3 +76,23 @@ def test_full_polynomials(cell, reduced): C1 = CkPolynomialSet(ref_complex, degree, order=1, variant="bubble") C1_tab = C1.tabulate(pts)[(0, 0)] assert span_greater_equal(tab, C1_tab) + + +def test_reduced_normal_derivative(cell): + fe = HCT(cell, reduced=True) + + ref_line = cell.construct_subelement(1) + Q = create_quadrature(ref_line, fe.degree()+2) + qpts, qwts = Q.get_points(), Q.get_weights() + + bary = ref_line.compute_barycentric_coordinates(qpts) + leg2 = eval_jacobi(0, 0, 2, bary[:, 1] - bary[:, 0]) + wts = numpy.multiply(leg2, qwts) + top = cell.get_topology() + for e in top[1]: + n = cell.compute_normal(e) + vals = fe.tabulate(1, qpts, entity=(1, e)) + fn = vals[(1, 0)] * n[0] + vals[(0, 1)] * n[1] + + fn = fn[:-3] + assert numpy.allclose(numpy.dot(fn, wts), 0)