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
ddfem/__init__.py
CHANGED
ddfem/boundary.py
ADDED
@@ -0,0 +1,223 @@
|
|
1
|
+
import inspect
|
2
|
+
|
3
|
+
import ufl
|
4
|
+
from ufl import replace, zero
|
5
|
+
from ufl.algorithms.ad import expand_derivatives
|
6
|
+
from ufl.core.expr import Expr
|
7
|
+
|
8
|
+
from .geometry.domain import Domain
|
9
|
+
from .geometry.primitive_base import SDF
|
10
|
+
|
11
|
+
|
12
|
+
class BoundaryCondition:
|
13
|
+
def __init__(self, value):
|
14
|
+
self.value = value
|
15
|
+
|
16
|
+
def __call__(self, *args, **kwds):
|
17
|
+
return self.value(*args, **kwds)
|
18
|
+
|
19
|
+
|
20
|
+
class BndValue(BoundaryCondition):
|
21
|
+
def __init__(self, value):
|
22
|
+
if isinstance(value, ufl.core.expr.Expr):
|
23
|
+
super().__init__(lambda t, x, u: value)
|
24
|
+
else:
|
25
|
+
num_args = len(inspect.signature(value).parameters)
|
26
|
+
if num_args == 1:
|
27
|
+
super().__init__(lambda t, x, u: value(x))
|
28
|
+
elif num_args == 2:
|
29
|
+
super().__init__(lambda t, x, u: value(t, x))
|
30
|
+
elif num_args == 3:
|
31
|
+
super().__init__(value)
|
32
|
+
# elif num_args == 4:
|
33
|
+
# self.SPLIT = True
|
34
|
+
# super().__init__(lambda t, x, u: value(t, x, u, n, n))
|
35
|
+
else:
|
36
|
+
raise ValueError(f"Boundary has {num_args} arguments.")
|
37
|
+
|
38
|
+
|
39
|
+
class BndFlux_v(BoundaryCondition):
|
40
|
+
pass
|
41
|
+
|
42
|
+
|
43
|
+
class BndFlux_c(BoundaryCondition):
|
44
|
+
pass
|
45
|
+
|
46
|
+
|
47
|
+
def classify_boundary(boundary_dict):
|
48
|
+
|
49
|
+
boundary_flux_cs = {} # Fluxes for the advection term
|
50
|
+
boundary_flux_vs = {} # Fluxes for the diffusion term
|
51
|
+
boundary_values = {} # Boundary values for Dirichlet
|
52
|
+
|
53
|
+
for k, f in boundary_dict.items():
|
54
|
+
|
55
|
+
if isinstance(k, (Expr, str)):
|
56
|
+
ids = [k]
|
57
|
+
elif callable(k):
|
58
|
+
ids = [k]
|
59
|
+
else:
|
60
|
+
try:
|
61
|
+
ids = []
|
62
|
+
for kk in k:
|
63
|
+
ids += [kk]
|
64
|
+
except TypeError:
|
65
|
+
ids = [k]
|
66
|
+
|
67
|
+
if isinstance(f, (tuple, list)):
|
68
|
+
assert len(f) == 2, "too many boundary fluxes provided"
|
69
|
+
if isinstance(f[0], BndFlux_v) and isinstance(f[1], BndFlux_c):
|
70
|
+
boundary_flux_vs.update([(kk, f[0]) for kk in ids])
|
71
|
+
boundary_flux_cs.update([(kk, f[1]) for kk in ids])
|
72
|
+
|
73
|
+
elif isinstance(f[0], BndFlux_c) and isinstance(f[1], BndFlux_v):
|
74
|
+
boundary_flux_vs.update([(kk, f[1]) for kk in ids])
|
75
|
+
boundary_flux_cs.update([(kk, f[0]) for kk in ids])
|
76
|
+
|
77
|
+
else:
|
78
|
+
raise ValueError("Need AFlux and DFlux")
|
79
|
+
|
80
|
+
elif isinstance(f, BndFlux_v):
|
81
|
+
boundary_flux_vs.update([(kk, f) for kk in ids])
|
82
|
+
|
83
|
+
elif isinstance(f, BndFlux_c):
|
84
|
+
boundary_flux_cs.update([(kk, f) for kk in ids])
|
85
|
+
|
86
|
+
elif isinstance(f, BndValue):
|
87
|
+
boundary_values.update([(kk, f) for kk in ids])
|
88
|
+
|
89
|
+
else:
|
90
|
+
print(type(k), type(f))
|
91
|
+
raise NotImplementedError(f"unknown boundary type {k} : {f}")
|
92
|
+
|
93
|
+
return boundary_flux_cs, boundary_flux_vs, boundary_values
|
94
|
+
|
95
|
+
|
96
|
+
def boundary_validation(Model, override_boundary_dict=None):
|
97
|
+
if override_boundary_dict is not None:
|
98
|
+
boundary_dict = override_boundary_dict
|
99
|
+
else:
|
100
|
+
boundary_dict = Model.boundary
|
101
|
+
|
102
|
+
hasFlux_c = hasattr(Model, "F_c")
|
103
|
+
hasFlux_v = hasattr(Model, "F_v")
|
104
|
+
|
105
|
+
boundary_flux_cs, boundary_flux_vs, boundary_values = classify_boundary(
|
106
|
+
boundary_dict
|
107
|
+
)
|
108
|
+
|
109
|
+
if hasFlux_c and hasFlux_v:
|
110
|
+
assert len(boundary_flux_cs) == len(
|
111
|
+
boundary_flux_vs
|
112
|
+
), "two bulk fluxes given, but one boundary fluxes provided"
|
113
|
+
|
114
|
+
if not hasFlux_c:
|
115
|
+
assert len(boundary_flux_cs) == 0, "No bulk Advection, but boundary flux given"
|
116
|
+
|
117
|
+
if not hasFlux_v:
|
118
|
+
assert len(boundary_flux_vs) == 0, "No bulk diffusion, but boundary flux given"
|
119
|
+
|
120
|
+
assert boundary_values.keys().isdisjoint(boundary_flux_cs)
|
121
|
+
assert boundary_values.keys().isdisjoint(boundary_flux_vs)
|
122
|
+
|
123
|
+
return boundary_flux_cs, boundary_flux_vs, boundary_values
|
124
|
+
|
125
|
+
|
126
|
+
class BoundaryTerms:
|
127
|
+
def __init__(self, Model, domainDescription):
|
128
|
+
self.Model = Model
|
129
|
+
|
130
|
+
if isinstance(domainDescription, Domain):
|
131
|
+
self.domain = domainDescription
|
132
|
+
else:
|
133
|
+
self.domain = Domain(domainDescription)
|
134
|
+
|
135
|
+
condition = lambda k: isinstance(k, str) or isinstance(k, SDF)
|
136
|
+
self.diffuse = {k: v for k, v in Model.boundary.items() if condition(k)}
|
137
|
+
self.physical = {k: v for k, v in Model.boundary.items() if not condition(k)}
|
138
|
+
|
139
|
+
self.boundary_flux_cs, self.boundary_flux_vs, self.boundary_values = (
|
140
|
+
boundary_validation(self.Model, override_boundary_dict=self.diffuse)
|
141
|
+
)
|
142
|
+
|
143
|
+
self.bV_weight = []
|
144
|
+
self.bF_weight = []
|
145
|
+
|
146
|
+
for model_key in self.boundary_values.keys():
|
147
|
+
phi_i_proj = self.domain.bndProjSDFs(model_key)
|
148
|
+
self.bV_weight.append(phi_i_proj)
|
149
|
+
|
150
|
+
for model_key in {*self.boundary_flux_cs.keys(), *self.boundary_flux_vs.keys()}:
|
151
|
+
phi_i_proj = self.domain.bndProjSDFs(model_key)
|
152
|
+
self.bF_weight.append(phi_i_proj)
|
153
|
+
|
154
|
+
if not self.boundary_values:
|
155
|
+
self.BndValueExt = None
|
156
|
+
|
157
|
+
if not self.boundary_flux_vs:
|
158
|
+
self.BndFlux_vExt = None
|
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
|
165
|
+
def _total_weight(self, x):
|
166
|
+
weight = 1e-10 # tol
|
167
|
+
for w_func in self.bV_weight + self.bF_weight:
|
168
|
+
weight += w_func(x)
|
169
|
+
return weight
|
170
|
+
|
171
|
+
def _boundary_extend(self, g, x):
|
172
|
+
g_tmp = expand_derivatives(g)
|
173
|
+
return replace(g_tmp, {x: self.domain.boundary_projection(x)})
|
174
|
+
|
175
|
+
# perhaps switch methods around so that BndValueExt is setup and then
|
176
|
+
# jumpD does sum(w)*U - BndValueExt. But then the exception needs to be caught...
|
177
|
+
def jumpV(self, t, x, U, U1=None):
|
178
|
+
jdv = zero(U.ufl_shape)
|
179
|
+
|
180
|
+
if U1 is None:
|
181
|
+
U1 = U
|
182
|
+
|
183
|
+
for g_func, w_func in zip(self.boundary_values.values(), self.bV_weight):
|
184
|
+
g_tmp = g_func(t, x, U) # g_func is a callable from self.boundary_values
|
185
|
+
g_ext = self._boundary_extend(g_tmp, x)
|
186
|
+
jdv += w_func(x) * (U1 - g_ext)
|
187
|
+
|
188
|
+
return jdv / self._total_weight(x)
|
189
|
+
|
190
|
+
def BndValueExt(self, t, x, U):
|
191
|
+
# called if self.BndValueExt was not set to None in __init__
|
192
|
+
z = zero(U.ufl_shape)
|
193
|
+
return -self.jumpV(t, x, U, z)
|
194
|
+
|
195
|
+
def jumpFv(self, t, x, U, DU, Fv):
|
196
|
+
# (sigma.n-gN)*ds(N) = - wN ( sigma.Dphi + gN|Dphi| )
|
197
|
+
# = wN ( (-sigma.Dphi) - gN(t,x,-Dphi/|Dphi|)|Dphi| )
|
198
|
+
# = wN ( sigma.sn - gN(t,x,sn) ) with sn = -Dphi
|
199
|
+
jdf = zero(U.ufl_shape)
|
200
|
+
|
201
|
+
fv_scaled = Fv * self.domain.scaled_normal(x)
|
202
|
+
for g_func, w_func in zip(self.boundary_flux_vs.values(), self.bF_weight):
|
203
|
+
g_tmp = g_func(t, x, U, DU, self.domain.normal(x))
|
204
|
+
g_ext = self._boundary_extend(g_tmp, x)
|
205
|
+
jdf += w_func(x) * (fv_scaled - g_ext * self.domain.surface_delta(x))
|
206
|
+
|
207
|
+
return jdf / self._total_weight(x)
|
208
|
+
# return jdf * (self._dbc_total_weight(x) / self._total_weight(x))
|
209
|
+
|
210
|
+
def BndFlux_vExt(self, t, x, U, DU):
|
211
|
+
# called if self.BndFlux_vExt was not set to None in __init__
|
212
|
+
z = zero(DU.ufl_shape)
|
213
|
+
return -self.jumpFv(t, x, U, DU, z)
|
214
|
+
|
215
|
+
def BndFlux_cExt(self, t, x, U):
|
216
|
+
jda = zero(U.ufl_shape)
|
217
|
+
|
218
|
+
for g_func, w_func in zip(self.boundary_flux_cs.values(), self.bF_weight):
|
219
|
+
g_tmp = g_func(t, x, U, self.domain.normal(x))
|
220
|
+
g_ext = self._boundary_extend(g_tmp, x)
|
221
|
+
jda += w_func(x) * -g_ext * self.domain.surface_delta(x)
|
222
|
+
|
223
|
+
return -jda / self._total_weight(x)
|
ddfem/dune.py
ADDED
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
|