fuse-element 0.1.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fuse/__init__.py +10 -0
- fuse/cells.py +850 -0
- fuse/dof.py +363 -0
- fuse/groups.py +388 -0
- fuse/serialisation.py +121 -0
- fuse/spaces/__init__.py +0 -0
- fuse/spaces/element_sobolev_spaces.py +80 -0
- fuse/spaces/interpolation_spaces.py +138 -0
- fuse/spaces/polynomial_spaces.py +203 -0
- fuse/traces.py +196 -0
- fuse/triples.py +470 -0
- fuse/utils.py +64 -0
- fuse_element-0.1.dev0.dist-info/LICENSE +165 -0
- fuse_element-0.1.dev0.dist-info/METADATA +17 -0
- fuse_element-0.1.dev0.dist-info/RECORD +17 -0
- fuse_element-0.1.dev0.dist-info/WHEEL +5 -0
- fuse_element-0.1.dev0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
from functools import total_ordering
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@total_ordering
|
|
5
|
+
class InterpolationSpace(object):
|
|
6
|
+
"""Symbolic representation of an interpolation function space
|
|
7
|
+
|
|
8
|
+
This implements a subset of the methods of a Python set so that
|
|
9
|
+
other spaces can be tested for inclusion.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, name, shape=None, parents=None):
|
|
13
|
+
"""Instantiate a InterpolationSpace object.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
name: The name of this space,
|
|
17
|
+
parents: A set of spaces of which this
|
|
18
|
+
space is a subspace.
|
|
19
|
+
"""
|
|
20
|
+
self.name = name
|
|
21
|
+
p = frozenset(parents or [])
|
|
22
|
+
# Ensure that the inclusion operations are transitive.
|
|
23
|
+
self.parents = p.union(*[p_.parents for p_ in p])
|
|
24
|
+
|
|
25
|
+
def __str__(self):
|
|
26
|
+
"""Format as a string."""
|
|
27
|
+
return self.name
|
|
28
|
+
|
|
29
|
+
def __repr__(self):
|
|
30
|
+
"""Representation."""
|
|
31
|
+
return f"InterpolationSpace({self.name!r}, {list(self.parents)!r})"
|
|
32
|
+
|
|
33
|
+
def __eq__(self, other):
|
|
34
|
+
"""Check equality."""
|
|
35
|
+
return isinstance(other, InterpolationSpace) and self.name == other.name and self.shape == other.shape
|
|
36
|
+
|
|
37
|
+
def __ne__(self, other):
|
|
38
|
+
"""Not equal."""
|
|
39
|
+
return not self == other
|
|
40
|
+
|
|
41
|
+
def __hash__(self):
|
|
42
|
+
"""Hash."""
|
|
43
|
+
return hash(("InterpolationSpace", self.name))
|
|
44
|
+
|
|
45
|
+
def __lt__(self, other):
|
|
46
|
+
"""In common with intrinsic Python sets, < indicates "is a proper subset of"."""
|
|
47
|
+
return other in self.parents
|
|
48
|
+
|
|
49
|
+
def _to_dict(self):
|
|
50
|
+
return {"space": str(self)}
|
|
51
|
+
|
|
52
|
+
def dict_id(self):
|
|
53
|
+
return "InterpolationSpace"
|
|
54
|
+
|
|
55
|
+
def _from_dict(obj_dict):
|
|
56
|
+
return InterpolationSpace(obj_dict["space"])
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class Sobolev(InterpolationSpace):
|
|
60
|
+
"""
|
|
61
|
+
Describes the Sobolev Space W_m,p
|
|
62
|
+
|
|
63
|
+
Arguments:
|
|
64
|
+
- Derivatives (m): the numbers of derivatives that are required to exist
|
|
65
|
+
- Lebesgue (p): The L_p space the derivatives are required to be in
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def __init__(self, derivatives, lebesgue, shape=None, name=None, parents=[]):
|
|
69
|
+
self.derivatives = derivatives
|
|
70
|
+
self.lebesgue = lebesgue
|
|
71
|
+
self.shape = shape
|
|
72
|
+
|
|
73
|
+
if name is None:
|
|
74
|
+
if derivatives == 0:
|
|
75
|
+
name = "L_" + str(lebesgue)
|
|
76
|
+
elif lebesgue == 2:
|
|
77
|
+
name = "H_" + str(derivatives)
|
|
78
|
+
else:
|
|
79
|
+
name = "W_" + str(derivatives) + ", " + str(lebesgue)
|
|
80
|
+
|
|
81
|
+
super(Sobolev, self).__init__(name, parents)
|
|
82
|
+
|
|
83
|
+
def __lt__(self, other):
|
|
84
|
+
"""In common with intrinsic Python sets, < indicates "is a proper subset of"."""
|
|
85
|
+
|
|
86
|
+
if isinstance(other, Sobolev):
|
|
87
|
+
if self.lebesgue >= other.lebesgue:
|
|
88
|
+
return other.derivatives < self.derivatives or other in self.parents
|
|
89
|
+
|
|
90
|
+
return other in self.parents
|
|
91
|
+
|
|
92
|
+
def __call__(self, shape):
|
|
93
|
+
return Sobolev(self.derivatives, self.lebesgue, shape, name=self.name, parents=self.parents)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class Continuous(InterpolationSpace):
|
|
97
|
+
"""
|
|
98
|
+
Describes the continuous space C_n
|
|
99
|
+
|
|
100
|
+
Arguments:
|
|
101
|
+
- Derivatives(n): the number of times the functions can be continously differentiated
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
def __init__(self, derivatives, shape=None, parents=[]):
|
|
105
|
+
self.derivatives = derivatives
|
|
106
|
+
self.shape = shape
|
|
107
|
+
name = "C_" + str(derivatives)
|
|
108
|
+
super(Continuous, self).__init__(name, parents)
|
|
109
|
+
|
|
110
|
+
def __lt__(self, other):
|
|
111
|
+
"""In common with intrinsic Python sets, < indicates "is a proper subset of"."""
|
|
112
|
+
|
|
113
|
+
if isinstance(other, Continuous):
|
|
114
|
+
return other.derivatives > self.derivatives
|
|
115
|
+
|
|
116
|
+
return other in self.parents
|
|
117
|
+
|
|
118
|
+
def __call__(self, shape):
|
|
119
|
+
return Continuous(self.derivatives, shape=shape, parents=self.parents)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
C0 = Continuous(0)
|
|
123
|
+
L2 = Sobolev(0, 2)
|
|
124
|
+
H1 = Sobolev(1, 2)
|
|
125
|
+
HDiv = Sobolev(0, 2, name="HDiv", parents=[L2])
|
|
126
|
+
HCurl = Sobolev(0, 2, name="HCurl", parents=[L2])
|
|
127
|
+
|
|
128
|
+
# Want to have
|
|
129
|
+
# c_0
|
|
130
|
+
# c_n
|
|
131
|
+
# w_m,p st mp < 1
|
|
132
|
+
|
|
133
|
+
# h_n ~ w_n,2
|
|
134
|
+
# l_2 ~ h_0
|
|
135
|
+
# h_1
|
|
136
|
+
|
|
137
|
+
# inclusions
|
|
138
|
+
# c_0
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
from FIAT.polynomial_set import ONPolynomialSet
|
|
2
|
+
from FIAT.quadrature_schemes import create_quadrature
|
|
3
|
+
from FIAT import expansions, polynomial_set, reference_element
|
|
4
|
+
from itertools import chain
|
|
5
|
+
from fuse.utils import tabulate_sympy, max_deg_sp_mat
|
|
6
|
+
import sympy as sp
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PolynomialSpace(object):
|
|
11
|
+
"""
|
|
12
|
+
contains: the degree of the maximum degree Lagrange space that is spanned by this element. If this
|
|
13
|
+
element's polynomial space does not include the constant function, this function should
|
|
14
|
+
return -1.
|
|
15
|
+
|
|
16
|
+
maxdegree: the degree of the minimum degree Lagrange space that spans this element.If this
|
|
17
|
+
element contains basis functions that are not in any Lagrange space, this property should
|
|
18
|
+
be None.
|
|
19
|
+
|
|
20
|
+
mindegree: the degree of the polynomial in the space with the lowest degree.
|
|
21
|
+
|
|
22
|
+
Note that on a simplex cells, the polynomial space of Lagrange space is a complete polynomial
|
|
23
|
+
space, but on other cells this is not true. For example, on quadrilateral cells, the degree 1
|
|
24
|
+
Lagrange space includes the degree 2 polynomial xy.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, maxdegree, contains=None, mindegree=0, set_shape=False):
|
|
28
|
+
self.maxdegree = maxdegree
|
|
29
|
+
self.mindegree = mindegree
|
|
30
|
+
|
|
31
|
+
if not contains and mindegree == 0:
|
|
32
|
+
self.contains = maxdegree
|
|
33
|
+
elif not contains and mindegree >= 0:
|
|
34
|
+
self.contains = -1
|
|
35
|
+
else:
|
|
36
|
+
self.contains = contains
|
|
37
|
+
|
|
38
|
+
self.set_shape = set_shape
|
|
39
|
+
|
|
40
|
+
def complete(self):
|
|
41
|
+
return self.mindegree == self.maxdegree
|
|
42
|
+
|
|
43
|
+
def degree(self):
|
|
44
|
+
return self.maxdegree
|
|
45
|
+
|
|
46
|
+
def to_ON_polynomial_set(self, ref_el, k=None):
|
|
47
|
+
# how does super/sub degrees work here
|
|
48
|
+
if not isinstance(ref_el, reference_element.Cell):
|
|
49
|
+
ref_el = ref_el.to_fiat()
|
|
50
|
+
sd = ref_el.get_spatial_dimension()
|
|
51
|
+
if self.set_shape:
|
|
52
|
+
shape = (sd,)
|
|
53
|
+
else:
|
|
54
|
+
shape = tuple()
|
|
55
|
+
|
|
56
|
+
if self.mindegree > 0:
|
|
57
|
+
base_ON = ONPolynomialSet(ref_el, self.maxdegree, shape, scale="orthonormal")
|
|
58
|
+
dimPmin = expansions.polynomial_dimension(ref_el, self.mindegree)
|
|
59
|
+
dimPmax = expansions.polynomial_dimension(ref_el, self.maxdegree)
|
|
60
|
+
if self.set_shape:
|
|
61
|
+
indices = list(chain(*(range(i * dimPmin, i * dimPmax) for i in range(sd))))
|
|
62
|
+
else:
|
|
63
|
+
indices = list(range(dimPmin, dimPmax))
|
|
64
|
+
restricted_ON = base_ON.take(indices)
|
|
65
|
+
return restricted_ON
|
|
66
|
+
return ONPolynomialSet(ref_el, self.maxdegree, shape, scale="orthonormal")
|
|
67
|
+
|
|
68
|
+
def __repr__(self):
|
|
69
|
+
res = ""
|
|
70
|
+
if self.complete():
|
|
71
|
+
res += "P" + str(self.maxdegree)
|
|
72
|
+
elif self.mindegree > 0:
|
|
73
|
+
res = "P" + "(min " + str(self.mindegree) + " max " + str(self.maxdegree) + ")"
|
|
74
|
+
else:
|
|
75
|
+
res += "Psub" + str(self.contains) + "sup" + str(self.maxdegree)
|
|
76
|
+
if self.set_shape:
|
|
77
|
+
res += "^d"
|
|
78
|
+
return res
|
|
79
|
+
|
|
80
|
+
def __mul__(self, x):
|
|
81
|
+
"""
|
|
82
|
+
When multiplying a Polynomial Space by a sympy object, you need to multiply with
|
|
83
|
+
the sympy object on the right. This is due to Sympy's implementation of __mul__ not
|
|
84
|
+
passing to this handler as it should.
|
|
85
|
+
"""
|
|
86
|
+
if isinstance(x, sp.Symbol):
|
|
87
|
+
return ConstructedPolynomialSpace([x], [self])
|
|
88
|
+
elif isinstance(x, sp.Matrix):
|
|
89
|
+
return ConstructedPolynomialSpace([x], [self])
|
|
90
|
+
else:
|
|
91
|
+
raise TypeError(f'Cannot multiply a PolySpace with {type(x)}')
|
|
92
|
+
|
|
93
|
+
__rmul__ = __mul__
|
|
94
|
+
|
|
95
|
+
def __add__(self, x):
|
|
96
|
+
return ConstructedPolynomialSpace([1, 1], [self, x])
|
|
97
|
+
|
|
98
|
+
def restrict(self, min_degree, max_degree):
|
|
99
|
+
return PolynomialSpace(max_degree, contains=-1, mindegree=min_degree, set_shape=self.set_shape)
|
|
100
|
+
|
|
101
|
+
def _to_dict(self):
|
|
102
|
+
return {"set_shape": self.set_shape, "min": self.mindegree, "contains": self.contains, "max": self.maxdegree}
|
|
103
|
+
|
|
104
|
+
def dict_id(self):
|
|
105
|
+
return "PolynomialSpace"
|
|
106
|
+
|
|
107
|
+
def _from_dict(obj_dict):
|
|
108
|
+
return PolynomialSpace(obj_dict["max"], obj_dict["contains"], obj_dict["min"], obj_dict["set_shape"])
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class ConstructedPolynomialSpace(PolynomialSpace):
|
|
112
|
+
"""
|
|
113
|
+
Sub degree is inherited from the largest of the component spaces,
|
|
114
|
+
super degree is unknown.
|
|
115
|
+
|
|
116
|
+
weights can either be 1 or a polynomial in x, where x in R^d
|
|
117
|
+
"""
|
|
118
|
+
def __init__(self, weights, spaces):
|
|
119
|
+
|
|
120
|
+
self.weights = weights
|
|
121
|
+
self.spaces = spaces
|
|
122
|
+
|
|
123
|
+
maxdegree = max([space.maxdegree for space in spaces])
|
|
124
|
+
mindegree = min([space.mindegree for space in spaces])
|
|
125
|
+
vec = any([s.set_shape for s in spaces])
|
|
126
|
+
|
|
127
|
+
super(ConstructedPolynomialSpace, self).__init__(maxdegree, -1, mindegree, set_shape=vec)
|
|
128
|
+
|
|
129
|
+
def __repr__(self):
|
|
130
|
+
return "+".join([str(w) + "*" + str(x) for (w, x) in zip(self.weights, self.spaces)])
|
|
131
|
+
|
|
132
|
+
def to_ON_polynomial_set(self, ref_el):
|
|
133
|
+
if not isinstance(ref_el, reference_element.Cell):
|
|
134
|
+
ref_el = ref_el.to_fiat()
|
|
135
|
+
k = max([s.maxdegree for s in self.spaces])
|
|
136
|
+
space_poly_sets = [s.to_ON_polynomial_set(ref_el) for s in self.spaces]
|
|
137
|
+
sd = ref_el.get_spatial_dimension()
|
|
138
|
+
|
|
139
|
+
if all([w == 1 for w in self.weights]):
|
|
140
|
+
weighted_sets = space_poly_sets
|
|
141
|
+
|
|
142
|
+
# otherwise have to work on this through tabulation
|
|
143
|
+
|
|
144
|
+
Q = create_quadrature(ref_el, 2 * (k + 1))
|
|
145
|
+
Qpts, Qwts = Q.get_points(), Q.get_weights()
|
|
146
|
+
weighted_sets = []
|
|
147
|
+
|
|
148
|
+
for (space, w) in zip(space_poly_sets, self.weights):
|
|
149
|
+
if not (isinstance(w, sp.Expr) or isinstance(w, sp.Matrix)):
|
|
150
|
+
weighted_sets.append(space)
|
|
151
|
+
else:
|
|
152
|
+
w_deg = max_deg_sp_mat(w)
|
|
153
|
+
Pkpw = ONPolynomialSet(ref_el, space.degree + w_deg, scale="orthonormal")
|
|
154
|
+
vec_Pkpw = ONPolynomialSet(ref_el, space.degree + w_deg, (sd,), scale="orthonormal")
|
|
155
|
+
|
|
156
|
+
space_at_Qpts = space.tabulate(Qpts)[(0,) * sd]
|
|
157
|
+
Pkpw_at_Qpts = Pkpw.tabulate(Qpts)[(0,) * sd]
|
|
158
|
+
|
|
159
|
+
tabulated_expr = tabulate_sympy(w, Qpts).T
|
|
160
|
+
scaled_at_Qpts = space_at_Qpts[:, None, :] * tabulated_expr[None, :, :]
|
|
161
|
+
PkHw_coeffs = np.dot(np.multiply(scaled_at_Qpts, Qwts), Pkpw_at_Qpts.T)
|
|
162
|
+
weighted_sets.append(polynomial_set.PolynomialSet(ref_el,
|
|
163
|
+
space.degree + w_deg,
|
|
164
|
+
space.degree + w_deg,
|
|
165
|
+
vec_Pkpw.get_expansion_set(),
|
|
166
|
+
PkHw_coeffs))
|
|
167
|
+
combined_sets = weighted_sets[0]
|
|
168
|
+
for i in range(1, len(weighted_sets)):
|
|
169
|
+
combined_sets = polynomial_set.polynomial_set_union_normalized(combined_sets, weighted_sets[i])
|
|
170
|
+
return combined_sets
|
|
171
|
+
|
|
172
|
+
def __mul__(self, x):
|
|
173
|
+
return ConstructedPolynomialSpace([x*w for w in self.weights],
|
|
174
|
+
self.spaces)
|
|
175
|
+
__rmul__ = __mul__
|
|
176
|
+
|
|
177
|
+
def __add__(self, x):
|
|
178
|
+
return ConstructedPolynomialSpace(self.weights.extend([1]),
|
|
179
|
+
self.spaces.extend(x))
|
|
180
|
+
|
|
181
|
+
def _to_dict(self):
|
|
182
|
+
super_dict = super(ConstructedPolynomialSpace, self)._to_dict()
|
|
183
|
+
super_dict["spaces"] = self.spaces
|
|
184
|
+
super_dict["weights"] = self.weights
|
|
185
|
+
return super_dict
|
|
186
|
+
|
|
187
|
+
def dict_id(self):
|
|
188
|
+
return "ConstructedPolynomialSpace"
|
|
189
|
+
|
|
190
|
+
def _from_dict(obj_dict):
|
|
191
|
+
return ConstructedPolynomialSpace(obj_dict["weights"], obj_dict["spaces"])
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
P0 = PolynomialSpace(0)
|
|
195
|
+
P1 = PolynomialSpace(1)
|
|
196
|
+
P2 = PolynomialSpace(2)
|
|
197
|
+
P3 = PolynomialSpace(3)
|
|
198
|
+
P4 = PolynomialSpace(4)
|
|
199
|
+
|
|
200
|
+
Q1 = PolynomialSpace(1, 2)
|
|
201
|
+
Q2 = PolynomialSpace(2, 3)
|
|
202
|
+
Q3 = PolynomialSpace(3, 4)
|
|
203
|
+
Q4 = PolynomialSpace(4, 5)
|
fuse/traces.py
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import sympy as sp
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
from fuse.utils import sympy_to_numpy
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Trace():
|
|
8
|
+
|
|
9
|
+
def __init__(self, cell):
|
|
10
|
+
self.domain = cell
|
|
11
|
+
|
|
12
|
+
def __call__(self, trace_entity, g):
|
|
13
|
+
raise NotImplementedError("Trace uninstanitated")
|
|
14
|
+
|
|
15
|
+
def plot(self, ax, coord, trace_entity, g, **kwargs):
|
|
16
|
+
raise NotImplementedError("Trace uninstanitated")
|
|
17
|
+
|
|
18
|
+
def tabulate(self, Qpts, trace_entity, g):
|
|
19
|
+
raise NotImplementedError("Tabulation uninstantiated")
|
|
20
|
+
|
|
21
|
+
def _to_dict(self):
|
|
22
|
+
return {"trace": str(self)}
|
|
23
|
+
|
|
24
|
+
def dict_id(self):
|
|
25
|
+
return "Trace"
|
|
26
|
+
|
|
27
|
+
def _from_dict(obj_dict):
|
|
28
|
+
# might want to actually save these as functions or something for ambiguity?
|
|
29
|
+
tr_id = obj_dict["trace"]
|
|
30
|
+
if tr_id == "H1":
|
|
31
|
+
return TrH1
|
|
32
|
+
elif tr_id == "HDiv":
|
|
33
|
+
return TrHDiv
|
|
34
|
+
elif tr_id == "HCurl":
|
|
35
|
+
return TrHCurl
|
|
36
|
+
elif tr_id == "Grad":
|
|
37
|
+
return TrGrad
|
|
38
|
+
elif tr_id == "Hess":
|
|
39
|
+
return TrHess
|
|
40
|
+
raise ValueError("Trace not found")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class TrH1(Trace):
|
|
44
|
+
|
|
45
|
+
def __init__(self, cell):
|
|
46
|
+
super(TrH1, self).__init__(cell)
|
|
47
|
+
|
|
48
|
+
def __call__(self, v, trace_entity, g):
|
|
49
|
+
return v
|
|
50
|
+
|
|
51
|
+
def plot(self, ax, coord, trace_entity, g, **kwargs):
|
|
52
|
+
ax.scatter(*coord, **kwargs)
|
|
53
|
+
|
|
54
|
+
def tabulate(self, Qpts, trace_entity, g):
|
|
55
|
+
return np.ones_like(Qpts)
|
|
56
|
+
|
|
57
|
+
def __repr__(self):
|
|
58
|
+
return "H1"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class TrHDiv(Trace):
|
|
62
|
+
|
|
63
|
+
def __init__(self, cell):
|
|
64
|
+
super(TrHDiv, self).__init__(cell)
|
|
65
|
+
|
|
66
|
+
def __call__(self, v, trace_entity, g):
|
|
67
|
+
def apply(*x):
|
|
68
|
+
result = np.dot(self.tabulate(None, trace_entity, g), np.array(v(*x)).squeeze())
|
|
69
|
+
if isinstance(result, np.float64):
|
|
70
|
+
# todo: might always be a float
|
|
71
|
+
return (result,)
|
|
72
|
+
return tuple(result)
|
|
73
|
+
return apply
|
|
74
|
+
|
|
75
|
+
def plot(self, ax, coord, trace_entity, g, **kwargs):
|
|
76
|
+
# plot dofs of the type associated with this space
|
|
77
|
+
permuted = self.domain.permute_entities(g, trace_entity.dimension)
|
|
78
|
+
orientation = [o for (ent, o) in permuted if ent == trace_entity.id][0]
|
|
79
|
+
|
|
80
|
+
entityBasis = np.array(trace_entity.orient(orientation).basis_vectors())
|
|
81
|
+
cellEntityBasis = np.array(self.domain.basis_vectors(entity=trace_entity))
|
|
82
|
+
basis = np.matmul(entityBasis, cellEntityBasis)
|
|
83
|
+
if len(coord) == 2:
|
|
84
|
+
vec = np.matmul(np.array([[0, 1], [-1, 0]]), basis.T)
|
|
85
|
+
else:
|
|
86
|
+
vec = np.cross(basis[0], basis[1])
|
|
87
|
+
ax.quiver(*coord, *vec, **kwargs)
|
|
88
|
+
|
|
89
|
+
def tabulate(self, Qpts, trace_entity, g):
|
|
90
|
+
entityBasis = np.array(trace_entity.basis_vectors())
|
|
91
|
+
cellEntityBasis = np.array(self.domain.basis_vectors(entity=trace_entity))
|
|
92
|
+
basis = np.matmul(entityBasis, cellEntityBasis)
|
|
93
|
+
|
|
94
|
+
if trace_entity.dimension == 1:
|
|
95
|
+
result = np.matmul(basis, np.array([[0, -1], [1, 0]]))
|
|
96
|
+
elif trace_entity.dimension == 2:
|
|
97
|
+
result = np.cross(basis[0], basis[1])
|
|
98
|
+
else:
|
|
99
|
+
raise ValueError("Immersion of HDiv edges not defined in 3D")
|
|
100
|
+
|
|
101
|
+
return result
|
|
102
|
+
|
|
103
|
+
def __repr__(self):
|
|
104
|
+
return "HDiv"
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class TrHCurl(Trace):
|
|
108
|
+
|
|
109
|
+
def __init__(self, cell):
|
|
110
|
+
super(TrHCurl, self).__init__(cell)
|
|
111
|
+
|
|
112
|
+
def __call__(self, v, trace_entity, g):
|
|
113
|
+
def apply(*x):
|
|
114
|
+
result = np.dot(self.tabulate(None, trace_entity, g), np.array(v(*x)).squeeze())
|
|
115
|
+
if isinstance(result, np.float64):
|
|
116
|
+
return (result,)
|
|
117
|
+
return tuple(result)
|
|
118
|
+
return apply
|
|
119
|
+
|
|
120
|
+
def tabulate(self, Qpts, trace_entity, g):
|
|
121
|
+
tangent = np.array(trace_entity.basis_vectors())
|
|
122
|
+
subEntityBasis = np.array(self.domain.basis_vectors(entity=trace_entity))
|
|
123
|
+
result = np.matmul(tangent, subEntityBasis)
|
|
124
|
+
return result
|
|
125
|
+
|
|
126
|
+
def plot(self, ax, coord, trace_entity, g, **kwargs):
|
|
127
|
+
permuted = self.domain.permute_entities(g, trace_entity.dimension)
|
|
128
|
+
orientation = [o for (ent, o) in permuted if ent == trace_entity.id][0]
|
|
129
|
+
|
|
130
|
+
tangent = np.array(trace_entity.orient(orientation).basis_vectors())
|
|
131
|
+
subEntityBasis = np.array(self.domain.basis_vectors(entity=trace_entity))
|
|
132
|
+
vec = np.matmul(tangent, subEntityBasis)[0]
|
|
133
|
+
ax.quiver(*coord, *vec, **kwargs)
|
|
134
|
+
|
|
135
|
+
def __repr__(self):
|
|
136
|
+
return "HCurl"
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class TrGrad(Trace):
|
|
140
|
+
|
|
141
|
+
def __init__(self, cell):
|
|
142
|
+
super(TrGrad, self).__init__(cell)
|
|
143
|
+
|
|
144
|
+
def __call__(self, v, trace_entity, g):
|
|
145
|
+
# Compute grad v and then dot with tangent rotated according to the group member
|
|
146
|
+
tangent = np.array(g(np.array(self.domain.basis_vectors())[0]))
|
|
147
|
+
|
|
148
|
+
def apply(*x):
|
|
149
|
+
X = sp.DeferredVector('x')
|
|
150
|
+
dX = tuple([X[i] for i in range(self.domain.dim())])
|
|
151
|
+
compute_v = v(*dX, sym=True)
|
|
152
|
+
grad_v = sp.Matrix([sp.diff(compute_v, dX[i]) for i in range(len(dX))])
|
|
153
|
+
eval_grad_v = sympy_to_numpy(grad_v, dX, v.attach_func(*x))
|
|
154
|
+
result = np.dot(tangent, np.array(eval_grad_v))
|
|
155
|
+
|
|
156
|
+
if not hasattr(result, "__iter__"):
|
|
157
|
+
return (result,)
|
|
158
|
+
return tuple(result)
|
|
159
|
+
return apply
|
|
160
|
+
|
|
161
|
+
def plot(self, ax, coord, trace_entity, g, **kwargs):
|
|
162
|
+
circle1 = plt.Circle(coord, 0.075, fill=False, **kwargs)
|
|
163
|
+
ax.add_patch(circle1)
|
|
164
|
+
|
|
165
|
+
def __repr__(self):
|
|
166
|
+
return "Grad"
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class TrHess(Trace):
|
|
170
|
+
|
|
171
|
+
def __init__(self, cell):
|
|
172
|
+
super(TrHess, self).__init__(cell)
|
|
173
|
+
|
|
174
|
+
def __call__(self, v, trace_entity, g):
|
|
175
|
+
b0, b1 = self.domain.basis_vectors()
|
|
176
|
+
tangent0 = np.array(g(b0))
|
|
177
|
+
tangent1 = np.array(g(b1))
|
|
178
|
+
|
|
179
|
+
def apply(*x):
|
|
180
|
+
X = sp.DeferredVector('x')
|
|
181
|
+
|
|
182
|
+
dX = tuple([X[i] for i in range(self.domain.dim())])
|
|
183
|
+
hess_v = sp.Matrix([[sp.diff(v(*dX, sym=True), dX[i], dX[j]) for i in range(len(dX))] for j in range(len(dX))])
|
|
184
|
+
eval_hess_v = sympy_to_numpy(hess_v, dX, v.attach_func(*x))
|
|
185
|
+
result = np.dot(np.matmul(tangent0, np.array(eval_hess_v)), tangent1)
|
|
186
|
+
if not hasattr(result, "__iter__"):
|
|
187
|
+
return (result,)
|
|
188
|
+
return tuple(result)
|
|
189
|
+
return apply
|
|
190
|
+
|
|
191
|
+
def plot(self, ax, coord, trace_entity, g, **kwargs):
|
|
192
|
+
circle1 = plt.Circle(coord, 0.15, fill=False, **kwargs)
|
|
193
|
+
ax.add_patch(circle1)
|
|
194
|
+
|
|
195
|
+
def __repr__(self):
|
|
196
|
+
return "Hess"
|