ddfem 1.0.0__py3-none-any.whl → 1.0.1__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 +12 -3
- 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 +3 -3
- ddfem/geometry/arc.py +10 -8
- ddfem/geometry/ball.py +24 -0
- ddfem/geometry/box.py +7 -8
- ddfem/geometry/domain.py +16 -4
- ddfem/geometry/domain_dune.py +16 -0
- ddfem/geometry/helpers.py +19 -12
- ddfem/geometry/pie.py +7 -7
- ddfem/geometry/plane.py +20 -0
- ddfem/geometry/primitive_base.py +129 -70
- ddfem/geometry/vesica.py +7 -6
- ddfem/model2ufl.py +10 -4
- ddfem/transformers/DDM1.py +10 -68
- ddfem/transformers/Fitted.py +22 -12
- ddfem/transformers/Mix0.py +10 -64
- ddfem/transformers/NNS.py +11 -72
- ddfem/transformers/NS.py +14 -79
- ddfem/transformers/__init__.py +1 -0
- ddfem/transformers/transformer_base.py +102 -15
- {ddfem-1.0.0.dist-info → ddfem-1.0.1.dist-info}/METADATA +2 -6
- ddfem-1.0.1.dist-info/RECORD +41 -0
- ddfem/base_model.py +0 -200
- ddfem/geometry/circle.py +0 -39
- ddfem-1.0.0.dist-info/RECORD +0 -27
- {ddfem-1.0.0.dist-info → ddfem-1.0.1.dist-info}/WHEEL +0 -0
- {ddfem-1.0.0.dist-info → ddfem-1.0.1.dist-info}/licenses/LICENSE +0 -0
- {ddfem-1.0.0.dist-info → ddfem-1.0.1.dist-info}/top_level.txt +0 -0
@@ -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
|
ddfem/geometry/__init__.py
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
from .arc import Arc
|
2
|
+
from .ball import Ball
|
2
3
|
from .box import Box
|
3
|
-
from .circle import Circle, Sphere
|
4
4
|
from .pie import Pie
|
5
|
-
from .
|
5
|
+
from .plane import Plane
|
6
6
|
from .primitive_base import (
|
7
7
|
SDF,
|
8
8
|
Intersection,
|
9
9
|
Invert,
|
10
|
-
Round,
|
11
10
|
Rotate,
|
12
11
|
Round,
|
13
12
|
Scale,
|
@@ -16,3 +15,4 @@ from .primitive_base import (
|
|
16
15
|
Union,
|
17
16
|
Xor,
|
18
17
|
)
|
18
|
+
from .vesica import Vesica
|
ddfem/geometry/arc.py
CHANGED
@@ -7,32 +7,34 @@ from .primitive_base import ORIGIN, SDF
|
|
7
7
|
class Arc(SDF):
|
8
8
|
"""SDF for an arc:
|
9
9
|
|
10
|
-
|
11
|
-
|
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
12
|
"""
|
13
|
+
|
13
14
|
def __init__(self, radius, angle, width, center=ORIGIN, *args, **kwargs):
|
14
|
-
super().__init__(*args, **kwargs)
|
15
15
|
self.radius = radius
|
16
16
|
self.angle = angle # angle of opening
|
17
17
|
self.width = width
|
18
18
|
|
19
|
-
assert len(center) == 2
|
20
19
|
if isinstance(center, (list, tuple)):
|
21
20
|
center = as_vector(center)
|
22
21
|
self.center = center
|
23
22
|
|
23
|
+
super().__init__(*args, **kwargs)
|
24
|
+
|
24
25
|
def __repr__(self):
|
25
|
-
return f"Arc({self.radius}, {self.angle}, {self.width}, {self.center})"
|
26
|
+
return f"Arc({self.radius}, {self.angle}, {self.width}, {self.center}, {self._repr_core()})"
|
26
27
|
|
27
28
|
def sdf(self, x):
|
29
|
+
# Note: Ignores z
|
28
30
|
y0_abs = abs(x[1])
|
29
|
-
coords = as_vector(
|
31
|
+
coords = as_vector([x[0], y0_abs])
|
30
32
|
|
31
33
|
center_radius = self.radius - self.width / 2
|
32
34
|
|
33
|
-
distance = ufl_length(
|
35
|
+
distance = ufl_length(coords) - center_radius
|
34
36
|
|
35
|
-
edge_point = center_radius * as_vector(
|
37
|
+
edge_point = center_radius * as_vector([cos(self.angle), sin(self.angle)])
|
36
38
|
|
37
39
|
left_coords = coords - edge_point
|
38
40
|
|
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
CHANGED
@@ -1,6 +1,4 @@
|
|
1
|
-
from ufl import as_vector, conditional
|
2
|
-
from ufl import max_value as Max
|
3
|
-
from ufl import min_value as Min
|
1
|
+
from ufl import as_vector, conditional, max_value, min_value
|
4
2
|
|
5
3
|
from .helpers import ufl_length
|
6
4
|
from .primitive_base import ORIGIN, SDF
|
@@ -8,27 +6,28 @@ from .primitive_base import ORIGIN, SDF
|
|
8
6
|
|
9
7
|
class Box(SDF):
|
10
8
|
def __init__(self, width, height, center=ORIGIN, *args, **kwargs):
|
11
|
-
super().__init__(*args, **kwargs)
|
12
9
|
self.width = width
|
13
10
|
self.height = height
|
14
11
|
|
15
|
-
assert len(center) == 2
|
16
12
|
if isinstance(center, (list, tuple)):
|
17
13
|
center = as_vector(center)
|
18
14
|
self.center = center
|
19
15
|
|
16
|
+
super().__init__(*args, **kwargs)
|
17
|
+
|
20
18
|
def __repr__(self):
|
21
|
-
return f"Box({self.width}, {self.height}, {self.center})"
|
19
|
+
return f"Box({self.width}, {self.height}, {self.center}, {self._repr_core()})"
|
22
20
|
|
23
21
|
def sdf(self, x):
|
22
|
+
# Note: Ignores z
|
24
23
|
# shift x
|
25
24
|
center_x = x - self.center
|
26
25
|
# aux functions
|
27
26
|
w0 = abs(center_x[0]) - self.width / 2
|
28
27
|
w1 = abs(center_x[1]) - self.height / 2
|
29
28
|
|
30
|
-
g =
|
29
|
+
g = max_value(w0, w1)
|
31
30
|
|
32
|
-
q = as_vector((
|
31
|
+
q = as_vector([max_value(w0, 0), max_value(w1, 0)])
|
33
32
|
|
34
33
|
return conditional(g > 0, ufl_length(q), g)
|
ddfem/geometry/domain.py
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
from ufl import conditional, dot, grad, sqrt
|
2
2
|
|
3
|
+
from .primitive_base import SDF
|
4
|
+
|
3
5
|
|
4
6
|
class Domain:
|
5
7
|
def __init__(self, omega):
|
@@ -9,6 +11,9 @@ class Domain:
|
|
9
11
|
tol = 1e-10
|
10
12
|
return (1 - tol) * self.omega.phi(x) + tol
|
11
13
|
|
14
|
+
def chi(self, x):
|
15
|
+
return self.omega.chi(x)
|
16
|
+
|
12
17
|
def scaled_normal(self, x):
|
13
18
|
return -grad(self.phi(x))
|
14
19
|
|
@@ -20,18 +25,25 @@ class Domain:
|
|
20
25
|
sd = conditional(self.surface_delta(x) > tol, self.surface_delta(x), tol)
|
21
26
|
return self.scaled_normal(x) / sd # grad(self.omega(x))
|
22
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
|
+
|
23
34
|
def bndSDFs(self, SDFname):
|
35
|
+
if isinstance(SDFname, SDF):
|
36
|
+
SDFname = SDFname.name
|
37
|
+
|
24
38
|
sdf = self.omega.search(SDFname)
|
25
39
|
if sdf is None:
|
26
40
|
raise ValueError(f"No SDF with name {SDFname}")
|
27
41
|
return sdf
|
28
42
|
|
29
43
|
def bndProjSDFs(self, SDFname):
|
30
|
-
sdf = self.
|
31
|
-
if sdf is None:
|
32
|
-
raise ValueError(f"No SDF with name {SDFname}")
|
44
|
+
sdf = self.bndSDFs(SDFname)
|
33
45
|
return self.generate_projSDF(sdf)
|
34
46
|
|
35
47
|
def generate_projSDF(self, sdf):
|
36
48
|
w = lambda x: sdf.phi(x) * (1 - sdf.phi(x))
|
37
|
-
return lambda x: w(self.
|
49
|
+
return lambda x: w(self.boundary_projection(x))
|
ddfem/geometry/domain_dune.py
CHANGED
@@ -13,6 +13,8 @@ class DomainDune(Domain):
|
|
13
13
|
self.gridView = gridView
|
14
14
|
|
15
15
|
self._phi = None
|
16
|
+
self._bndProj = None
|
17
|
+
self._extProj = None
|
16
18
|
self._bndProjSDFs = {}
|
17
19
|
|
18
20
|
self.fullSDF = self.gridFunction(self.omega(self.x), name="full-sdf")
|
@@ -30,6 +32,20 @@ class DomainDune(Domain):
|
|
30
32
|
|
31
33
|
return self._phi
|
32
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
|
+
|
33
49
|
def generate_projSDF(self, sdf):
|
34
50
|
projSDF = self._bndProjSDFs.get(sdf.name)
|
35
51
|
if projSDF is None:
|
ddfem/geometry/helpers.py
CHANGED
@@ -1,12 +1,19 @@
|
|
1
|
-
from ufl import conditional, dot, sqrt
|
2
|
-
|
3
|
-
|
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)
|
4
11
|
|
5
12
|
|
6
13
|
def ufl_length(p):
|
14
|
+
return sqrt(dot(p, p) + 1e-10)
|
15
|
+
# return max_value(sqrt(dot(p, p)), 1e-10)
|
7
16
|
# return sqrt(dot(p, p))
|
8
|
-
# return ufl_max(sqrt(dot(p, p) + 1e-10), 1e-10)
|
9
|
-
return sqrt(dot(p, p))
|
10
17
|
|
11
18
|
|
12
19
|
def ufl_sign(p):
|
@@ -20,15 +27,15 @@ def ufl_clamp(p, minimum, maximum):
|
|
20
27
|
if isinstance(p, (float, int)):
|
21
28
|
return min(max(p, minimum), maximum)
|
22
29
|
|
23
|
-
def ufl_max(p1, p2):
|
24
|
-
|
30
|
+
# def ufl_max(p1, p2):
|
31
|
+
# return conditional(p2 < p1, p1, p2)
|
25
32
|
|
26
|
-
def ufl_min(p1, p2):
|
27
|
-
|
33
|
+
# def ufl_min(p1, p2):
|
34
|
+
# return conditional(p1 < p2, p1, p2)
|
28
35
|
|
29
|
-
return ufl_min(ufl_max(p, minimum), maximum)
|
30
|
-
# using
|
31
|
-
return
|
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)
|
32
39
|
|
33
40
|
|
34
41
|
def ufl_cross(p1, p2):
|
ddfem/geometry/pie.py
CHANGED
@@ -1,6 +1,4 @@
|
|
1
|
-
from ufl import as_vector, cos, dot, sin
|
2
|
-
from ufl import max_value as Max
|
3
|
-
from ufl import min_value as Min
|
1
|
+
from ufl import as_vector, cos, dot, max_value, min_value, sin
|
4
2
|
|
5
3
|
from .helpers import ufl_clamp, ufl_cross, ufl_length, ufl_sign
|
6
4
|
from .primitive_base import ORIGIN, SDF
|
@@ -8,16 +6,18 @@ from .primitive_base import ORIGIN, SDF
|
|
8
6
|
|
9
7
|
class Pie(SDF):
|
10
8
|
def __init__(self, radius, angle, *args, **kwargs):
|
11
|
-
super().__init__(*args, **kwargs)
|
12
9
|
self.radius = radius
|
13
10
|
self.angle = angle # angle of cicle (not opening)
|
14
11
|
|
12
|
+
super().__init__(*args, **kwargs)
|
13
|
+
|
15
14
|
def __repr__(self):
|
16
|
-
return f"Pie({self.radius}, {self.angle})"
|
15
|
+
return f"Pie({self.radius}, {self.angle}, {self._repr_core()})"
|
17
16
|
|
18
17
|
def sdf(self, x):
|
18
|
+
# Note: Ignores z
|
19
19
|
x0_abs = abs(x[0])
|
20
|
-
coords = as_vector(
|
20
|
+
coords = as_vector([x0_abs, x[1]])
|
21
21
|
|
22
22
|
circle_dist = ufl_length(coords) - self.radius
|
23
23
|
|
@@ -28,4 +28,4 @@ class Pie(SDF):
|
|
28
28
|
rejc = coords - proj
|
29
29
|
edge_dist = ufl_length(rejc) * ufl_sign(ufl_cross(coords, trig))
|
30
30
|
|
31
|
-
return
|
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
|