Skip to content

Commit 794bee8

Browse files
Add dde.icbc.Interface2DBC for 2D interface boundary condition. (#1497)
1 parent bd348aa commit 794bee8

File tree

3 files changed

+89
-0
lines changed

3 files changed

+89
-0
lines changed

deepxde/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
# Backward compatibility
3737
from .icbc import (
3838
DirichletBC,
39+
Interface2DBC,
3940
NeumannBC,
4041
OperatorBC,
4142
PeriodicBC,

deepxde/icbc/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
__all__ = [
44
"BC",
55
"DirichletBC",
6+
"Interface2DBC",
67
"NeumannBC",
78
"RobinBC",
89
"PeriodicBC",
@@ -15,6 +16,7 @@
1516
from .boundary_conditions import (
1617
BC,
1718
DirichletBC,
19+
Interface2DBC,
1820
NeumannBC,
1921
RobinBC,
2022
PeriodicBC,

deepxde/icbc/boundary_conditions.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
__all__ = [
44
"BC",
55
"DirichletBC",
6+
"Interface2DBC",
67
"NeumannBC",
78
"OperatorBC",
89
"PeriodicBC",
@@ -263,6 +264,91 @@ def error(self, X, inputs, outputs, beg, end, aux_var=None):
263264
return self.func(inputs, outputs, X)[beg:end] - self.values
264265

265266

267+
class Interface2DBC:
268+
"""2D interface boundary condition.
269+
270+
This BC applies to the case with the following conditions:
271+
(1) the network output has two elements, i.e., output = [y1, y2],
272+
(2) the 2D geometry is ``dde.geometry.Rectangle`` or ``dde.geometry.Polygon``, which has two edges of the same length,
273+
(3) uniform boundary points are used, i.e., in ``dde.data.PDE`` or ``dde.data.TimePDE``, ``train_distribution="uniform"``.
274+
For a pair of points on the two edges, compute <output_1, d1> for the point on the first edge
275+
and <output_2, d2> for the point on the second edge in the n/t direction ('n' for normal or 't' for tangent).
276+
Here, <v1, v2> is the dot product between vectors v1 and v2;
277+
and d1 and d2 are the n/t vectors of the first and second edges, respectively.
278+
In the normal case, d1 and d2 are the outward normal vectors;
279+
and in the tangent case, d1 and d2 are the outward normal vectors rotated 90 degrees clockwise.
280+
The points on the two edges are paired as follows: the boundary points on one edge are sampled clockwise,
281+
and the points on the other edge are sampled counterclockwise. Then, compare the sum with 'values',
282+
i.e., the error is calculated as <output_1, d1> + <output_2, d2> - values,
283+
where 'values' is the argument `func` evaluated on the first edge.
284+
285+
Args:
286+
geom: a ``dde.geometry.Rectangle`` or ``dde.geometry.Polygon`` instance.
287+
func: the target discontinuity between edges, evaluated on the first edge,
288+
e.g., ``func=lambda x: 0`` means no discontinuity is wanted.
289+
on_boundary1: First edge func. (x, Geometry.on_boundary(x)) -> True/False.
290+
on_boundary2: Second edge func. (x, Geometry.on_boundary(x)) -> True/False.
291+
direction (string): "normal" or "tangent".
292+
"""
293+
294+
def __init__(self, geom, func, on_boundary1, on_boundary2, direction="normal"):
295+
self.geom = geom
296+
self.func = npfunc_range_autocache(utils.return_tensor(func))
297+
self.on_boundary1 = lambda x, on: np.array(
298+
[on_boundary1(x[i], on[i]) for i in range(len(x))]
299+
)
300+
self.on_boundary2 = lambda x, on: np.array(
301+
[on_boundary2(x[i], on[i]) for i in range(len(x))]
302+
)
303+
self.direction = direction
304+
305+
self.boundary_normal = npfunc_range_autocache(
306+
utils.return_tensor(self.geom.boundary_normal)
307+
)
308+
309+
def collocation_points(self, X):
310+
on_boundary = self.geom.on_boundary(X)
311+
X1 = X[self.on_boundary1(X, on_boundary)]
312+
X2 = X[self.on_boundary2(X, on_boundary)]
313+
# Flip order of X2 when dde.geometry.Polygon is used
314+
if self.geom.__class__.__name__ == "Polygon":
315+
X2 = np.flip(X2, axis=0)
316+
return np.vstack((X1, X2))
317+
318+
def error(self, X, inputs, outputs, beg, end, aux_var=None):
319+
mid = beg + (end - beg) // 2
320+
if not mid - beg == end - mid:
321+
raise RuntimeError(
322+
"There is a different number of points on each edge,\n\
323+
this is likely because the chosen edges do not have the same length."
324+
)
325+
values = self.func(X, beg, mid, aux_var)
326+
if bkd.ndim(values) == 2 and bkd.shape(values)[1] != 1:
327+
raise RuntimeError("BC function should return an array of shape N by 1")
328+
left_n = self.boundary_normal(X, beg, mid, None)
329+
right_n = self.boundary_normal(X, mid, end, None)
330+
if self.direction == "normal":
331+
left_side = outputs[beg:mid, :]
332+
right_side = outputs[mid:end, :]
333+
left_values = bkd.sum(left_side * left_n, 1, keepdims=True)
334+
right_values = bkd.sum(right_side * right_n, 1, keepdims=True)
335+
336+
elif self.direction == "tangent":
337+
# Tangent vector is [n[1],-n[0]] on edge 1
338+
left_side1 = outputs[beg:mid, 0:1]
339+
left_side2 = outputs[beg:mid, 1:2]
340+
right_side1 = outputs[mid:end, 0:1]
341+
right_side2 = outputs[mid:end, 1:2]
342+
left_values_1 = bkd.sum(left_side1 * left_n[:, 1:2], 1, keepdims=True)
343+
left_values_2 = bkd.sum(-left_side2 * left_n[:, 0:1], 1, keepdims=True)
344+
left_values = left_values_1 + left_values_2
345+
right_values_1 = bkd.sum(right_side1 * right_n[:, 1:2], 1, keepdims=True)
346+
right_values_2 = bkd.sum(-right_side2 * right_n[:, 0:1], 1, keepdims=True)
347+
right_values = right_values_1 + right_values_2
348+
349+
return left_values + right_values - values
350+
351+
266352
def npfunc_range_autocache(func):
267353
"""Call a NumPy function on a range of the input ndarray.
268354

0 commit comments

Comments
 (0)