Skip to content

Commit

Permalink
Optimize distance calculations (#203)
Browse files Browse the repository at this point in the history
- use `numpy.linalg.norm` instead of `scipy.spatial.distance` (and remove scipy dependency)
- cache all `Circle` instances when we create an instance of `Rings`, and compute all separations via numpy

Co-authored-by: Stefanie Molin <24376333+stefmolin@users.noreply.github.com>
  • Loading branch information
JCGoran and stefmolin authored Jul 22, 2024
1 parent 980b167 commit 4f0d702
Show file tree
Hide file tree
Showing 5 changed files with 17 additions and 9 deletions.
1 change: 0 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@
'Pillow': ('https://pillow.readthedocs.io/en/stable/', None),
'pytest': ('https://pytest.org/en/stable/', None),
'python': ('https://docs.python.org/3/', None),
'scipy': ('https://docs.scipy.org/doc/scipy/', None),
}


Expand Down
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ dependencies = [
"matplotlib>=3.3",
"numpy>=1.20",
"pandas>=1.2",
"scipy>=1.10.0",
"tqdm>=4.64.1",
]
optional-dependencies.dev = [
Expand Down
6 changes: 4 additions & 2 deletions src/data_morph/shapes/bases/line_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import Iterable

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.axes import Axes

from ...plotting.style import plot_with_custom_style
Expand Down Expand Up @@ -75,8 +76,9 @@ def _distance_point_to_line(
.. _this VBA code: http://local.wasp.uwa.edu.au/~pbourke/geometry/pointline/source.vba
"""
start, end = line
start, end = np.array(line)
line_mag = self._euclidean_distance(start, end)
point = np.array(point)

if line_mag < 0.00000001:
# Arbitrarily large value
Expand All @@ -100,7 +102,7 @@ def _distance_point_to_line(
# Intersecting point is on the line, use the formula
ix = x1 + u * (x2 - x1)
iy = y1 + u * (y2 - y1)
distance = self._euclidean_distance(point, (ix, iy))
distance = self._euclidean_distance(point, np.array((ix, iy)))

return distance

Expand Down
6 changes: 3 additions & 3 deletions src/data_morph/shapes/bases/shape.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
from numbers import Number
from typing import Iterable, Optional

import numpy as np
from matplotlib.axes import Axes
from scipy.spatial import distance


class Shape(ABC):
Expand Down Expand Up @@ -67,9 +67,9 @@ def _euclidean_distance(a: Iterable[Number], b: Iterable[Number]) -> float:
See Also
--------
scipy.spatial.distance.euclidean : Euclidean distance calculation.
numpy.linalg.norm : Euclidean distance calculation.
"""
return distance.euclidean(a, b)
return np.linalg.norm(a - b)

def _recursive_repr(self, attr: Optional[str] = None) -> str:
"""
Expand Down
12 changes: 10 additions & 2 deletions src/data_morph/shapes/circles.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ def __init__(self, dataset: Dataset, r: Number = None) -> None:
self.r: Number = r or dataset.df.std().mean() * 1.5
"""numbers.Number: The radius of the circle."""

self._center = np.array([self.cx, self.cy])

def __repr__(self) -> str:
return f'<{self.__class__.__name__} cx={self.cx} cy={self.cy} r={self.r}>'

Expand All @@ -60,7 +62,7 @@ def distance(self, x: Number, y: Number) -> float:
float
The absolute distance between this circle's edge and the point (x, y).
"""
return abs(self._euclidean_distance((self.cx, self.cy), (x, y)) - self.r)
return abs(self._euclidean_distance(self._center, np.array([x, y])) - self.r)

@plot_with_custom_style
def plot(self, ax: Axes = None) -> Axes:
Expand Down Expand Up @@ -125,6 +127,9 @@ def __init__(self, dataset: Dataset, num_rings: int = 4) -> None:
]
"""list[Circle]: The individual rings represented by :class:`Circle` objects."""

self._centers = np.array([circle._center for circle in self.circles])
self._radii = np.array([circle.r for circle in self.circles])

def __repr__(self) -> str:
return self._recursive_repr('circles')

Expand All @@ -150,7 +155,10 @@ def distance(self, x: Number, y: Number) -> float:
Rings consists of multiple circles, so we use the minimum
distance to one of the circles.
"""
return min(circle.distance(x, y) for circle in self.circles)
point = np.array([x, y])
return np.min(
np.abs(np.linalg.norm(self._centers - point, axis=1) - self._radii)
)

@plot_with_custom_style
def plot(self, ax: Axes = None) -> Axes:
Expand Down

0 comments on commit 4f0d702

Please sign in to comment.