Skip to content

Commit

Permalink
Cleaned some methods concerning geometry (#518)
Browse files Browse the repository at this point in the history
The method `polar_coordinates_real` has been deprecated and will be
removed in a future release.
  • Loading branch information
david-zwicker authored Jan 9, 2024
1 parent cd1fb19 commit 9420dc9
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 61 deletions.
134 changes: 80 additions & 54 deletions pde/grids/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -512,55 +512,111 @@ def uniform_cell_volumes(self) -> bool:
else:
return all(np.asarray(vols).ndim == 0 for vols in self.cell_volume_data)

def difference_vector_real(self, p1: np.ndarray, p2: np.ndarray) -> np.ndarray:
def _difference_vector(
self,
p1: np.ndarray,
p2: np.ndarray,
*,
coords: CoordsType,
periodic: Sequence[bool],
axes_bounds: tuple[tuple[float, float], ...] | None,
) -> np.ndarray:
"""return Cartesian vector(s) pointing from p1 to p2
In case of periodic boundary conditions, the shortest vector is returned.
Warning:
This function assumes a Cartesian coordinate system and might thus not work
for curvilinear coordinates.
Args:
p1 (:class:`~numpy.ndarray`):
First point(s)
p2 (:class:`~numpy.ndarray`):
Second point(s)
coords (str):
The coordinate system in which the points are specified.
periodic (sequence of bool):
Indicates which cartesian axes are periodic
axes_bounds (sequence of pair of floats):
Indicates the bounds of the cartesian axes
Returns:
:class:`~numpy.ndarray`: The difference vectors between the points with
periodic boundary conditions applied.
periodic boundary conditions applied.
"""
diff = np.atleast_1d(p2) - np.atleast_1d(p1)
assert diff.shape[-1] == self.num_axes
x1 = self.transform(p1, source=coords, target="cartesian")
x2 = self.transform(p2, source=coords, target="cartesian")
if axes_bounds is None:
axes_bounds = self.axes_bounds

for i, periodic in enumerate(self.periodic):
if periodic:
size = self.axes_bounds[i][1] - self.axes_bounds[i][0]
diff = np.atleast_1d(x2) - np.atleast_1d(x1)
assert diff.shape[-1] == self.dim

for i, per in enumerate(periodic):
if per:
size = axes_bounds[i][1] - axes_bounds[i][0]
diff[..., i] = (diff[..., i] + size / 2) % size - size / 2
return diff # type: ignore

def distance_real(self, p1: np.ndarray, p2: np.ndarray) -> float:
def difference_vector(
self, p1: np.ndarray, p2: np.ndarray, *, coords: CoordsType = "grid"
) -> np.ndarray:
"""return Cartesian vector(s) pointing from p1 to p2
In case of periodic boundary conditions, the shortest vector is returned.
Args:
p1 (:class:`~numpy.ndarray`):
First point(s)
p2 (:class:`~numpy.ndarray`):
Second point(s)
coords (str):
The coordinate system in which the points are specified. Valid values are
`cartesian`, `cell`, and `grid`; see :meth:`~pde.grids.base.GridBase.transform`.
Returns:
:class:`~numpy.ndarray`: The difference vectors between the points with
periodic boundary conditions applied.
"""
return self._difference_vector(
p1, p2, coords=coords, periodic=[False] * self.dim, axes_bounds=None
)

def difference_vector_real(self, p1: np.ndarray, p2: np.ndarray) -> np.ndarray:
# deprecated on 2024-01-09
warnings.warn(
"`difference_vector_real` has been renamed to `difference_vector`",
DeprecationWarning,
)
return self.difference_vector(p1, p2)

def distance(
self, p1: np.ndarray, p2: np.ndarray, *, coords: CoordsType = "grid"
) -> float:
"""Calculate the distance between two points given in real coordinates
This takes periodic boundary conditions into account if necessary.
Warning:
This function calculates the Euclidean distance and might thus give wrong
results for coordinates given in curvilinear coordinate systems.
Args:
p1 (:class:`~numpy.ndarray`):
First position
p2 (:class:`~numpy.ndarray`):
Second position
coords (str):
The coordinate system in which the points are specified. Valid values are
`cartesian`, `cell`, and `grid`; see :meth:`~pde.grids.base.GridBase.transform`.
Returns:
float: Distance between the two positions
"""
diff = self.difference_vector_real(p1, p2)
diff = self.difference_vector(p1, p2, coords=coords)
return np.linalg.norm(diff, axis=-1) # type: ignore

def distance_real(self, p1: np.ndarray, p2: np.ndarray) -> float:
# deprecated on 2024-01-09
warnings.warn(
"`distance_real` has been renamed to `distance`",
DeprecationWarning,
)
return self.distance(p1, p2)

def _get_basis(
self, points: np.ndarray, *, coords: CoordsType = "grid"
) -> np.ndarray:
Expand All @@ -569,7 +625,7 @@ def _get_basis(
Args:
points (:class:`~numpy.ndarray`):
Coordinates of the point(s) where the basis determined
coords (:class:`~numpy.ndarray`):
coords (str):
Coordinate system in which points are specified. Valid values are
`cartesian`, `grid`, and `cell`; see :meth:`~pde.grids.base.GridBase.transform`.
Expand Down Expand Up @@ -666,7 +722,7 @@ def _vector_to_cartesian(
The coordinates of the point(s) where the vectors are specified.
components (:class:`~numpy.ndarray`):
The components of the vectors at the given points
coords (:class:`~numpy.ndarray`):
coords (str):
The coordinate system in which the point is specified. Valid values are
`cartesian`, `cell`, and `grid`; see :meth:`~pde.grids.base.GridBase.transform`.
Expand Down Expand Up @@ -753,38 +809,6 @@ def normalize_point(

return point

def _grid_to_cell(
self, grid_coords: np.ndarray, *, truncate: bool = True, normalize: bool = False
) -> np.ndarray:
"""convert grid coordinates to cell coordinates
Args:
grid_coords (:class:`~numpy.ndarray`):
The grid coordinates to convert
truncate (bool):
Flag indicating whether the resulting cell coordinates are integers
marking what cell the point belongs to or whether fractional coordinates
are returned. The default is to return integers.
normalize (bool):
Flag indicating whether the points should be normalized, e.g., whether
periodic boundary conditions should be imposed.
Returns:
:class:`~numpy.ndarray`: The cell coordinates
"""
if normalize:
coords = self.normalize_point(grid_coords)
else:
coords = np.atleast_1d(grid_coords)

# convert from grid coordinates to cells indices
c_min = np.array(self.axes_bounds)[:, 0]
cells = (coords - c_min) / self.discretization
if truncate:
return cells.astype(int) # type: ignore
else:
return cells # type: ignore

def transform(
self,
coordinates: np.ndarray,
Expand Down Expand Up @@ -850,7 +874,8 @@ def transform(
if target == "grid":
return grid_coords
if target == "cell":
return self._grid_to_cell(grid_coords, truncate=False, normalize=False)
c_min = np.array(self.axes_bounds)[:, 0]
return (grid_coords - c_min) / self.discretization # type: ignore

elif source == "cell":
# Cell coordinates given
Expand Down Expand Up @@ -881,15 +906,15 @@ def transform(
if target == "cartesian":
return self.point_to_cartesian(grid_coords, full=full)
elif target == "cell":
return self._grid_to_cell(grid_coords, truncate=False, normalize=False)
c_min = np.array(self.axes_bounds)[:, 0]
return (grid_coords - c_min) / self.discretization # type: ignore
elif target == "grid":
return grid_coords

else:
raise ValueError(f"Unknown source coordinates `{source}`")
raise ValueError(f"Unknown target coordinates `{target}`")

@abstractmethod
def polar_coordinates_real(
self, origin: np.ndarray, *, ret_angle: bool = False
) -> np.ndarray | tuple[np.ndarray, ...]:
Expand All @@ -903,6 +928,7 @@ def polar_coordinates_real(
`False` only the distance to the origin is returned for each support
point of the grid. If `True`, the distance and angles are returned.
"""
raise NotImplementedError

def contains_point(
self,
Expand Down
14 changes: 13 additions & 1 deletion pde/grids/cartesian.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from __future__ import annotations

import itertools
import warnings
from typing import TYPE_CHECKING, Any, Generator, Sequence

import numpy as np
Expand Down Expand Up @@ -283,6 +284,13 @@ def get_random_point(
else:
raise ValueError(f"Unknown coordinate system `{coords}`")

def difference_vector(
self, p1: np.ndarray, p2: np.ndarray, *, coords: CoordsType = "grid"
) -> np.ndarray:
return self._difference_vector(
p1, p2, coords=coords, periodic=self.periodic, axes_bounds=self.axes_bounds
)

def get_line_data(self, data: np.ndarray, extract: str = "auto") -> dict[str, Any]:
"""return a line cut through the given data
Expand Down Expand Up @@ -461,12 +469,16 @@ def polar_coordinates_real(
:class:`~numpy.ndarray` or tuple of :class:`~numpy.ndarray`:
Coordinates values in polar coordinates
"""
# deprecated on 2024-01-09
warnings.warn(
"`polar_coordinates_real` will be removed soon", DeprecationWarning
)
origin = np.array(origin, dtype=np.double, ndmin=1)
if len(origin) != self.dim:
raise DimensionError("Dimensions are not compatible")

# calculate the difference vector between all cells and the origin
diff = self.difference_vector_real(origin, self.cell_coords)
diff = self.difference_vector(origin, self.cell_coords)
dist: np.ndarray = np.linalg.norm(diff, axis=-1) # get distance

# determine distance and optionally angles for these vectors
Expand Down
14 changes: 13 additions & 1 deletion pde/grids/cylindrical.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from __future__ import annotations

import warnings
from typing import TYPE_CHECKING, Any, Generator, Literal, Sequence

import numpy as np
Expand Down Expand Up @@ -268,6 +269,13 @@ def get_random_point(
else:
raise ValueError(f"Unknown coordinate system `{coords}`")

def difference_vector(
self, p1: np.ndarray, p2: np.ndarray, *, coords: CoordsType = "grid"
) -> np.ndarray:
return self._difference_vector(
p1, p2, coords=coords, periodic=self.periodic, axes_bounds=self.axes_bounds
)

def get_line_data(self, data: np.ndarray, extract: str = "auto") -> dict[str, Any]:
"""return a line cut for the cylindrical grid
Expand Down Expand Up @@ -459,6 +467,10 @@ def polar_coordinates_real(
each support point of the grid. If `True`, the distance and angles are
returned.
"""
# deprecated on 2024-01-09
warnings.warn(
"`polar_coordinates_real` will be removed soon", DeprecationWarning
)
origin = np.array(origin, dtype=np.double, ndmin=1)
if len(origin) != self.dim:
raise DimensionError("Dimensions are not compatible")
Expand All @@ -467,7 +479,7 @@ def polar_coordinates_real(
raise RuntimeError("Origin must lie on symmetry axis for cylindrical grid")

# calculate the difference vector between all cells and the origin
diff = self.difference_vector_real(np.array([0, origin[2]]), self.cell_coords)
diff = self.difference_vector(np.array([0, origin[2]]), self.cell_coords)
dist: np.ndarray = np.linalg.norm(diff, axis=-1) # get distance

if ret_angle:
Expand Down
5 changes: 5 additions & 0 deletions pde/grids/spherical.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from __future__ import annotations

import warnings
from abc import ABCMeta
from typing import TYPE_CHECKING, Any, Literal, TypeVar

Expand Down Expand Up @@ -371,6 +372,10 @@ def polar_coordinates_real(
point of the grid. If `True`, the distance and angles are returned. Note
that in the case of spherical grids, this angle is zero by convention.
"""
# deprecated on 2024-01-09
warnings.warn(
"`polar_coordinates_real` will be removed soon", DeprecationWarning
)
# check the consistency of the origin argument, which can be set for other grids
if origin is not None:
origin = np.array(origin, dtype=np.double, ndmin=1)
Expand Down
10 changes: 5 additions & 5 deletions tests/grids/test_cartesian_grids.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def norm_numba_wrap(x):
p = rng.random(1) * grid.shape
d, a = grid.polar_coordinates_real(p, ret_angle=True)
c2 = grid.from_polar_coordinates(d, a, p)
assert np.allclose(grid.distance_real(c1, c2), 0)
assert np.allclose(grid.distance(c1, c2), 0)

# test boundary points
np.testing.assert_equal(grid._boundary_coordinates(0, False), np.array([0]))
Expand Down Expand Up @@ -138,7 +138,7 @@ def test_unit_grid_2d(rng):
p = rng.random(2) * grid.shape
d, a = grid.polar_coordinates_real(p, ret_angle=True)
c2 = grid.from_polar_coordinates(d, a, p)
assert np.allclose(grid.distance_real(c1, c2), 0)
assert np.allclose(grid.distance(c1, c2), 0)

# test boundary points
np.testing.assert_equal(
Expand Down Expand Up @@ -221,7 +221,7 @@ def test_rect_grid_1d(rng):
p = rng.random(1) * grid.shape
d, a = grid.polar_coordinates_real(p, ret_angle=True)
c2 = grid.from_polar_coordinates(d, a, p)
assert np.allclose(grid.distance_real(c1, c2), 0)
assert np.allclose(grid.distance(c1, c2), 0)


def test_rect_grid_2d(rng):
Expand Down Expand Up @@ -252,7 +252,7 @@ def test_rect_grid_2d(rng):
d, a = grid.polar_coordinates_real(p, ret_angle=True)
c2 = grid.from_polar_coordinates(d, a, p)

assert np.allclose(grid.distance_real(c1, c2), 0)
assert np.allclose(grid.distance(c1, c2), 0)


def test_rect_grid_3d(rng):
Expand Down Expand Up @@ -297,7 +297,7 @@ def test_unit_rect_grid(periodic, rng):

for _ in range(10):
p1, p2 = rng.normal(scale=10, size=(2, dim))
assert g1.distance_real(p1, p2) == pytest.approx(g2.distance_real(p1, p2))
assert g1.distance(p1, p2) == pytest.approx(g2.distance(p1, p2))

p0 = rng.normal(scale=10, size=dim)
np.testing.assert_allclose(
Expand Down

0 comments on commit 9420dc9

Please sign in to comment.