ddfem 0.0.0__py3-none-any.whl → 1.0.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/geometry/pie.py ADDED
@@ -0,0 +1,31 @@
1
+ from ufl import as_vector, cos, dot, sin
2
+ from ufl import max_value as Max
3
+ from ufl import min_value as Min
4
+
5
+ from .helpers import ufl_clamp, ufl_cross, ufl_length, ufl_sign
6
+ from .primitive_base import ORIGIN, SDF
7
+
8
+
9
+ class Pie(SDF):
10
+ def __init__(self, radius, angle, *args, **kwargs):
11
+ super().__init__(*args, **kwargs)
12
+ self.radius = radius
13
+ self.angle = angle # angle of cicle (not opening)
14
+
15
+ def __repr__(self):
16
+ return f"Pie({self.radius}, {self.angle})"
17
+
18
+ def sdf(self, x):
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(circle_dist, edge_dist)
@@ -0,0 +1,279 @@
1
+ from ufl import (
2
+ as_matrix,
3
+ as_vector,
4
+ conditional,
5
+ cos,
6
+ grad,
7
+ pi,
8
+ replace,
9
+ sin,
10
+ tanh,
11
+ )
12
+ from ufl import max_value as Max
13
+ from ufl import min_value as Min
14
+
15
+ from ufl.algorithms import expand_indices
16
+ from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering
17
+ from ufl.algorithms.apply_derivatives import apply_derivatives
18
+
19
+ ORIGIN = as_vector([0, 0])
20
+
21
+
22
+ class SDF:
23
+ def __init__(self, epsilon=None, name=None, children=None):
24
+ self.name = name
25
+ self.epsilon = epsilon
26
+ self.child_sdf = children if children else []
27
+
28
+ def sdf(self, x):
29
+ raise NotImplementedError
30
+
31
+ def __call__(self, x):
32
+ return self.sdf(x)
33
+
34
+ def search(self, child_name):
35
+ if self.name == child_name:
36
+ return self
37
+
38
+ queue = self.child_sdf.copy()
39
+
40
+ while queue:
41
+ current_child = queue.pop(0)
42
+
43
+ if current_child.name == child_name:
44
+ return current_child
45
+
46
+ for child in current_child.child_sdf:
47
+ queue.append(child)
48
+
49
+ return None
50
+
51
+ def propgate_epsilon(self, epsilon):
52
+ self.epsilon = epsilon
53
+ for child in self.child_sdf:
54
+ child.propgate_epsilon(epsilon)
55
+
56
+ def phi(self, x, epsilon=None):
57
+ if not epsilon:
58
+ epsilon = self.epsilon
59
+ assert self.epsilon, "Must define epsilon"
60
+ return 0.5 * (1 - tanh((3 * self.sdf(x) / epsilon)))
61
+
62
+ def chi(self, x):
63
+ return conditional(self.sdf(x) <= 0, 1, 0)
64
+
65
+ def projection(self, x):
66
+ return -grad(self.sdf(x)) * self.sdf(x)
67
+
68
+ def boundary_projection(self, x):
69
+ return x + self.projection(x)
70
+
71
+ def external_projection(self, x):
72
+ # return self.chi(x) * x + self.boundary_projection(x) * (1 - self.chi(x))
73
+ return x + self.projection(x) * (1 - self.chi(x))
74
+
75
+ def union(self, other):
76
+ return Union(self, other)
77
+
78
+ def subtraction(self, other):
79
+ return Subtraction(self, other)
80
+
81
+ def intersection(self, other):
82
+ return Intersection(self, other)
83
+
84
+ def xor(self, other):
85
+ return Xor(self, other)
86
+
87
+ def scale(self, sc):
88
+ return Scale(self, sc)
89
+
90
+ def invert(self):
91
+ return Invert(self)
92
+
93
+ def rotate(self, angle, radians=True):
94
+ return Rotate(self, angle, radians)
95
+
96
+ def translate(self, vector):
97
+ return Translate(self, vector)
98
+
99
+ def round(self, sc):
100
+ return Round(self, sc)
101
+
102
+ def __or__(self, other):
103
+ return self.union(other)
104
+
105
+ def __and__(self, other):
106
+ return self.intersection(other)
107
+
108
+ def __sub__(self, other):
109
+ return self.subtraction(other)
110
+
111
+ def __xor__(self, other):
112
+ return self.xor(other)
113
+
114
+ def __mul__(self, other):
115
+ if isinstance(other, (int, float)):
116
+ return self.scale(other)
117
+ raise TypeError(f"Cannot multiply a SDF with {type(other)}")
118
+
119
+ def __rmul__(self, other):
120
+ if isinstance(other, (int, float)):
121
+ return self.scale(other)
122
+ raise TypeError(f"Cannot multiply a SDF with {type(other)}")
123
+
124
+ class BaseOperator(SDF):
125
+ def __init__(self, epsilon, children, *args, **kwargs):
126
+
127
+ if not epsilon and all(child.epsilon for child in children):
128
+ if len(children) == 1:
129
+ epsilon = children[0].epsilon
130
+ else:
131
+ epsilon = Min(*[child.epsilon for child in children])
132
+
133
+ super().__init__(children=children, epsilon=epsilon, *args, **kwargs)
134
+
135
+ def __repr__(self):
136
+ return f"{self.__class__.__name__}({', '.join(map(repr, self.child_sdf))})"
137
+
138
+ def __getitem__(self, key):
139
+ return self.child_sdf[key]
140
+
141
+
142
+ class Union(BaseOperator):
143
+ """Union of two SDFs (OR) - not perfect(negative)"""
144
+
145
+ def __init__(self, sdf1, sdf2, epsilon=None, name=None, *args, **kwargs):
146
+ super().__init__(epsilon=epsilon, children=[sdf1, sdf2], *args, **kwargs)
147
+ if self.name is None:
148
+ self.name = f"({sdf1.name}|{sdf2.name})"
149
+
150
+ def sdf(self, x):
151
+ return Min(self.child_sdf[0].sdf(x), self.child_sdf[1].sdf(x))
152
+
153
+
154
+ class Subtraction(BaseOperator):
155
+ """Subtraction of two SDFs (difference) - not perfect"""
156
+
157
+ def __init__(self, sdf1, sdf2, epsilon=None, name=None, *args, **kwargs):
158
+ super().__init__(epsilon=epsilon, children=[sdf1, sdf2], *args, **kwargs)
159
+ if self.name is None:
160
+ self.name = f"({sdf1.name}-{sdf2.name})"
161
+
162
+ def sdf(self, x):
163
+ return Max(self.child_sdf[0].sdf(x), -self.child_sdf[1].sdf(x))
164
+
165
+
166
+ class Intersection(BaseOperator):
167
+ """Intersection of two SDFs (AND) - not perfect"""
168
+
169
+ def __init__(self, sdf1, sdf2, epsilon=None, name=None, *args, **kwargs):
170
+ super().__init__(epsilon=epsilon, children=[sdf1, sdf2], *args, **kwargs)
171
+ if self.name is None:
172
+ self.name = f"({sdf1.name}&{sdf2.name})"
173
+
174
+ def sdf(self, x):
175
+ return Max(self.child_sdf[0].sdf(x), self.child_sdf[1].sdf(x))
176
+
177
+
178
+ class Xor(BaseOperator):
179
+ """Xor of two SDFs (AND) - perfect"""
180
+
181
+ def __init__(self, sdf1, sdf2, epsilon=None, name=None, *args, **kwargs):
182
+ super().__init__(epsilon=epsilon, children=[sdf1, sdf2], *args, **kwargs)
183
+ if self.name is None:
184
+ self.name = f"({sdf1.name}^{sdf2.name})"
185
+
186
+ def sdf(self, x):
187
+ a_x = self.child_sdf[0].sdf(x)
188
+ b_x = self.child_sdf[1].sdf(x)
189
+ return Max(Min(a_x, b_x), -Max(a_x, b_x))
190
+
191
+
192
+ class Invert(BaseOperator):
193
+ """Inverts SDF"""
194
+
195
+ def __init__(self, sdf1, epsilon=None, name=None, *args, **kwargs):
196
+ super().__init__(epsilon=epsilon, children=[sdf1], *args, **kwargs)
197
+ if self.name is None:
198
+ self.name = f"(-{sdf1.name})"
199
+
200
+ def sdf(self, x):
201
+ return -self.child_sdf[0].sdf(x)
202
+
203
+
204
+ class Scale(BaseOperator):
205
+ """Scales SDF"""
206
+
207
+ def __init__(self, sdf1, scale, epsilon=None, name=None, *args, **kwargs):
208
+ super().__init__(epsilon=epsilon, children=[sdf1], *args, **kwargs)
209
+ self.scale = scale
210
+ if self.name is None:
211
+ self.name = f"({scale}*{sdf1.name})"
212
+
213
+ def sdf(self, x):
214
+ return self.child_sdf[0].sdf(x / self.scale) * self.scale
215
+
216
+ def __repr__(self):
217
+ return f"Scale({repr(self.child_sdf[0])}, {self.scale})"
218
+
219
+
220
+ class Rotate(BaseOperator):
221
+ """Rotates SDF, counterclockwise of orgin"""
222
+
223
+ def __init__(
224
+ self, sdf1, angle, radians=True, epsilon=None, name=None, *args, **kwargs
225
+ ):
226
+ super().__init__(epsilon=epsilon, children=[sdf1], *args, **kwargs)
227
+ if self.name is None:
228
+ self.name = f"({angle}@{sdf1.name})"
229
+
230
+ if not radians:
231
+ angle *= pi / 180
232
+ self.angle = angle
233
+
234
+ def sdf(self, x):
235
+ c = cos(self.angle)
236
+ s = sin(self.angle)
237
+
238
+ r = as_matrix(((c, -s), (s, c)))
239
+ return self.child_sdf[0].sdf(r.T * x)
240
+
241
+ def __repr__(self):
242
+ return f"Rotate({repr(self.child_sdf[0])}, {self.angle})"
243
+
244
+
245
+ class Translate(BaseOperator):
246
+ """Translates SDF"""
247
+
248
+ def __init__(self, sdf1, vec, epsilon=None, name=None, *args, **kwargs):
249
+ super().__init__(epsilon=epsilon, children=[sdf1], *args, **kwargs)
250
+ if self.name is None:
251
+ self.name = f"({vec}+{sdf1.name})"
252
+
253
+ if isinstance(vec, (list, tuple)):
254
+ vec = as_vector(vec)
255
+ self.vec = vec
256
+
257
+ def sdf(self, x):
258
+ return self.child_sdf[0].sdf(x - self.vec)
259
+
260
+ def __repr__(self):
261
+ return f"Translate({repr(self.child_sdf[0])}, {self.vec})"
262
+
263
+
264
+ class Round(BaseOperator):
265
+ """Rounds SDF"""
266
+
267
+ def __init__(self, sdf1, scale, epsilon=None, name=None, *args, **kwargs):
268
+ super().__init__(epsilon=epsilon, children=[sdf1], *args, **kwargs)
269
+ if self.name is None:
270
+ self.name = f"({scale}~{sdf1.name})"
271
+
272
+ assert scale > 0
273
+ self._scale = scale # careful not to overwrite SDF.scale here
274
+
275
+ def sdf(self, x):
276
+ return self.child_sdf[0].sdf(x) - self._scale
277
+
278
+ def __repr__(self):
279
+ return f"Round({repr(self.child_sdf[0])}, {self.scale})"
@@ -0,0 +1,48 @@
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
+ super().__init__(*args, **kwargs)
19
+
20
+ assert distance != 0, "This is a circle, use Circle class"
21
+ assert distance < radius, "No shape exists, circles cancel each other out"
22
+ assert (
23
+ smooth_radius * distance < 0
24
+ ), "For a smooth edge, smooth_radius needs to be opposite sign of distance"
25
+
26
+ self.radius = radius
27
+ self.distance = distance
28
+ self.smooth_radius = smooth_radius
29
+
30
+ def __repr__(self):
31
+ return f"Vesica({self.radius}, {self.distance}, {self.smooth_radius})"
32
+
33
+ def sdf(self, x):
34
+ x0_abs = abs(x[0])
35
+ x1_abs = abs(x[1])
36
+
37
+ b = sqrt((self.radius + self.smooth_radius) ** 2 - self.distance**2)
38
+
39
+ circle_coords = as_vector((x0_abs + self.distance, x[1]))
40
+
41
+ return (
42
+ conditional(
43
+ (x1_abs - b) * self.distance > x0_abs * b,
44
+ ufl_length(as_vector((x0_abs, x1_abs - b))) * ufl_sign(self.distance),
45
+ ufl_length(circle_coords) - self.radius - self.smooth_radius,
46
+ )
47
+ + self.smooth_radius
48
+ )
ddfem/model2ufl.py ADDED
@@ -0,0 +1,145 @@
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
+ t = initialTime
58
+
59
+ f_c_model = None
60
+ if hasattr(Model, "F_c"):
61
+ f_c_model = inner(Model.F_c(t, x, u), grad(v)) * dx # -div F_c v
62
+ if hasattr(Model, "S_e"):
63
+ se = (
64
+ inner(as_vector(Model.S_e(t, x, u, grad(u))), v) * dx
65
+ ) # (-div F_c + S_e) * v
66
+ if f_c_model is not None:
67
+ f_c_model += se
68
+ else:
69
+ f_c_model = se
70
+
71
+ f_v_model = None
72
+ if hasattr(Model, "F_v"):
73
+ f_v_model = inner(Model.F_v(t, x, u, grad(u)), grad(v)) * dx # -div F_v v
74
+
75
+ if hasattr(Model, "S_i"):
76
+ si = inner(as_vector(Model.S_i(t, x, u, grad(u))), v) * dx # (-div F_v + S_i) v
77
+ if f_v_model is not None:
78
+ f_v_model += si
79
+ else:
80
+ f_v_model = si
81
+
82
+ # need to extract boundary information from Model
83
+ (
84
+ boundary_flux_cs,
85
+ boundary_flux_vs,
86
+ boundary_values,
87
+ hasBoundaryValue,
88
+ ) = boundaries_ufl(Model, space, t)
89
+
90
+ dirichletBCs = [
91
+ DirichletBC(space, item[1], item[0]) for item in boundary_values.items()
92
+ ]
93
+ boundary_flux_vs = -sum(
94
+ [inner(item[1], v) * ds(item[0]) for item in boundary_flux_vs.items()]
95
+ ) # keep all forms on left hand side
96
+ boundary_flux_cs = -sum(
97
+ [inner(item[1], v) * ds(item[0]) for item in boundary_flux_cs.items()]
98
+ ) # keep all forms on left hand side
99
+
100
+ return (
101
+ f_c_model,
102
+ f_v_model,
103
+ {
104
+ "dirichletBCs": dirichletBCs,
105
+ "boundary_flux_cs": boundary_flux_cs,
106
+ "boundary_flux_vs": boundary_flux_vs,
107
+ "hasBoundaryValue": hasBoundaryValue,
108
+ },
109
+ )
110
+
111
+
112
+ def model2ufl(
113
+ Model, space, initialTime=0, DirichletBC=DirichletBC, *, returnFull=False
114
+ ):
115
+ class M(Model):
116
+ if hasattr(Model, "S_e"):
117
+
118
+ def S_e(t, x, U, DU):
119
+ return -Model.S_e(t, x, U, DU)
120
+
121
+ if hasattr(Model, "S_i"):
122
+
123
+ def S_i(t, x, U, DU):
124
+ return -Model.S_i(t, x, U, DU)
125
+
126
+ if hasattr(Model, "F_c"):
127
+
128
+ def F_c(t, x, U):
129
+ return -Model.F_c(t, x, U)
130
+
131
+ f_c_model, f_v_model, boundary_model = model_ufl(M, space, initialTime, DirichletBC)
132
+ boundary_model["boundary_flux_cs"] = -boundary_model["boundary_flux_cs"]
133
+ form = boundary_model["boundary_flux_cs"] + boundary_model["boundary_flux_vs"]
134
+ if f_c_model is not None:
135
+ form += f_c_model
136
+ if f_v_model is not None:
137
+ form += f_v_model
138
+
139
+ if not returnFull:
140
+ return [form == 0, *boundary_model["dirichletBCs"]]
141
+ else:
142
+ boundary_model["f_c_model"] = f_c_model
143
+ boundary_model["f_v_model"] = f_v_model
144
+ boundary_model["form"] = form
145
+ return boundary_model
@@ -0,0 +1,94 @@
1
+ from ufl import grad, zero
2
+
3
+ from .transformer_base import transformer_base
4
+
5
+
6
+ def DDM1(OriginalModel, domainDescription):
7
+ Model = transformer_base(OriginalModel, domainDescription)
8
+
9
+ class DDModel(Model):
10
+ def sigma(t, x, U, DU=None):
11
+ if DU:
12
+ return DU
13
+ return grad(U)
14
+
15
+ def S_e_source(t, x, U, DU):
16
+ return DDModel.phi(x) * Model.S_e(t, x, U, DDModel.sigma(t, x, U, DU))
17
+
18
+ def S_e_convection(t, x, U, DU):
19
+ return DDModel.BT.BndFlux_cExt(t, x, U)
20
+
21
+ if hasattr(Model, "S_e") and hasattr(Model, "F_c"):
22
+ print("DDM1: S_e and F_c")
23
+
24
+ def S_e(t, x, U, DU):
25
+ return DDModel.S_e_source(t, x, U, DU) + DDModel.S_e_convection(
26
+ t, x, U, DU
27
+ )
28
+
29
+ elif hasattr(Model, "S_e"):
30
+ print("DDM1: S_e")
31
+
32
+ def S_e(t, x, U, DU):
33
+ return DDModel.S_e_source(t, x, U, DU)
34
+
35
+ elif hasattr(Model, "F_c"):
36
+ print("DDM1: F_c")
37
+
38
+ def S_e(t, x, U, DU):
39
+ return DDModel.S_e_convection(t, x, U, DU)
40
+
41
+ def S_i_stability(t, x, U, DU):
42
+ return -Model.stabFactor * (
43
+ DDModel.BT.jumpV(t, x, U) * (1 - DDModel.phi(x)) / (DDModel.epsilon**3)
44
+ )
45
+
46
+ def S_i_source(t, x, U, DU):
47
+ return DDModel.phi(x) * Model.S_i(t, x, U, DDModel.sigma(t, x, U, DU))
48
+
49
+ def S_i_diffusion(t, x, U, DU):
50
+ if DDModel.BT.BndFlux_vExt is not None:
51
+ diffusion = DDModel.BT.BndFlux_vExt(t, x, U, DU)
52
+ else:
53
+ diffusion = zero(U.ufl_shape)
54
+ return diffusion
55
+
56
+ if hasattr(Model, "S_i") and hasattr(Model, "F_v"):
57
+ print("DDM1: S_i and F_v")
58
+
59
+ def S_i(t, x, U, DU):
60
+ return (
61
+ DDModel.S_i_stability(t, x, U, DU)
62
+ + DDModel.S_i_source(t, x, U, DU)
63
+ + DDModel.S_i_diffusion(t, x, U, DU)
64
+ )
65
+
66
+ elif hasattr(Model, "F_v"):
67
+ print("DDM1: F_v")
68
+
69
+ def S_i(t, x, U, DU):
70
+ return DDModel.S_i_stability(t, x, U, DU) + DDModel.S_i_diffusion(
71
+ t, x, U, DU
72
+ )
73
+
74
+ elif hasattr(Model, "S_i"):
75
+ print("DDM1: S_i")
76
+
77
+ def S_i(t, x, U, DU):
78
+ return DDModel.S_i_stability(t, x, U, DU) + DDModel.S_i_source(
79
+ t, x, U, DU
80
+ )
81
+
82
+ if hasattr(Model, "F_c"):
83
+ print("DDM1: F_c")
84
+
85
+ def F_c(t, x, U):
86
+ return DDModel.phi(x) * Model.F_c(t, x, U)
87
+
88
+ if hasattr(Model, "F_v"):
89
+ print("DDM1: F_v")
90
+
91
+ def F_v(t, x, U, DU):
92
+ return DDModel.phi(x) * Model.F_v(t, x, U, DDModel.sigma(t, x, U, DU))
93
+
94
+ return DDModel
@@ -0,0 +1,67 @@
1
+ from functools import reduce
2
+
3
+ from ufl import Min, conditional, eq, grad
4
+
5
+ from ..boundary import BndFlux_c, BndFlux_v, BndValue, boundary_validation
6
+ from .transformer_base import transformer_base
7
+
8
+
9
+ def Fitted(OriginalModel, domainDescription):
10
+ Model = transformer_base(OriginalModel, domainDescription)
11
+
12
+ class Fitted(Model):
13
+ def sigma(t, x, U, DU=None):
14
+ if DU:
15
+ return DU
16
+ return grad(U)
17
+
18
+ boundary = Model.BT.physical
19
+ bndSDFs = {k: Model.domain.bndSDFs(k) for k in Model.BT.diffuse.keys()}
20
+
21
+ def make_boundary_function(key, mv, bndSDFs=bndSDFs):
22
+ sdf = bndSDFs[key]
23
+ closest_sdf = lambda x: reduce(
24
+ Min,
25
+ ([abs(v(x)) for b, v in bndSDFs.items()]),
26
+ )
27
+
28
+ boundary_map = lambda x: conditional(eq(closest_sdf(x), abs(sdf(x))), 1, 0)
29
+
30
+ if isinstance(mv, BndFlux_v):
31
+ return BndFlux_v(
32
+ lambda t, x, u, DU, n: boundary_map(x) * mv(t, x, u, DU, n),
33
+ )
34
+
35
+ elif isinstance(mv, BndFlux_c):
36
+ return BndFlux_c(
37
+ lambda t, x, u, n: boundary_map(x) * mv(t, x, u, n),
38
+ )
39
+
40
+ boundary_flux_cs, boundary_flux_vs, boundary_values = boundary_validation(
41
+ Model, override_boundary_dict=Model.BT.diffuse
42
+ )
43
+
44
+ def make_boundary_conditional(key, bndSDFs=bndSDFs, tol=0.01):
45
+ sdf = bndSDFs[key]
46
+ return lambda x: abs(sdf(x)) < tol
47
+
48
+ for bc_key, bc_value in boundary_values.items():
49
+ boundary[make_boundary_conditional(bc_key)] = bc_value
50
+
51
+ for bc_key in boundary_flux_cs.keys() | boundary_flux_vs.keys():
52
+ if bc_key in boundary_flux_cs and bc_key in boundary_flux_vs:
53
+ af = make_boundary_function(bc_key, boundary_flux_cs[bc_key])
54
+ df = make_boundary_function(bc_key, boundary_flux_vs[bc_key])
55
+ boundary[make_boundary_conditional(bc_key)] = [af, df]
56
+
57
+ elif bc_key in boundary_flux_cs and bc_key not in boundary_flux_vs:
58
+ af = make_boundary_function(bc_key, boundary_flux_cs[bc_key])
59
+ boundary[make_boundary_conditional(bc_key)] = af
60
+
61
+ elif bc_key not in boundary_flux_cs and bc_key in boundary_flux_vs:
62
+ df = make_boundary_function(bc_key, boundary_flux_vs[bc_key])
63
+ boundary[make_boundary_conditional(bc_key)] = df
64
+ else:
65
+ raise ValueError()
66
+
67
+ return Fitted