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.
Files changed (43) hide show
  1. ddfem/__init__.py +4 -0
  2. ddfem/boundary.py +12 -3
  3. ddfem/examples/__init__.py +0 -0
  4. ddfem/examples/advection_diffusion.py +74 -0
  5. ddfem/examples/beam.py +147 -0
  6. ddfem/examples/cahn_hilliard.py +67 -0
  7. ddfem/examples/chemical_reaction.py +88 -0
  8. ddfem/examples/constant.py +46 -0
  9. ddfem/examples/five_circle_flat.py +197 -0
  10. ddfem/examples/forchheimer.py +48 -0
  11. ddfem/examples/hyperelasticity.py +88 -0
  12. ddfem/examples/linear_elasticity.py +45 -0
  13. ddfem/examples/plaplace.py +29 -0
  14. ddfem/examples/single_circle.py +135 -0
  15. ddfem/examples/triple_circle.py +217 -0
  16. ddfem/examples/triple_circle_beam.py +208 -0
  17. ddfem/geometry/__init__.py +3 -3
  18. ddfem/geometry/arc.py +10 -8
  19. ddfem/geometry/ball.py +24 -0
  20. ddfem/geometry/box.py +7 -8
  21. ddfem/geometry/domain.py +16 -4
  22. ddfem/geometry/domain_dune.py +16 -0
  23. ddfem/geometry/helpers.py +19 -12
  24. ddfem/geometry/pie.py +7 -7
  25. ddfem/geometry/plane.py +20 -0
  26. ddfem/geometry/primitive_base.py +129 -70
  27. ddfem/geometry/vesica.py +7 -6
  28. ddfem/model2ufl.py +10 -4
  29. ddfem/transformers/DDM1.py +10 -68
  30. ddfem/transformers/Fitted.py +22 -12
  31. ddfem/transformers/Mix0.py +10 -64
  32. ddfem/transformers/NNS.py +11 -72
  33. ddfem/transformers/NS.py +14 -79
  34. ddfem/transformers/__init__.py +1 -0
  35. ddfem/transformers/transformer_base.py +102 -15
  36. {ddfem-1.0.0.dist-info → ddfem-1.0.1.dist-info}/METADATA +2 -6
  37. ddfem-1.0.1.dist-info/RECORD +41 -0
  38. ddfem/base_model.py +0 -200
  39. ddfem/geometry/circle.py +0 -39
  40. ddfem-1.0.0.dist-info/RECORD +0 -27
  41. {ddfem-1.0.0.dist-info → ddfem-1.0.1.dist-info}/WHEEL +0 -0
  42. {ddfem-1.0.0.dist-info → ddfem-1.0.1.dist-info}/licenses/LICENSE +0 -0
  43. {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
@@ -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 .vesica import Vesica
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
- 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).
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((x[0], y0_abs))
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(x) - center_radius
35
+ distance = ufl_length(coords) - center_radius
34
36
 
35
- edge_point = center_radius * as_vector((cos(self.angle), sin(self.angle)))
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 = Max(w0, w1)
29
+ g = max_value(w0, w1)
31
30
 
32
- q = as_vector((Max(w0, 0), Max(w1, 0)))
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.omega.search(SDFname)
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.omega.boundary_projection(x))
49
+ return lambda x: w(self.boundary_projection(x))
@@ -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
- from ufl import max_value as Max
3
- from ufl import min_value as Min
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
- return conditional(p2 < p1, p1, p2)
30
+ # def ufl_max(p1, p2):
31
+ # return conditional(p2 < p1, p1, p2)
25
32
 
26
- def ufl_min(p1, p2):
27
- return conditional(p1 < p2, p1, p2)
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 Min/Max, seems to break shape Pie?
31
- return Min(Max(p, minimum), maximum)
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((x0_abs, x[1]))
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 Max(circle_dist, edge_dist)
31
+ return max_value(circle_dist, edge_dist)
@@ -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