From 866d061755f87af022e250115d0f166b2b6b7614 Mon Sep 17 00:00:00 2001
From: Lars Quentin <lars.quentin@stud.uni-goettingen.de>
Date: Thu, 7 Jul 2022 17:42:43 +0200
Subject: [PATCH 1/6] :memo: __init__.py docstring

---
 curvepy/__init__.py | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/curvepy/__init__.py b/curvepy/__init__.py
index 6f12c90..e2a8efe 100644
--- a/curvepy/__init__.py
+++ b/curvepy/__init__.py
@@ -1,3 +1,13 @@
 """
-Welcome to curvepy! More documentation will be written soon.
+Welcome to curvepy!
+
+curvepy is an computational geometric library. It's main focus lies on
+
+- Different Bézier curve implementations and their viability
+- A Green-Sibson/Bowyer/Watson based Delaunay triangulation, with translation in it's Voronoi dual graph
+
+The technical information can be found in the single modules, the mathematical explanations and design decisions
+can be found in the accompanying paper.
+
+Please read the paper for more information.
 """
-- 
GitLab


From e3118270c34a412d5791cf05d421c3880738f366 Mon Sep 17 00:00:00 2001
From: Lars Quentin <lars.quentin@stud.uni-goettingen.de>
Date: Fri, 8 Jul 2022 09:38:33 +0200
Subject: [PATCH 2/6] :memo: utilities.py docstrings

---
 curvepy/utilities.py | 31 ++++++++++++++++++++++++++-----
 1 file changed, 26 insertions(+), 5 deletions(-)

diff --git a/curvepy/utilities.py b/curvepy/utilities.py
index 053e903..74b9bb9 100644
--- a/curvepy/utilities.py
+++ b/curvepy/utilities.py
@@ -110,15 +110,12 @@ a + x(b + x(c+x*(d)))
 
 def horner(m: np.ndarray, t: float = 0.5) -> Tuple[Union[float, Any], ...]:
     """
-    TODO show which problem this is
-    TODO besserer Name sowie auch BezierCurveHorner mit horner-bez
-    TODO First coeff == Highest Degree
-    Method using horner's method to calculate point with given t
+    Method using horner schema of the De Casteljau algorithm to calculate a single point.
 
     Parameters
     ----------
     m: np.ndarray:
-        array containing coefficients
+        array containing coefficients. Note that the first coefficient is the highest degree.
 
     t: float:
         value for which point is calculated
@@ -233,8 +230,32 @@ def intersect_lines(p1: np.ndarray, p2: np.ndarray, p3: np.ndarray, p4: np.ndarr
 
 
 def flatten_list_of_lists(xss: List[List[Any]]) -> List[Any]:
+    """
+    Reduces one list dimension by using using it's `__add__`.
+
+    Parameters
+    ----------
+    xss: List[List[Any]]
+        Any list that needs a reduced version.
+
+    Returns
+    -------
+    The same list, flattened by one dimension
+    """
     return sum(xss, [])
 
 
 def prod(xs: Iterable[Number]):
+    """
+    The multiplical product of a bunch of numbers.
+
+    Parameters
+    ----------
+    xs: Iterable[Number]
+        The numbers to multiply
+
+    Returns
+    -------
+    The product, 1 if empty.
+    """
     return functools.reduce(operator.mul, xs, 1)
-- 
GitLab


From 009de4e2f67760f47a1343eb8c9545a9c36c08c8 Mon Sep 17 00:00:00 2001
From: Lars Quentin <lars.quentin@stud.uni-goettingen.de>
Date: Fri, 8 Jul 2022 09:42:55 +0200
Subject: [PATCH 3/6] :coffin: removed dead enum

---
 curvepy/types.py | 10 +---------
 1 file changed, 1 insertion(+), 9 deletions(-)

diff --git a/curvepy/types.py b/curvepy/types.py
index 2cf8357..4cc526f 100644
--- a/curvepy/types.py
+++ b/curvepy/types.py
@@ -5,7 +5,6 @@ import sys
 from abc import ABC, abstractmethod
 from collections.abc import Sequence
 from dataclasses import dataclass
-from enum import Enum
 from functools import cached_property, lru_cache, partial
 from typing import (Any, Callable, Deque, Dict, List, NamedTuple, Optional,
                     Tuple, Union)
@@ -15,13 +14,6 @@ import scipy.special as scs
 
 from curvepy.utilities import create_straight_line_function
 
-
-class CurveTypes(Enum):
-    bezier_curve = 0
-    bezier_curve_threaded = 1
-    bezier_curve_blossoms = 2
-
-
 Point2D = Tuple[float, float]
 Edge2D = Tuple[Point2D, Point2D]
 
@@ -366,7 +358,7 @@ class MinMaxBox:
 
     def __contains__(self, point: Tuple[float, ...]) -> bool:
         return self.dim() == len(point) \
-            and all(self[2 * i] <= point[i] <= self[(2 * i) + 1] for i in range(len(point)))
+               and all(self[2 * i] <= point[i] <= self[(2 * i) + 1] for i in range(len(point)))
 
     def same_dimension(self, other: MinMaxBox):
         return len(self) == len(other)
-- 
GitLab


From 39cdcf86e5cba2ba0bb9bda5a466ebb14667737f Mon Sep 17 00:00:00 2001
From: Lars Quentin <lars.quentin@stud.uni-goettingen.de>
Date: Fri, 8 Jul 2022 10:40:45 +0200
Subject: [PATCH 4/6] :memo: types

---
 curvepy/types.py | 331 +++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 319 insertions(+), 12 deletions(-)

diff --git a/curvepy/types.py b/curvepy/types.py
index 4cc526f..0cf47bc 100644
--- a/curvepy/types.py
+++ b/curvepy/types.py
@@ -19,6 +19,20 @@ Edge2D = Tuple[Point2D, Point2D]
 
 
 class TriangleNode(NamedTuple):
+    """
+    Auxiliary datastructure for easier triangle graph traversal.
+    Views the triangle from one specific point.
+
+    Attributes
+    -------
+    ccw: Point2D
+        The point counterclockwise to our pt.
+    cw: Point2D
+        The point clockwise to our pt.
+    pt: Point2D
+        The third point, from which cw and ccw are semantically defined.
+    ccc: The circumcircle center.
+    """
     ccw: Point2D
     cw: Point2D
     pt: Point2D
@@ -26,27 +40,99 @@ class TriangleNode(NamedTuple):
 
 
 class Circle:
+    """
+    A computational representation of a geometric circle.
+
+
+    Parameters
+    ----------
+    center: Point2D
+        The circle center
+    radius: float
+        The circle radius
+
+    Attributes
+    -------
+    _center: np.ndarray
+        The circle center
+    radius: float
+        The radius
+    """
     def __init__(self, center: Point2D, radius: float):
         self._center = np.array(center)
         self.radius = radius
 
     @property
     def center(self) -> Point2D:
+        """
+        Property that converts and returns the circle center.
+
+        Returns
+        -------
+        Point2D
+            The circle center
+        """
         return tuple(self._center)
 
     def __contains__(self, pt: Point2D) -> bool:
+        """
+        Checks whether a point lies within the convex set spanned by the circle.
+
+        Parameters
+        ----------
+        pt: Point2D
+            The point which needs to be checked.
+
+        Returns
+        -------
+        Whether pt lies in the circle.
+        """
         return np.linalg.norm(np.array(pt) - self._center) <= self.radius
 
     def __str__(self) -> str:
+        """
+        Generating human readable string representation of circles.
+
+        Returns
+        -------
+        Human readable string representation of circles.
+        """
         return f"(CENTER: {self.center}, RADIUS: {self.radius})"
 
     def __repr__(self) -> str:
+        """
+        Generating machine readable string representation of circles.
+
+        Returns
+        -------
+        Machine readable string representation of circles.
+        """
         return f"<CIRCLE: {str(self)}>"
 
     def __eq__(self, other: Any) -> bool:
+        """
+        Checks whether 2 objects are the same.
+        An object is the same iff it is a circle with the same center and radius.
+
+        Parameters
+        ----------
+        other: Any
+            The compared object.
+
+        Returns
+        -------
+        Whether they are the same circle.
+        """
         return isinstance(other, Circle) and self.center == other.center and self.radius == other.radius
 
     def __hash__(self) -> int:
+        """
+        Hashing function, hashes the 3 numbers (x, y, r).
+
+        Returns
+        -------
+        The hash generated by a unique seed.
+        """
         return hash((*self.center, self.radius))
 
 
@@ -54,6 +140,13 @@ class Polygon(Sequence):
     """
     Class for creating a 2D or 3D Polygon.
 
+    Parameters
+    ----------
+    points: List[np.ndarray]
+        The points the Polygon is made of
+    make_copy: bool
+        whether the points should be deep copied
+
     Attributes
     ----------
     _points: np.ndArray
@@ -62,7 +155,6 @@ class Polygon(Sequence):
         dimension of the polygon
     _piece_funcs: list
         list containing all between each points, _points[i] and _points[i+1]
-
     """
 
     def __init__(self, points: List[np.ndarray], make_copy: bool = True) -> None:
@@ -89,13 +181,27 @@ class Polygon(Sequence):
 
     def __getitem__(self, item: Any) -> Callable[[float], np.ndarray]:
         """
-        Throws ValueError when casting to int.
-        Throws IndexError when out of bounds.
-        And probably something else.
+        Allows us to use the index operator on the underlying vertex array.
+
+        Parameters
+        ----------
+        item: Any
+            The index
+
+        Returns
+        -------
+        The vertex at the provided index.
         """
         return self._piece_funcs[int(item)]
 
     def __len__(self) -> int:
+        """
+        Returns the number of points that make up the polygon.
+
+        Returns
+        -------
+        The number of points that make up the polygon.
+        """
         return len(self._points)
 
     def blossom(self, ts: List[float]) -> np.ndarray:
@@ -120,7 +226,13 @@ class Polygon(Sequence):
 
 
 class AbstractTriangle(ABC):
+    """
+    Abstract class representing a Triangle.
 
+    Attributes
+    ----------
+    _points: The 3 points representing a triangle.
+    """
     @abstractmethod
     def __init__(self):
         # In order to not have unresolved references to _points
@@ -128,16 +240,37 @@ class AbstractTriangle(ABC):
 
     @cached_property
     def lines(self) -> Union[List[Edge2D], List[Tuple[np.ndarray]]]:
+        """
+        Returns the 3 lines of a triangle.
+
+        Returns
+        -------
+        The 3 lines of a triangle.
+        """
         a, b, c = self.points
         return [(a, b), (b, c), (a, c)]
 
     @cached_property
     def points(self) -> Union[Tuple[Point2D, Point2D, Point2D], List[np.ndarray]]:
+        """
+        Returns the 3 points of a triangle.
+
+        Returns
+        -------
+        The 3 points of a triangle.
+        """
         # If it was mutable caching would break
         return self._points
 
     @cached_property
     def area(self) -> float:
+        """
+        Returns the area of the triangle.
+
+        Returns
+        -------
+        The area of the triangle.
+        """
         a, b, c = self.points
         # print(f"{a.shape}, {b.shape}, {c.shape}")
         return self.calc_area(np.array(a), np.array(b), np.array(c))
@@ -169,9 +302,11 @@ class AbstractTriangle(ABC):
     @cached_property
     def circumcircle(self) -> Circle:
         """
-        see: https://www.ics.uci.edu/~eppstein/junkyard/circumcenter.html
-        :return:
+        Computes the circumcircle of a triangle.
 
+        Returns
+        -------
+        The circumcircle of a triangle.
         """
         a, b, c = self.points
 
@@ -184,27 +319,87 @@ class AbstractTriangle(ABC):
         return Circle(center=center, radius=radius)
 
     def __str__(self) -> str:
+        """
+        Generating human readable string representation of triangles.
+
+        Returns
+        -------
+        Human readable string representation of triangles.
+        """
         return str(self.points)
 
     def __repr__(self) -> str:
+        """
+        Generating machine readable string representation of triangles.
+
+        Returns
+        -------
+        Machine readable string representation of triangles.
+        """
         return f"<TRIANLGE: {str(self)}>"
 
 
 class TupleTriangle(AbstractTriangle):
+    """
+    Class for creating a tuple based triangle.
+
+    Parameters
+    ----------
+    a: Point2D
+        The first point of a triangle.
+    b: Point2D
+        The second point of a triangle.
+    c: Point2D
+        The third point of a triangle.
+
+    Attributes
+    ----------
+    _points: Tuple[Point2D, Point2D, Point2D]
+        tuple containing copy of points that create the polygon
+    """
     def __init__(self, a: Point2D, b: Point2D, c: Point2D):
         self._points: Tuple[Point2D, Point2D, Point2D] = (a, b, c)
 
     def __eq__(self, other: Any) -> bool:
+        """
+        Checks whether 2 objects are the same.
+        An object is the same iff it is a triangle with the same points.
+
+        Parameters
+        ----------
+        other: Any
+            The compared object.
+
+        Returns
+        -------
+        Whether they are the same triangle.
+        """
         return isinstance(other, TupleTriangle) and sorted(self.points) == sorted(other.points)
 
     def __hash__(self) -> int:
+        """
+        Hashing function, hashes the 3 points.
+        It is sorted such that ordering does not matter.
+
+        Returns
+        -------
+        The hash generated by a unique seed.
+        """
         return hash(tuple(sorted(self.points)))
 
 
 class PolygonTriangle(Polygon, AbstractTriangle):
+    """
+    Class for creating a polygon based triangle.
 
+    Parameters
+    ----------
+    points: List[np.ndarray]
+        The points the Polygon is made of
+    make_copy: bool
+        whether the points should be deep copied
+    """
     def __init__(self, points: List[np.ndarray], make_copy: bool = True) -> None:
-        # points.append(points[0])
         # https://stackoverflow.com/a/26927718
         Polygon.__init__(self, points, make_copy)
 
@@ -233,8 +428,7 @@ class PolygonTriangle(Polygon, AbstractTriangle):
         for all points of the TupleTriangle to the plane, so that cramer's rule can easily be applied to them
         in order to calculate the calc_area of the TupleTriangle corresponding to every 3 out of the 4 points.
         But this method does not overwrite the self._points.
-
-        TODO this is allowed since we use a parallel projection, which is a proper affine transformation.
+        This is allowed since we use a parallel projection, which is a proper affine transformation.
 
         Parameters
         ----------
@@ -288,8 +482,8 @@ class PolygonTriangle(Polygon, AbstractTriangle):
     def get_bary_coords(self, p: np.ndarray) -> np.ndarray:
         """
         Calculates the barycentric coordinates of p with respect to the points defining the TupleTriangle.
-        TODO: Explizit dazuschreiben, dass wir bei rg(A) = 1 trotz Punkt auf der Hyperline verweigern weil degenerate.
-        TODO: write that p needs to have same dim.
+        Note that the rg(A) can't be 1 because this would be a degenerated case.
+        Futher note that p needs to have the same dimension.
 
         Parameters
         ----------
@@ -314,7 +508,31 @@ class PolygonTriangle(Polygon, AbstractTriangle):
 
 
 class Triangle:
+    """
+    Helper "Factory" that decides which Triangle to instanciate
+    """
     def __call__(self, a, b, c):
+        """
+        Helper method that decides which Triangle to instanciate.
+        Think of it as a constructor with less overhead.
+        Pythons call order is
+        - `__call__`
+        - `__new__`
+        - `__init__`
+
+        Parameters
+        ----------
+        a
+            The first point
+        b
+            The second point
+        c
+            The third point
+
+        Returns
+        -------
+        The appropriate triangle.
+        """
         if isinstance(a, tuple):
             return TupleTriangle(a, b, c)
         elif isinstance(a, np.ndarray):
@@ -328,10 +546,25 @@ VoronoiRegions2D = Dict[Point2D, Deque[TriangleNode]]
 
 @dataclass(frozen=True)
 class MinMaxBox:
+    """
+    Dataclass to represent a MinMaxBox, which is an axis parallel rectangle.
+
+    Attributes
+    ----------
+    min_maxs: List[float]
+        the xmin, xmax, ymin, ymax, ... (for more dimensions)
+    """
     min_maxs: List[float]  # [x_min, x_max, y_min, y_max, ...]
 
     @cached_property
     def area(self) -> float:
+        """
+        Calculates the area of the MinMaxBox.
+
+        Returns
+        -------
+        The area of the MinMaxBox.
+        """
         return math.prod([d_max - d_min for d_min, d_max in zip(self[::2], self[1::2])])
 
     @classmethod
@@ -342,28 +575,102 @@ class MinMaxBox:
         return cls(sum([[m[i, :].min(), m[i, :].max()] for i in range(m.shape[0])], []))
 
     def __getitem__(self, item) -> Union[float, List[float]]:
+        """
+        Overrides the [] operator to access the values of the underlying list.
+
+        Parameters
+        ----------
+        item: index
+            Index or slice
+
+        Returns
+        -------
+        The sliced value of the underlying list.
+        """
         return self.min_maxs.__getitem__(item)
 
     def __and__(self, other: MinMaxBox) -> Optional[MinMaxBox]:
+        """
+        Returns the intersection area of two MinMaxBoxes.
+
+        Parameters
+        ----------
+        other: MinMaxBox
+            The other MinMaxBox from which to generate the intersection.
+
+        Returns
+        -------
+        The intersection area of two MinMaxBoxes.
+        """
         return self.intersect(other)
 
     __rand__ = __and__
 
     # For iterators
     def __len__(self):
+        """
+        Returns the length of the underlying list to make iterators works.
+
+        Returns
+        -------
+        The length of the underlying list to make iterators works.
+        """
         return len(self.min_maxs)
 
     def dim(self):
+        """
+        Returns the number of box dimensions.
+
+        Returns
+        -------
+        The number of box dimensions.
+        """
         return len(self) // 2
 
     def __contains__(self, point: Tuple[float, ...]) -> bool:
+        """
+        Returns whether a point is contained in the MinMaxBox
+
+        Parameters
+        ----------
+        point: Tuple[float, ...]
+            The point. Has to be the same dimension to be contained.
+
+        Returns
+        -------
+        Whether a point is contained in the MinMaxBox
+        """
         return self.dim() == len(point) \
                and all(self[2 * i] <= point[i] <= self[(2 * i) + 1] for i in range(len(point)))
 
-    def same_dimension(self, other: MinMaxBox):
+    def same_dimension(self, other: MinMaxBox) -> bool:
+        """
+        Checks whether 2 MinMaxBoxes have the same dimension.
+
+        Parameters
+        ----------
+        other: MinMaxBox
+            The other MinMaxBox to compare to.
+
+        Returns
+        -------
+        Whether 2 MinMaxBoxes have the same dimension.
+        """
         return len(self) == len(other)
 
     def intersect(self, other: MinMaxBox) -> Optional[MinMaxBox]:
+        """
+        Returns the intersection of two MinMaxBoxes, if they intersect, otherwise None.
+
+        Parameters
+        ----------
+        other: MinMaxBox
+            The other MinMaxBox to check the intersection with.
+
+        Returns
+        -------
+        The intersection of two MinMaxBoxes, if they intersect, otherwise None.
+        """
         if not self.same_dimension(other):
             return None
         res = np.zeros((len(self),))
-- 
GitLab


From 2fea993c4b7bc45adccc427172898eb99fae8740 Mon Sep 17 00:00:00 2001
From: Lars Quentin <lars.quentin@stud.uni-goettingen.de>
Date: Fri, 8 Jul 2022 10:41:34 +0200
Subject: [PATCH 5/6] :memo: formatter

---
 curvepy/types.py | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/curvepy/types.py b/curvepy/types.py
index 0cf47bc..d3a2eaa 100644
--- a/curvepy/types.py
+++ b/curvepy/types.py
@@ -58,6 +58,7 @@ class Circle:
     radius: float
         The radius
     """
+
     def __init__(self, center: Point2D, radius: float):
         self._center = np.array(center)
         self.radius = radius
@@ -233,6 +234,7 @@ class AbstractTriangle(ABC):
     ----------
     _points: The 3 points representing a triangle.
     """
+
     @abstractmethod
     def __init__(self):
         # In order to not have unresolved references to _points
@@ -357,6 +359,7 @@ class TupleTriangle(AbstractTriangle):
     _points: Tuple[Point2D, Point2D, Point2D]
         tuple containing copy of points that create the polygon
     """
+
     def __init__(self, a: Point2D, b: Point2D, c: Point2D):
         self._points: Tuple[Point2D, Point2D, Point2D] = (a, b, c)
 
@@ -399,6 +402,7 @@ class PolygonTriangle(Polygon, AbstractTriangle):
     make_copy: bool
         whether the points should be deep copied
     """
+
     def __init__(self, points: List[np.ndarray], make_copy: bool = True) -> None:
         # https://stackoverflow.com/a/26927718
         Polygon.__init__(self, points, make_copy)
@@ -511,6 +515,7 @@ class Triangle:
     """
     Helper "Factory" that decides which Triangle to instanciate
     """
+
     def __call__(self, a, b, c):
         """
         Helper method that decides which Triangle to instanciate.
-- 
GitLab


From ca26eee8076a3d6a526efeacab002a8b7c1e3703 Mon Sep 17 00:00:00 2001
From: Lars Quentin <lars.quentin@stud.uni-goettingen.de>
Date: Fri, 8 Jul 2022 10:52:48 +0200
Subject: [PATCH 6/6] :green_heart: make CI happy?

---
 curvepy/types.py | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/curvepy/types.py b/curvepy/types.py
index d3a2eaa..8db3d2a 100644
--- a/curvepy/types.py
+++ b/curvepy/types.py
@@ -645,8 +645,7 @@ class MinMaxBox:
         -------
         Whether a point is contained in the MinMaxBox
         """
-        return self.dim() == len(point) \
-               and all(self[2 * i] <= point[i] <= self[(2 * i) + 1] for i in range(len(point)))
+        return self.dim() == len(point) and all(self[2 * i] <= point[i] <= self[(2 * i) + 1] for i in range(len(point)))
 
     def same_dimension(self, other: MinMaxBox) -> bool:
         """
-- 
GitLab