ddfem 0.0.0__py3-none-any.whl → 0.9.0__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.
- ddfem/__init__.py +4 -0
- ddfem/boundary.py +223 -0
- ddfem/dune.py +3 -0
- ddfem/examples/__init__.py +0 -0
- ddfem/examples/advection_diffusion.py +74 -0
- ddfem/examples/beam.py +147 -0
- ddfem/examples/cahn_hilliard.py +67 -0
- ddfem/examples/chemical_reaction.py +88 -0
- ddfem/examples/constant.py +46 -0
- ddfem/examples/five_circle_flat.py +197 -0
- ddfem/examples/forchheimer.py +48 -0
- ddfem/examples/hyperelasticity.py +88 -0
- ddfem/examples/linear_elasticity.py +45 -0
- ddfem/examples/plaplace.py +29 -0
- ddfem/examples/single_circle.py +135 -0
- ddfem/examples/triple_circle.py +217 -0
- ddfem/examples/triple_circle_beam.py +208 -0
- ddfem/geometry/__init__.py +18 -0
- ddfem/geometry/arc.py +48 -0
- ddfem/geometry/ball.py +24 -0
- ddfem/geometry/box.py +33 -0
- ddfem/geometry/domain.py +49 -0
- ddfem/geometry/domain_dune.py +82 -0
- ddfem/geometry/helpers.py +42 -0
- ddfem/geometry/pie.py +31 -0
- ddfem/geometry/plane.py +20 -0
- ddfem/geometry/primitive_base.py +338 -0
- ddfem/geometry/vesica.py +49 -0
- ddfem/model2ufl.py +151 -0
- ddfem/transformers/DDM1.py +36 -0
- ddfem/transformers/Fitted.py +77 -0
- ddfem/transformers/Mix0.py +107 -0
- ddfem/transformers/NNS.py +82 -0
- ddfem/transformers/NS.py +86 -0
- ddfem/transformers/__init__.py +6 -0
- ddfem/transformers/transformer_base.py +213 -0
- ddfem-0.9.0.dist-info/METADATA +26 -0
- ddfem-0.9.0.dist-info/RECORD +41 -0
- {ddfem-0.0.0.dist-info → ddfem-0.9.0.dist-info}/WHEEL +1 -1
- ddfem-0.9.0.dist-info/licenses/LICENSE +19 -0
- ddfem-0.0.0.dist-info/METADATA +0 -5
- ddfem-0.0.0.dist-info/RECORD +0 -5
- {ddfem-0.0.0.dist-info → ddfem-0.9.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,42 @@
|
|
1
|
+
from ufl import conditional, dot, max_value, min_value, sqrt, ln, exp
|
2
|
+
|
3
|
+
|
4
|
+
# https://en.wikipedia.org/wiki/Smooth_maximum, https://en.wikipedia.org/wiki/LogSumExp
|
5
|
+
def smax_value(a, b, s):
|
6
|
+
return 1 / s * ln(exp(s * a) + exp(s * b))
|
7
|
+
|
8
|
+
|
9
|
+
def smin_value(a, b, s):
|
10
|
+
return smax_value(a, b, -s)
|
11
|
+
|
12
|
+
|
13
|
+
def ufl_length(p):
|
14
|
+
return sqrt(dot(p, p) + 1e-10)
|
15
|
+
# return max_value(sqrt(dot(p, p)), 1e-10)
|
16
|
+
# return sqrt(dot(p, p))
|
17
|
+
|
18
|
+
|
19
|
+
def ufl_sign(p):
|
20
|
+
if isinstance(p, (float, int)):
|
21
|
+
return 1 if p > 0 else -1
|
22
|
+
|
23
|
+
return conditional(p > 0, 1, -1)
|
24
|
+
|
25
|
+
|
26
|
+
def ufl_clamp(p, minimum, maximum):
|
27
|
+
if isinstance(p, (float, int)):
|
28
|
+
return min(max(p, minimum), maximum)
|
29
|
+
|
30
|
+
# def ufl_max(p1, p2):
|
31
|
+
# return conditional(p2 < p1, p1, p2)
|
32
|
+
|
33
|
+
# def ufl_min(p1, p2):
|
34
|
+
# return conditional(p1 < p2, p1, p2)
|
35
|
+
|
36
|
+
# return ufl_min(ufl_max(p, minimum), maximum)
|
37
|
+
# # using min_value/max_value, seems to break shape Pie?
|
38
|
+
return min_value(max_value(p, minimum), maximum)
|
39
|
+
|
40
|
+
|
41
|
+
def ufl_cross(p1, p2):
|
42
|
+
return p1[0] * p2[1] - p1[1] * p2[0]
|
ddfem/geometry/pie.py
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
from ufl import as_vector, cos, dot, max_value, min_value, sin
|
2
|
+
|
3
|
+
from .helpers import ufl_clamp, ufl_cross, ufl_length, ufl_sign
|
4
|
+
from .primitive_base import ORIGIN, SDF
|
5
|
+
|
6
|
+
|
7
|
+
class Pie(SDF):
|
8
|
+
def __init__(self, radius, angle, *args, **kwargs):
|
9
|
+
self.radius = radius
|
10
|
+
self.angle = angle # angle of cicle (not opening)
|
11
|
+
|
12
|
+
super().__init__(*args, **kwargs)
|
13
|
+
|
14
|
+
def __repr__(self):
|
15
|
+
return f"Pie({self.radius}, {self.angle}, {self._repr_core()})"
|
16
|
+
|
17
|
+
def sdf(self, x):
|
18
|
+
# Note: Ignores z
|
19
|
+
x0_abs = abs(x[0])
|
20
|
+
coords = as_vector([x0_abs, x[1]])
|
21
|
+
|
22
|
+
circle_dist = ufl_length(coords) - self.radius
|
23
|
+
|
24
|
+
trig = as_vector([sin(self.angle / 2), cos(self.angle / 2)])
|
25
|
+
|
26
|
+
# projection of coords on to trig, clamped to within circle
|
27
|
+
proj = ufl_clamp(dot(coords, trig), 0, self.radius) * trig
|
28
|
+
rejc = coords - proj
|
29
|
+
edge_dist = ufl_length(rejc) * ufl_sign(ufl_cross(coords, trig))
|
30
|
+
|
31
|
+
return max_value(circle_dist, edge_dist)
|
ddfem/geometry/plane.py
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
from ufl import as_vector, dot
|
2
|
+
|
3
|
+
from .primitive_base import SDF
|
4
|
+
|
5
|
+
|
6
|
+
class Plane(SDF):
|
7
|
+
def __init__(self, normal, offset, *args, **kwargs):
|
8
|
+
if isinstance(normal, (list, tuple)):
|
9
|
+
normal = as_vector(normal)
|
10
|
+
self.normal = normal
|
11
|
+
|
12
|
+
self.offset = offset
|
13
|
+
|
14
|
+
super().__init__(*args, **kwargs)
|
15
|
+
|
16
|
+
def __repr__(self):
|
17
|
+
return f"Plane({self.normal}, {self.offset}, {self._repr_core()})"
|
18
|
+
|
19
|
+
def sdf(self, x):
|
20
|
+
return dot(x, self.normal) + self.offset
|
@@ -0,0 +1,338 @@
|
|
1
|
+
from ufl import (
|
2
|
+
as_matrix,
|
3
|
+
as_vector,
|
4
|
+
conditional,
|
5
|
+
cos,
|
6
|
+
grad,
|
7
|
+
max_value,
|
8
|
+
min_value,
|
9
|
+
pi,
|
10
|
+
sin,
|
11
|
+
tanh,
|
12
|
+
)
|
13
|
+
|
14
|
+
from .helpers import smax_value, smin_value, ufl_length
|
15
|
+
|
16
|
+
ORIGIN = as_vector([0, 0])
|
17
|
+
|
18
|
+
|
19
|
+
class SDF:
|
20
|
+
def __init__(self, epsilon=None, name=None, children=None):
|
21
|
+
self.epsilon = epsilon
|
22
|
+
self.child_sdf = children if children else []
|
23
|
+
|
24
|
+
self.name = name
|
25
|
+
if self.name is None:
|
26
|
+
self.name = repr(self)
|
27
|
+
|
28
|
+
def _repr_core(self):
|
29
|
+
return f"epsilon={self.epsilon}, name={self.name}, children={self.child_sdf}"
|
30
|
+
|
31
|
+
def __repr__(self):
|
32
|
+
return f"{self.__class__.__name__}({self._repr_core()})"
|
33
|
+
|
34
|
+
def sdf(self, x):
|
35
|
+
raise NotImplementedError
|
36
|
+
|
37
|
+
def __call__(self, x):
|
38
|
+
return self.sdf(x)
|
39
|
+
|
40
|
+
def search(self, child_name):
|
41
|
+
if self.name == child_name:
|
42
|
+
return self
|
43
|
+
|
44
|
+
queue = self.child_sdf.copy()
|
45
|
+
|
46
|
+
while queue:
|
47
|
+
current_child = queue.pop(0)
|
48
|
+
|
49
|
+
if current_child.name == child_name:
|
50
|
+
return current_child
|
51
|
+
|
52
|
+
for child in current_child.child_sdf:
|
53
|
+
queue.append(child)
|
54
|
+
|
55
|
+
return None
|
56
|
+
|
57
|
+
def propagate_epsilon(self, epsilon):
|
58
|
+
self.epsilon = epsilon
|
59
|
+
for child in self.child_sdf:
|
60
|
+
child.propagate_epsilon(epsilon)
|
61
|
+
|
62
|
+
def phi(self, x, epsilon=None):
|
63
|
+
if not epsilon:
|
64
|
+
epsilon = self.epsilon
|
65
|
+
assert self.epsilon, "Must define epsilon"
|
66
|
+
return 0.5 * (1 - tanh((3 * self.sdf(x) / epsilon)))
|
67
|
+
|
68
|
+
def chi(self, x):
|
69
|
+
return conditional(self.sdf(x) <= 0, 1, 0)
|
70
|
+
|
71
|
+
def projection(self, x):
|
72
|
+
return -grad(self.sdf(x)) * self.sdf(x)
|
73
|
+
|
74
|
+
def boundary_projection(self, x):
|
75
|
+
return x + self.projection(x)
|
76
|
+
|
77
|
+
def external_projection(self, x):
|
78
|
+
# return self.chi(x) * x + self.boundary_projection(x) * (1 - self.chi(x))
|
79
|
+
return x + self.projection(x) * (1 - self.chi(x))
|
80
|
+
|
81
|
+
def union(self, other):
|
82
|
+
return Union(self, other)
|
83
|
+
|
84
|
+
def subtraction(self, other):
|
85
|
+
return Subtraction(self, other)
|
86
|
+
|
87
|
+
def intersection(self, other):
|
88
|
+
return Intersection(self, other)
|
89
|
+
|
90
|
+
def xor(self, other):
|
91
|
+
return Xor(self, other)
|
92
|
+
|
93
|
+
def scale(self, sc):
|
94
|
+
return Scale(self, sc)
|
95
|
+
|
96
|
+
def invert(self):
|
97
|
+
return Invert(self)
|
98
|
+
|
99
|
+
def rotate(self, angle, radians=True):
|
100
|
+
return Rotate(self, angle, radians)
|
101
|
+
|
102
|
+
def translate(self, vector):
|
103
|
+
return Translate(self, vector)
|
104
|
+
|
105
|
+
def round(self, sc):
|
106
|
+
return Round(self, sc)
|
107
|
+
|
108
|
+
def extrude(self, length, split_ends=False):
|
109
|
+
if split_ends:
|
110
|
+
from .plane import Plane
|
111
|
+
|
112
|
+
ext = Extrusion(self, length * 2, name=f"{self.name}_ext")
|
113
|
+
ext = Translate(ext, [0, 0, -length / 2], name=f"{self.name}_sides")
|
114
|
+
bot = Plane([0, 0, -1], 0, name=f"{self.name}_bot")
|
115
|
+
top = Plane([0, 0, 1], -length, name=f"{self.name}_top")
|
116
|
+
z_interval = bot & top
|
117
|
+
ext = ext & z_interval
|
118
|
+
else:
|
119
|
+
return Extrusion(self, length, name=f"{self.name}_ext")
|
120
|
+
return ext
|
121
|
+
|
122
|
+
def revolve(self, offset=0, axis="x"):
|
123
|
+
return Revolution(self, offset, axis)
|
124
|
+
|
125
|
+
def __or__(self, other):
|
126
|
+
return self.union(other)
|
127
|
+
|
128
|
+
def __and__(self, other):
|
129
|
+
return self.intersection(other)
|
130
|
+
|
131
|
+
def __sub__(self, other):
|
132
|
+
return self.subtraction(other)
|
133
|
+
|
134
|
+
def __xor__(self, other):
|
135
|
+
return self.xor(other)
|
136
|
+
|
137
|
+
def __mul__(self, other):
|
138
|
+
return self.scale(other)
|
139
|
+
|
140
|
+
def __rmul__(self, other):
|
141
|
+
if isinstance(other, (int, float)):
|
142
|
+
return self.scale(other)
|
143
|
+
raise TypeError(f"Cannot multiply a SDF with {type(other)}")
|
144
|
+
|
145
|
+
# possibly use a 'Constant' but then multiple changes will influence previous usage?
|
146
|
+
smoothing = None
|
147
|
+
|
148
|
+
def max_value(a, b):
|
149
|
+
if SDF.smoothing is None:
|
150
|
+
return max_value(a, b)
|
151
|
+
else:
|
152
|
+
return smax_value(a, b, SDF.smoothing)
|
153
|
+
|
154
|
+
def min_value(a, b):
|
155
|
+
if SDF.smoothing is None:
|
156
|
+
return min_value(a, b)
|
157
|
+
else:
|
158
|
+
return smin_value(a, b, SDF.smoothing)
|
159
|
+
|
160
|
+
|
161
|
+
class BaseOperator(SDF):
|
162
|
+
def __init__(self, children, epsilon=None, *args, **kwargs):
|
163
|
+
|
164
|
+
if not epsilon and all(child.epsilon for child in children):
|
165
|
+
epsilon = children[0].epsilon
|
166
|
+
for child in children:
|
167
|
+
epsilon = max_value(epsilon, child.epsilon)
|
168
|
+
|
169
|
+
super().__init__(children=children, epsilon=epsilon, *args, **kwargs)
|
170
|
+
|
171
|
+
def __getitem__(self, key):
|
172
|
+
return self.child_sdf[key]
|
173
|
+
|
174
|
+
|
175
|
+
class Union(BaseOperator):
|
176
|
+
"""Union of two SDFs (OR) - not perfect(negative)"""
|
177
|
+
|
178
|
+
def __init__(self, sdf1, sdf2, *args, **kwargs):
|
179
|
+
super().__init__(children=[sdf1, sdf2], *args, **kwargs)
|
180
|
+
|
181
|
+
def sdf(self, x):
|
182
|
+
return SDF.min_value(self.child_sdf[0].sdf(x), self.child_sdf[1].sdf(x))
|
183
|
+
|
184
|
+
|
185
|
+
class Subtraction(BaseOperator):
|
186
|
+
"""Subtraction of two SDFs (difference) - not perfect"""
|
187
|
+
|
188
|
+
def __init__(self, sdf1, sdf2, *args, **kwargs):
|
189
|
+
super().__init__(children=[sdf1, sdf2], *args, **kwargs)
|
190
|
+
|
191
|
+
def sdf(self, x):
|
192
|
+
return SDF.max_value(self.child_sdf[0].sdf(x), -self.child_sdf[1].sdf(x))
|
193
|
+
|
194
|
+
|
195
|
+
class Intersection(BaseOperator):
|
196
|
+
"""Intersection of two SDFs (AND) - not perfect"""
|
197
|
+
|
198
|
+
def __init__(self, sdf1, sdf2, *args, **kwargs):
|
199
|
+
super().__init__(children=[sdf1, sdf2], *args, **kwargs)
|
200
|
+
|
201
|
+
def sdf(self, x):
|
202
|
+
return SDF.max_value(self.child_sdf[0].sdf(x), self.child_sdf[1].sdf(x))
|
203
|
+
|
204
|
+
|
205
|
+
class Xor(BaseOperator):
|
206
|
+
"""Xor of two SDFs (AND) - perfect"""
|
207
|
+
|
208
|
+
def __init__(self, sdf1, sdf2, *args, **kwargs):
|
209
|
+
super().__init__(children=[sdf1, sdf2], *args, **kwargs)
|
210
|
+
|
211
|
+
def sdf(self, x):
|
212
|
+
a_x = self.child_sdf[0].sdf(x)
|
213
|
+
b_x = self.child_sdf[1].sdf(x)
|
214
|
+
return SDF.max_value(SDF.min_value(a_x, b_x), -SDF.max_value(a_x, b_x))
|
215
|
+
|
216
|
+
|
217
|
+
class Invert(BaseOperator):
|
218
|
+
"""Inverts SDF"""
|
219
|
+
|
220
|
+
def __init__(self, sdf1, *args, **kwargs):
|
221
|
+
super().__init__(children=[sdf1], *args, **kwargs)
|
222
|
+
|
223
|
+
def sdf(self, x):
|
224
|
+
return -self.child_sdf[0].sdf(x)
|
225
|
+
|
226
|
+
|
227
|
+
class Scale(BaseOperator):
|
228
|
+
"""Scales SDF"""
|
229
|
+
|
230
|
+
def __init__(self, sdf1, scale, *args, **kwargs):
|
231
|
+
if not isinstance(scale, (int, float)):
|
232
|
+
raise TypeError(f"Cannot scale a SDF with {type(scale)}")
|
233
|
+
elif not scale > 0:
|
234
|
+
raise ValueError("Cannot scale a SDF with nonpositive")
|
235
|
+
else:
|
236
|
+
self.scale = scale
|
237
|
+
|
238
|
+
super().__init__(children=[sdf1], *args, **kwargs)
|
239
|
+
|
240
|
+
def sdf(self, x):
|
241
|
+
return self.child_sdf[0].sdf(x / self.scale) * self.scale
|
242
|
+
|
243
|
+
def __repr__(self):
|
244
|
+
return f"Scale({self.scale}, {self._repr_core()})"
|
245
|
+
|
246
|
+
|
247
|
+
class Rotate(BaseOperator):
|
248
|
+
"""Rotates SDF, counterclockwise of orgin"""
|
249
|
+
|
250
|
+
def __init__(self, sdf1, angle, radians=True, *args, **kwargs):
|
251
|
+
if not radians:
|
252
|
+
angle *= pi / 180
|
253
|
+
self.angle = angle
|
254
|
+
|
255
|
+
super().__init__(children=[sdf1], *args, **kwargs)
|
256
|
+
|
257
|
+
def sdf(self, x):
|
258
|
+
c = cos(self.angle)
|
259
|
+
s = sin(self.angle)
|
260
|
+
|
261
|
+
r = as_matrix(((c, -s), (s, c)))
|
262
|
+
return self.child_sdf[0].sdf(r.T * x)
|
263
|
+
|
264
|
+
def __repr__(self):
|
265
|
+
return f"Rotate({self.angle}, {self._repr_core()})"
|
266
|
+
|
267
|
+
|
268
|
+
class Translate(BaseOperator):
|
269
|
+
"""Translates SDF"""
|
270
|
+
|
271
|
+
def __init__(self, sdf1, vec, *args, **kwargs):
|
272
|
+
if isinstance(vec, (list, tuple)):
|
273
|
+
vec = as_vector(vec)
|
274
|
+
self.vec = vec
|
275
|
+
|
276
|
+
super().__init__(children=[sdf1], *args, **kwargs)
|
277
|
+
|
278
|
+
def sdf(self, x):
|
279
|
+
return self.child_sdf[0].sdf(x - self.vec)
|
280
|
+
|
281
|
+
def __repr__(self):
|
282
|
+
return f"Translate({self.vec}, {self._repr_core()})"
|
283
|
+
|
284
|
+
|
285
|
+
class Round(BaseOperator):
|
286
|
+
"""Rounds SDF"""
|
287
|
+
|
288
|
+
def __init__(self, sdf1, scale, *args, **kwargs):
|
289
|
+
assert scale > 0
|
290
|
+
self._scale = scale # careful not to overwrite SDF.scale here
|
291
|
+
|
292
|
+
super().__init__(children=[sdf1], *args, **kwargs)
|
293
|
+
|
294
|
+
def sdf(self, x):
|
295
|
+
return self.child_sdf[0].sdf(x) - self._scale
|
296
|
+
|
297
|
+
def __repr__(self):
|
298
|
+
return f"Round({self._scale}, {self._repr_core()})"
|
299
|
+
|
300
|
+
|
301
|
+
class Extrusion(BaseOperator):
|
302
|
+
"""Extrude SDF"""
|
303
|
+
|
304
|
+
def __init__(self, sdf1, extrude_length, *args, **kwargs):
|
305
|
+
self.extrude_length = extrude_length
|
306
|
+
|
307
|
+
super().__init__(children=[sdf1], *args, **kwargs)
|
308
|
+
|
309
|
+
def sdf(self, x):
|
310
|
+
d = self.child_sdf[0].sdf(as_vector([x[0], x[1]]))
|
311
|
+
w = abs(x[2]) - self.extrude_length
|
312
|
+
return SDF.min_value(SDF.max_value(d, w), 0) + ufl_length(
|
313
|
+
as_vector([SDF.max_value(d, 0), SDF.max_value(w, 0)])
|
314
|
+
)
|
315
|
+
|
316
|
+
def __repr__(self):
|
317
|
+
return f"Extrusion({self.extrude_length}, {self._repr_core()})"
|
318
|
+
|
319
|
+
|
320
|
+
class Revolution(BaseOperator):
|
321
|
+
"""Revolve SDF"""
|
322
|
+
|
323
|
+
def __init__(self, sdf1, offset, axis, *args, **kwargs):
|
324
|
+
self.offset = offset
|
325
|
+
assert axis in ["x", "y"], "Can only revolve around the x or y axis"
|
326
|
+
self.axis = axis
|
327
|
+
|
328
|
+
super().__init__(children=[sdf1], *args, **kwargs)
|
329
|
+
|
330
|
+
def sdf(self, x):
|
331
|
+
if self.axis == "x":
|
332
|
+
q = as_vector([x[0], ufl_length(as_vector([x[1], x[2]])) - self.offset])
|
333
|
+
elif self.axis == "y":
|
334
|
+
q = as_vector([ufl_length(as_vector([x[0], x[2]])) - self.offset, x[1]])
|
335
|
+
return self.child_sdf[0].sdf(q)
|
336
|
+
|
337
|
+
def __repr__(self):
|
338
|
+
return f"Revolution({self.offset}, {self._repr_core()})"
|
ddfem/geometry/vesica.py
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
from ufl import as_vector, conditional, sqrt
|
2
|
+
|
3
|
+
from .helpers import ufl_length, ufl_sign
|
4
|
+
from .primitive_base import ORIGIN, SDF
|
5
|
+
|
6
|
+
|
7
|
+
class Vesica(SDF):
|
8
|
+
def __init__(self, radius, distance, smooth_radius, *args, **kwargs):
|
9
|
+
"""Generates sign distance function and domain coordinates for a Vesica.
|
10
|
+
i.e. Union of two circles.
|
11
|
+
Centred at (0,0)
|
12
|
+
|
13
|
+
Args:
|
14
|
+
radius (float,): Radius of each circle.
|
15
|
+
distance (float): Distance of circle center from y-axis.
|
16
|
+
smooth_radius (float): Smoothing of domain,so no sharp corner when circles connection.
|
17
|
+
"""
|
18
|
+
assert distance != 0, "This is a circle, use Ball class"
|
19
|
+
assert distance < radius, "No shape exists, circles cancel each other out"
|
20
|
+
assert (
|
21
|
+
smooth_radius * distance < 0
|
22
|
+
), "For a smooth edge, smooth_radius needs to be opposite sign of distance"
|
23
|
+
|
24
|
+
self.radius = radius
|
25
|
+
self.distance = distance
|
26
|
+
self.smooth_radius = smooth_radius
|
27
|
+
|
28
|
+
super().__init__(*args, **kwargs)
|
29
|
+
|
30
|
+
def __repr__(self):
|
31
|
+
return f"Vesica({self.radius}, {self.distance}, {self.smooth_radius}, {self._repr_core()})"
|
32
|
+
|
33
|
+
def sdf(self, x):
|
34
|
+
# Note: Ignores z
|
35
|
+
x0_abs = abs(x[0])
|
36
|
+
x1_abs = abs(x[1])
|
37
|
+
|
38
|
+
b = sqrt((self.radius + self.smooth_radius) ** 2 - self.distance**2)
|
39
|
+
|
40
|
+
circle_coords = as_vector([x0_abs + self.distance, x[1]])
|
41
|
+
|
42
|
+
return (
|
43
|
+
conditional(
|
44
|
+
(x1_abs - b) * self.distance > x0_abs * b,
|
45
|
+
ufl_length(as_vector([x0_abs, x1_abs - b])) * ufl_sign(self.distance),
|
46
|
+
ufl_length(circle_coords) - self.radius - self.smooth_radius,
|
47
|
+
)
|
48
|
+
+ self.smooth_radius
|
49
|
+
)
|
ddfem/model2ufl.py
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
from ufl import (
|
2
|
+
FacetNormal,
|
3
|
+
SpatialCoordinate,
|
4
|
+
TestFunction,
|
5
|
+
TrialFunction,
|
6
|
+
as_vector,
|
7
|
+
ds,
|
8
|
+
dx,
|
9
|
+
grad,
|
10
|
+
inner,
|
11
|
+
)
|
12
|
+
|
13
|
+
from .boundary import boundary_validation
|
14
|
+
|
15
|
+
|
16
|
+
def boundaries_ufl(Model, space, t):
|
17
|
+
boundary_flux_cs, boundary_flux_vs, boundary_values = boundary_validation(Model)
|
18
|
+
|
19
|
+
u = TrialFunction(space)
|
20
|
+
n = FacetNormal(space.cell())
|
21
|
+
x = SpatialCoordinate(space.cell())
|
22
|
+
|
23
|
+
boundary_flux_cs = {
|
24
|
+
(k(x) if callable(k) else k): f(t, x, u, n) for k, f in boundary_flux_cs.items()
|
25
|
+
}
|
26
|
+
boundary_flux_vs = {
|
27
|
+
(k(x) if callable(k) else k): f(t, x, u, grad(u), n)
|
28
|
+
for k, f in boundary_flux_vs.items()
|
29
|
+
}
|
30
|
+
boundary_values = {
|
31
|
+
(k(x) if callable(k) else k): f(t, x, u) for k, f in boundary_values.items()
|
32
|
+
}
|
33
|
+
hasBoundaryValue = {k: True for k in boundary_values.keys()}
|
34
|
+
|
35
|
+
return (
|
36
|
+
boundary_flux_cs,
|
37
|
+
boundary_flux_vs,
|
38
|
+
boundary_values,
|
39
|
+
hasBoundaryValue,
|
40
|
+
)
|
41
|
+
|
42
|
+
|
43
|
+
class DirichletBC:
|
44
|
+
def __init__(self, space, value, domain=None):
|
45
|
+
self.space = space
|
46
|
+
self.value = value
|
47
|
+
self.domain = domain
|
48
|
+
|
49
|
+
def __str__(self):
|
50
|
+
return str(self.value) + str(self.domain)
|
51
|
+
|
52
|
+
|
53
|
+
def model_ufl(Model, space, initialTime=0, DirichletBC=DirichletBC):
|
54
|
+
u = TrialFunction(space)
|
55
|
+
v = TestFunction(space)
|
56
|
+
x = SpatialCoordinate(space.cell())
|
57
|
+
|
58
|
+
t = initialTime
|
59
|
+
|
60
|
+
f_c_model = None
|
61
|
+
if hasattr(Model, "F_c"):
|
62
|
+
f_c_model = inner(Model.F_c(t, x, u), grad(v)) # -div F_c v
|
63
|
+
if hasattr(Model, "S_e"):
|
64
|
+
# there is an issue with S_e returning 'zero' and zero*dx leading to UFL error
|
65
|
+
se = (
|
66
|
+
inner(as_vector(Model.S_e(t, x, u, grad(u))), v)
|
67
|
+
) # (-div F_c + S_e) * v
|
68
|
+
if f_c_model is not None:
|
69
|
+
f_c_model += se
|
70
|
+
else:
|
71
|
+
f_c_model = se
|
72
|
+
|
73
|
+
f_v_model = None
|
74
|
+
if hasattr(Model, "F_v"):
|
75
|
+
f_v_model = inner(Model.F_v(t, x, u, grad(u)), grad(v)) # -div F_v v
|
76
|
+
|
77
|
+
if hasattr(Model, "S_i"):
|
78
|
+
si = inner(as_vector(Model.S_i(t, x, u, grad(u))), v) # (-div F_v + S_i) v
|
79
|
+
if f_v_model is not None:
|
80
|
+
f_v_model += si
|
81
|
+
else:
|
82
|
+
f_v_model = si
|
83
|
+
|
84
|
+
# need to extract boundary information from Model
|
85
|
+
(
|
86
|
+
boundary_flux_cs,
|
87
|
+
boundary_flux_vs,
|
88
|
+
boundary_values,
|
89
|
+
hasBoundaryValue,
|
90
|
+
) = boundaries_ufl(Model, space, t)
|
91
|
+
|
92
|
+
dirichletBCs = [
|
93
|
+
DirichletBC(space, item[1], item[0]) for item in boundary_values.items()
|
94
|
+
]
|
95
|
+
boundary_flux_vs = -sum(
|
96
|
+
[inner(item[1], v) * ds(item[0]) for item in boundary_flux_vs.items()]
|
97
|
+
) # keep all forms on left hand side
|
98
|
+
boundary_flux_cs = -sum(
|
99
|
+
[inner(item[1], v) * ds(item[0]) for item in boundary_flux_cs.items()]
|
100
|
+
) # keep all forms on left hand side
|
101
|
+
|
102
|
+
if f_c_model:
|
103
|
+
f_c_model = f_c_model * dx
|
104
|
+
if f_v_model:
|
105
|
+
f_v_model = f_v_model * dx
|
106
|
+
return (
|
107
|
+
f_c_model,
|
108
|
+
f_v_model,
|
109
|
+
{
|
110
|
+
"dirichletBCs": dirichletBCs,
|
111
|
+
"boundary_flux_cs": boundary_flux_cs,
|
112
|
+
"boundary_flux_vs": boundary_flux_vs,
|
113
|
+
"hasBoundaryValue": hasBoundaryValue,
|
114
|
+
},
|
115
|
+
)
|
116
|
+
|
117
|
+
|
118
|
+
def model2ufl(
|
119
|
+
Model, space, initialTime=0, DirichletBC=DirichletBC, *, returnFull=False
|
120
|
+
):
|
121
|
+
class M(Model):
|
122
|
+
if hasattr(Model, "S_e"):
|
123
|
+
|
124
|
+
def S_e(t, x, U, DU):
|
125
|
+
return -Model.S_e(t, x, U, DU)
|
126
|
+
|
127
|
+
if hasattr(Model, "S_i"):
|
128
|
+
|
129
|
+
def S_i(t, x, U, DU):
|
130
|
+
return -Model.S_i(t, x, U, DU)
|
131
|
+
|
132
|
+
if hasattr(Model, "F_c"):
|
133
|
+
|
134
|
+
def F_c(t, x, U):
|
135
|
+
return -Model.F_c(t, x, U)
|
136
|
+
|
137
|
+
f_c_model, f_v_model, boundary_model = model_ufl(M, space, initialTime, DirichletBC)
|
138
|
+
boundary_model["boundary_flux_cs"] = -boundary_model["boundary_flux_cs"]
|
139
|
+
form = boundary_model["boundary_flux_cs"] + boundary_model["boundary_flux_vs"]
|
140
|
+
if f_c_model is not None:
|
141
|
+
form += f_c_model
|
142
|
+
if f_v_model is not None:
|
143
|
+
form += f_v_model
|
144
|
+
|
145
|
+
if not returnFull:
|
146
|
+
return [form == 0, *boundary_model["dirichletBCs"]]
|
147
|
+
else:
|
148
|
+
boundary_model["f_c_model"] = f_c_model
|
149
|
+
boundary_model["f_v_model"] = f_v_model
|
150
|
+
boundary_model["form"] = form
|
151
|
+
return boundary_model
|
@@ -0,0 +1,36 @@
|
|
1
|
+
from ufl import grad, zero
|
2
|
+
|
3
|
+
from .transformer_base import transformer
|
4
|
+
|
5
|
+
|
6
|
+
@transformer
|
7
|
+
def DDM1(Model):
|
8
|
+
class DDModel(Model):
|
9
|
+
def S_e_source(t, x, U, DU):
|
10
|
+
return DDModel.phi(x) * Model.S_e(t, x, U, DDModel.sigma(t, x, U, DU))
|
11
|
+
|
12
|
+
def S_e_convection(t, x, U, DU):
|
13
|
+
return -DDModel.BT.BndFlux_cExt(t, x, U)
|
14
|
+
|
15
|
+
def S_outside(t, x, U, DU):
|
16
|
+
return -(
|
17
|
+
DDModel.BT.jumpV(t, x, U) * (1 - DDModel.phi(x)) / (DDModel.epsilon**3)
|
18
|
+
)
|
19
|
+
|
20
|
+
def S_i_source(t, x, U, DU):
|
21
|
+
return DDModel.phi(x) * Model.S_i(t, x, U, DDModel.sigma(t, x, U, DU))
|
22
|
+
|
23
|
+
def S_i_diffusion(t, x, U, DU):
|
24
|
+
if DDModel.BT.BndFlux_vExt is not None:
|
25
|
+
diffusion = DDModel.BT.BndFlux_vExt(t, x, U, DU)
|
26
|
+
else:
|
27
|
+
diffusion = zero(U.ufl_shape)
|
28
|
+
return diffusion
|
29
|
+
|
30
|
+
def F_c(t, x, U):
|
31
|
+
return DDModel.phi(x) * Model.F_c(t, x, U)
|
32
|
+
|
33
|
+
def F_v(t, x, U, DU):
|
34
|
+
return DDModel.phi(x) * Model.F_v(t, x, U, DDModel.sigma(t, x, U, DU))
|
35
|
+
|
36
|
+
return DDModel
|