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,217 @@
|
|
1
|
+
import numpy as np
|
2
|
+
|
3
|
+
try:
|
4
|
+
import pygmsh
|
5
|
+
except ImportError:
|
6
|
+
pygmsh = None
|
7
|
+
|
8
|
+
try:
|
9
|
+
import dune
|
10
|
+
except ImportError:
|
11
|
+
print(
|
12
|
+
"""
|
13
|
+
Example code requires dune to run. To install run
|
14
|
+
pip install dune-fem
|
15
|
+
"""
|
16
|
+
)
|
17
|
+
|
18
|
+
from dune.alugrid import aluConformGrid as leafGridView
|
19
|
+
from dune.fem import adapt, mark, markNeighbors
|
20
|
+
from dune.fem.function import gridFunction
|
21
|
+
from dune.fem.space import lagrange
|
22
|
+
from dune.fem.view import adaptiveLeafGridView
|
23
|
+
from dune.grid import cartesianDomain
|
24
|
+
from dune.ufl import Constant, Space
|
25
|
+
from ufl import SpatialCoordinate, sqrt
|
26
|
+
|
27
|
+
from ddfem import geometry as gm
|
28
|
+
from ddfem.geometry.domain_dune import DomainDune
|
29
|
+
|
30
|
+
|
31
|
+
def getDomain(
|
32
|
+
initialRefine, version, inverted, adaptLevels=0, epsFactor=4.5, smoothing=None
|
33
|
+
):
|
34
|
+
|
35
|
+
gm.SDF.smoothing = smoothing
|
36
|
+
shiftx, shifty = sqrt(2) * 1e-6, sqrt(3) * 1e-6
|
37
|
+
domain_range = [[-0.5 + shiftx, -0.5 + shifty], [0.5 + shiftx, 0.5 + shifty]]
|
38
|
+
initial_gridsize = [100 * 2**initialRefine] * 2
|
39
|
+
h = sqrt(
|
40
|
+
((domain_range[1][0] - domain_range[0][0]) / initial_gridsize[0]) ** 2
|
41
|
+
+ ((domain_range[1][1] - domain_range[0][1]) / initial_gridsize[1]) ** 2
|
42
|
+
)
|
43
|
+
|
44
|
+
def get_eps(h):
|
45
|
+
return Constant(epsFactor * h * 0.5 ** (adaptLevels / 2), "epsilon")
|
46
|
+
|
47
|
+
balls = [
|
48
|
+
[0.3, [0.15, 0.15], "b1"],
|
49
|
+
[0.3, [-0.15, -0.15], "b2"],
|
50
|
+
[0.4, [0, 0], "b3"],
|
51
|
+
]
|
52
|
+
|
53
|
+
b = [gm.Ball(c[0], c[1], name=c[2]) for c in balls]
|
54
|
+
sdfs = [b[0] | b[1], b[2]]
|
55
|
+
sdfs[0].name = "sides"
|
56
|
+
sdfs[1].name = "ends"
|
57
|
+
omega = sdfs[0] & sdfs[1]
|
58
|
+
if inverted:
|
59
|
+
omega = omega.invert()
|
60
|
+
omega.name = "full"
|
61
|
+
|
62
|
+
h_max = h * 3
|
63
|
+
h_min = h / 2
|
64
|
+
radius = 5
|
65
|
+
|
66
|
+
x = SpatialCoordinate(Space(2))
|
67
|
+
sdf = omega(x)
|
68
|
+
|
69
|
+
def spacing(x, y, epsilon):
|
70
|
+
r_min = epsilon.value
|
71
|
+
r_max = radius * epsilon.value
|
72
|
+
dist = np.abs(sdf((x, y)))
|
73
|
+
if dist <= r_min:
|
74
|
+
return geom.characteristic_length_min
|
75
|
+
elif dist >= r_max:
|
76
|
+
return geom.characteristic_length_max
|
77
|
+
else:
|
78
|
+
# Linear
|
79
|
+
m = (geom.characteristic_length_max - geom.characteristic_length_min) / (
|
80
|
+
r_max - r_min
|
81
|
+
)
|
82
|
+
return m * (dist - r_min) + geom.characteristic_length_min
|
83
|
+
|
84
|
+
if version == "cartesian":
|
85
|
+
domain = cartesianDomain(*domain_range, initial_gridsize)
|
86
|
+
epsilon = get_eps(h)
|
87
|
+
|
88
|
+
elif version == "fitted":
|
89
|
+
if pygmsh is None:
|
90
|
+
raise AttributeError("'fitted' requires install pygmsh")
|
91
|
+
with pygmsh.occ.Geometry() as geom:
|
92
|
+
geom.characteristic_length_max = h_max
|
93
|
+
geom.characteristic_length_min = h_min
|
94
|
+
epsilon = get_eps(h_min)
|
95
|
+
|
96
|
+
disks = [geom.add_disk([c[1][0], c[1][1], 0.0], c[0]) for c in balls]
|
97
|
+
|
98
|
+
ds = geom.boolean_union([disks[0], disks[1]])
|
99
|
+
shape = geom.boolean_intersection([ds, disks[2]])
|
100
|
+
if inverted:
|
101
|
+
rectangle = geom.add_rectangle(
|
102
|
+
[domain_range[0][0], domain_range[0][1], 0.0],
|
103
|
+
domain_range[1][0] - domain_range[0][0],
|
104
|
+
domain_range[1][1] - domain_range[0][1],
|
105
|
+
)
|
106
|
+
geom.boolean_difference(rectangle, shape)
|
107
|
+
|
108
|
+
geom.set_mesh_size_callback(
|
109
|
+
lambda dim, tag, x, y, z, lc: spacing(x, y, epsilon),
|
110
|
+
ignore_other_mesh_sizes=True,
|
111
|
+
)
|
112
|
+
mesh = geom.generate_mesh()
|
113
|
+
points, cells = mesh.points, mesh.cells_dict
|
114
|
+
domain = {
|
115
|
+
"vertices": points[:, :2].astype(float),
|
116
|
+
"simplices": cells["triangle"].astype(int),
|
117
|
+
}
|
118
|
+
|
119
|
+
elif version == "dune_adaptive":
|
120
|
+
gridsize = [int(j * h / h_max) for j in initial_gridsize]
|
121
|
+
domain = cartesianDomain(*domain_range, gridsize)
|
122
|
+
|
123
|
+
elif version == "gmsh_adaptive":
|
124
|
+
if pygmsh is None:
|
125
|
+
raise AttributeError("'gmsh_adaptive' requires install pygmsh")
|
126
|
+
with pygmsh.occ.Geometry() as geom:
|
127
|
+
geom.characteristic_length_max = h_max
|
128
|
+
geom.characteristic_length_min = h_min
|
129
|
+
epsilon = get_eps(h_min)
|
130
|
+
|
131
|
+
geom.add_rectangle(
|
132
|
+
[domain_range[0][0], domain_range[0][1], 0.0],
|
133
|
+
domain_range[1][0] - domain_range[0][0],
|
134
|
+
domain_range[1][1] - domain_range[0][1],
|
135
|
+
)
|
136
|
+
|
137
|
+
geom.set_mesh_size_callback(
|
138
|
+
lambda dim, tag, x, y, z, lc: spacing(x, y, epsilon),
|
139
|
+
ignore_other_mesh_sizes=True,
|
140
|
+
)
|
141
|
+
mesh = geom.generate_mesh()
|
142
|
+
points, cells = mesh.points, mesh.cells_dict
|
143
|
+
domain = {
|
144
|
+
"vertices": points[:, :2].astype(float),
|
145
|
+
"simplices": cells["triangle"].astype(int),
|
146
|
+
}
|
147
|
+
|
148
|
+
elif version == "gmsh_embedded":
|
149
|
+
if pygmsh is None:
|
150
|
+
raise AttributeError("'fitted' requires install pygmsh")
|
151
|
+
with pygmsh.occ.Geometry() as geom:
|
152
|
+
geom.characteristic_length_max = h_max
|
153
|
+
geom.characteristic_length_min = h_min
|
154
|
+
epsilon = get_eps(h_min)
|
155
|
+
|
156
|
+
disks = [geom.add_disk([c[1][0], c[1][1], 0.0], c[0]) for c in balls]
|
157
|
+
|
158
|
+
ds = geom.boolean_union([disks[0], disks[1]])
|
159
|
+
shape = geom.boolean_intersection([ds, disks[2]])
|
160
|
+
rectangle = geom.add_rectangle(
|
161
|
+
[domain_range[0][0], domain_range[0][1], 0.0],
|
162
|
+
domain_range[1][0] - domain_range[0][0],
|
163
|
+
domain_range[1][1] - domain_range[0][1],
|
164
|
+
)
|
165
|
+
|
166
|
+
geom.boolean_fragments(rectangle, shape)
|
167
|
+
|
168
|
+
geom.set_mesh_size_callback(
|
169
|
+
lambda dim, tag, x, y, z, lc: spacing(x, y, epsilon),
|
170
|
+
ignore_other_mesh_sizes=True,
|
171
|
+
)
|
172
|
+
mesh = geom.generate_mesh()
|
173
|
+
points, cells = mesh.points, mesh.cells_dict
|
174
|
+
domain = {
|
175
|
+
"vertices": points[:, :2].astype(float),
|
176
|
+
"simplices": cells["triangle"].astype(int),
|
177
|
+
}
|
178
|
+
|
179
|
+
else:
|
180
|
+
raise ValueError("invalid mesh type")
|
181
|
+
|
182
|
+
gridView = adaptiveLeafGridView(leafGridView(domain))
|
183
|
+
|
184
|
+
if version == "dune_adaptive":
|
185
|
+
omega.epsilon = get_eps(h_min)
|
186
|
+
omega.epsilon.value *= radius
|
187
|
+
epsilon_value = omega.epsilon.value
|
188
|
+
|
189
|
+
marker = mark
|
190
|
+
|
191
|
+
refinements = int(2 * np.log2(h_max / h_min))
|
192
|
+
|
193
|
+
region = gridFunction(
|
194
|
+
omega.phi(x) * (1 - omega.phi(x)), gridView=gridView
|
195
|
+
) # interface
|
196
|
+
|
197
|
+
for j in range(1, refinements + 1):
|
198
|
+
|
199
|
+
omega.epsilon.value = epsilon_value * j / refinements
|
200
|
+
marker(region, 0.00247262315663, maxLevel=refinements) # 1 epsilon
|
201
|
+
|
202
|
+
adapt(gridView.hierarchicalGrid)
|
203
|
+
|
204
|
+
h_min = h_max * 0.5 ** (j / 2)
|
205
|
+
epsilon = get_eps(h_min)
|
206
|
+
|
207
|
+
omega.propagate_epsilon(epsilon)
|
208
|
+
domain = omega
|
209
|
+
|
210
|
+
domain = DomainDune(omega, x, gridView)
|
211
|
+
domain.adapt(level=adaptLevels)
|
212
|
+
|
213
|
+
print(
|
214
|
+
f"h_max={h_max}, h_min={h_min * 0.5 ** (adaptLevels / 2)}, epsilon={epsilon.value}"
|
215
|
+
)
|
216
|
+
|
217
|
+
return gridView, domain
|
@@ -0,0 +1,208 @@
|
|
1
|
+
import numpy as np
|
2
|
+
|
3
|
+
try:
|
4
|
+
import pygmsh
|
5
|
+
except ImportError:
|
6
|
+
pygmsh = None
|
7
|
+
|
8
|
+
try:
|
9
|
+
import dune
|
10
|
+
except ImportError:
|
11
|
+
print(
|
12
|
+
"""
|
13
|
+
Example code requires dune to run. To install run
|
14
|
+
pip install dune-fem
|
15
|
+
"""
|
16
|
+
)
|
17
|
+
|
18
|
+
from dune.alugrid import aluConformGrid as leafGridView
|
19
|
+
from dune.fem import adapt, mark, markNeighbors
|
20
|
+
from dune.fem.function import gridFunction
|
21
|
+
from dune.fem.space import lagrange
|
22
|
+
from dune.fem.utility import gridWidth
|
23
|
+
from dune.fem.view import adaptiveLeafGridView
|
24
|
+
from dune.grid import cartesianDomain
|
25
|
+
from dune.ufl import Constant, Space
|
26
|
+
from ufl import SpatialCoordinate, as_vector, sqrt
|
27
|
+
|
28
|
+
from ddfem import geometry as gm
|
29
|
+
from ddfem.geometry.domain_dune import DomainDune
|
30
|
+
|
31
|
+
|
32
|
+
def getDomain(initialRefine, version, adaptLevels=0, epsFactor=4.5, dirichlet=True):
|
33
|
+
|
34
|
+
# gm.SDF.smoothing = 50
|
35
|
+
|
36
|
+
shiftx, shifty = sqrt(2) * 1e-6, sqrt(3) * 1e-6
|
37
|
+
|
38
|
+
if dirichlet:
|
39
|
+
domain_range = [
|
40
|
+
[-0.8 + shiftx, -0.8 + shifty, 0.1], # -0.5
|
41
|
+
[0.8 + shiftx, 0.8 + shifty, 3.3], # 3.2
|
42
|
+
]
|
43
|
+
initial_gridsize = [60, 60, 60]
|
44
|
+
else:
|
45
|
+
domain_range = [
|
46
|
+
[-0.8 + shiftx, -0.8 + shifty, 0.0 - 0.5],
|
47
|
+
[0.8 + shiftx, 0.8 + shifty, 3.2 + 0.5],
|
48
|
+
]
|
49
|
+
initial_gridsize = [60, 60, 80]
|
50
|
+
initial_gridsize = [i * 2**initialRefine for i in initial_gridsize]
|
51
|
+
h = sqrt(
|
52
|
+
((domain_range[1][0] - domain_range[0][0]) / initial_gridsize[0]) ** 2
|
53
|
+
+ ((domain_range[1][1] - domain_range[0][1]) / initial_gridsize[1]) ** 2
|
54
|
+
+ ((domain_range[1][2] - domain_range[0][2]) / initial_gridsize[2]) ** 2
|
55
|
+
)
|
56
|
+
|
57
|
+
def get_eps(h):
|
58
|
+
return Constant(epsFactor * h * 0.5 ** (adaptLevels / 3), "epsilon")
|
59
|
+
|
60
|
+
balls = [
|
61
|
+
[0.3, [0.15, 0.15], "b1"],
|
62
|
+
[0.3, [-0.15, -0.15], "b2"],
|
63
|
+
[0.4, [0, 0], "b3"],
|
64
|
+
]
|
65
|
+
|
66
|
+
b = [gm.Ball(c[0], c[1], name=c[2]) for c in balls]
|
67
|
+
sdfs = [b[0] | b[1], b[2]]
|
68
|
+
face_2d = sdfs[0] & sdfs[1]
|
69
|
+
|
70
|
+
face_2d.name = "beam"
|
71
|
+
|
72
|
+
if dirichlet:
|
73
|
+
length = 4
|
74
|
+
else:
|
75
|
+
length = 3.2
|
76
|
+
omega = face_2d.extrude(length, True)
|
77
|
+
omega.name = "full"
|
78
|
+
|
79
|
+
h_max = h * 3
|
80
|
+
h_min = h / 2
|
81
|
+
radius = 3
|
82
|
+
|
83
|
+
x = SpatialCoordinate(Space(3))
|
84
|
+
sdf = omega(x)
|
85
|
+
|
86
|
+
def spacing(x, y, z, epsilon):
|
87
|
+
r_min = epsilon.value
|
88
|
+
r_max = radius * epsilon.value
|
89
|
+
dist = np.abs(sdf((x, y, z)))
|
90
|
+
if dist <= r_min:
|
91
|
+
return geom.characteristic_length_min
|
92
|
+
elif dist >= r_max:
|
93
|
+
return geom.characteristic_length_max
|
94
|
+
else:
|
95
|
+
# Linear
|
96
|
+
m = (geom.characteristic_length_max - geom.characteristic_length_min) / (
|
97
|
+
r_max - r_min
|
98
|
+
)
|
99
|
+
return m * (dist - r_min) + geom.characteristic_length_min
|
100
|
+
|
101
|
+
if version == "cartesian":
|
102
|
+
domain = cartesianDomain(*domain_range, initial_gridsize)
|
103
|
+
epsilon = get_eps(h)
|
104
|
+
|
105
|
+
elif version == "fitted":
|
106
|
+
if pygmsh is None:
|
107
|
+
raise AttributeError("'fitted' requires install pygmsh")
|
108
|
+
with pygmsh.occ.Geometry() as geom:
|
109
|
+
geom.characteristic_length_max = h_max
|
110
|
+
geom.characteristic_length_min = h_min
|
111
|
+
epsilon = get_eps(h_min)
|
112
|
+
|
113
|
+
disks = [geom.add_disk([c[1][0], c[1][1], 0.0], c[0]) for c in balls]
|
114
|
+
|
115
|
+
ds = geom.boolean_union([disks[0], disks[1]])
|
116
|
+
shape = geom.boolean_intersection([ds, disks[2]])
|
117
|
+
geom.extrude(shape, [0, 0, length])
|
118
|
+
|
119
|
+
geom.set_mesh_size_callback(
|
120
|
+
lambda dim, tag, x, y, z, lc: spacing(x, y, z, epsilon),
|
121
|
+
ignore_other_mesh_sizes=True,
|
122
|
+
)
|
123
|
+
mesh = geom.generate_mesh()
|
124
|
+
points, cells = mesh.points, mesh.cells_dict
|
125
|
+
domain = {
|
126
|
+
"vertices": points[:,].astype(float),
|
127
|
+
"simplices": cells["tetra"].astype(int),
|
128
|
+
}
|
129
|
+
|
130
|
+
elif version == "dune_adaptive":
|
131
|
+
gridsize = [int(j * h / h_max) for j in initial_gridsize]
|
132
|
+
domain = cartesianDomain(*domain_range, gridsize)
|
133
|
+
|
134
|
+
elif version == "gmsh_adaptive":
|
135
|
+
if pygmsh is None:
|
136
|
+
raise AttributeError("'gmsh_adaptive' requires install pygmsh")
|
137
|
+
with pygmsh.occ.Geometry() as geom:
|
138
|
+
geom.characteristic_length_max = h_max
|
139
|
+
geom.characteristic_length_min = h_min
|
140
|
+
epsilon = get_eps(h_min)
|
141
|
+
|
142
|
+
geom.add_box(
|
143
|
+
[0.0, 0.0, 0.0],
|
144
|
+
[
|
145
|
+
domain_range[1][0] - domain_range[0][0],
|
146
|
+
domain_range[1][1] - domain_range[0][1],
|
147
|
+
domain_range[1][2] - domain_range[0][2],
|
148
|
+
],
|
149
|
+
)
|
150
|
+
|
151
|
+
geom.set_mesh_size_callback(
|
152
|
+
lambda dim, tag, x, y, z, lc: spacing(x, y, z, epsilon),
|
153
|
+
ignore_other_mesh_sizes=True,
|
154
|
+
)
|
155
|
+
mesh = geom.generate_mesh()
|
156
|
+
points, cells = mesh.points, mesh.cells_dict
|
157
|
+
domain = {
|
158
|
+
"vertices": points[:,].astype(float),
|
159
|
+
"simplices": cells["tetra"].astype(int),
|
160
|
+
}
|
161
|
+
else:
|
162
|
+
raise ValueError("invalid mesh type")
|
163
|
+
|
164
|
+
gridView = adaptiveLeafGridView(leafGridView(domain))
|
165
|
+
# return gridView, None
|
166
|
+
|
167
|
+
if version == "dune_adaptive":
|
168
|
+
# omega.epsilon = get_eps(h)
|
169
|
+
omega.epsilon = get_eps(h_min)
|
170
|
+
omega.epsilon.value *= radius
|
171
|
+
epsilon_value = omega.epsilon.value
|
172
|
+
|
173
|
+
marker = mark
|
174
|
+
|
175
|
+
refinements = int(3 * np.log2(h_max / h_min))
|
176
|
+
region = gridFunction(
|
177
|
+
omega.phi(x) * (1 - omega.phi(x)), gridView=gridView
|
178
|
+
) # interface
|
179
|
+
|
180
|
+
for j in range(1, refinements + 1):
|
181
|
+
print("refining:", j, gridView.size(2), flush=True)
|
182
|
+
if gridView.size(2) > 5e5:
|
183
|
+
assert j == refinements
|
184
|
+
j -= 1
|
185
|
+
break
|
186
|
+
omega.epsilon.value = epsilon_value * j / refinements
|
187
|
+
marker(region, 0.00247262315663, maxLevel=refinements) # epsilon
|
188
|
+
adapt(gridView.hierarchicalGrid)
|
189
|
+
|
190
|
+
# markNeighbors(region, 0.1, maxLevel=refinements)
|
191
|
+
# marker(region, 0.2, maxLevel=refinements)
|
192
|
+
# adapt(gridView.hierarchicalGrid)
|
193
|
+
# omega.epsilon.value = omega.epsilon.value * 2**(-1/3)
|
194
|
+
|
195
|
+
h_min = h_max * 0.5 ** (j / 3)
|
196
|
+
epsilon = get_eps(h_min)
|
197
|
+
|
198
|
+
omega.propagate_epsilon(epsilon)
|
199
|
+
domain = omega
|
200
|
+
|
201
|
+
domain = DomainDune(omega, x, gridView)
|
202
|
+
# domain.adapt(level=adaptLevels)
|
203
|
+
|
204
|
+
print(
|
205
|
+
f"h_max={h_max}, h_min={h_min * 0.5 ** (adaptLevels / 3)}, epsilon={epsilon.value}"
|
206
|
+
)
|
207
|
+
|
208
|
+
return gridView, domain
|
@@ -0,0 +1,18 @@
|
|
1
|
+
from .arc import Arc
|
2
|
+
from .ball import Ball
|
3
|
+
from .box import Box
|
4
|
+
from .pie import Pie
|
5
|
+
from .plane import Plane
|
6
|
+
from .primitive_base import (
|
7
|
+
SDF,
|
8
|
+
Intersection,
|
9
|
+
Invert,
|
10
|
+
Rotate,
|
11
|
+
Round,
|
12
|
+
Scale,
|
13
|
+
Subtraction,
|
14
|
+
Translate,
|
15
|
+
Union,
|
16
|
+
Xor,
|
17
|
+
)
|
18
|
+
from .vesica import Vesica
|
ddfem/geometry/arc.py
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
from ufl import as_vector, conditional, cos, sin
|
2
|
+
|
3
|
+
from .helpers import ufl_length
|
4
|
+
from .primitive_base import ORIGIN, SDF
|
5
|
+
|
6
|
+
|
7
|
+
class Arc(SDF):
|
8
|
+
"""SDF for an arc:
|
9
|
+
|
10
|
+
Provides the signed distance function for an arc given the
|
11
|
+
radius, angle of opening, the width, and the center (which defaults to the origin).
|
12
|
+
"""
|
13
|
+
|
14
|
+
def __init__(self, radius, angle, width, center=ORIGIN, *args, **kwargs):
|
15
|
+
self.radius = radius
|
16
|
+
self.angle = angle # angle of opening
|
17
|
+
self.width = width
|
18
|
+
|
19
|
+
if isinstance(center, (list, tuple)):
|
20
|
+
center = as_vector(center)
|
21
|
+
self.center = center
|
22
|
+
|
23
|
+
super().__init__(*args, **kwargs)
|
24
|
+
|
25
|
+
def __repr__(self):
|
26
|
+
return f"Arc({self.radius}, {self.angle}, {self.width}, {self.center}, {self._repr_core()})"
|
27
|
+
|
28
|
+
def sdf(self, x):
|
29
|
+
# Note: Ignores z
|
30
|
+
y0_abs = abs(x[1])
|
31
|
+
coords = as_vector([x[0], y0_abs])
|
32
|
+
|
33
|
+
center_radius = self.radius - self.width / 2
|
34
|
+
|
35
|
+
distance = ufl_length(coords) - center_radius
|
36
|
+
|
37
|
+
edge_point = center_radius * as_vector([cos(self.angle), sin(self.angle)])
|
38
|
+
|
39
|
+
left_coords = coords - edge_point
|
40
|
+
|
41
|
+
sign_dist = conditional(
|
42
|
+
(sin(self.angle) * x[0]) > (cos(self.angle) * y0_abs),
|
43
|
+
ufl_length(left_coords),
|
44
|
+
abs(distance),
|
45
|
+
)
|
46
|
+
|
47
|
+
sign_dist -= self.width / 2
|
48
|
+
return sign_dist
|
ddfem/geometry/ball.py
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
from ufl import as_vector
|
2
|
+
|
3
|
+
from .helpers import ufl_length
|
4
|
+
from .primitive_base import ORIGIN, SDF
|
5
|
+
|
6
|
+
|
7
|
+
class Ball(SDF):
|
8
|
+
def __init__(self, radius, center=ORIGIN, *args, **kwargs):
|
9
|
+
self.radius = radius
|
10
|
+
|
11
|
+
if isinstance(center, (list, tuple)):
|
12
|
+
center = as_vector(center)
|
13
|
+
self.center = center
|
14
|
+
|
15
|
+
super().__init__(*args, **kwargs)
|
16
|
+
|
17
|
+
def __repr__(self):
|
18
|
+
return f"Ball({self.radius}, {self.center}, {self._repr_core()})"
|
19
|
+
|
20
|
+
def sdf(self, x):
|
21
|
+
# Note ignore z, if center 2d
|
22
|
+
xx = as_vector([x[i] for i in range(len(self.center))])
|
23
|
+
center_x = xx - self.center
|
24
|
+
return ufl_length(center_x) - self.radius
|
ddfem/geometry/box.py
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
from ufl import as_vector, conditional, max_value, min_value
|
2
|
+
|
3
|
+
from .helpers import ufl_length
|
4
|
+
from .primitive_base import ORIGIN, SDF
|
5
|
+
|
6
|
+
|
7
|
+
class Box(SDF):
|
8
|
+
def __init__(self, width, height, center=ORIGIN, *args, **kwargs):
|
9
|
+
self.width = width
|
10
|
+
self.height = height
|
11
|
+
|
12
|
+
if isinstance(center, (list, tuple)):
|
13
|
+
center = as_vector(center)
|
14
|
+
self.center = center
|
15
|
+
|
16
|
+
super().__init__(*args, **kwargs)
|
17
|
+
|
18
|
+
def __repr__(self):
|
19
|
+
return f"Box({self.width}, {self.height}, {self.center}, {self._repr_core()})"
|
20
|
+
|
21
|
+
def sdf(self, x):
|
22
|
+
# Note: Ignores z
|
23
|
+
# shift x
|
24
|
+
center_x = x - self.center
|
25
|
+
# aux functions
|
26
|
+
w0 = abs(center_x[0]) - self.width / 2
|
27
|
+
w1 = abs(center_x[1]) - self.height / 2
|
28
|
+
|
29
|
+
g = max_value(w0, w1)
|
30
|
+
|
31
|
+
q = as_vector([max_value(w0, 0), max_value(w1, 0)])
|
32
|
+
|
33
|
+
return conditional(g > 0, ufl_length(q), g)
|
ddfem/geometry/domain.py
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
from ufl import conditional, dot, grad, sqrt
|
2
|
+
|
3
|
+
from .primitive_base import SDF
|
4
|
+
|
5
|
+
|
6
|
+
class Domain:
|
7
|
+
def __init__(self, omega):
|
8
|
+
self.omega = omega
|
9
|
+
|
10
|
+
def phi(self, x):
|
11
|
+
tol = 1e-10
|
12
|
+
return (1 - tol) * self.omega.phi(x) + tol
|
13
|
+
|
14
|
+
def chi(self, x):
|
15
|
+
return self.omega.chi(x)
|
16
|
+
|
17
|
+
def scaled_normal(self, x):
|
18
|
+
return -grad(self.phi(x))
|
19
|
+
|
20
|
+
def surface_delta(self, x):
|
21
|
+
return sqrt(dot(self.scaled_normal(x), self.scaled_normal(x)))
|
22
|
+
|
23
|
+
def normal(self, x):
|
24
|
+
tol = 1e-10
|
25
|
+
sd = conditional(self.surface_delta(x) > tol, self.surface_delta(x), tol)
|
26
|
+
return self.scaled_normal(x) / sd # grad(self.omega(x))
|
27
|
+
|
28
|
+
def boundary_projection(self, x):
|
29
|
+
return x + self.omega.projection(x)
|
30
|
+
|
31
|
+
def external_projection(self, x):
|
32
|
+
return x + self.omega.projection(x) * (1 - self.chi(x))
|
33
|
+
|
34
|
+
def bndSDFs(self, SDFname):
|
35
|
+
if isinstance(SDFname, SDF):
|
36
|
+
SDFname = SDFname.name
|
37
|
+
|
38
|
+
sdf = self.omega.search(SDFname)
|
39
|
+
if sdf is None:
|
40
|
+
raise ValueError(f"No SDF with name {SDFname}")
|
41
|
+
return sdf
|
42
|
+
|
43
|
+
def bndProjSDFs(self, SDFname):
|
44
|
+
sdf = self.bndSDFs(SDFname)
|
45
|
+
return self.generate_projSDF(sdf)
|
46
|
+
|
47
|
+
def generate_projSDF(self, sdf):
|
48
|
+
w = lambda x: sdf.phi(x) * (1 - sdf.phi(x))
|
49
|
+
return lambda x: w(self.boundary_projection(x))
|
@@ -0,0 +1,82 @@
|
|
1
|
+
import time
|
2
|
+
|
3
|
+
import dune.fem
|
4
|
+
import dune.grid
|
5
|
+
|
6
|
+
from .domain import Domain
|
7
|
+
|
8
|
+
|
9
|
+
class DomainDune(Domain):
|
10
|
+
def __init__(self, omega, x, gridView):
|
11
|
+
super().__init__(omega)
|
12
|
+
self.x = x
|
13
|
+
self.gridView = gridView
|
14
|
+
|
15
|
+
self._phi = None
|
16
|
+
self._bndProj = None
|
17
|
+
self._extProj = None
|
18
|
+
self._bndProjSDFs = {}
|
19
|
+
|
20
|
+
self.fullSDF = self.gridFunction(self.omega(self.x), name="full-sdf")
|
21
|
+
|
22
|
+
def gridFunction(self, expr, name):
|
23
|
+
start_ = time.time()
|
24
|
+
gf = dune.fem.function.gridFunction(expr, name=name, gridView=self.gridView)
|
25
|
+
print(f"{name} setup: {time.time() - start_}")
|
26
|
+
return gf
|
27
|
+
|
28
|
+
def phi(self, x):
|
29
|
+
if self._phi is None:
|
30
|
+
p = super().phi(self.x)
|
31
|
+
self._phi = self.gridFunction(p, "phidomain")
|
32
|
+
|
33
|
+
return self._phi
|
34
|
+
|
35
|
+
def boundary_projection(self, x):
|
36
|
+
if self._bndProj is None:
|
37
|
+
p = super().boundary_projection(self.x)
|
38
|
+
self._bndProj = self.gridFunction(p, "bndproj")
|
39
|
+
|
40
|
+
return self._bndProj
|
41
|
+
|
42
|
+
def external_projection(self, x):
|
43
|
+
if self._extProj is None:
|
44
|
+
p = super().external_projection(self.x)
|
45
|
+
self._extProj = self.gridFunction(p, "extproj")
|
46
|
+
|
47
|
+
return self._extProj
|
48
|
+
|
49
|
+
def generate_projSDF(self, sdf):
|
50
|
+
projSDF = self._bndProjSDFs.get(sdf.name)
|
51
|
+
if projSDF is None:
|
52
|
+
projSDF = super().generate_projSDF(sdf)
|
53
|
+
gf = self.gridFunction(projSDF(self.x), f"sdfproj{sdf.name}")
|
54
|
+
self._bndProjSDFs[sdf.name] = lambda x: gf
|
55
|
+
projSDF = self._bndProjSDFs[sdf.name]
|
56
|
+
return projSDF
|
57
|
+
|
58
|
+
def adapt(self, level, lowerTol=-0.1, upperTol=0.1):
|
59
|
+
for _ in range(level):
|
60
|
+
|
61
|
+
def mark(e):
|
62
|
+
v = self.fullSDF(e, self.gridView.dimension * [1.0 / 3.0])
|
63
|
+
return (
|
64
|
+
dune.grid.Marker.refine
|
65
|
+
if v > lowerTol and v < upperTol
|
66
|
+
else dune.grid.Marker.keep
|
67
|
+
)
|
68
|
+
|
69
|
+
self.gridView.hierarchicalGrid.adapt(mark)
|
70
|
+
print(self.gridView.size(0))
|
71
|
+
lowerTol /= 2
|
72
|
+
upperTol /= 2
|
73
|
+
|
74
|
+
def _filter(self, tolerance=1):
|
75
|
+
sd = self.fullSDF
|
76
|
+
tol = tolerance # * self.epsilon.value: issue here with epsilon being a UFL expression due to CSG approach
|
77
|
+
phiFilter = (
|
78
|
+
lambda e: sd(e, self.gridView.dimension * [1.0 / 3.0]) < tol
|
79
|
+
) # bary needs fixing for squares
|
80
|
+
return dune.fem.view.filteredGridView(
|
81
|
+
self.gridView, phiFilter, domainId=1, useFilteredIndexSet=True
|
82
|
+
)
|