When applying periodic boundary conditions to Constructive Solid Geometry (CSG) objects (CSGUnion, CSGDifference, CSGIntersection), the mapped points frequently fall entirely outside the computational domain or land in the middle of it.
This occurs because the CSG classes do not calculate their periodic mappings based on the new, combined shape's bounding box. Instead, they strictly delegate the mapping to the parent geometries (e.g., calling self.geom1.periodic_point(x)). If the CSG operation alters the bounding box along the periodic axis, the parent geometry uses its original, now-incorrect bbox to calculate the shift.
Expected Behavior:
Periodic boundaries should wrap exactly from one side of the new CSG domain to the other.
-
For Union: Gluing a $1 \times 1$ square and a $1 \times 1$ square into a $2 \times 1$ rectangle should map $x=0$ to $x=2$.
-
For Difference: Subtracting half of a $2 \times 1$ rectangle to leave a $1 \times 1$ square should map $x=0$ to $x=1$.
Actual Behavior:
Because it delegates to the parents:
-
For Union: It asks the left parent to map $x=0$. The parent thinks its right wall is at $x=1$, so it dumps the point in the middle of the new fluid domain.
-
For Difference: It asks the parent to map $x=0$. The parent thinks its right wall is at $x=2$, so it throws the point into empty space outside the new domain.
Steps to Reproduce:
The script below demonstrates both failures visually and numerically on the current main branch.
import deepxde as dde
import numpy as np
def demonstrate_csg_periodic_flaws():
print("=== DeepXDE CSG periodic_point Flaw ===")
# ---------------------------------------------------------
# TEST 1: CSGDifference (Maps outside the domain)
# ---------------------------------------------------------
print("\n--- 1. Testing CSGDifference ---")
geom1 = dde.geometry.Rectangle([0, 0], [2, 1])
geom2 = dde.geometry.Rectangle([1, 0], [2, 1])
# Leaves a new square domain from x=0 to x=1
diff_geom = dde.geometry.CSGDifference(geom1, geom2)
test_point_diff = np.array([[0.0, 0.5]])
mapped_diff = diff_geom.periodic_point(test_point_diff, component=0)
print(f"New Domain: x in [0, 1]")
print(f"Original point: {test_point_diff.tolist()}")
print(f"Expected mapped: [[1.0, 0.5]]")
print(f"ACTUAL mapped: {mapped_diff.tolist()}")
if mapped_diff[0, 0] == 2.0:
print("-> BUG: Mapped to parent's bbox (x=2), landing OUTSIDE the domain.")
# ---------------------------------------------------------
# TEST 2: CSGUnion (Maps into the middle of the domain)
# ---------------------------------------------------------
print("\n--- 2. Testing CSGUnion ---")
geom3 = dde.geometry.Rectangle([0, 0], [1, 1])
geom4 = dde.geometry.Rectangle([1, 0], [2, 1])
# Creates a wide rectangle domain from x=0 to x=2
union_geom = dde.geometry.CSGUnion(geom3, geom4)
test_point_union = np.array([[0.0, 0.5]])
mapped_union = union_geom.periodic_point(test_point_union, component=0)
print(f"New Domain: x in [0, 2]")
print(f"Original point: {test_point_union.tolist()}")
print(f"Expected mapped: [[2.0, 0.5]]")
print(f"ACTUAL mapped: {mapped_union.tolist()}")
if mapped_union[0, 0] == 1.0:
print("-> BUG: Mapped to parent's bbox (x=1), landing in the MIDDLE of the domain.")
if __name__ == "__main__":
demonstrate_csg_periodic_flaws()
Suggested Resolution:
The periodic_point method in CSG geometries likely needs to be decoupled from self.geom1 and self.geom2. Instead, the CSG classes should compute the boundary shift using their own combined self.bbox, similar to how base geometries like Rectangle handle it.
When applying periodic boundary conditions to Constructive Solid Geometry (CSG) objects (
CSGUnion,CSGDifference,CSGIntersection), the mapped points frequently fall entirely outside the computational domain or land in the middle of it.This occurs because the CSG classes do not calculate their periodic mappings based on the new, combined shape's bounding box. Instead, they strictly delegate the mapping to the parent geometries (e.g., calling
self.geom1.periodic_point(x)). If the CSG operation alters the bounding box along the periodic axis, the parent geometry uses its original, now-incorrectbboxto calculate the shift.Expected Behavior:
Periodic boundaries should wrap exactly from one side of the new CSG domain to the other.
Actual Behavior:
Because it delegates to the parents:
Steps to Reproduce:
The script below demonstrates both failures visually and numerically on the current
mainbranch.Suggested Resolution:
The
periodic_pointmethod in CSG geometries likely needs to be decoupled fromself.geom1andself.geom2. Instead, the CSG classes should compute the boundary shift using their own combinedself.bbox, similar to how base geometries likeRectanglehandle it.