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.
Files changed (43) hide show
  1. ddfem/__init__.py +4 -0
  2. ddfem/boundary.py +223 -0
  3. ddfem/dune.py +3 -0
  4. ddfem/examples/__init__.py +0 -0
  5. ddfem/examples/advection_diffusion.py +74 -0
  6. ddfem/examples/beam.py +147 -0
  7. ddfem/examples/cahn_hilliard.py +67 -0
  8. ddfem/examples/chemical_reaction.py +88 -0
  9. ddfem/examples/constant.py +46 -0
  10. ddfem/examples/five_circle_flat.py +197 -0
  11. ddfem/examples/forchheimer.py +48 -0
  12. ddfem/examples/hyperelasticity.py +88 -0
  13. ddfem/examples/linear_elasticity.py +45 -0
  14. ddfem/examples/plaplace.py +29 -0
  15. ddfem/examples/single_circle.py +135 -0
  16. ddfem/examples/triple_circle.py +217 -0
  17. ddfem/examples/triple_circle_beam.py +208 -0
  18. ddfem/geometry/__init__.py +18 -0
  19. ddfem/geometry/arc.py +48 -0
  20. ddfem/geometry/ball.py +24 -0
  21. ddfem/geometry/box.py +33 -0
  22. ddfem/geometry/domain.py +49 -0
  23. ddfem/geometry/domain_dune.py +82 -0
  24. ddfem/geometry/helpers.py +42 -0
  25. ddfem/geometry/pie.py +31 -0
  26. ddfem/geometry/plane.py +20 -0
  27. ddfem/geometry/primitive_base.py +338 -0
  28. ddfem/geometry/vesica.py +49 -0
  29. ddfem/model2ufl.py +151 -0
  30. ddfem/transformers/DDM1.py +36 -0
  31. ddfem/transformers/Fitted.py +77 -0
  32. ddfem/transformers/Mix0.py +107 -0
  33. ddfem/transformers/NNS.py +82 -0
  34. ddfem/transformers/NS.py +86 -0
  35. ddfem/transformers/__init__.py +6 -0
  36. ddfem/transformers/transformer_base.py +213 -0
  37. ddfem-0.9.0.dist-info/METADATA +26 -0
  38. ddfem-0.9.0.dist-info/RECORD +41 -0
  39. {ddfem-0.0.0.dist-info → ddfem-0.9.0.dist-info}/WHEEL +1 -1
  40. ddfem-0.9.0.dist-info/licenses/LICENSE +19 -0
  41. ddfem-0.0.0.dist-info/METADATA +0 -5
  42. ddfem-0.0.0.dist-info/RECORD +0 -5
  43. {ddfem-0.0.0.dist-info → ddfem-0.9.0.dist-info}/top_level.txt +0 -0
ddfem/__init__.py CHANGED
@@ -0,0 +1,4 @@
1
+ from . import geometry
2
+ from . import transformers
3
+ from . import boundary
4
+ from .model2ufl import model2ufl
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
@@ -0,0 +1,3 @@
1
+ import geometry
2
+ from geometry import dunedomain
3
+ geometry.domain =
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