ddfem 1.0.0__tar.gz → 1.0.1__tar.gz
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-1.0.0 → ddfem-1.0.1}/PKG-INFO +2 -6
- {ddfem-1.0.0 → ddfem-1.0.1}/README.md +0 -5
- ddfem-1.0.1/ddfem/__init__.py +4 -0
- {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/boundary.py +12 -3
- ddfem-1.0.1/ddfem/examples/advection_diffusion.py +74 -0
- ddfem-1.0.1/ddfem/examples/beam.py +147 -0
- ddfem-1.0.1/ddfem/examples/cahn_hilliard.py +67 -0
- ddfem-1.0.1/ddfem/examples/chemical_reaction.py +88 -0
- ddfem-1.0.1/ddfem/examples/constant.py +46 -0
- ddfem-1.0.1/ddfem/examples/five_circle_flat.py +197 -0
- ddfem-1.0.1/ddfem/examples/forchheimer.py +48 -0
- ddfem-1.0.1/ddfem/examples/hyperelasticity.py +88 -0
- ddfem-1.0.1/ddfem/examples/linear_elasticity.py +45 -0
- ddfem-1.0.1/ddfem/examples/plaplace.py +29 -0
- ddfem-1.0.1/ddfem/examples/single_circle.py +135 -0
- ddfem-1.0.1/ddfem/examples/triple_circle.py +217 -0
- ddfem-1.0.1/ddfem/examples/triple_circle_beam.py +208 -0
- {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/geometry/__init__.py +3 -3
- {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/geometry/arc.py +10 -8
- ddfem-1.0.1/ddfem/geometry/ball.py +24 -0
- {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/geometry/box.py +7 -8
- {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/geometry/domain.py +16 -4
- {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/geometry/domain_dune.py +16 -0
- ddfem-1.0.1/ddfem/geometry/helpers.py +42 -0
- {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/geometry/pie.py +7 -7
- ddfem-1.0.1/ddfem/geometry/plane.py +20 -0
- ddfem-1.0.1/ddfem/geometry/primitive_base.py +338 -0
- {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/geometry/vesica.py +7 -6
- {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/model2ufl.py +10 -4
- ddfem-1.0.1/ddfem/transformers/DDM1.py +36 -0
- {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/transformers/Fitted.py +22 -12
- {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/transformers/Mix0.py +10 -64
- {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/transformers/NNS.py +11 -72
- {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/transformers/NS.py +14 -79
- {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/transformers/__init__.py +1 -0
- {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/transformers/transformer_base.py +102 -15
- {ddfem-1.0.0 → ddfem-1.0.1}/ddfem.egg-info/PKG-INFO +2 -6
- {ddfem-1.0.0 → ddfem-1.0.1}/ddfem.egg-info/SOURCES.txt +16 -2
- {ddfem-1.0.0 → ddfem-1.0.1}/ddfem.egg-info/requires.txt +1 -0
- {ddfem-1.0.0 → ddfem-1.0.1}/pyproject.toml +3 -3
- ddfem-1.0.0/ddfem/base_model.py +0 -200
- ddfem-1.0.0/ddfem/geometry/circle.py +0 -39
- ddfem-1.0.0/ddfem/geometry/helpers.py +0 -35
- ddfem-1.0.0/ddfem/geometry/primitive_base.py +0 -279
- ddfem-1.0.0/ddfem/transformers/DDM1.py +0 -94
- {ddfem-1.0.0 → ddfem-1.0.1}/LICENSE +0 -0
- {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/dune.py +0 -0
- {ddfem-1.0.0/ddfem → ddfem-1.0.1/ddfem/examples}/__init__.py +0 -0
- {ddfem-1.0.0 → ddfem-1.0.1}/ddfem.egg-info/dependency_links.txt +0 -0
- {ddfem-1.0.0 → ddfem-1.0.1}/ddfem.egg-info/top_level.txt +0 -0
- {ddfem-1.0.0 → ddfem-1.0.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ddfem
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.1
|
4
4
|
Summary: Diffuse domain finite element solver
|
5
5
|
Author-email: Luke Benfield <luke.benfield@warwick.ac.uk>, Andreas Dedner <a.s.dedner@warwick.ac.uk>
|
6
6
|
License-Expression: MIT
|
@@ -12,14 +12,10 @@ Requires-Python: >=3.9
|
|
12
12
|
Description-Content-Type: text/markdown
|
13
13
|
License-File: LICENSE
|
14
14
|
Requires-Dist: fenics-ufl>=2022
|
15
|
+
Requires-Dist: numpy
|
15
16
|
Dynamic: license-file
|
16
17
|
|
17
18
|
# Diffuse Domain Finite Element Methods
|
18
19
|
|
19
20
|
This is a package for solving complex PDEs based on the diffuse domain idea.
|
20
21
|
|
21
|
-
Build package by running
|
22
|
-
|
23
|
-
```bash
|
24
|
-
python3 -m build
|
25
|
-
```
|
@@ -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 ==
|
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.
|
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__
|
@@ -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
|
@@ -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
|