|
3 | 3 | __all__ = [ |
4 | 4 | "BC", |
5 | 5 | "DirichletBC", |
| 6 | + "Interface2DBC", |
6 | 7 | "NeumannBC", |
7 | 8 | "OperatorBC", |
8 | 9 | "PeriodicBC", |
@@ -263,6 +264,91 @@ def error(self, X, inputs, outputs, beg, end, aux_var=None): |
263 | 264 | return self.func(inputs, outputs, X)[beg:end] - self.values |
264 | 265 |
|
265 | 266 |
|
| 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 | + |
266 | 352 | def npfunc_range_autocache(func): |
267 | 353 | """Call a NumPy function on a range of the input ndarray. |
268 | 354 |
|
|
0 commit comments