Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Add derivatives for SDF #539

Merged
merged 3 commits into from Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/ldc/ldc2d_unsteady_Re10.py
Expand Up @@ -173,7 +173,7 @@
# manually collate input data for visualization,
# (interior+boundary) x all timestamps
for t in range(NTIME_PDE):
for key in vis_points:
for key in geom["time_rect"].dim_keys:
vis_points[key] = np.concatenate(
(
vis_points[key],
Expand Down
6 changes: 5 additions & 1 deletion ppsci/constraint/initial_constraint.py
Expand Up @@ -34,7 +34,7 @@


class InitialConstraint(base.Constraint):
"""Class for initial constraint.
"""Class for initial interior constraint.
Args:
output_expr (Dict[str, Callable]): Function in dict for computing output.
Expand All @@ -53,6 +53,8 @@ class InitialConstraint(base.Constraint):
Defaults to False.
weight_dict (Optional[Dict[str, Callable]]): Define the weight of each
constraint variable. Defaults to None.
compute_sdf_derivatives (Optional[bool]): Whether compute derivatives for SDF.
Defaults to False.
name (str, optional): Name of constraint object. Defaults to "IC".
Examples:
Expand Down Expand Up @@ -86,6 +88,7 @@ def __init__(
criteria: Optional[Callable] = None,
evenly: bool = False,
weight_dict: Optional[Dict[str, Callable]] = None,
compute_sdf_derivatives: bool = False,
name: str = "IC",
):
self.label_dict = label_dict
Expand All @@ -107,6 +110,7 @@ def __init__(
random,
criteria,
evenly,
compute_sdf_derivatives,
)
if "area" in input:
input["area"] *= dataloader_cfg["iters_per_epoch"]
Expand Down
4 changes: 4 additions & 0 deletions ppsci/constraint/interior_constraint.py
Expand Up @@ -53,6 +53,8 @@ class InteriorConstraint(base.Constraint):
Defaults to False.
weight_dict (Optional[Dict[str, Union[Callable, float]]]): Define the
weight of each constraint variable. Defaults to None.
compute_sdf_derivatives (Optional[bool]): Whether compute derivatives for SDF.
Defaults to False.
name (str, optional): Name of constraint object. Defaults to "EQ".
Examples:
Expand Down Expand Up @@ -83,6 +85,7 @@ def __init__(
criteria: Optional[Callable] = None,
evenly: bool = False,
weight_dict: Optional[Dict[str, Union[Callable, float]]] = None,
compute_sdf_derivatives: bool = False,
name: str = "EQ",
):
self.label_dict = label_dict
Expand All @@ -104,6 +107,7 @@ def __init__(
random,
criteria,
evenly,
compute_sdf_derivatives,
)
if "area" in input:
input["area"] *= dataloader_cfg["iters_per_epoch"]
Expand Down
37 changes: 36 additions & 1 deletion ppsci/geometry/geometry.py
Expand Up @@ -70,6 +70,7 @@ def sample_interior(
random="pseudo",
criteria=None,
evenly=False,
compute_sdf_derivatives: bool = False,
):
"""Sample random points in the geometry and return those meet criteria."""
x = np.empty(shape=(n, self.ndim), dtype=paddle.get_default_dtype())
Expand Down Expand Up @@ -106,11 +107,18 @@ def sample_interior(
if hasattr(self, "sdf_func"):
sdf = -self.sdf_func(x)
sdf_dict = misc.convert_to_dict(sdf, ("sdf",))
sdf_derivs_dict = {}
if compute_sdf_derivatives:
sdf_derivs = -self.sdf_derivatives(x)
sdf_derivs_dict = misc.convert_to_dict(
sdf_derivs, tuple(f"sdf__{key}" for key in self.dim_keys)
)
else:
sdf_dict = {}
sdf_derivs_dict = {}
x_dict = misc.convert_to_dict(x, self.dim_keys)

return {**x_dict, **sdf_dict}
return {**x_dict, **sdf_dict, **sdf_derivs_dict}

def sample_boundary(self, n, random="pseudo", criteria=None, evenly=False):
"""Compute the random points in the geometry and return those meet criteria."""
Expand Down Expand Up @@ -196,6 +204,33 @@ def periodic_point(self, x: np.ndarray, component: int):
"""Compute the periodic image of x."""
raise NotImplementedError(f"{self}.periodic_point to be implemented")

def sdf_derivatives(self, x: np.ndarray, epsilon: float = 1e-4) -> np.ndarray:
"""Compute derivatives of SDF function.
Args:
x (np.ndarray): Points for computing SDF derivatives using central
difference. The shape is [N, D], D is the number of dimension of
geometry.
epsilon (float): Derivative step. Defaults to 1e-4.
Returns:
np.ndarray: Derivatives of corresponding SDF function.
The shape is [N, D]. D is the number of dimension of geometry.
"""
if not hasattr(self, "sdf_func"):
raise NotImplementedError(
f"{misc.typename(self)}.sdf_func should be implemented "
"when using 'sdf_derivatives'."
)
# Only compute sdf derivatives for those already implement `sdf_func` method.
sdf_derivs = np.empty_like(x)
for i in range(self.ndim):
h = np.zeros_like(x)
h[:, i] += epsilon / 2
derivs_at_i = (self.sdf_func(x + h) - self.sdf_func(x - h)) / epsilon
sdf_derivs[:, i : i + 1] = derivs_at_i
return sdf_derivs

def union(self, other):
"""CSG Union."""
from ppsci.geometry import csg
Expand Down
18 changes: 16 additions & 2 deletions ppsci/geometry/mesh.py
Expand Up @@ -427,7 +427,14 @@ def random_points(self, n, random="pseudo", criteria=None):
all_areas = np.full((n, 1), cuboid_volume * (_nvalid / _nsample) / n)
return all_points, all_areas

def sample_interior(self, n, random="pseudo", criteria=None, evenly=False):
def sample_interior(
self,
n,
random="pseudo",
criteria=None,
evenly=False,
compute_sdf_derivatives: bool = False,
):
"""Sample random points in the geometry and return those meet criteria."""
if evenly:
# TODO(sensen): implement uniform sample for mesh interior.
Expand All @@ -444,7 +451,14 @@ def sample_interior(self, n, random="pseudo", criteria=None, evenly=False):
sdf = -self.sdf_func(points)
sdf_dict = misc.convert_to_dict(sdf, ("sdf",))

return {**x_dict, **area_dict, **sdf_dict}
sdf_derivs_dict = {}
if compute_sdf_derivatives:
sdf_derivs = -self.sdf_derivatives(points)
sdf_derivs_dict = misc.convert_to_dict(
sdf_derivs, tuple(f"sdf__{key}" for key in self.dim_keys)
)

return {**x_dict, **area_dict, **sdf_dict, **sdf_derivs_dict}

def union(self, other: "Mesh"):
if not checker.dynamic_import_to_globals(["pymesh"]):
Expand Down
27 changes: 25 additions & 2 deletions ppsci/geometry/timedomain.py
Expand Up @@ -541,7 +541,12 @@ def periodic_point(self, x, component):
return txp

def sample_initial_interior(
self, n: int, random: str = "pseudo", criteria=None, evenly=False
self,
n: int,
random: str = "pseudo",
criteria=None,
evenly=False,
compute_sdf_derivatives: bool = False,
):
"""Sample random points in the time-geometry and return those meet criteria."""
x = np.empty(shape=(n, self.ndim), dtype=paddle.get_default_dtype())
Expand Down Expand Up @@ -570,7 +575,25 @@ def sample_initial_interior(
"Sample initial interior points failed, "
"please check correctness of geometry and given creteria."
)
return misc.convert_to_dict(x, self.dim_keys)

# if sdf_func added, return x_dict and sdf_dict, else, only return the x_dict
if hasattr(self.geometry, "sdf_func"):
# compute sdf excluding timet t
sdf = -self.geometry.sdf_func(x[..., 1:])
sdf_dict = misc.convert_to_dict(sdf, ("sdf",))
sdf_derivs_dict = {}
if compute_sdf_derivatives:
# compute sdf derivatives excluding timet t
sdf_derivs = -self.geometry.sdf_derivatives(x[..., 1:])
sdf_derivs_dict = misc.convert_to_dict(
sdf_derivs, tuple(f"sdf__{key}" for key in self.geometry.dim_keys)
)
else:
sdf_dict = {}
sdf_derivs_dict = {}
x_dict = misc.convert_to_dict(x, self.dim_keys)

return {**x_dict, **sdf_dict, **sdf_derivs_dict}

def __str__(self) -> str:
"""Return the name of class"""
Expand Down