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.
Files changed (51) hide show
  1. {ddfem-1.0.0 → ddfem-1.0.1}/PKG-INFO +2 -6
  2. {ddfem-1.0.0 → ddfem-1.0.1}/README.md +0 -5
  3. ddfem-1.0.1/ddfem/__init__.py +4 -0
  4. {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/boundary.py +12 -3
  5. ddfem-1.0.1/ddfem/examples/advection_diffusion.py +74 -0
  6. ddfem-1.0.1/ddfem/examples/beam.py +147 -0
  7. ddfem-1.0.1/ddfem/examples/cahn_hilliard.py +67 -0
  8. ddfem-1.0.1/ddfem/examples/chemical_reaction.py +88 -0
  9. ddfem-1.0.1/ddfem/examples/constant.py +46 -0
  10. ddfem-1.0.1/ddfem/examples/five_circle_flat.py +197 -0
  11. ddfem-1.0.1/ddfem/examples/forchheimer.py +48 -0
  12. ddfem-1.0.1/ddfem/examples/hyperelasticity.py +88 -0
  13. ddfem-1.0.1/ddfem/examples/linear_elasticity.py +45 -0
  14. ddfem-1.0.1/ddfem/examples/plaplace.py +29 -0
  15. ddfem-1.0.1/ddfem/examples/single_circle.py +135 -0
  16. ddfem-1.0.1/ddfem/examples/triple_circle.py +217 -0
  17. ddfem-1.0.1/ddfem/examples/triple_circle_beam.py +208 -0
  18. {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/geometry/__init__.py +3 -3
  19. {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/geometry/arc.py +10 -8
  20. ddfem-1.0.1/ddfem/geometry/ball.py +24 -0
  21. {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/geometry/box.py +7 -8
  22. {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/geometry/domain.py +16 -4
  23. {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/geometry/domain_dune.py +16 -0
  24. ddfem-1.0.1/ddfem/geometry/helpers.py +42 -0
  25. {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/geometry/pie.py +7 -7
  26. ddfem-1.0.1/ddfem/geometry/plane.py +20 -0
  27. ddfem-1.0.1/ddfem/geometry/primitive_base.py +338 -0
  28. {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/geometry/vesica.py +7 -6
  29. {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/model2ufl.py +10 -4
  30. ddfem-1.0.1/ddfem/transformers/DDM1.py +36 -0
  31. {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/transformers/Fitted.py +22 -12
  32. {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/transformers/Mix0.py +10 -64
  33. {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/transformers/NNS.py +11 -72
  34. {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/transformers/NS.py +14 -79
  35. {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/transformers/__init__.py +1 -0
  36. {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/transformers/transformer_base.py +102 -15
  37. {ddfem-1.0.0 → ddfem-1.0.1}/ddfem.egg-info/PKG-INFO +2 -6
  38. {ddfem-1.0.0 → ddfem-1.0.1}/ddfem.egg-info/SOURCES.txt +16 -2
  39. {ddfem-1.0.0 → ddfem-1.0.1}/ddfem.egg-info/requires.txt +1 -0
  40. {ddfem-1.0.0 → ddfem-1.0.1}/pyproject.toml +3 -3
  41. ddfem-1.0.0/ddfem/base_model.py +0 -200
  42. ddfem-1.0.0/ddfem/geometry/circle.py +0 -39
  43. ddfem-1.0.0/ddfem/geometry/helpers.py +0 -35
  44. ddfem-1.0.0/ddfem/geometry/primitive_base.py +0 -279
  45. ddfem-1.0.0/ddfem/transformers/DDM1.py +0 -94
  46. {ddfem-1.0.0 → ddfem-1.0.1}/LICENSE +0 -0
  47. {ddfem-1.0.0 → ddfem-1.0.1}/ddfem/dune.py +0 -0
  48. {ddfem-1.0.0/ddfem → ddfem-1.0.1/ddfem/examples}/__init__.py +0 -0
  49. {ddfem-1.0.0 → ddfem-1.0.1}/ddfem.egg-info/dependency_links.txt +0 -0
  50. {ddfem-1.0.0 → ddfem-1.0.1}/ddfem.egg-info/top_level.txt +0 -0
  51. {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.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
- ```
@@ -2,8 +2,3 @@
2
2
 
3
3
  This is a package for solving complex PDEs based on the diffuse domain idea.
4
4
 
5
- Build package by running
6
-
7
- ```bash
8
- python3 -m build
9
- ```
@@ -0,0 +1,4 @@
1
+ from . import geometry
2
+ from . import transformers
3
+ from . import boundary
4
+ from .model2ufl import model2ufl
@@ -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__
@@ -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