Skip to content

Commit

Permalink
Merge pull request #274 from ajhynes7/add_cylinder_surface_area
Browse files Browse the repository at this point in the history
Add cylinder surface area
  • Loading branch information
ajhynes7 committed Jul 26, 2021
2 parents 352d1c4 + 4d29679 commit 9979778
Show file tree
Hide file tree
Showing 17 changed files with 88 additions and 55 deletions.
2 changes: 2 additions & 0 deletions docs/source/api_reference/skspatial.objects.Cylinder.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ Methods
~skspatial.objects.Cylinder.from_points
~skspatial.objects.Cylinder.intersect_line
~skspatial.objects.Cylinder.is_point_within
~skspatial.objects.Cylinder.lateral_surface_area
~skspatial.objects.Cylinder.length
~skspatial.objects.Cylinder.plot_3d
~skspatial.objects.Cylinder.surface_area
~skspatial.objects.Cylinder.to_mesh
~skspatial.objects.Cylinder.to_points
~skspatial.objects.Cylinder.volume
2 changes: 0 additions & 2 deletions mypy.ini

This file was deleted.

5 changes: 1 addition & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ classifiers = [
]

packages = [{ include = "skspatial", from = "src" }]
include = ["tests"]


[tool.poetry.dependencies]
Expand All @@ -37,9 +38,6 @@ matplotlib = "^3"
# Pre-commit
pre-commit = { version = "2.11.1", optional = true }

# Types
mypy = { version = "0.812", optional = true }

# Testing
pytest = { version = "6.2.2", optional = true }
pytest-cov = { version = "2.11.1", optional = true }
Expand All @@ -59,7 +57,6 @@ build-backend = "poetry.core.masonry.api"

[tool.poetry.extras]
pre_commit = ["pre-commit"]
types = ["mypy"]
base_test = ["pytest", "pytest-cov"]
property = ["hypothesis"]
docs = ["Sphinx", "numpydoc", "sphinx-bootstrap-theme", "sphinx-gallery"]
3 changes: 2 additions & 1 deletion src/skspatial/_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from functools import wraps
from typing import Any
from typing import Callable
from typing import Optional

import numpy as np

Expand Down Expand Up @@ -69,7 +70,7 @@ def wrapper(*args):
return wrapper


def _solve_quadratic(a: float, b: float, c: float, n_digits: int | None = None) -> np.ndarray:
def _solve_quadratic(a: float, b: float, c: float, n_digits: Optional[int] = None) -> np.ndarray:
"""
Solve a quadratic equation.
Expand Down
10 changes: 3 additions & 7 deletions src/skspatial/objects/_base_array.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Private base classes for arrays."""
import warnings
from typing import cast
from typing import Type
from typing import TypeVar

Expand Down Expand Up @@ -161,18 +160,15 @@ def round(self, decimals: int = 0) -> Array: # type: ignore[override] # noqa:
Point([1. , 2. , 3.53])
"""
result = np.around(self, decimals=decimals, out=self)
result = cast(Array, result)

return result
return np.around(self, decimals=decimals, out=self)


class _BaseArray1D(_BaseArray):
"""Private base class for spatial objects based on a single 1D NumPy array."""

def __new__(cls: Type[Array1D], array: array_like) -> Array1D:

obj = super().__new__(cls, array)
obj = super().__new__(cls, array) # pytype: disable=wrong-arg-count

if obj.ndim != 1:
raise ValueError("The array must be 1D.")
Expand Down Expand Up @@ -231,7 +227,7 @@ class _BaseArray2D(_BaseArray):

def __new__(cls: Type[Array2D], array: array_like) -> Array2D:

obj = super().__new__(cls, array)
obj = super().__new__(cls, array) # pytype: disable=wrong-arg-count

if obj.ndim != 2:
raise ValueError("The array must be 2D.")
Expand Down
3 changes: 3 additions & 0 deletions src/skspatial/objects/_base_spatial.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
class _PlotterMixin:

dimension: int

def plotter(self, **kwargs):
"""Return a function that plots the object when passed a matplotlib axes."""
if self.dimension == 2:
Expand Down
55 changes: 54 additions & 1 deletion src/skspatial/objects/cylinder.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Module for the Cylinder class."""
from __future__ import annotations

from typing import Optional
from typing import Tuple

import numpy as np
Expand Down Expand Up @@ -178,6 +179,58 @@ def length(self) -> np.float64:
"""
return self.vector.norm()

def lateral_surface_area(self) -> np.float64:
"""
Return the lateral surface area of the cylinder.
Returns
-------
np.float64
Lateral surface area of the cylinder.
Examples
--------
>>> from skspatial.objects import Cylinder
>>> Cylinder([0, 0, 0], [0, 0, 1], 1).lateral_surface_area().round(3)
6.283
>>> Cylinder([0, 0, 0], [0, 0, 1], 2).lateral_surface_area().round(3)
12.566
>>> Cylinder([0, 0, 0], [0, 0, 2], 2).lateral_surface_area().round(3)
25.133
"""
return 2 * np.pi * self.radius * self.length()

def surface_area(self) -> np.float64:
"""
Return the total surface area of the cylinder.
This is the lateral surface area plus the area of the two circular caps.
Returns
-------
np.float64
Total surface area of the cylinder.
Examples
--------
>>> from skspatial.objects import Cylinder
>>> Cylinder([0, 0, 0], [0, 0, 1], 1).surface_area().round(3)
12.566
>>> Cylinder([0, 0, 0], [0, 0, 1], 2).surface_area().round(3)
37.699
>>> Cylinder([0, 0, 0], [0, 0, 2], 2).surface_area().round(3)
50.265
"""
return self.lateral_surface_area() + 2 * np.pi * self.radius ** 2

def volume(self) -> np.float64:
r"""
Return the volume of the cylinder.
Expand Down Expand Up @@ -256,7 +309,7 @@ def is_point_within(self, point: array_like) -> bool:

return within_radius and within_planes

def intersect_line(self, line: Line, n_digits: int | None = None) -> Tuple[Point, Point]:
def intersect_line(self, line: Line, n_digits: Optional[int] = None) -> Tuple[Point, Point]:
"""
Intersect the cylinder with a 3D line.
Expand Down
4 changes: 3 additions & 1 deletion src/skspatial/objects/line.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""Module for the Line class."""
from __future__ import annotations

from typing import Optional

import numpy as np
from matplotlib.axes import Axes
from mpl_toolkits.mplot3d import Axes3D
Expand Down Expand Up @@ -576,7 +578,7 @@ def intersect_line(self, other: Line, **kwargs) -> Point:
return self.point + vector_a_scaled

@classmethod
def best_fit(cls, points: array_like, tol: float | None = None, **kwargs) -> Line:
def best_fit(cls, points: array_like, tol: Optional[float] = None, **kwargs) -> Line:
"""
Return the line of best fit for a set of points.
Expand Down
10 changes: 2 additions & 8 deletions src/skspatial/objects/plane.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Module for the Plane class."""
from __future__ import annotations

from typing import cast
from typing import Optional
from typing import Tuple

import numpy as np
Expand Down Expand Up @@ -236,12 +236,6 @@ def cartesian(self) -> Tuple[np.number, np.number, np.number, np.number]:

d = -self.normal.dot(self.point)

# Type casting to satisfy mypy.
a = cast(np.number, a)
b = cast(np.number, b)
c = cast(np.number, c)
d = cast(np.number, d)

return a, b, c, d

def project_point(self, point: array_like) -> Point:
Expand Down Expand Up @@ -567,7 +561,7 @@ def intersect_plane(self, other: Plane, **kwargs) -> Line:
return Line(point_line, direction_line)

@classmethod
def best_fit(cls, points: array_like, tol: float | None = None, **kwargs) -> Plane:
def best_fit(cls, points: array_like, tol: Optional[float] = None, **kwargs) -> Plane:
"""
Return the plane of best fit for a set of 3D points.
Expand Down
Empty file removed stubs/matplotlib/__init__.pyi
Empty file.
7 changes: 0 additions & 7 deletions stubs/matplotlib/axes/__init__.pyi

This file was deleted.

4 changes: 0 additions & 4 deletions stubs/matplotlib/figure/__init__.pyi

This file was deleted.

12 changes: 0 additions & 12 deletions stubs/matplotlib/pyplot/__init__.pyi

This file was deleted.

Empty file removed stubs/mpl_toolkits/__init__.pyi
Empty file.
6 changes: 0 additions & 6 deletions stubs/mpl_toolkits/mplot3d/__init__.pyi

This file was deleted.

16 changes: 16 additions & 0 deletions tests/unit/objects/test_cylinder.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,22 @@ def test_properties(cylinder, length_expected, volume_expected):
assert isclose(cylinder.volume(), volume_expected)


@pytest.mark.parametrize(
("cylinder", "lateral_surface_area_expected", "surface_area_expected"),
[
(Cylinder([0, 0, 0], [0, 0, 1], 1), 2 * pi, 4 * pi),
(Cylinder([0, 0, 0], [0, 0, 2], 1), 4 * pi, 6 * pi),
(Cylinder([0, 0, 0], [0, 0, 1], 2), 4 * pi, 12 * pi),
(Cylinder([0, 0, 0], [0, 0, 2], 2), 8 * pi, 16 * pi),
(Cylinder([0, 0, 0], [0, 0, -2], 2), 8 * pi, 16 * pi),
],
)
def test_surface_area(cylinder, lateral_surface_area_expected, surface_area_expected):

assert isclose(cylinder.lateral_surface_area(), lateral_surface_area_expected)
assert isclose(cylinder.surface_area(), surface_area_expected)


@pytest.mark.parametrize(
("cylinder", "point", "bool_expected"),
[
Expand Down
4 changes: 2 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ commands =

[testenv:types]
commands =
poetry install -E types
poetry run mypy src/
pip install pytype==2021.7.19
pytype src/

[testenv:readme]
commands =
Expand Down

0 comments on commit 9979778

Please sign in to comment.