From 846b35943913958c6c80609eeb03d30f49d605b0 Mon Sep 17 00:00:00 2001 From: Carlos Adir Date: Mon, 18 Aug 2025 23:42:01 +0200 Subject: [PATCH] feat: add computation of density functions --- docs/source/rst/theory.rst | 12 +- src/shapepy/__init__.py | 2 +- src/shapepy/analytic/base.py | 2 +- src/shapepy/bool2d/base.py | 34 +++++ src/shapepy/bool2d/boolean.py | 4 +- src/shapepy/bool2d/shape.py | 49 ++----- src/shapepy/common.py | 8 ++ src/shapepy/geometry/integral.py | 29 ++-- src/shapepy/tools.py | 15 ++ tests/bool2d/test_contains.py | 163 +-------------------- tests/bool2d/test_density.py | 200 ++++++++++++++++++++++++++ tests/bool2d/test_empty_whole.py | 72 ++++++---- tests/geometry/test_integral.py | 74 +++++----- tests/geometry/test_jordan_polygon.py | 14 +- 14 files changed, 389 insertions(+), 289 deletions(-) create mode 100644 tests/bool2d/test_density.py diff --git a/docs/source/rst/theory.rst b/docs/source/rst/theory.rst index 27a21037..5151f627 100644 --- a/docs/source/rst/theory.rst +++ b/docs/source/rst/theory.rst @@ -36,7 +36,7 @@ and how they interact with each other. * :ref:`theory_bool_xor` * :ref:`theory_generalities` * :ref:`onedimen_integration` - * :ref:`winding_function` + * :ref:`lebesgue_density` ----------------------------------------------------------------------------- @@ -480,7 +480,7 @@ If this projected point is equal to the point itself, then the point is on the c Point in Shape -------------- -The :ref:`winding_function` is used to determine if the shape contains the point. +The :ref:`lebesgue_density` is used to determine if the shape contains the point. Basically this function tells if a point is inside the shape, or outside, or at the boundary: @@ -1124,12 +1124,12 @@ There are available schemas are bellow, with some nodes/weights depending on :ma - 1/2 - 1 -.. _winding_function: +.. _lebesgue_density: -Winding function ----------------- +Lebesgue Density function +------------------------- -The **Winding function** is a function on the plane, based on the a shape :math:`S`, that +The **Density function** is a function on the plane, based on the a shape :math:`S`, that * Is equal to :math:`1` for interior points * Is equal to :math:`0` for exterior points diff --git a/src/shapepy/__init__.py b/src/shapepy/__init__.py index caf56fd6..defee46b 100644 --- a/src/shapepy/__init__.py +++ b/src/shapepy/__init__.py @@ -10,7 +10,7 @@ from .bool2d.base import EmptyShape, WholeShape from .bool2d.primitive import Primitive from .bool2d.shape import ConnectedShape, DisjointShape, SimpleShape -from .common import move, rotate, scale +from .common import lebesgue_density, move, rotate, scale from .geometry.integral import IntegrateJordan from .geometry.jordancurve import JordanCurve from .geometry.point import Point2D diff --git a/src/shapepy/analytic/base.py b/src/shapepy/analytic/base.py index f6946b83..4bab6689 100644 --- a/src/shapepy/analytic/base.py +++ b/src/shapepy/analytic/base.py @@ -40,7 +40,7 @@ def pow_keys(exp: int) -> Set[int]: return pow_keys(exp // 2) | pow_keys(exp - exp // 2) | {exp} -# pylint: disable=too-few-public-methods +# pylint: disable=duplicate-code class IAnalytic(ABC): """ Interface Class for Analytic classes diff --git a/src/shapepy/bool2d/base.py b/src/shapepy/bool2d/base.py index bdb9fa05..4945459e 100644 --- a/src/shapepy/bool2d/base.py +++ b/src/shapepy/bool2d/base.py @@ -148,6 +148,33 @@ def rotate(self, angle: Angle) -> SubSetR2: """ raise NotImplementedError + @abstractmethod + def density(self, center: Point2D) -> Real: + """ + Computes the density of the subset around given point + + Parameters + ---------- + + center : Point2D + The position to measure the density + + :return: The density in the interval [0, 1] + :rtype: Real + + Example use + ----------- + >>> from shapepy import Primitive + >>> circle = Primitive.circle(radius=1) + >>> circle.density((0, 0)) + 1 + >>> circle.density((5, 0)) + 0 + >>> circle.density((1, 0)) + 0.5 + """ + raise NotImplementedError + class EmptyShape(SubSetR2): """EmptyShape is a singleton class to represent an empty shape @@ -210,6 +237,9 @@ def scale(self, _): def rotate(self, _): return self + def density(self, center: Point2D) -> Real: + return 0 + class WholeShape(SubSetR2): """WholeShape is a singleton class to represent all plane @@ -272,7 +302,11 @@ def scale(self, _): def rotate(self, _): return self + def density(self, center: Point2D) -> Real: + return 1 + +# pylint: disable=duplicate-code class Future: """ Class that stores methods that are further defined. diff --git a/src/shapepy/bool2d/boolean.py b/src/shapepy/bool2d/boolean.py index c72efbd1..a8a47c6c 100644 --- a/src/shapepy/bool2d/boolean.py +++ b/src/shapepy/bool2d/boolean.py @@ -223,8 +223,8 @@ def midpoints_one_shape( for i, jordan in enumerate(shapea.jordans): for j, segment in enumerate(jordan.piecewise): mid_point = segment(Fraction(1, 2)) - wind = shapeb.winding(mid_point) - mid_point_in = (wind > 0 and closed) or wind == 1 + density = shapeb.density(mid_point) + mid_point_in = (density > 0 and closed) or density == 1 if not inside ^ mid_point_in: yield (i, j) diff --git a/src/shapepy/bool2d/shape.py b/src/shapepy/bool2d/shape.py index 7a875ea2..130fdfe0 100644 --- a/src/shapepy/bool2d/shape.py +++ b/src/shapepy/bool2d/shape.py @@ -13,12 +13,12 @@ from typing import Iterable, Set, Tuple, Union from ..geometry.box import Box -from ..geometry.integral import winding_number +from ..geometry.integral import lebesgue_density_jordan from ..geometry.jordancurve import JordanCurve from ..geometry.point import Point2D from ..scalar.angle import Angle from ..scalar.reals import Real -from ..tools import Is, To +from ..tools import Is, To, prod from .base import EmptyShape, SubSetR2 @@ -141,9 +141,9 @@ def __contains__(self, other: SubSetR2) -> bool: return self.__contains_point(other) def __contains_point(self, point: Point2D) -> bool: - wind = self.winding(point) + density = self.density(point) - return wind > 0 if self.boundary else wind == 1 + return density > 0 if self.boundary else density == 1 def __contains_jordan(self, jordan: JordanCurve) -> bool: piecewise = jordan.parametrize() @@ -210,15 +210,8 @@ def box(self) -> Box: """ return self.jordan.box() - def winding(self, point: Point2D) -> Real: - """Gives the winding number. - - 0 means the point is outside the domain - 1 means the point is inside the domain - between 0 and 1 means its on the boundary""" - point = To.point(point) - wind = winding_number(self.jordan, center=point) - return wind + def density(self, center: Point2D) -> Real: + return lebesgue_density_jordan(self.jordan, center) class ConnectedShape(SubSetR2): @@ -351,17 +344,9 @@ def box(self) -> Box: box |= sub.jordan.box() return box - def winding(self, point: Point2D) -> Real: - """Gives the winding number. - - 0 means the point is outside the domain - 1 means the point is inside the domain - between 0 and 1 means its on the boundary""" - point = To.point(point) - wind = 1 - for subset in self.subshapes: - wind *= subset.winding(point) - return wind + def density(self, center: Point2D) -> Real: + center = To.point(center) + return prod(sub.density(center) for sub in self.subshapes) class DisjointShape(SubSetR2): @@ -506,18 +491,10 @@ def box(self) -> Box: box |= sub.box() return box - def winding(self, point: Point2D) -> Real: - """Gives the winding number. - - 0 means the point is outside the domain - 1 means the point is inside the domain - between 0 and 1 means its on the boundary""" - point = To.point(point) - for subset in self.subshapes: - wind = subset.winding(point) - if wind > 0: - return wind - return 0 + def density(self, center: Point2D) -> Real: + center = To.point(center) + result = sum(sub.density(center) for sub in self.subshapes) + return min(result, 1) def divide_connecteds( diff --git a/src/shapepy/common.py b/src/shapepy/common.py index 02758fc1..77e102d9 100644 --- a/src/shapepy/common.py +++ b/src/shapepy/common.py @@ -3,6 +3,7 @@ from copy import deepcopy from typing import Any, Tuple +from .bool2d.base import SubSetR2 from .scalar.angle import Angle from .scalar.reals import Real @@ -88,3 +89,10 @@ def derivate(obj: Any) -> Any: Derivates the analytic function or the curve """ return deepcopy(obj).derivate() + + +def lebesgue_density(subset: SubSetR2, center: Tuple[Real, Real]) -> Real: + """ + Calcules the density of given subset around given point + """ + return subset.density(center) diff --git a/src/shapepy/geometry/integral.py b/src/shapepy/geometry/integral.py index adb30f77..fefb7dac 100644 --- a/src/shapepy/geometry/integral.py +++ b/src/shapepy/geometry/integral.py @@ -66,22 +66,25 @@ def polynomial(jordan: JordanCurve, expx: int, expy: int): # pylint: disable=too-many-locals @debug("shapepy.geometry.integral") -def winding_number( - jordan: JordanCurve, center: Optional[Point2D] = (0.0, 0.0) +def lebesgue_density_jordan( + jordan: JordanCurve, point: Optional[Point2D] = (0.0, 0.0) ) -> Union[int, float]: - """Computes the winding number from jordan curve + """Computes the lebesgue density number from jordan curve - Returns [-1, -0.5, 0, 0.5 or 1] + Returns a value in the interval [0, 1]: + * 0 -> means the point is outside the interior region + * 1 -> means the point is completly inside the interior + * between 0 and 1, it's on the boundary """ - center = To.point(center) + point = To.point(point) box = jordan.box() - if center not in box: - wind = 0 if jordan.area > 0 else 1 - return wind + if point not in box: + density = 0 if jordan.area > 0 else 1 + return density segments = tuple(jordan.parametrize()) for i, segmenti in enumerate(segments): - if center == segmenti(0): + if point == segmenti(0): segmentj = segments[(i - 1) % len(segments)] deltapi = segmenti(0, 1) deltapj = segmentj(1, 1) @@ -94,8 +97,8 @@ def winding_number( integrator = AdaptativeIntegrator(direct, 1e-6) radangle = 0 for segment in segments: - deltax: IAnalytic = segment.xfunc - center.xcoord - deltay: IAnalytic = segment.yfunc - center.ycoord + deltax: IAnalytic = segment.xfunc - point.xcoord + deltay: IAnalytic = segment.yfunc - point.ycoord radius_square = deltax * deltax + deltay * deltay if find_minimum(radius_square, [0, 1]) < 1e-6: return 0.5 @@ -104,5 +107,5 @@ def winding_number( lambda t, cf, rs: cf(t) / rs(t), cf=crossf, rs=radius_square ) radangle += integrator.integrate(function, [0, 1]) - wind = round(radangle / Math.tau) - return wind if jordan.area > 0 else 1 + wind + density = round(radangle / Math.tau) + return density if jordan.area > 0 else 1 + density diff --git a/src/shapepy/tools.py b/src/shapepy/tools.py index ab50a1ea..31ee44f0 100644 --- a/src/shapepy/tools.py +++ b/src/shapepy/tools.py @@ -108,6 +108,21 @@ def wrapper(*args, **kwargs): return decorator +def prod(values: Iterable[Any]) -> Any: + """Computes the product of given objects + + Example + ------- + >>> prod([3, 2, 5]) + 30 + """ + values = iter(values) + result = next(values) + for value in values: + result *= value + return result + + def reverse(objs: Iterable[Any]) -> Iterable[Any]: """Reverts the list/tuple""" return tuple(objs)[::-1] diff --git a/tests/bool2d/test_contains.py b/tests/bool2d/test_contains.py index ae62eec6..c955c1d0 100644 --- a/tests/bool2d/test_contains.py +++ b/tests/bool2d/test_contains.py @@ -18,6 +18,7 @@ depends=[ "tests/geometry/test_integral.py::test_all", "tests/bool2d/test_primitive.py::test_end", + "tests/bool2d/test_density.py::test_end", ], scope="session", ) @@ -176,166 +177,6 @@ def test_end(self): pass -class TestWinding: - """ - Tests the respective position - """ - - @pytest.mark.order(23) - @pytest.mark.dependency( - depends=["test_begin", "TestObjectsInEmptyWhole::test_end"] - ) - def test_begin(self): - pass - - @pytest.mark.order(23) - @pytest.mark.dependency( - depends=[ - "TestWinding::test_begin", - ] - ) - def test_simple_shape(self): - shape = Primitive.triangle(3) - # Corners - points_winding = { - (0, 0): 0.25, - (3, 0): 0.125, - (0, 3): 0.125, - } - for point, wind in points_winding.items(): - assert shape.winding(point) == wind - # Mid edges - points_winding = { - (1, 0): 0.5, - (2, 0): 0.5, - (2, 1): 0.5, - (1, 2): 0.5, - (0, 2): 0.5, - (0, 1): 0.5, - } - for point, wind in points_winding.items(): - assert shape.winding(point) == wind - # Interior exterior - points_winding = { - (1, 1): 1, - (2, 2): 0, - (3, 3): 0, - (-1, -1): 0, - } - for point, wind in points_winding.items(): - assert shape.winding(point) == wind - - @pytest.mark.order(23) - @pytest.mark.dependency( - depends=[ - "TestWinding::test_begin", - "TestWinding::test_simple_shape", - ] - ) - def test_connected_shape(self): - big = Primitive.square(side=6) - small = Primitive.square(side=2) - shape = ConnectedShape([big, ~small]) - # Corners - points_winding = { - (1, 1): 0.75, - (-1, 1): 0.75, - (-1, -1): 0.75, - (1, -1): 0.75, - (3, 3): 0.25, - (-3, 3): 0.25, - (-3, -3): 0.25, - (3, -3): 0.25, - } - for point, wind in points_winding.items(): - test = shape.winding(point) - print(f"wind of {point} = {test}") - assert test == wind - # Mid edges - points_winding = { - (1, 0): 0.5, - (0, 1): 0.5, - (-1, 0): 0.5, - (0, -1): 0.5, - (3, 0): 0.5, - (0, 3): 0.5, - (-3, 0): 0.5, - (0, -3): 0.5, - } - for point, wind in points_winding.items(): - assert shape.winding(point) == wind - # Interior exterior - points_winding = { - (0, 0): 0, - (2, 2): 1, - (0, 2): 1, - (-2, 2): 1, - (-2, 0): 1, - (-2, -2): 1, - (0, -2): 1, - (2, -2): 1, - (2, 0): 1, - } - for point, wind in points_winding.items(): - assert shape.winding(point) == wind - - @pytest.mark.order(23) - @pytest.mark.dependency( - depends=[ - "TestWinding::test_begin", - "TestWinding::test_simple_shape", - "TestWinding::test_connected_shape", - ] - ) - def test_disjoint_shape(self): - squarel = Primitive.square(side=2, center=(-3, 0)) - squarer = Primitive.square(side=2, center=(3, 0)) - shape = DisjointShape([squarel, squarer]) - # Corner - points_winding = { - (-4, -1): 0.25, - (-2, -1): 0.25, - (-2, 1): 0.25, - (-4, 1): 0.25, - (4, -1): 0.25, - (2, -1): 0.25, - (2, 1): 0.25, - (4, 1): 0.25, - } - for point, wind in points_winding.items(): - assert shape.winding(point) == wind - # Mid edge - points_winding = { - (-3, -1): 0.5, - (-2, 0): 0.5, - (-3, 1): 0.5, - (-4, 0): 0.5, - (3, -1): 0.5, - (2, 0): 0.5, - (3, 1): 0.5, - (4, 0): 0.5, - } - for point, wind in points_winding.items(): - assert shape.winding(point) == wind - # Interior exterior - points_winding = {(0, 0): 0, (-3, 0): 1, (3, 0): 1} - for point, wind in points_winding.items(): - assert shape.winding(point) == wind - - @pytest.mark.order(23) - @pytest.mark.timeout(10) - @pytest.mark.dependency( - depends=[ - "TestWinding::test_begin", - "TestWinding::test_simple_shape", - "TestWinding::test_connected_shape", - "TestWinding::test_disjoint_shape", - ] - ) - def test_end(self): - pass - - class TestObjectsInJordan: """ Tests the respective position @@ -346,7 +187,6 @@ class TestObjectsInJordan: depends=[ "test_begin", "TestObjectsInEmptyWhole::test_end", - "TestWinding::test_end", ] ) def test_begin(self): @@ -439,7 +279,6 @@ class TestObjectsInSimple: depends=[ "test_begin", "TestObjectsInEmptyWhole::test_end", - "TestWinding::test_end", "TestObjectsInJordan::test_end", ] ) diff --git a/tests/bool2d/test_density.py b/tests/bool2d/test_density.py new file mode 100644 index 00000000..1a0ecd5d --- /dev/null +++ b/tests/bool2d/test_density.py @@ -0,0 +1,200 @@ +""" +This file contains the code to test the relative position +of an object with respect to another +""" + +import pytest + +from shapepy import lebesgue_density +from shapepy.bool2d.base import EmptyShape, WholeShape +from shapepy.bool2d.primitive import Primitive +from shapepy.bool2d.shape import ConnectedShape, DisjointShape +from shapepy.geometry.point import polar +from shapepy.scalar.angle import Angle + + +@pytest.mark.order(22) +@pytest.mark.dependency( + depends=[ + "tests/geometry/test_integral.py::test_all", + "tests/bool2d/test_empty_whole.py::test_end", + ], + scope="session", +) +def test_begin(): + pass + + +@pytest.mark.order(22) +@pytest.mark.dependency(depends=["test_begin"]) +def test_empty_whole(): + empty = EmptyShape() + whole = WholeShape() + for point in [(0, 0), (1, 0), (0, 1)]: + assert lebesgue_density(empty, point) == 0 + assert lebesgue_density(whole, point) == 1 + assert empty.density(point) == 0 + assert whole.density(point) == 1 + + for deg in range(0, 360, 30): + angle = Angle.degrees(deg) + point = polar(float("inf"), angle) + assert lebesgue_density(empty, point) == 0 + assert lebesgue_density(whole, point) == 1 + assert empty.density(point) == 0 + assert whole.density(point) == 1 + + +@pytest.mark.order(22) +@pytest.mark.dependency(depends=["test_begin", "test_empty_whole"]) +def test_simple_shape(): + shape = Primitive.triangle(3) + # Corners + points_density = { + (0, 0): 0.25, + (3, 0): 0.125, + (0, 3): 0.125, + } + for point, value in points_density.items(): + assert lebesgue_density(shape, point) == value + assert shape.density(point) == value + # Mid edges + points_density = { + (1, 0): 0.5, + (2, 0): 0.5, + (2, 1): 0.5, + (1, 2): 0.5, + (0, 2): 0.5, + (0, 1): 0.5, + } + for point, value in points_density.items(): + assert lebesgue_density(shape, point) == value + assert shape.density(point) == value + # Interior exterior + points_density = { + (1, 1): 1, + (2, 2): 0, + (3, 3): 0, + (-1, -1): 0, + } + for point, value in points_density.items(): + assert lebesgue_density(shape, point) == value + assert shape.density(point) == value + + +@pytest.mark.order(22) +@pytest.mark.dependency( + depends=[ + "test_begin", + "test_empty_whole", + "test_simple_shape", + ] +) +def test_connected_shape(): + big = Primitive.square(side=6) + small = Primitive.square(side=2) + shape = ConnectedShape([big, ~small]) + # Corners + points_density = { + (1, 1): 0.75, + (-1, 1): 0.75, + (-1, -1): 0.75, + (1, -1): 0.75, + (3, 3): 0.25, + (-3, 3): 0.25, + (-3, -3): 0.25, + (3, -3): 0.25, + } + for point, value in points_density.items(): + assert lebesgue_density(shape, point) == value + assert shape.density(point) == value + # Mid edges + points_density = { + (1, 0): 0.5, + (0, 1): 0.5, + (-1, 0): 0.5, + (0, -1): 0.5, + (3, 0): 0.5, + (0, 3): 0.5, + (-3, 0): 0.5, + (0, -3): 0.5, + } + for point, value in points_density.items(): + assert lebesgue_density(shape, point) == value + assert shape.density(point) == value + # Interior exterior + points_density = { + (0, 0): 0, + (2, 2): 1, + (0, 2): 1, + (-2, 2): 1, + (-2, 0): 1, + (-2, -2): 1, + (0, -2): 1, + (2, -2): 1, + (2, 0): 1, + } + for point, value in points_density.items(): + assert lebesgue_density(shape, point) == value + assert shape.density(point) == value + + +@pytest.mark.order(22) +@pytest.mark.dependency( + depends=[ + "test_begin", + "test_simple_shape", + "test_connected_shape", + ] +) +def test_disjoint_shape(): + squarel = Primitive.square(side=2, center=(-3, 0)) + squarer = Primitive.square(side=2, center=(3, 0)) + shape = DisjointShape([squarel, squarer]) + # Corner + points_density = { + (-4, -1): 0.25, + (-2, -1): 0.25, + (-2, 1): 0.25, + (-4, 1): 0.25, + (4, -1): 0.25, + (2, -1): 0.25, + (2, 1): 0.25, + (4, 1): 0.25, + } + for point, value in points_density.items(): + assert lebesgue_density(shape, point) == value + assert shape.density(point) == value + # Mid edge + points_density = { + (-3, -1): 0.5, + (-2, 0): 0.5, + (-3, 1): 0.5, + (-4, 0): 0.5, + (3, -1): 0.5, + (2, 0): 0.5, + (3, 1): 0.5, + (4, 0): 0.5, + } + for point, value in points_density.items(): + assert lebesgue_density(shape, point) == value + assert shape.density(point) == value + # Interior exterior + points_density = {(0, 0): 0, (-3, 0): 1, (3, 0): 1} + for point, value in points_density.items(): + assert lebesgue_density(shape, point) == value + assert shape.density(point) == value + + +@pytest.mark.order(22) +@pytest.mark.dependency( + depends=[ + "test_begin", + "test_empty_whole", + "test_simple_shape", + "test_connected_shape", + "test_disjoint_shape", + ] +) +def test_end(): + pass diff --git a/tests/bool2d/test_empty_whole.py b/tests/bool2d/test_empty_whole.py index 0baffc04..26480253 100644 --- a/tests/bool2d/test_empty_whole.py +++ b/tests/bool2d/test_empty_whole.py @@ -7,16 +7,18 @@ import pytest -from shapepy import move, rotate, scale +from shapepy import lebesgue_density, move, rotate, scale from shapepy.bool2d.base import EmptyShape, WholeShape from shapepy.bool2d.primitive import Primitive +from shapepy.geometry.point import polar from shapepy.scalar.angle import Angle -@pytest.mark.order(24) +@pytest.mark.order(21) @pytest.mark.dependency( depends=[ - "tests/bool2d/test_primitive.py::test_end", + "tests/geometry/test_integral.py::test_all", + "tests/geometry/test_jordan_curve.py::test_all", ], scope="session", ) @@ -24,14 +26,14 @@ def test_begin(): pass -@pytest.mark.order(24) +@pytest.mark.order(21) @pytest.mark.dependency(depends=["test_begin"]) def test_singleton(): assert EmptyShape() is EmptyShape() assert WholeShape() is WholeShape() -@pytest.mark.order(24) +@pytest.mark.order(21) @pytest.mark.dependency(depends=["test_singleton"]) def test_invert(): empty = EmptyShape() @@ -42,7 +44,7 @@ def test_invert(): assert ~(~whole) is whole -@pytest.mark.order(24) +@pytest.mark.order(21) @pytest.mark.dependency(depends=["test_singleton", "test_invert"]) def test_neg(): empty = EmptyShape() @@ -53,7 +55,7 @@ def test_neg(): assert -(-whole) is whole -@pytest.mark.order(24) +@pytest.mark.order(21) @pytest.mark.dependency(depends=["test_singleton"]) def test_or(): empty = EmptyShape() @@ -69,7 +71,7 @@ def test_or(): assert whole + whole is whole -@pytest.mark.order(24) +@pytest.mark.order(21) @pytest.mark.dependency(depends=["test_singleton"]) def test_and(): empty = EmptyShape() @@ -85,7 +87,7 @@ def test_and(): assert whole * whole is whole -@pytest.mark.order(24) +@pytest.mark.order(21) @pytest.mark.dependency( depends=["test_singleton", "test_or", "test_and", "test_invert"] ) @@ -98,7 +100,7 @@ def test_xor(): assert whole ^ whole is empty -@pytest.mark.order(24) +@pytest.mark.order(21) @pytest.mark.dependency( depends=["test_singleton", "test_or", "test_and", "test_invert"] ) @@ -111,7 +113,7 @@ def test_sub(): assert whole - whole is empty -@pytest.mark.order(24) +@pytest.mark.order(21) @pytest.mark.dependency(depends=["test_singleton"]) def test_bool(): empty = EmptyShape() @@ -120,7 +122,7 @@ def test_bool(): assert bool(whole) is True -@pytest.mark.order(24) +@pytest.mark.order(21) @pytest.mark.dependency(depends=["test_singleton"]) def test_copy(): empty = EmptyShape() @@ -131,7 +133,7 @@ def test_copy(): assert deepcopy(whole) is whole -@pytest.mark.order(24) +@pytest.mark.order(21) @pytest.mark.dependency(depends=["test_singleton"]) def test_move(): empty = EmptyShape() @@ -143,7 +145,7 @@ def test_move(): assert move(whole, (1, 0)) is whole -@pytest.mark.order(24) +@pytest.mark.order(21) @pytest.mark.dependency(depends=["test_singleton"]) def test_scale(): empty = EmptyShape() @@ -155,7 +157,7 @@ def test_scale(): assert scale(whole, (3, 2)) is whole -@pytest.mark.order(24) +@pytest.mark.order(21) @pytest.mark.dependency(depends=["test_singleton"]) def test_rotate(): empty = EmptyShape() @@ -168,7 +170,7 @@ def test_rotate(): assert rotate(whole, angle) is whole -@pytest.mark.order(24) +@pytest.mark.order(21) @pytest.mark.dependency(depends=["test_singleton"]) def test_print(): empty = EmptyShape() @@ -180,14 +182,14 @@ def test_print(): assert isinstance(repr(whole), str) -@pytest.mark.order(24) +@pytest.mark.order(21) @pytest.mark.dependency(depends=["test_singleton"]) def test_hash(): assert hash(EmptyShape()) == 0 assert hash(WholeShape()) == 1 -@pytest.mark.order(24) +@pytest.mark.order(21) @pytest.mark.dependency(depends=["test_singleton"]) def test_contains(): empty = EmptyShape() @@ -199,8 +201,28 @@ def test_contains(): assert whole in whole +@pytest.mark.order(21) +@pytest.mark.dependency(depends=["test_singleton"]) +def test_density(): + empty = EmptyShape() + whole = WholeShape() + for point in [(0, 0), (1, 0), (0, 1)]: + assert empty.density(point) == 0 + assert whole.density(point) == 1 + assert lebesgue_density(empty, point) == 0 + assert lebesgue_density(whole, point) == 1 + + for deg in range(0, 360, 30): + angle = Angle.degrees(deg) + point = polar(float("inf"), angle) + assert empty.density(point) == 0 + assert whole.density(point) == 1 + assert lebesgue_density(empty, point) == 0 + assert lebesgue_density(whole, point) == 1 + + class TestBoolShape: - @pytest.mark.order(24) + @pytest.mark.order(21) @pytest.mark.dependency( depends=[ "test_begin", @@ -219,12 +241,13 @@ class TestBoolShape: "test_print", "test_hash", "test_contains", + "test_density", ] ) def test_begin(self): pass - @pytest.mark.order(24) + @pytest.mark.order(21) @pytest.mark.timeout(40) @pytest.mark.dependency(depends=["TestBoolShape::test_begin"]) def test_simple(self): @@ -258,7 +281,7 @@ def test_simple(self): assert empty - shape is empty assert whole - shape == ~shape - @pytest.mark.order(24) + @pytest.mark.order(21) @pytest.mark.timeout(40) @pytest.mark.dependency(depends=["TestBoolShape::test_begin"]) def test_connected(self): @@ -294,13 +317,13 @@ def test_connected(self): assert empty - shape is empty assert whole - shape == ~shape - @pytest.mark.order(24) + @pytest.mark.order(21) @pytest.mark.timeout(40) @pytest.mark.dependency(depends=["TestBoolShape::test_begin"]) def test_disjoint(self): pass - @pytest.mark.order(24) + @pytest.mark.order(21) @pytest.mark.dependency( depends=[ "TestBoolShape::test_begin", @@ -313,7 +336,7 @@ def test_end(self): pass -@pytest.mark.order(24) +@pytest.mark.order(21) @pytest.mark.dependency( depends=[ "test_begin", @@ -332,6 +355,7 @@ def test_end(self): "test_print", "test_hash", "test_contains", + "test_density", "TestBoolShape::test_end", ] ) diff --git a/tests/geometry/test_integral.py b/tests/geometry/test_integral.py index 12d26708..38c05833 100644 --- a/tests/geometry/test_integral.py +++ b/tests/geometry/test_integral.py @@ -3,7 +3,7 @@ import pytest from shapepy.geometry.factory import FactoryJordan -from shapepy.geometry.integral import winding_number +from shapepy.geometry.integral import lebesgue_density_jordan from shapepy.geometry.segment import Segment @@ -65,7 +65,7 @@ def test_area(): assert jordan.area == -6 -class TestWinding: +class TestDensity: """ Tests the respective position """ @@ -78,7 +78,7 @@ def test_begin(self): @pytest.mark.order(15) @pytest.mark.dependency( depends=[ - "TestWinding::test_begin", + "TestDensity::test_begin", ] ) def test_standard_square(self): @@ -107,19 +107,19 @@ def test_standard_square(self): (-1, 1): 0.25, } for point in interiors: - assert winding_number(jordan, point) == 1 + assert lebesgue_density_jordan(jordan, point) == 1 for point in exteriors: - assert winding_number(jordan, point) == 0 + assert lebesgue_density_jordan(jordan, point) == 0 for point in mid_edges: - assert winding_number(jordan, point) == 0.5 - for point, wind in corners.items(): - assert winding_number(jordan, point) == wind + assert lebesgue_density_jordan(jordan, point) == 0.5 + for point, density in corners.items(): + assert lebesgue_density_jordan(jordan, point) == density @pytest.mark.order(15) @pytest.mark.dependency( depends=[ - "TestWinding::test_begin", - "TestWinding::test_standard_square", + "TestDensity::test_begin", + "TestDensity::test_standard_square", ] ) def test_inverted_square(self): @@ -148,20 +148,20 @@ def test_inverted_square(self): (-1, 1): 0.75, } for point in interiors: - assert winding_number(jordan, point) == 1 + assert lebesgue_density_jordan(jordan, point) == 1 for point in exteriors: - assert winding_number(jordan, point) == 0 + assert lebesgue_density_jordan(jordan, point) == 0 for point in mid_edges: - assert winding_number(jordan, point) == 0.5 - for point, wind in corners.items(): - assert winding_number(jordan, point) == wind + assert lebesgue_density_jordan(jordan, point) == 0.5 + for point, density in corners.items(): + assert lebesgue_density_jordan(jordan, point) == density @pytest.mark.order(15) @pytest.mark.dependency( depends=[ - "TestWinding::test_begin", - "TestWinding::test_standard_square", - "TestWinding::test_inverted_square", + "TestDensity::test_begin", + "TestDensity::test_standard_square", + "TestDensity::test_inverted_square", ] ) def test_standard_triangle(self): @@ -189,20 +189,20 @@ def test_standard_triangle(self): (0, 3): 0.125, } for point in interiors: - assert winding_number(jordan, point) == 1 + assert lebesgue_density_jordan(jordan, point) == 1 for point in exteriors: - assert winding_number(jordan, point) == 0 + assert lebesgue_density_jordan(jordan, point) == 0 for point in mid_edges: - assert winding_number(jordan, point) == 0.5 - for point, wind in corners.items(): - assert winding_number(jordan, point) == wind + assert lebesgue_density_jordan(jordan, point) == 0.5 + for point, density in corners.items(): + assert lebesgue_density_jordan(jordan, point) == density @pytest.mark.order(15) @pytest.mark.dependency( depends=[ - "TestWinding::test_begin", - "TestWinding::test_inverted_square", - "TestWinding::test_standard_triangle", + "TestDensity::test_begin", + "TestDensity::test_inverted_square", + "TestDensity::test_standard_triangle", ] ) def test_inverted_triangle(self): @@ -230,22 +230,22 @@ def test_inverted_triangle(self): (0, 3): 0.875, } for point in interiors: - assert winding_number(jordan, point) == 1 + assert lebesgue_density_jordan(jordan, point) == 1 for point in exteriors: - assert winding_number(jordan, point) == 0 + assert lebesgue_density_jordan(jordan, point) == 0 for point in mid_edges: - assert winding_number(jordan, point) == 0.5 - for point, wind in corners.items(): - assert winding_number(jordan, point) == wind + assert lebesgue_density_jordan(jordan, point) == 0.5 + for point, density in corners.items(): + assert lebesgue_density_jordan(jordan, point) == density @pytest.mark.order(15) @pytest.mark.dependency( depends=[ - "TestWinding::test_begin", - "TestWinding::test_standard_square", - "TestWinding::test_inverted_square", - "TestWinding::test_standard_triangle", - "TestWinding::test_inverted_triangle", + "TestDensity::test_begin", + "TestDensity::test_standard_square", + "TestDensity::test_inverted_square", + "TestDensity::test_standard_triangle", + "TestDensity::test_inverted_triangle", ] ) def test_all(self): @@ -260,7 +260,7 @@ def test_all(self): "test_segment_length", "test_jordan_length", "test_area", - "TestWinding::test_all", + "TestDensity::test_all", ] ) def test_all(): diff --git a/tests/geometry/test_jordan_polygon.py b/tests/geometry/test_jordan_polygon.py index 86fbd42d..494c09f0 100644 --- a/tests/geometry/test_jordan_polygon.py +++ b/tests/geometry/test_jordan_polygon.py @@ -8,7 +8,7 @@ import pytest from shapepy.geometry.factory import FactoryJordan -from shapepy.geometry.integral import winding_number +from shapepy.geometry.integral import lebesgue_density_jordan from shapepy.geometry.jordancurve import clean_jordan from shapepy.scalar.angle import Angle @@ -335,22 +335,22 @@ def test_begin(self): @pytest.mark.order(15) @pytest.mark.timeout(10) @pytest.mark.dependency(depends=["TestIntegrateJordan::test_begin"]) - def test_winding_regular_polygon(self): + def test_density_regular_polygon(self): # Counter clockwise for nsides in range(3, 10): angles = np.linspace(0, math.tau, nsides + 1) ctrlpoints = np.vstack([np.cos(angles), np.sin(angles)]).T jordancurve = FactoryJordan.polygon(ctrlpoints) - wind = winding_number(jordancurve) - assert abs(wind - 1) < 1e-9 + density = lebesgue_density_jordan(jordancurve) + assert abs(density - 1) < 1e-9 # Clockwise for nsides in range(3, 10): angles = np.linspace(math.tau, 0, nsides + 1) ctrlpoints = np.vstack([np.cos(angles), np.sin(angles)]).T jordancurve = FactoryJordan.polygon(ctrlpoints) - wind = winding_number(jordancurve) - assert abs(wind - 0) < 1e-9 + density = lebesgue_density_jordan(jordancurve) + assert abs(density - 0) < 1e-9 @pytest.mark.order(15) @pytest.mark.timeout(10) @@ -423,7 +423,7 @@ def test_lenght_regular_polygon(self): @pytest.mark.dependency( depends=[ "TestIntegrateJordan::test_begin", - "TestIntegrateJordan::test_winding_regular_polygon", + "TestIntegrateJordan::test_density_regular_polygon", "TestIntegrateJordan::test_lenght_triangle", "TestIntegrateJordan::test_lenght_square", "TestIntegrateJordan::test_lenght_regular_polygon",