ddfem 1.0.0__py3-none-any.whl → 1.0.2__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 (45) hide show
  1. ddfem/__init__.py +5 -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 +19 -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 +11 -4
  29. ddfem/plot.py +13 -0
  30. ddfem/transformers/DDM1.py +10 -68
  31. ddfem/transformers/Fitted.py +22 -12
  32. ddfem/transformers/Mix0.py +10 -64
  33. ddfem/transformers/NNS.py +11 -72
  34. ddfem/transformers/NS.py +14 -79
  35. ddfem/transformers/__init__.py +1 -0
  36. ddfem/transformers/transformer_base.py +102 -15
  37. {ddfem-1.0.0.dist-info → ddfem-1.0.2.dist-info}/METADATA +5 -6
  38. ddfem-1.0.2.dist-info/RECORD +41 -0
  39. ddfem/base_model.py +0 -200
  40. ddfem/dune.py +0 -3
  41. ddfem/geometry/circle.py +0 -39
  42. ddfem-1.0.0.dist-info/RECORD +0 -27
  43. {ddfem-1.0.0.dist-info → ddfem-1.0.2.dist-info}/WHEEL +0 -0
  44. {ddfem-1.0.0.dist-info → ddfem-1.0.2.dist-info}/licenses/LICENSE +0 -0
  45. {ddfem-1.0.0.dist-info → ddfem-1.0.2.dist-info}/top_level.txt +0 -0
ddfem/__init__.py CHANGED
@@ -0,0 +1,5 @@
1
+ from . import geometry
2
+ from . import transformers
3
+ from . import boundary
4
+ from .model2ufl import model2ufl
5
+ from .plot import plotSolution
ddfem/boundary.py CHANGED
@@ -6,6 +6,7 @@ from ufl.algorithms.ad import expand_derivatives
6
6
  from ufl.core.expr import Expr
7
7
 
8
8
  from .geometry.domain import Domain
9
+ from .geometry.primitive_base import SDF
9
10
 
10
11
 
11
12
  class BoundaryCondition:
@@ -22,7 +23,9 @@ class BndValue(BoundaryCondition):
22
23
  super().__init__(lambda t, x, u: value)
23
24
  else:
24
25
  num_args = len(inspect.signature(value).parameters)
25
- if num_args == 2:
26
+ if num_args == 1:
27
+ super().__init__(lambda t, x, u: value(x))
28
+ elif num_args == 2:
26
29
  super().__init__(lambda t, x, u: value(t, x))
27
30
  elif num_args == 3:
28
31
  super().__init__(value)
@@ -129,7 +132,7 @@ class BoundaryTerms:
129
132
  else:
130
133
  self.domain = Domain(domainDescription)
131
134
 
132
- condition = lambda k: isinstance(k, str)
135
+ condition = lambda k: isinstance(k, str) or isinstance(k, SDF)
133
136
  self.diffuse = {k: v for k, v in Model.boundary.items() if condition(k)}
134
137
  self.physical = {k: v for k, v in Model.boundary.items() if not condition(k)}
135
138
 
@@ -154,6 +157,11 @@ class BoundaryTerms:
154
157
  if not self.boundary_flux_vs:
155
158
  self.BndFlux_vExt = None
156
159
 
160
+ def _dbc_total_weight(self, x):
161
+ weight = 1e-10
162
+ for w_func in self.bV_weight:
163
+ weight += w_func(x)
164
+ return weight
157
165
  def _total_weight(self, x):
158
166
  weight = 1e-10 # tol
159
167
  for w_func in self.bV_weight + self.bF_weight:
@@ -162,7 +170,7 @@ class BoundaryTerms:
162
170
 
163
171
  def _boundary_extend(self, g, x):
164
172
  g_tmp = expand_derivatives(g)
165
- return replace(g_tmp, {x: self.domain.omega.boundary_projection(x)})
173
+ return replace(g_tmp, {x: self.domain.boundary_projection(x)})
166
174
 
167
175
  # perhaps switch methods around so that BndValueExt is setup and then
168
176
  # jumpD does sum(w)*U - BndValueExt. But then the exception needs to be caught...
@@ -197,6 +205,7 @@ class BoundaryTerms:
197
205
  jdf += w_func(x) * (fv_scaled - g_ext * self.domain.surface_delta(x))
198
206
 
199
207
  return jdf / self._total_weight(x)
208
+ # return jdf * (self._dbc_total_weight(x) / self._total_weight(x))
200
209
 
201
210
  def BndFlux_vExt(self, t, x, U, DU):
202
211
  # called if self.BndFlux_vExt was not set to None in __init__
File without changes
@@ -0,0 +1,74 @@
1
+ from dune.ufl import Constant
2
+ from ufl import as_vector, conditional, div, dot, grad, outer, sqrt
3
+
4
+ from ddfem.boundary import BndFlux_c, BndFlux_v, BndValue
5
+
6
+
7
+ def adModel(exact, D, withVelocity, inverted):
8
+ class Model:
9
+ solution = exact
10
+ dimRange = 1
11
+ diffFactor = Constant(D)
12
+
13
+ # this should probably be a vector of dimRange and then used by
14
+ # componentwise multiplication with (u-g):
15
+ outFactor_i = diffFactor
16
+
17
+ if withVelocity:
18
+
19
+ outFactor_e = 1
20
+
21
+ def diff(t, x):
22
+ return Model.diffFactor * (1 - 0.5 * dot(x, x))
23
+
24
+ def F_v(t, x, U, DU):
25
+ return Model.diff(t, x) * DU
26
+
27
+ if withVelocity:
28
+
29
+ def b(t, x):
30
+ return Constant([0.9, 0.5]) + 3 * as_vector([x[1], -x[0]])
31
+
32
+ def F_c(t, x, U):
33
+ return outer(U, Model.b(t, x))
34
+
35
+ if exact:
36
+
37
+ def S_i(t, x, U, DU):
38
+ return -div(Model.F_v(t, x, exact(t, x), grad(exact(t, x)))) + (
39
+ exact(t, x) - U
40
+ )
41
+
42
+ if exact and withVelocity:
43
+
44
+ def S_e(t, x, U, DU):
45
+ return div(Model.F_c(t, x, exact(t, x)))
46
+
47
+ if exact:
48
+ valD = BndValue(lambda t, x: exact(t, x))
49
+ valFv = BndFlux_v(
50
+ lambda t, x, U, DU, n: Model.F_v(t, x, exact(t, x), grad(exact(t, x)))
51
+ * n
52
+ )
53
+
54
+ if withVelocity:
55
+ valFc = BndFlux_c(lambda t, x, U, n: Model.F_c(t, x, exact(t, x)) * n)
56
+ valN = [valFc, valFv]
57
+ else:
58
+ valN = valFv
59
+
60
+ boundary = {
61
+ "sides": valD,
62
+ "ends": valN,
63
+ }
64
+ else:
65
+ x0 = as_vector([-0.5, -0.5])
66
+ bnd = lambda x: conditional(dot(x - Model.x0, x - Model.x0) < 0.15, 10, 0)
67
+ valD = BndValue(lambda t, x, U: as_vector([Model.bnd(x)]))
68
+ boundary = {"full": valD}
69
+
70
+ if inverted:
71
+ for i in range(1, 5):
72
+ boundary[i] = valD
73
+
74
+ return Model
ddfem/examples/beam.py ADDED
@@ -0,0 +1,147 @@
1
+ import numpy as np
2
+ import pygmsh
3
+ from dune.alugrid import aluConformGrid as leafGridView
4
+ from dune.fem import adapt, mark, markNeighbors
5
+ from dune.fem.function import gridFunction
6
+ from dune.fem.space import lagrange
7
+ from dune.fem.view import adaptiveLeafGridView
8
+ from dune.grid import cartesianDomain
9
+ from dune.ufl import Constant, Space
10
+ from ufl import SpatialCoordinate, sqrt
11
+
12
+ from ddfem import geometry as gm
13
+ from ddfem.geometry.domain_dune import DomainDune
14
+
15
+
16
+ def getDomain(initialRefine, version, adaptLevels=0, epsFactor=4.5, *args, **kwargs):
17
+
18
+ domain_range = [[-0.1, -0.1], [1.1, 0.25]]
19
+ initial_gridsize = [360 * 2**initialRefine, 105 * 2**initialRefine]
20
+ h = sqrt(
21
+ ((domain_range[1][0] - domain_range[0][0]) / initial_gridsize[0]) ** 2
22
+ + ((domain_range[1][1] - domain_range[0][1]) / initial_gridsize[1]) ** 2
23
+ )
24
+
25
+ def get_eps(h):
26
+ return Constant(epsFactor * h * 0.5 ** (adaptLevels / 2), "epsilon")
27
+
28
+ rectangles = [
29
+ [2, 1, [1, 0.075], "left"],
30
+ [2, 0.15, [0, 0.075], "other"],
31
+ ]
32
+
33
+ sdfs = [gm.Box(c[0], c[1], c[2], name=c[3]) for c in rectangles]
34
+ omega = sdfs[0] & sdfs[1]
35
+ omega.name = "full"
36
+
37
+ h_max = h * 3
38
+ h_min = h / 2
39
+ radius = 5
40
+
41
+ x = SpatialCoordinate(Space(2))
42
+ sdf = omega(x)
43
+
44
+ def spacing(x, y, epsilon):
45
+ r_min = epsilon.value
46
+ r_max = radius * epsilon.value
47
+ dist = np.abs(sdf((x, y)))
48
+ if dist <= r_min:
49
+ return geom.characteristic_length_min
50
+ elif dist >= r_max:
51
+ return geom.characteristic_length_max
52
+ else:
53
+ # Linear
54
+ m = (geom.characteristic_length_max - geom.characteristic_length_min) / (
55
+ r_max - r_min
56
+ )
57
+ return m * (dist - r_min) + geom.characteristic_length_min
58
+
59
+ if version == "cartesian":
60
+ domain = cartesianDomain(*domain_range, initial_gridsize)
61
+ epsilon = get_eps(h)
62
+
63
+ elif version == "fitted":
64
+ with pygmsh.occ.Geometry() as geom:
65
+ geom.characteristic_length_max = h_max
66
+ geom.characteristic_length_min = h_min
67
+ epsilon = get_eps(h_min)
68
+
69
+ rec = geom.add_rectangle([0, 0, 0], 1, 0.15)
70
+
71
+ geom.set_mesh_size_callback(
72
+ lambda dim, tag, x, y, z, lc: spacing(x, y, epsilon),
73
+ ignore_other_mesh_sizes=True,
74
+ )
75
+
76
+ mesh = geom.generate_mesh()
77
+ points, cells = mesh.points, mesh.cells_dict
78
+ domain = {
79
+ "vertices": points[:, :2].astype(float),
80
+ "simplices": cells["triangle"].astype(int),
81
+ }
82
+
83
+ elif version == "dune_adaptive":
84
+ gridsize = [int(j * h / h_max) for j in initial_gridsize]
85
+ domain = cartesianDomain(*domain_range, gridsize)
86
+
87
+ elif version == "gmsh_adaptive":
88
+ with pygmsh.occ.Geometry() as geom:
89
+ geom.characteristic_length_max = h_max
90
+ geom.characteristic_length_min = h_min
91
+ epsilon = get_eps(h_min)
92
+
93
+ geom.set_mesh_size_callback(
94
+ lambda dim, tag, x, y, z, lc: spacing(x, y, epsilon),
95
+ ignore_other_mesh_sizes=True,
96
+ )
97
+
98
+ geom.add_rectangle(
99
+ [domain_range[0][0], domain_range[0][1], 0.0],
100
+ domain_range[1][0] - domain_range[0][0],
101
+ domain_range[1][1] - domain_range[0][1],
102
+ )
103
+
104
+ mesh = geom.generate_mesh()
105
+ points, cells = mesh.points, mesh.cells_dict
106
+ domain = {
107
+ "vertices": points[:, :2].astype(float),
108
+ "simplices": cells["triangle"].astype(int),
109
+ }
110
+
111
+ else:
112
+ raise ValueError("invalid mesh type")
113
+
114
+ gridView = adaptiveLeafGridView(leafGridView(domain))
115
+
116
+ if version == "dune_adaptive":
117
+ omega.epsilon = get_eps(h_min)
118
+ omega.epsilon.value *= radius
119
+ epsilon_value = omega.epsilon.value
120
+
121
+ marker = mark
122
+
123
+ refinements = int(2 * np.log2(h_max / h_min))
124
+
125
+ region = gridFunction(
126
+ omega.phi(x) * (1 - omega.phi(x)), gridView=gridView
127
+ ) # interface
128
+
129
+ for j in range(1, refinements + 1):
130
+
131
+ omega.epsilon.value = epsilon_value * j / refinements
132
+ marker(region, 0.00247262315663, maxLevel=refinements) # 1 epsilon
133
+
134
+ adapt(gridView.hierarchicalGrid)
135
+
136
+ h_min = h_max * 0.5 ** (j / 2)
137
+ epsilon = get_eps(h_min)
138
+
139
+ omega.propagate_epsilon(epsilon)
140
+ domain = omega
141
+
142
+ domain = DomainDune(omega, x, gridView)
143
+ # domain.adapt(level=adaptLevels)
144
+
145
+ print(f"h={h * 0.5 ** (adaptLevels / 2)}, epsilon={epsilon.value}")
146
+
147
+ return gridView, domain
@@ -0,0 +1,67 @@
1
+ from dune.ufl import Constant
2
+ from ufl import as_tensor, as_vector, dot, grad, inner, zero
3
+
4
+ from ddfem.boundary import BndFlux_c, BndFlux_v, BndValue
5
+
6
+
7
+ def chModel():
8
+ class CHModel:
9
+ dimRange = [1, 1]
10
+
11
+ M = Constant(1, name="M")
12
+ lmbda = Constant(1.0e-02, name="lmbda") # surface parameter
13
+ dt = Constant(5.0e-06, name="dt") # time step
14
+ theta = Constant(
15
+ 0.5, name="theta"
16
+ ) # theta=1 -> backward Euler, theta=0.5 -> Crank-Nicolson
17
+
18
+ u_h_n = None
19
+ Du_h_n = None
20
+
21
+ def setup(u_h, DDMModel):
22
+ CHModel.u_h_n = u_h.copy()
23
+ CHModel.Du_h_n = lambda t, x: DDMModel.sigma(
24
+ t, x, CHModel.u_h_n, grad(CHModel.u_h_n)
25
+ )
26
+
27
+ def energy(U, DU):
28
+ C, MU = as_vector([U[0]]), as_vector([U[1]])
29
+ DC, DMU = as_tensor([DU[0, :]]), as_tensor([DU[1, :]])
30
+
31
+ f = 100 * dot(C, C) * dot(as_vector([1]) - C, as_vector([1]) - C)
32
+
33
+ return CHModel.lmbda / 2 * inner(DC, DC) + f
34
+
35
+ def F_v(t, x, U, DU):
36
+ C, MU = as_vector([U[0]]), as_vector([U[1]])
37
+ DC, DMU = as_tensor([DU[0, :]]), as_tensor([DU[1, :]])
38
+ c_h_n, mu_h_n = as_vector([CHModel.u_h_n[0]]), as_vector([CHModel.u_h_n[1]])
39
+ Du_h_n = CHModel.Du_h_n(t, x)
40
+ Dc_h_n, Dmu_h_n = as_vector([Du_h_n[0, :]]), as_vector([Du_h_n[1, :]])
41
+
42
+ mu_mid = (1.0 - CHModel.theta) * Dmu_h_n + CHModel.theta * DMU
43
+ concentration_F_v = CHModel.M * mu_mid
44
+ potential_F_v = CHModel.lmbda * DC
45
+
46
+ return as_tensor([concentration_F_v[0, :], potential_F_v[0, :]])
47
+
48
+ def S_i(t, x, U, DU):
49
+ C, MU = as_vector([U[0]]), as_vector([U[1]])
50
+ DC, DMU = as_tensor([DU[0, :]]), as_tensor([DU[1, :]])
51
+ c_h_n, mu_h_n = as_vector([CHModel.u_h_n[0]]), as_vector([CHModel.u_h_n[1]])
52
+
53
+ concentration_S_i = -(C - c_h_n) / CHModel.dt
54
+ potential_S_i = (
55
+ MU - (200 + 400 * dot(C, C)) * C + as_vector([600 * dot(C, C)])
56
+ )
57
+
58
+ return as_vector([concentration_S_i[0], potential_S_i[0]])
59
+
60
+ def valF_v(t, x, MU, DMU, n):
61
+ return zero(2)
62
+
63
+ boundary = {
64
+ "full": BndFlux_v(valF_v),
65
+ }
66
+
67
+ return CHModel
@@ -0,0 +1,88 @@
1
+ from dune.ufl import Constant
2
+ from ufl import as_vector, conditional, dot, grad, outer, zero
3
+
4
+ from ddfem.boundary import BndFlux_c, BndFlux_v, BndValue
5
+
6
+
7
+ def crModel():
8
+ class PotentialModel:
9
+ dimRange = 1
10
+ outFactor_i = Constant(1, "outFactor")
11
+
12
+ def F_v(t, x, U, DU):
13
+ return DU
14
+
15
+ def S_e(t, x, U, DU):
16
+ return as_vector([-1])
17
+
18
+ def valD(t, x, U):
19
+ return zero(1)
20
+
21
+ boundary = {
22
+ "full": BndValue(valD),
23
+ }
24
+
25
+ class ChemModel:
26
+ dimRange = 3
27
+
28
+ dt = Constant(0.05, "dt")
29
+ diff = Constant(1e-3, "diff") # this is about the boundary for stability
30
+ # outFactor_i = diff
31
+
32
+ P1 = as_vector([-0.25, -0.25]) # midpoint of first source (close to boundary)
33
+ P2 = as_vector([0.25, 0.25]) # midpoint of second source (close to boundary)
34
+
35
+ u_h_n = None
36
+ dpsi_h = None
37
+
38
+ def setup(u_h, DDMPotentialModel, psi_h):
39
+ ChemModel.u_h_n = u_h.copy(name="u_h_n")
40
+ ChemModel.u_h_n.interpolate(zero(ChemModel.dimRange))
41
+
42
+ ChemModel.dpsi_h = lambda x: DDMPotentialModel.sigma(
43
+ 0, x, psi_h, grad(psi_h)
44
+ )
45
+
46
+ def f1(x):
47
+ q = x - ChemModel.P1
48
+ return conditional(dot(q, q) < 0.04**2, 5, 0)
49
+
50
+ def f2(x):
51
+ q = x - ChemModel.P2
52
+ return conditional(dot(q, q) < 0.04**2, 5, 0)
53
+
54
+ def f(t, x):
55
+ return conditional(
56
+ t < 10,
57
+ as_vector([ChemModel.f1(x), ChemModel.f2(x), 0]),
58
+ as_vector([0, 0, 0]),
59
+ )
60
+
61
+ def r(U):
62
+ return 10 * as_vector([U[0] * U[1], U[0] * U[1], -2 * U[0] * U[1]])
63
+
64
+ def F_v(t, x, U, DU):
65
+ return ChemModel.diff * DU
66
+
67
+ def velocity(x):
68
+ return as_vector([-ChemModel.dpsi_h(x)[0, 1], ChemModel.dpsi_h(x)[0, 0]])
69
+
70
+ def F_c(t, x, U):
71
+ return outer(U, ChemModel.velocity(x))
72
+
73
+ def S_i(t, x, U, DU):
74
+ return -(U - ChemModel.u_h_n) / ChemModel.dt
75
+
76
+ def S_e(t, x, U, DU):
77
+ return ChemModel.f(t, x) - ChemModel.r(ChemModel.u_h_n)
78
+
79
+ # boundary = {"full": BndValue(lambda t, x, U: zero(3))}
80
+
81
+ boundary = {
82
+ "full": [
83
+ BndFlux_c(lambda t, x, U, n: zero(ChemModel.dimRange)),
84
+ BndFlux_v(lambda t, x, U, DU, n: zero(ChemModel.dimRange)),
85
+ ]
86
+ }
87
+
88
+ return PotentialModel, ChemModel
@@ -0,0 +1,46 @@
1
+ from dune.ufl import Constant
2
+ from ufl import as_vector, conditional, div, dot, exp, grad, inner, sqrt, outer, zero
3
+ import ufl
4
+ from ddfem.boundary import AFluxBC, DFluxBC, ValueBC
5
+
6
+ def CModel(inverted):
7
+ withAdv = False
8
+
9
+ class Model:
10
+ dimRange = 1
11
+ stabFactor = Constant(1)
12
+
13
+ def initial(x):
14
+ return 2
15
+
16
+ def exact(t, x):
17
+ return as_vector((2+x[0]-x[0],)) # better way of avoiding 'Cannot determine geometric dimension from expression'
18
+
19
+ def K(U, DU):
20
+ return 1e-2
21
+
22
+ def F_v(t, x, U, DU):
23
+ return Model.K(U, DU) * DU
24
+
25
+ if withAdv:
26
+ def F_c(t, x, U):
27
+ return outer(U, as_vector([-2,0.5]) )
28
+
29
+ if withAdv:
30
+ bndFlux = [ AFluxBC( lambda t,x,U,n: Model.F_c(t,x,U)*n ),
31
+ DFluxBC( lambda t,x,U,DU,n: Model.F_v(t,x,U,DU)*n ) ]
32
+ else:
33
+ # this works: bndFlux = DFluxBC( lambda t,x,U,DU,n: zero(U.ufl_shape)
34
+ bndFlux = DFluxBC( lambda t,x,U,DU,n: Model.F_v(t,x,U,DU)*n )
35
+ boundary = {
36
+ # "full": ValueBC( lambda t,x,U: as_vector([2.]) ),
37
+ "full": bndFlux
38
+ # "sides": ValueBC( lambda t,x,U: as_vector([2.]) ),
39
+ # "ends": [AFluxBC( lambda t,x,U,n: Model.F_c(t,x,U)*n ),
40
+ # DFluxBC( lambda t,x,U,DU,n: Model.F_v(t,x,U,DU)*n )]
41
+ }
42
+
43
+ if inverted:
44
+ boundary[range(1,5)] = ValueBC( lambda t,x,U: as_vector([2.]) )
45
+
46
+ return Model
@@ -0,0 +1,197 @@
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,
33
+ version,
34
+ adaptLevels=0,
35
+ epsFactor=4.5,
36
+ smoothing=None,
37
+ *args,
38
+ **kwargs,
39
+ ):
40
+
41
+ gm.SDF.smoothing = smoothing
42
+ shiftx, shifty = sqrt(2) * 1e-6, sqrt(3) * 1e-6
43
+ domain_range = [[-1.7 + shiftx, -1.3 + shifty], [1.7 + shiftx, 1.3 + shifty]]
44
+ initial_gridsize = [170 * 2**initialRefine, 130 * 2**initialRefine]
45
+ h = sqrt(
46
+ ((domain_range[1][0] - domain_range[0][0]) / initial_gridsize[0]) ** 2
47
+ + ((domain_range[1][1] - domain_range[0][1]) / initial_gridsize[1]) ** 2
48
+ )
49
+
50
+ def get_eps(h):
51
+ return Constant(epsFactor * h * 0.5 ** (adaptLevels / 2), "epsilon")
52
+
53
+ balls = [
54
+ [1, [0, 0], "Center"],
55
+ [0.5, [0, 0.8], "TopCut"],
56
+ [0.5, [0, -0.8], "BotCut"],
57
+ [0.5, [1, 0], "RightAdd"],
58
+ [0.5, [-1, 0], "LeftAdd"],
59
+ [1.4, [0, 0], "Cutoff"],
60
+ ]
61
+
62
+ b = [gm.Ball(c[0], c[1], name=c[2]) for c in balls]
63
+ # omega = (b[0] - b[1] - b[2] | b[3] | b[4]) & b[5]
64
+
65
+ bulk = b[0] - b[1] - b[2] | b[3] | b[4]
66
+ bulk.name = "bulk"
67
+
68
+ left_cutoff = b[5] | gm.Plane([-1, 0], -0.4)
69
+ left_cutoff.name = "left"
70
+ right_cutoff = b[5] | gm.Plane([1, 0], -0.4)
71
+ right_cutoff.name = "right"
72
+
73
+ cutoff = left_cutoff & right_cutoff # b[5]
74
+
75
+ omega = bulk & cutoff
76
+ omega.name = "full"
77
+
78
+ h_max = h * 3
79
+ h_min = h / 2
80
+ radius = 5
81
+
82
+ x = SpatialCoordinate(Space(2))
83
+ sdf = omega(x)
84
+
85
+ def spacing(x, y, epsilon):
86
+ r_min = epsilon.value
87
+ r_max = radius * epsilon.value
88
+ dist = np.abs(sdf((x, y)))
89
+ if dist <= r_min:
90
+ return geom.characteristic_length_min
91
+ elif dist >= r_max:
92
+ return geom.characteristic_length_max
93
+ else:
94
+ # Linear
95
+ m = (geom.characteristic_length_max - geom.characteristic_length_min) / (
96
+ r_max - r_min
97
+ )
98
+ return m * (dist - r_min) + geom.characteristic_length_min
99
+
100
+ if version == "cartesian":
101
+ domain = cartesianDomain(*domain_range, initial_gridsize)
102
+ epsilon = get_eps(h)
103
+
104
+ elif version == "fitted":
105
+ if pygmsh is None:
106
+ raise AttributeError("'fitted' requires install pygmsh")
107
+ with pygmsh.occ.Geometry() as geom:
108
+ geom.characteristic_length_max = h_max
109
+ geom.characteristic_length_min = h_min
110
+ epsilon = get_eps(h_min)
111
+
112
+ disks = [geom.add_disk([c[1][0], c[1][1], 0.0], c[0]) for c in balls]
113
+
114
+ disk_removed1 = geom.boolean_difference(disks[0], disks[1])
115
+ disk_removed2 = geom.boolean_difference(disk_removed1, disks[2])
116
+
117
+ disk_add = geom.boolean_union([disk_removed2, disks[3], disks[4]])
118
+ shape = geom.boolean_intersection([disk_add, disks[5]])
119
+
120
+ geom.set_mesh_size_callback(
121
+ lambda dim, tag, x, y, z, lc: spacing(x, y, epsilon),
122
+ ignore_other_mesh_sizes=True,
123
+ )
124
+ mesh = geom.generate_mesh()
125
+ points, cells = mesh.points, mesh.cells_dict
126
+ domain = {
127
+ "vertices": points[:, :2].astype(float),
128
+ "simplices": cells["triangle"].astype(int),
129
+ }
130
+
131
+ elif version == "dune_adaptive":
132
+ gridsize = [int(j * h / h_max) for j in initial_gridsize]
133
+ domain = cartesianDomain(*domain_range, gridsize)
134
+
135
+ elif version == "gmsh_adaptive":
136
+ if pygmsh is None:
137
+ raise AttributeError("'gmsh_adaptive' requires install pygmsh")
138
+ with pygmsh.occ.Geometry() as geom:
139
+ geom.characteristic_length_max = h_max
140
+ geom.characteristic_length_min = h_min
141
+ epsilon = get_eps(h_min)
142
+
143
+ geom.add_rectangle(
144
+ [domain_range[0][0], domain_range[0][1], 0.0],
145
+ domain_range[1][0] - domain_range[0][0],
146
+ domain_range[1][1] - domain_range[0][1],
147
+ )
148
+
149
+ geom.set_mesh_size_callback(
150
+ lambda dim, tag, x, y, z, lc: spacing(x, y, epsilon),
151
+ ignore_other_mesh_sizes=True,
152
+ )
153
+ mesh = geom.generate_mesh()
154
+ points, cells = mesh.points, mesh.cells_dict
155
+ domain = {
156
+ "vertices": points[:, :2].astype(float),
157
+ "simplices": cells["triangle"].astype(int),
158
+ }
159
+ else:
160
+ raise ValueError("invalid mesh type")
161
+
162
+ gridView = adaptiveLeafGridView(leafGridView(domain))
163
+
164
+ if version == "dune_adaptive":
165
+ omega.epsilon = get_eps(h_min)
166
+ omega.epsilon.value *= radius
167
+ epsilon_value = omega.epsilon.value
168
+
169
+ marker = mark
170
+
171
+ refinements = int(2 * np.log2(h_max / h_min))
172
+
173
+ region = gridFunction(
174
+ omega.phi(x) * (1 - omega.phi(x)), gridView=gridView
175
+ ) # interface
176
+
177
+ for j in range(1, refinements + 1):
178
+
179
+ omega.epsilon.value = epsilon_value * j / refinements
180
+ marker(region, 0.00247262315663, maxLevel=refinements) # 1 epsilon
181
+
182
+ adapt(gridView.hierarchicalGrid)
183
+
184
+ h_min = h_max * 0.5 ** (j / 2)
185
+ epsilon = get_eps(h_min)
186
+
187
+ omega.propagate_epsilon(epsilon)
188
+ domain = omega
189
+
190
+ domain = DomainDune(omega, x, gridView)
191
+ domain.adapt(level=adaptLevels)
192
+
193
+ print(
194
+ f"h_max={h_max}, h_min={h_min * 0.5 ** (adaptLevels / 2)}, epsilon={epsilon.value}"
195
+ )
196
+
197
+ return gridView, domain