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
@@ -0,0 +1,42 @@
1
+ from ufl import conditional, dot, max_value, min_value, sqrt, ln, exp
2
+
3
+
4
+ # https://en.wikipedia.org/wiki/Smooth_maximum, https://en.wikipedia.org/wiki/LogSumExp
5
+ def smax_value(a, b, s):
6
+ return 1 / s * ln(exp(s * a) + exp(s * b))
7
+
8
+
9
+ def smin_value(a, b, s):
10
+ return smax_value(a, b, -s)
11
+
12
+
13
+ def ufl_length(p):
14
+ return sqrt(dot(p, p) + 1e-10)
15
+ # return max_value(sqrt(dot(p, p)), 1e-10)
16
+ # return sqrt(dot(p, p))
17
+
18
+
19
+ def ufl_sign(p):
20
+ if isinstance(p, (float, int)):
21
+ return 1 if p > 0 else -1
22
+
23
+ return conditional(p > 0, 1, -1)
24
+
25
+
26
+ def ufl_clamp(p, minimum, maximum):
27
+ if isinstance(p, (float, int)):
28
+ return min(max(p, minimum), maximum)
29
+
30
+ # def ufl_max(p1, p2):
31
+ # return conditional(p2 < p1, p1, p2)
32
+
33
+ # def ufl_min(p1, p2):
34
+ # return conditional(p1 < p2, p1, p2)
35
+
36
+ # return ufl_min(ufl_max(p, minimum), maximum)
37
+ # # using min_value/max_value, seems to break shape Pie?
38
+ return min_value(max_value(p, minimum), maximum)
39
+
40
+
41
+ def ufl_cross(p1, p2):
42
+ return p1[0] * p2[1] - p1[1] * p2[0]
ddfem/geometry/pie.py ADDED
@@ -0,0 +1,31 @@
1
+ from ufl import as_vector, cos, dot, max_value, min_value, sin
2
+
3
+ from .helpers import ufl_clamp, ufl_cross, ufl_length, ufl_sign
4
+ from .primitive_base import ORIGIN, SDF
5
+
6
+
7
+ class Pie(SDF):
8
+ def __init__(self, radius, angle, *args, **kwargs):
9
+ self.radius = radius
10
+ self.angle = angle # angle of cicle (not opening)
11
+
12
+ super().__init__(*args, **kwargs)
13
+
14
+ def __repr__(self):
15
+ return f"Pie({self.radius}, {self.angle}, {self._repr_core()})"
16
+
17
+ def sdf(self, x):
18
+ # Note: Ignores z
19
+ x0_abs = abs(x[0])
20
+ coords = as_vector([x0_abs, x[1]])
21
+
22
+ circle_dist = ufl_length(coords) - self.radius
23
+
24
+ trig = as_vector([sin(self.angle / 2), cos(self.angle / 2)])
25
+
26
+ # projection of coords on to trig, clamped to within circle
27
+ proj = ufl_clamp(dot(coords, trig), 0, self.radius) * trig
28
+ rejc = coords - proj
29
+ edge_dist = ufl_length(rejc) * ufl_sign(ufl_cross(coords, trig))
30
+
31
+ return max_value(circle_dist, edge_dist)
@@ -0,0 +1,20 @@
1
+ from ufl import as_vector, dot
2
+
3
+ from .primitive_base import SDF
4
+
5
+
6
+ class Plane(SDF):
7
+ def __init__(self, normal, offset, *args, **kwargs):
8
+ if isinstance(normal, (list, tuple)):
9
+ normal = as_vector(normal)
10
+ self.normal = normal
11
+
12
+ self.offset = offset
13
+
14
+ super().__init__(*args, **kwargs)
15
+
16
+ def __repr__(self):
17
+ return f"Plane({self.normal}, {self.offset}, {self._repr_core()})"
18
+
19
+ def sdf(self, x):
20
+ return dot(x, self.normal) + self.offset
@@ -0,0 +1,338 @@
1
+ from ufl import (
2
+ as_matrix,
3
+ as_vector,
4
+ conditional,
5
+ cos,
6
+ grad,
7
+ max_value,
8
+ min_value,
9
+ pi,
10
+ sin,
11
+ tanh,
12
+ )
13
+
14
+ from .helpers import smax_value, smin_value, ufl_length
15
+
16
+ ORIGIN = as_vector([0, 0])
17
+
18
+
19
+ class SDF:
20
+ def __init__(self, epsilon=None, name=None, children=None):
21
+ self.epsilon = epsilon
22
+ self.child_sdf = children if children else []
23
+
24
+ self.name = name
25
+ if self.name is None:
26
+ self.name = repr(self)
27
+
28
+ def _repr_core(self):
29
+ return f"epsilon={self.epsilon}, name={self.name}, children={self.child_sdf}"
30
+
31
+ def __repr__(self):
32
+ return f"{self.__class__.__name__}({self._repr_core()})"
33
+
34
+ def sdf(self, x):
35
+ raise NotImplementedError
36
+
37
+ def __call__(self, x):
38
+ return self.sdf(x)
39
+
40
+ def search(self, child_name):
41
+ if self.name == child_name:
42
+ return self
43
+
44
+ queue = self.child_sdf.copy()
45
+
46
+ while queue:
47
+ current_child = queue.pop(0)
48
+
49
+ if current_child.name == child_name:
50
+ return current_child
51
+
52
+ for child in current_child.child_sdf:
53
+ queue.append(child)
54
+
55
+ return None
56
+
57
+ def propagate_epsilon(self, epsilon):
58
+ self.epsilon = epsilon
59
+ for child in self.child_sdf:
60
+ child.propagate_epsilon(epsilon)
61
+
62
+ def phi(self, x, epsilon=None):
63
+ if not epsilon:
64
+ epsilon = self.epsilon
65
+ assert self.epsilon, "Must define epsilon"
66
+ return 0.5 * (1 - tanh((3 * self.sdf(x) / epsilon)))
67
+
68
+ def chi(self, x):
69
+ return conditional(self.sdf(x) <= 0, 1, 0)
70
+
71
+ def projection(self, x):
72
+ return -grad(self.sdf(x)) * self.sdf(x)
73
+
74
+ def boundary_projection(self, x):
75
+ return x + self.projection(x)
76
+
77
+ def external_projection(self, x):
78
+ # return self.chi(x) * x + self.boundary_projection(x) * (1 - self.chi(x))
79
+ return x + self.projection(x) * (1 - self.chi(x))
80
+
81
+ def union(self, other):
82
+ return Union(self, other)
83
+
84
+ def subtraction(self, other):
85
+ return Subtraction(self, other)
86
+
87
+ def intersection(self, other):
88
+ return Intersection(self, other)
89
+
90
+ def xor(self, other):
91
+ return Xor(self, other)
92
+
93
+ def scale(self, sc):
94
+ return Scale(self, sc)
95
+
96
+ def invert(self):
97
+ return Invert(self)
98
+
99
+ def rotate(self, angle, radians=True):
100
+ return Rotate(self, angle, radians)
101
+
102
+ def translate(self, vector):
103
+ return Translate(self, vector)
104
+
105
+ def round(self, sc):
106
+ return Round(self, sc)
107
+
108
+ def extrude(self, length, split_ends=False):
109
+ if split_ends:
110
+ from .plane import Plane
111
+
112
+ ext = Extrusion(self, length * 2, name=f"{self.name}_ext")
113
+ ext = Translate(ext, [0, 0, -length / 2], name=f"{self.name}_sides")
114
+ bot = Plane([0, 0, -1], 0, name=f"{self.name}_bot")
115
+ top = Plane([0, 0, 1], -length, name=f"{self.name}_top")
116
+ z_interval = bot & top
117
+ ext = ext & z_interval
118
+ else:
119
+ return Extrusion(self, length, name=f"{self.name}_ext")
120
+ return ext
121
+
122
+ def revolve(self, offset=0, axis="x"):
123
+ return Revolution(self, offset, axis)
124
+
125
+ def __or__(self, other):
126
+ return self.union(other)
127
+
128
+ def __and__(self, other):
129
+ return self.intersection(other)
130
+
131
+ def __sub__(self, other):
132
+ return self.subtraction(other)
133
+
134
+ def __xor__(self, other):
135
+ return self.xor(other)
136
+
137
+ def __mul__(self, other):
138
+ return self.scale(other)
139
+
140
+ def __rmul__(self, other):
141
+ if isinstance(other, (int, float)):
142
+ return self.scale(other)
143
+ raise TypeError(f"Cannot multiply a SDF with {type(other)}")
144
+
145
+ # possibly use a 'Constant' but then multiple changes will influence previous usage?
146
+ smoothing = None
147
+
148
+ def max_value(a, b):
149
+ if SDF.smoothing is None:
150
+ return max_value(a, b)
151
+ else:
152
+ return smax_value(a, b, SDF.smoothing)
153
+
154
+ def min_value(a, b):
155
+ if SDF.smoothing is None:
156
+ return min_value(a, b)
157
+ else:
158
+ return smin_value(a, b, SDF.smoothing)
159
+
160
+
161
+ class BaseOperator(SDF):
162
+ def __init__(self, children, epsilon=None, *args, **kwargs):
163
+
164
+ if not epsilon and all(child.epsilon for child in children):
165
+ epsilon = children[0].epsilon
166
+ for child in children:
167
+ epsilon = max_value(epsilon, child.epsilon)
168
+
169
+ super().__init__(children=children, epsilon=epsilon, *args, **kwargs)
170
+
171
+ def __getitem__(self, key):
172
+ return self.child_sdf[key]
173
+
174
+
175
+ class Union(BaseOperator):
176
+ """Union of two SDFs (OR) - not perfect(negative)"""
177
+
178
+ def __init__(self, sdf1, sdf2, *args, **kwargs):
179
+ super().__init__(children=[sdf1, sdf2], *args, **kwargs)
180
+
181
+ def sdf(self, x):
182
+ return SDF.min_value(self.child_sdf[0].sdf(x), self.child_sdf[1].sdf(x))
183
+
184
+
185
+ class Subtraction(BaseOperator):
186
+ """Subtraction of two SDFs (difference) - not perfect"""
187
+
188
+ def __init__(self, sdf1, sdf2, *args, **kwargs):
189
+ super().__init__(children=[sdf1, sdf2], *args, **kwargs)
190
+
191
+ def sdf(self, x):
192
+ return SDF.max_value(self.child_sdf[0].sdf(x), -self.child_sdf[1].sdf(x))
193
+
194
+
195
+ class Intersection(BaseOperator):
196
+ """Intersection of two SDFs (AND) - not perfect"""
197
+
198
+ def __init__(self, sdf1, sdf2, *args, **kwargs):
199
+ super().__init__(children=[sdf1, sdf2], *args, **kwargs)
200
+
201
+ def sdf(self, x):
202
+ return SDF.max_value(self.child_sdf[0].sdf(x), self.child_sdf[1].sdf(x))
203
+
204
+
205
+ class Xor(BaseOperator):
206
+ """Xor of two SDFs (AND) - perfect"""
207
+
208
+ def __init__(self, sdf1, sdf2, *args, **kwargs):
209
+ super().__init__(children=[sdf1, sdf2], *args, **kwargs)
210
+
211
+ def sdf(self, x):
212
+ a_x = self.child_sdf[0].sdf(x)
213
+ b_x = self.child_sdf[1].sdf(x)
214
+ return SDF.max_value(SDF.min_value(a_x, b_x), -SDF.max_value(a_x, b_x))
215
+
216
+
217
+ class Invert(BaseOperator):
218
+ """Inverts SDF"""
219
+
220
+ def __init__(self, sdf1, *args, **kwargs):
221
+ super().__init__(children=[sdf1], *args, **kwargs)
222
+
223
+ def sdf(self, x):
224
+ return -self.child_sdf[0].sdf(x)
225
+
226
+
227
+ class Scale(BaseOperator):
228
+ """Scales SDF"""
229
+
230
+ def __init__(self, sdf1, scale, *args, **kwargs):
231
+ if not isinstance(scale, (int, float)):
232
+ raise TypeError(f"Cannot scale a SDF with {type(scale)}")
233
+ elif not scale > 0:
234
+ raise ValueError("Cannot scale a SDF with nonpositive")
235
+ else:
236
+ self.scale = scale
237
+
238
+ super().__init__(children=[sdf1], *args, **kwargs)
239
+
240
+ def sdf(self, x):
241
+ return self.child_sdf[0].sdf(x / self.scale) * self.scale
242
+
243
+ def __repr__(self):
244
+ return f"Scale({self.scale}, {self._repr_core()})"
245
+
246
+
247
+ class Rotate(BaseOperator):
248
+ """Rotates SDF, counterclockwise of orgin"""
249
+
250
+ def __init__(self, sdf1, angle, radians=True, *args, **kwargs):
251
+ if not radians:
252
+ angle *= pi / 180
253
+ self.angle = angle
254
+
255
+ super().__init__(children=[sdf1], *args, **kwargs)
256
+
257
+ def sdf(self, x):
258
+ c = cos(self.angle)
259
+ s = sin(self.angle)
260
+
261
+ r = as_matrix(((c, -s), (s, c)))
262
+ return self.child_sdf[0].sdf(r.T * x)
263
+
264
+ def __repr__(self):
265
+ return f"Rotate({self.angle}, {self._repr_core()})"
266
+
267
+
268
+ class Translate(BaseOperator):
269
+ """Translates SDF"""
270
+
271
+ def __init__(self, sdf1, vec, *args, **kwargs):
272
+ if isinstance(vec, (list, tuple)):
273
+ vec = as_vector(vec)
274
+ self.vec = vec
275
+
276
+ super().__init__(children=[sdf1], *args, **kwargs)
277
+
278
+ def sdf(self, x):
279
+ return self.child_sdf[0].sdf(x - self.vec)
280
+
281
+ def __repr__(self):
282
+ return f"Translate({self.vec}, {self._repr_core()})"
283
+
284
+
285
+ class Round(BaseOperator):
286
+ """Rounds SDF"""
287
+
288
+ def __init__(self, sdf1, scale, *args, **kwargs):
289
+ assert scale > 0
290
+ self._scale = scale # careful not to overwrite SDF.scale here
291
+
292
+ super().__init__(children=[sdf1], *args, **kwargs)
293
+
294
+ def sdf(self, x):
295
+ return self.child_sdf[0].sdf(x) - self._scale
296
+
297
+ def __repr__(self):
298
+ return f"Round({self._scale}, {self._repr_core()})"
299
+
300
+
301
+ class Extrusion(BaseOperator):
302
+ """Extrude SDF"""
303
+
304
+ def __init__(self, sdf1, extrude_length, *args, **kwargs):
305
+ self.extrude_length = extrude_length
306
+
307
+ super().__init__(children=[sdf1], *args, **kwargs)
308
+
309
+ def sdf(self, x):
310
+ d = self.child_sdf[0].sdf(as_vector([x[0], x[1]]))
311
+ w = abs(x[2]) - self.extrude_length
312
+ return SDF.min_value(SDF.max_value(d, w), 0) + ufl_length(
313
+ as_vector([SDF.max_value(d, 0), SDF.max_value(w, 0)])
314
+ )
315
+
316
+ def __repr__(self):
317
+ return f"Extrusion({self.extrude_length}, {self._repr_core()})"
318
+
319
+
320
+ class Revolution(BaseOperator):
321
+ """Revolve SDF"""
322
+
323
+ def __init__(self, sdf1, offset, axis, *args, **kwargs):
324
+ self.offset = offset
325
+ assert axis in ["x", "y"], "Can only revolve around the x or y axis"
326
+ self.axis = axis
327
+
328
+ super().__init__(children=[sdf1], *args, **kwargs)
329
+
330
+ def sdf(self, x):
331
+ if self.axis == "x":
332
+ q = as_vector([x[0], ufl_length(as_vector([x[1], x[2]])) - self.offset])
333
+ elif self.axis == "y":
334
+ q = as_vector([ufl_length(as_vector([x[0], x[2]])) - self.offset, x[1]])
335
+ return self.child_sdf[0].sdf(q)
336
+
337
+ def __repr__(self):
338
+ return f"Revolution({self.offset}, {self._repr_core()})"
@@ -0,0 +1,49 @@
1
+ from ufl import as_vector, conditional, sqrt
2
+
3
+ from .helpers import ufl_length, ufl_sign
4
+ from .primitive_base import ORIGIN, SDF
5
+
6
+
7
+ class Vesica(SDF):
8
+ def __init__(self, radius, distance, smooth_radius, *args, **kwargs):
9
+ """Generates sign distance function and domain coordinates for a Vesica.
10
+ i.e. Union of two circles.
11
+ Centred at (0,0)
12
+
13
+ Args:
14
+ radius (float,): Radius of each circle.
15
+ distance (float): Distance of circle center from y-axis.
16
+ smooth_radius (float): Smoothing of domain,so no sharp corner when circles connection.
17
+ """
18
+ assert distance != 0, "This is a circle, use Ball class"
19
+ assert distance < radius, "No shape exists, circles cancel each other out"
20
+ assert (
21
+ smooth_radius * distance < 0
22
+ ), "For a smooth edge, smooth_radius needs to be opposite sign of distance"
23
+
24
+ self.radius = radius
25
+ self.distance = distance
26
+ self.smooth_radius = smooth_radius
27
+
28
+ super().__init__(*args, **kwargs)
29
+
30
+ def __repr__(self):
31
+ return f"Vesica({self.radius}, {self.distance}, {self.smooth_radius}, {self._repr_core()})"
32
+
33
+ def sdf(self, x):
34
+ # Note: Ignores z
35
+ x0_abs = abs(x[0])
36
+ x1_abs = abs(x[1])
37
+
38
+ b = sqrt((self.radius + self.smooth_radius) ** 2 - self.distance**2)
39
+
40
+ circle_coords = as_vector([x0_abs + self.distance, x[1]])
41
+
42
+ return (
43
+ conditional(
44
+ (x1_abs - b) * self.distance > x0_abs * b,
45
+ ufl_length(as_vector([x0_abs, x1_abs - b])) * ufl_sign(self.distance),
46
+ ufl_length(circle_coords) - self.radius - self.smooth_radius,
47
+ )
48
+ + self.smooth_radius
49
+ )
ddfem/model2ufl.py ADDED
@@ -0,0 +1,151 @@
1
+ from ufl import (
2
+ FacetNormal,
3
+ SpatialCoordinate,
4
+ TestFunction,
5
+ TrialFunction,
6
+ as_vector,
7
+ ds,
8
+ dx,
9
+ grad,
10
+ inner,
11
+ )
12
+
13
+ from .boundary import boundary_validation
14
+
15
+
16
+ def boundaries_ufl(Model, space, t):
17
+ boundary_flux_cs, boundary_flux_vs, boundary_values = boundary_validation(Model)
18
+
19
+ u = TrialFunction(space)
20
+ n = FacetNormal(space.cell())
21
+ x = SpatialCoordinate(space.cell())
22
+
23
+ boundary_flux_cs = {
24
+ (k(x) if callable(k) else k): f(t, x, u, n) for k, f in boundary_flux_cs.items()
25
+ }
26
+ boundary_flux_vs = {
27
+ (k(x) if callable(k) else k): f(t, x, u, grad(u), n)
28
+ for k, f in boundary_flux_vs.items()
29
+ }
30
+ boundary_values = {
31
+ (k(x) if callable(k) else k): f(t, x, u) for k, f in boundary_values.items()
32
+ }
33
+ hasBoundaryValue = {k: True for k in boundary_values.keys()}
34
+
35
+ return (
36
+ boundary_flux_cs,
37
+ boundary_flux_vs,
38
+ boundary_values,
39
+ hasBoundaryValue,
40
+ )
41
+
42
+
43
+ class DirichletBC:
44
+ def __init__(self, space, value, domain=None):
45
+ self.space = space
46
+ self.value = value
47
+ self.domain = domain
48
+
49
+ def __str__(self):
50
+ return str(self.value) + str(self.domain)
51
+
52
+
53
+ def model_ufl(Model, space, initialTime=0, DirichletBC=DirichletBC):
54
+ u = TrialFunction(space)
55
+ v = TestFunction(space)
56
+ x = SpatialCoordinate(space.cell())
57
+
58
+ t = initialTime
59
+
60
+ f_c_model = None
61
+ if hasattr(Model, "F_c"):
62
+ f_c_model = inner(Model.F_c(t, x, u), grad(v)) # -div F_c v
63
+ if hasattr(Model, "S_e"):
64
+ # there is an issue with S_e returning 'zero' and zero*dx leading to UFL error
65
+ se = (
66
+ inner(as_vector(Model.S_e(t, x, u, grad(u))), v)
67
+ ) # (-div F_c + S_e) * v
68
+ if f_c_model is not None:
69
+ f_c_model += se
70
+ else:
71
+ f_c_model = se
72
+
73
+ f_v_model = None
74
+ if hasattr(Model, "F_v"):
75
+ f_v_model = inner(Model.F_v(t, x, u, grad(u)), grad(v)) # -div F_v v
76
+
77
+ if hasattr(Model, "S_i"):
78
+ si = inner(as_vector(Model.S_i(t, x, u, grad(u))), v) # (-div F_v + S_i) v
79
+ if f_v_model is not None:
80
+ f_v_model += si
81
+ else:
82
+ f_v_model = si
83
+
84
+ # need to extract boundary information from Model
85
+ (
86
+ boundary_flux_cs,
87
+ boundary_flux_vs,
88
+ boundary_values,
89
+ hasBoundaryValue,
90
+ ) = boundaries_ufl(Model, space, t)
91
+
92
+ dirichletBCs = [
93
+ DirichletBC(space, item[1], item[0]) for item in boundary_values.items()
94
+ ]
95
+ boundary_flux_vs = -sum(
96
+ [inner(item[1], v) * ds(item[0]) for item in boundary_flux_vs.items()]
97
+ ) # keep all forms on left hand side
98
+ boundary_flux_cs = -sum(
99
+ [inner(item[1], v) * ds(item[0]) for item in boundary_flux_cs.items()]
100
+ ) # keep all forms on left hand side
101
+
102
+ if f_c_model:
103
+ f_c_model = f_c_model * dx
104
+ if f_v_model:
105
+ f_v_model = f_v_model * dx
106
+ return (
107
+ f_c_model,
108
+ f_v_model,
109
+ {
110
+ "dirichletBCs": dirichletBCs,
111
+ "boundary_flux_cs": boundary_flux_cs,
112
+ "boundary_flux_vs": boundary_flux_vs,
113
+ "hasBoundaryValue": hasBoundaryValue,
114
+ },
115
+ )
116
+
117
+
118
+ def model2ufl(
119
+ Model, space, initialTime=0, DirichletBC=DirichletBC, *, returnFull=False
120
+ ):
121
+ class M(Model):
122
+ if hasattr(Model, "S_e"):
123
+
124
+ def S_e(t, x, U, DU):
125
+ return -Model.S_e(t, x, U, DU)
126
+
127
+ if hasattr(Model, "S_i"):
128
+
129
+ def S_i(t, x, U, DU):
130
+ return -Model.S_i(t, x, U, DU)
131
+
132
+ if hasattr(Model, "F_c"):
133
+
134
+ def F_c(t, x, U):
135
+ return -Model.F_c(t, x, U)
136
+
137
+ f_c_model, f_v_model, boundary_model = model_ufl(M, space, initialTime, DirichletBC)
138
+ boundary_model["boundary_flux_cs"] = -boundary_model["boundary_flux_cs"]
139
+ form = boundary_model["boundary_flux_cs"] + boundary_model["boundary_flux_vs"]
140
+ if f_c_model is not None:
141
+ form += f_c_model
142
+ if f_v_model is not None:
143
+ form += f_v_model
144
+
145
+ if not returnFull:
146
+ return [form == 0, *boundary_model["dirichletBCs"]]
147
+ else:
148
+ boundary_model["f_c_model"] = f_c_model
149
+ boundary_model["f_v_model"] = f_v_model
150
+ boundary_model["form"] = form
151
+ return boundary_model
@@ -0,0 +1,36 @@
1
+ from ufl import grad, zero
2
+
3
+ from .transformer_base import transformer
4
+
5
+
6
+ @transformer
7
+ def DDM1(Model):
8
+ class DDModel(Model):
9
+ def S_e_source(t, x, U, DU):
10
+ return DDModel.phi(x) * Model.S_e(t, x, U, DDModel.sigma(t, x, U, DU))
11
+
12
+ def S_e_convection(t, x, U, DU):
13
+ return -DDModel.BT.BndFlux_cExt(t, x, U)
14
+
15
+ def S_outside(t, x, U, DU):
16
+ return -(
17
+ DDModel.BT.jumpV(t, x, U) * (1 - DDModel.phi(x)) / (DDModel.epsilon**3)
18
+ )
19
+
20
+ def S_i_source(t, x, U, DU):
21
+ return DDModel.phi(x) * Model.S_i(t, x, U, DDModel.sigma(t, x, U, DU))
22
+
23
+ def S_i_diffusion(t, x, U, DU):
24
+ if DDModel.BT.BndFlux_vExt is not None:
25
+ diffusion = DDModel.BT.BndFlux_vExt(t, x, U, DU)
26
+ else:
27
+ diffusion = zero(U.ufl_shape)
28
+ return diffusion
29
+
30
+ def F_c(t, x, U):
31
+ return DDModel.phi(x) * Model.F_c(t, x, U)
32
+
33
+ def F_v(t, x, U, DU):
34
+ return DDModel.phi(x) * Model.F_v(t, x, U, DDModel.sigma(t, x, U, DU))
35
+
36
+ return DDModel