|
9 | 9 | # SPDX-License-Identifier: LGPL-3.0-or-later
|
10 | 10 |
|
11 | 11 |
|
12 |
| -from FIAT.finite_element import CiarletElement |
13 |
| -from FIAT.dual_set import DualSet |
14 |
| -from FIAT.polynomial_set import ONSymTensorPolynomialSet, ONPolynomialSet |
15 |
| -from FIAT.functional import ( |
16 |
| - PointwiseInnerProductEvaluation as InnerProduct, |
17 |
| - FrobeniusIntegralMoment as FIM, |
18 |
| - IntegralMomentOfTensorDivergence, |
19 |
| - IntegralLegendreNormalNormalMoment, |
20 |
| - IntegralLegendreNormalTangentialMoment) |
21 |
| - |
22 |
| -from FIAT.quadrature import make_quadrature |
| 12 | +from FIAT import finite_element, dual_set, polynomial_set |
| 13 | +from FIAT.reference_element import TRIANGLE |
| 14 | +from FIAT.quadrature_schemes import create_quadrature |
| 15 | +from FIAT.functional import (ComponentPointEvaluation, |
| 16 | + TensorBidirectionalIntegralMoment, |
| 17 | + IntegralMomentOfTensorDivergence, |
| 18 | + IntegralLegendreNormalNormalMoment, |
| 19 | + IntegralLegendreNormalTangentialMoment) |
23 | 20 |
|
24 | 21 | import numpy
|
25 | 22 |
|
26 | 23 |
|
27 |
| -class ArnoldWintherNCDual(DualSet): |
28 |
| - def __init__(self, cell, degree=2): |
29 |
| - if not degree == 2: |
30 |
| - raise ValueError("Nonconforming Arnold-Winther elements are" |
31 |
| - "only defined for degree 2.") |
32 |
| - dofs = [] |
33 |
| - dof_ids = {} |
34 |
| - dof_ids[0] = {0: [], 1: [], 2: []} |
35 |
| - dof_ids[1] = {0: [], 1: [], 2: []} |
36 |
| - dof_ids[2] = {0: []} |
| 24 | +class ArnoldWintherNCDual(dual_set.DualSet): |
| 25 | + def __init__(self, ref_el, degree=2): |
| 26 | + if degree != 2: |
| 27 | + raise ValueError("Nonconforming Arnold-Winther elements are only defined for degree 2.") |
| 28 | + top = ref_el.get_topology() |
| 29 | + sd = ref_el.get_spatial_dimension() |
| 30 | + entity_ids = {dim: {entity: [] for entity in sorted(top[dim])} for dim in sorted(top)} |
| 31 | + nodes = [] |
37 | 32 |
|
38 |
| - dof_cur = 0 |
39 | 33 | # no vertex dofs
|
40 | 34 | # proper edge dofs now (not the contraints)
|
41 |
| - # moments of normal . sigma against constants and linears. |
42 |
| - for entity_id in range(3): # a triangle has 3 edges |
43 |
| - for order in (0, 1): |
44 |
| - dofs += [IntegralLegendreNormalNormalMoment(cell, entity_id, order, 6), |
45 |
| - IntegralLegendreNormalTangentialMoment(cell, entity_id, order, 6)] |
46 |
| - dof_ids[1][entity_id] = list(range(dof_cur, dof_cur+4)) |
47 |
| - dof_cur += 4 |
| 35 | + # edge dofs: bidirectional nn and nt moments against P1. |
| 36 | + qdegree = degree + 2 |
| 37 | + for entity in sorted(top[1]): |
| 38 | + cur = len(nodes) |
| 39 | + for order in range(2): |
| 40 | + nodes.append(IntegralLegendreNormalNormalMoment(ref_el, entity, order, qdegree)) |
| 41 | + nodes.append(IntegralLegendreNormalTangentialMoment(ref_el, entity, order, qdegree)) |
| 42 | + entity_ids[1][entity].extend(range(cur, len(nodes))) |
48 | 43 |
|
49 | 44 | # internal dofs: constant moments of three unique components
|
50 |
| - Q = make_quadrature(cell, 2) |
51 |
| - |
52 |
| - e1 = numpy.array([1.0, 0.0]) # euclidean basis 1 |
53 |
| - e2 = numpy.array([0.0, 1.0]) # euclidean basis 2 |
54 |
| - basis = [(e1, e1), (e1, e2), (e2, e2)] # basis for symmetric matrices |
55 |
| - for (v1, v2) in basis: |
56 |
| - v1v2t = numpy.outer(v1, v2) |
57 |
| - fatqp = numpy.zeros((2, 2, len(Q.pts))) |
58 |
| - for i, y in enumerate(v1v2t): |
59 |
| - for j, x in enumerate(y): |
60 |
| - for k in range(len(Q.pts)): |
61 |
| - fatqp[i, j, k] = x |
62 |
| - dofs.append(FIM(cell, Q, fatqp)) |
63 |
| - dof_ids[2][0] = list(range(dof_cur, dof_cur + 3)) |
64 |
| - dof_cur += 3 |
| 45 | + cur = len(nodes) |
| 46 | + n = list(map(ref_el.compute_scaled_normal, sorted(top[sd-1]))) |
| 47 | + Q = create_quadrature(ref_el, degree) |
| 48 | + phi = numpy.full(Q.get_weights().shape, 1/ref_el.volume()) |
| 49 | + nodes.extend(TensorBidirectionalIntegralMoment(ref_el, n[i+1], n[j+1], Q, phi) |
| 50 | + for i in range(sd) for j in range(i, sd)) |
| 51 | + entity_ids[2][0].extend(range(cur, len(nodes))) |
65 | 52 |
|
66 | 53 | # put the constraint dofs last.
|
67 |
| - for entity_id in range(3): |
68 |
| - dof = IntegralLegendreNormalNormalMoment(cell, entity_id, 2, 6) |
69 |
| - dofs.append(dof) |
70 |
| - dof_ids[1][entity_id].append(dof_cur) |
71 |
| - dof_cur += 1 |
| 54 | + for entity in sorted(top[1]): |
| 55 | + cur = len(nodes) |
| 56 | + nodes.append(IntegralLegendreNormalNormalMoment(ref_el, entity, 2, qdegree)) |
| 57 | + entity_ids[1][entity].append(cur) |
72 | 58 |
|
73 |
| - super().__init__(dofs, cell, dof_ids) |
| 59 | + super().__init__(nodes, ref_el, entity_ids) |
74 | 60 |
|
75 | 61 |
|
76 |
| -class ArnoldWintherNC(CiarletElement): |
| 62 | +class ArnoldWintherNC(finite_element.CiarletElement): |
77 | 63 | """The definition of the nonconforming Arnold-Winther element.
|
78 | 64 | """
|
79 |
| - def __init__(self, cell, degree=2): |
80 |
| - assert degree == 2, "Only defined for degree 2" |
81 |
| - Ps = ONSymTensorPolynomialSet(cell, degree) |
82 |
| - Ls = ArnoldWintherNCDual(cell, degree) |
| 65 | + def __init__(self, ref_el, degree=2): |
| 66 | + if ref_el.shape != TRIANGLE: |
| 67 | + raise ValueError(f"{type(self).__name__} only defined on triangles") |
| 68 | + Ps = polynomial_set.ONSymTensorPolynomialSet(ref_el, degree) |
| 69 | + Ls = ArnoldWintherNCDual(ref_el, degree) |
| 70 | + formdegree = ref_el.get_spatial_dimension() - 1 |
83 | 71 | mapping = "double contravariant piola"
|
| 72 | + super().__init__(Ps, Ls, degree, formdegree, mapping=mapping) |
84 | 73 |
|
85 |
| - super().__init__(Ps, Ls, degree, mapping=mapping) |
86 | 74 |
|
87 |
| - |
88 |
| -class ArnoldWintherDual(DualSet): |
89 |
| - def __init__(self, cell, degree=3): |
90 |
| - if not degree == 3: |
91 |
| - raise ValueError("Arnold-Winther elements are" |
92 |
| - "only defined for degree 3.") |
93 |
| - dofs = [] |
94 |
| - dof_ids = {} |
95 |
| - dof_ids[0] = {0: [], 1: [], 2: []} |
96 |
| - dof_ids[1] = {0: [], 1: [], 2: []} |
97 |
| - dof_ids[2] = {0: []} |
98 |
| - |
99 |
| - dof_cur = 0 |
| 75 | +class ArnoldWintherDual(dual_set.DualSet): |
| 76 | + def __init__(self, ref_el, degree=3): |
| 77 | + if degree != 3: |
| 78 | + raise ValueError("Arnold-Winther elements are only defined for degree 3.") |
| 79 | + top = ref_el.get_topology() |
| 80 | + sd = ref_el.get_spatial_dimension() |
| 81 | + shp = (sd, sd) |
| 82 | + entity_ids = {dim: {entity: [] for entity in sorted(top[dim])} for dim in sorted(top)} |
| 83 | + nodes = [] |
100 | 84 |
|
101 | 85 | # vertex dofs
|
102 |
| - vs = cell.get_vertices() |
103 |
| - e1 = numpy.array([1.0, 0.0]) |
104 |
| - e2 = numpy.array([0.0, 1.0]) |
105 |
| - basis = [(e1, e1), (e1, e2), (e2, e2)] |
106 |
| - |
107 |
| - dof_cur = 0 |
108 |
| - |
109 |
| - for entity_id in range(3): |
110 |
| - node = tuple(vs[entity_id]) |
111 |
| - for (v1, v2) in basis: |
112 |
| - dofs.append(InnerProduct(cell, v1, v2, node)) |
113 |
| - dof_ids[0][entity_id] = list(range(dof_cur, dof_cur + 3)) |
114 |
| - dof_cur += 3 |
115 |
| - |
116 |
| - # edge dofs now |
117 |
| - # moments of normal . sigma against constants and linears. |
118 |
| - for entity_id in range(3): |
119 |
| - for order in (0, 1): |
120 |
| - dofs += [IntegralLegendreNormalNormalMoment(cell, entity_id, order, 6), |
121 |
| - IntegralLegendreNormalTangentialMoment(cell, entity_id, order, 6)] |
122 |
| - dof_ids[1][entity_id] = list(range(dof_cur, dof_cur+4)) |
123 |
| - dof_cur += 4 |
124 |
| - |
125 |
| - # internal dofs: constant moments of three unique components |
126 |
| - Q = make_quadrature(cell, 3) |
127 |
| - |
128 |
| - e1 = numpy.array([1.0, 0.0]) # euclidean basis 1 |
129 |
| - e2 = numpy.array([0.0, 1.0]) # euclidean basis 2 |
130 |
| - basis = [(e1, e1), (e1, e2), (e2, e2)] # basis for symmetric matrices |
131 |
| - for (v1, v2) in basis: |
132 |
| - v1v2t = numpy.outer(v1, v2) |
133 |
| - fatqp = numpy.zeros((2, 2, len(Q.pts))) |
134 |
| - for k in range(len(Q.pts)): |
135 |
| - fatqp[:, :, k] = v1v2t |
136 |
| - dofs.append(FIM(cell, Q, fatqp)) |
137 |
| - dof_ids[2][0] = list(range(dof_cur, dof_cur + 3)) |
138 |
| - dof_cur += 3 |
139 |
| - |
140 |
| - # Constraint dofs |
141 |
| - |
142 |
| - Q = make_quadrature(cell, 5) |
143 |
| - |
144 |
| - onp = ONPolynomialSet(cell, 2, (2,)) |
145 |
| - pts = Q.get_points() |
146 |
| - onpvals = onp.tabulate(pts)[0, 0] |
147 |
| - |
148 |
| - for i in list(range(3, 6)) + list(range(9, 12)): |
149 |
| - dofs.append(IntegralMomentOfTensorDivergence(cell, Q, |
150 |
| - onpvals[i, :, :])) |
151 |
| - |
152 |
| - dof_ids[2][0] += list(range(dof_cur, dof_cur+6)) |
153 |
| - |
154 |
| - super().__init__(dofs, cell, dof_ids) |
155 |
| - |
156 |
| - |
157 |
| -class ArnoldWinther(CiarletElement): |
| 86 | + for v in sorted(top[0]): |
| 87 | + cur = len(nodes) |
| 88 | + pt, = ref_el.make_points(0, v, degree) |
| 89 | + nodes.extend(ComponentPointEvaluation(ref_el, (i, j), shp, pt) |
| 90 | + for i in range(sd) for j in range(i, sd)) |
| 91 | + entity_ids[0][v].extend(range(cur, len(nodes))) |
| 92 | + |
| 93 | + # edge dofs: bidirectional nn and nt moments against P_{k-2} |
| 94 | + max_order = degree - 2 |
| 95 | + qdegree = degree + max_order |
| 96 | + for entity in sorted(top[1]): |
| 97 | + cur = len(nodes) |
| 98 | + for order in range(max_order+1): |
| 99 | + nodes.append(IntegralLegendreNormalNormalMoment(ref_el, entity, order, qdegree)) |
| 100 | + nodes.append(IntegralLegendreNormalTangentialMoment(ref_el, entity, order, qdegree)) |
| 101 | + entity_ids[1][entity].extend(range(cur, len(nodes))) |
| 102 | + |
| 103 | + # internal dofs: moments of unique components against P_{k-3} |
| 104 | + n = list(map(ref_el.compute_scaled_normal, sorted(top[sd-1]))) |
| 105 | + Q = create_quadrature(ref_el, 2*(degree-1)) |
| 106 | + P = polynomial_set.ONPolynomialSet(ref_el, degree-3, scale="L2 piola") |
| 107 | + phis = P.tabulate(Q.get_points())[(0,)*sd] |
| 108 | + nodes.extend(TensorBidirectionalIntegralMoment(ref_el, n[i+1], n[j+1], Q, phi) |
| 109 | + for phi in phis for i in range(sd) for j in range(i, sd)) |
| 110 | + |
| 111 | + # constraint dofs: moments of divergence against P_{k-1} \ P_{k-2} |
| 112 | + P = polynomial_set.ONPolynomialSet(ref_el, degree-1, shape=(sd,)) |
| 113 | + dimPkm1 = P.expansion_set.get_num_members(degree-1) |
| 114 | + dimPkm2 = P.expansion_set.get_num_members(degree-2) |
| 115 | + PH = P.take([i + j * dimPkm1 for j in range(sd) for i in range(dimPkm2, dimPkm1)]) |
| 116 | + phis = PH.tabulate(Q.get_points())[(0,)*sd] |
| 117 | + nodes.extend(IntegralMomentOfTensorDivergence(ref_el, Q, phi) for phi in phis) |
| 118 | + |
| 119 | + entity_ids[2][0].extend(range(cur, len(nodes))) |
| 120 | + super().__init__(nodes, ref_el, entity_ids) |
| 121 | + |
| 122 | + |
| 123 | +class ArnoldWinther(finite_element.CiarletElement): |
158 | 124 | """The definition of the conforming Arnold-Winther element.
|
159 | 125 | """
|
160 |
| - def __init__(self, cell, degree=3): |
161 |
| - assert degree == 3, "Only defined for degree 3" |
162 |
| - Ps = ONSymTensorPolynomialSet(cell, degree) |
163 |
| - Ls = ArnoldWintherDual(cell, degree) |
| 126 | + def __init__(self, ref_el, degree=3): |
| 127 | + if ref_el.shape != TRIANGLE: |
| 128 | + raise ValueError(f"{type(self).__name__} only defined on triangles") |
| 129 | + Ps = polynomial_set.ONSymTensorPolynomialSet(ref_el, degree) |
| 130 | + Ls = ArnoldWintherDual(ref_el, degree) |
| 131 | + formdegree = ref_el.get_spatial_dimension() - 1 |
164 | 132 | mapping = "double contravariant piola"
|
165 |
| - super().__init__(Ps, Ls, degree, mapping=mapping) |
| 133 | + super().__init__(Ps, Ls, degree, formdegree, mapping=mapping) |
0 commit comments