Repository: heitzmann/gdstk (language: python)

Issue:
Better allocators
Replace standard malloc with something more efficient, possibly tailored for each case (allocation of points vs properties, etc.).
This should be a good start: https://en.wikipedia.org/wiki/Slab_allocation


Discussion / hints:
Initial work merged in d9b2c96ec58a527a192628b7b829be50d0f46b4b
Tests show that the arena performs better, but more evidence (with real use-case layouts) would be preferable to the benchmarks. 
Memory usage can vary too much depending on the application and type of layout being built. That means malloc should be the best choice as a general case allocator.

I'll close this issue, but keep the interface that allows a custom allocator to be used if necessary.

Fix patch:
diff --git a/gdstk/gdstk.pyi b/gdstk/gdstk.pyi
index ce6941db1..b43fd15d2 100644
--- a/gdstk/gdstk.pyi
+++ b/gdstk/gdstk.pyi
@@ -433,6 +433,7 @@ class Polygon:
     ): ...
     def apply_repetition(self) -> list[Self]: ...
     def area(self) -> float: ...
+    def perimeter(self) -> float: ...
     def bounding_box(self) -> tuple[tuple[float, float], tuple[float, float]]: ...
     def contain(self, *points: tuple[float, float] | complex) -> bool | tuple[bool, ...]: ...
     def contain_all(self, *points: tuple[float, float] | complex) -> bool: ...
diff --git a/include/gdstk/polygon.hpp b/include/gdstk/polygon.hpp
index 02d50e396..4a44cd1a5 100644
--- a/include/gdstk/polygon.hpp
+++ b/include/gdstk/polygon.hpp
@@ -45,6 +45,9 @@ struct Polygon {
     // Polygon area excluding repetitions with sign indicating orientation
     // (positive for counter clockwise)
     double signed_area() const;
+    
+    // Total polygon perimeter including any repetitions
+    double perimeter() const;
 
     // Check if the points are inside this polygon (points lying on the edges
     // or coinciding with a vertex of the polygon are considered inside).
diff --git a/python/docstrings.cpp b/python/docstrings.cpp
index 8c156937d..1cbda1cac 100644
--- a/python/docstrings.cpp
+++ b/python/docstrings.cpp
@@ -392,6 +392,13 @@ Polygon area.
 Returns:
     Area of the polygon.)!");
 
+PyDoc_STRVAR(polygon_object_perimeter_doc, R"!(perimeter() -> float
+
+Polygon perimeter.
+
+Returns:
+    Perimeter of the polygon.)!");
+
 PyDoc_STRVAR(polygon_object_bounding_box_doc, R"!(bounding_box() -> tuple
 
 Calculate the polygon bounding box.
diff --git a/python/polygon_object.cpp b/python/polygon_object.cpp
index 2eb651a67..987d930c3 100644
--- a/python/polygon_object.cpp
+++ b/python/polygon_object.cpp
@@ -66,6 +66,11 @@ static PyObject* polygon_object_area(PolygonObject* self, PyObject*) {
     return PyFloat_FromDouble(area);
 }
 
+static PyObject* polygon_object_perimeter(PolygonObject* self, PyObject*) {
+    const double perimeter = self->polygon->perimeter();
+    return PyFloat_FromDouble(perimeter);
+}
+
 static PyObject* polygon_object_bounding_box(PolygonObject* self, PyObject*) {
     Vec2 min, max;
     self->polygon->bounding_box(min, max);
@@ -427,6 +432,7 @@ static PyMethodDef polygon_object_methods[] = {
     {"copy", (PyCFunction)polygon_object_copy, METH_NOARGS, polygon_object_copy_doc},
     {"__deepcopy__", (PyCFunction)polygon_object_deepcopy, METH_VARARGS | METH_KEYWORDS, polygon_object_deepcopy_doc},
     {"area", (PyCFunction)polygon_object_area, METH_NOARGS, polygon_object_area_doc},
+    {"perimeter", (PyCFunction)polygon_object_perimeter, METH_NOARGS, polygon_object_perimeter_doc},
     {"bounding_box", (PyCFunction)polygon_object_bounding_box, METH_NOARGS,
      polygon_object_bounding_box_doc},
     {"contain", (PyCFunction)polygon_object_contain, METH_VARARGS, polygon_object_contain_doc},
diff --git a/src/polygon.cpp b/src/polygon.cpp
index 329938cab..c436b0b59 100644
--- a/src/polygon.cpp
+++ b/src/polygon.cpp
@@ -80,6 +80,24 @@ double Polygon::signed_area() const {
     return 0.5 * result;
 }
 
+double Polygon::perimeter() const {
+    if (point_array.count < 3) return 0;
+    double result = 0;
+    Vec2* p = point_array.items;
+    Vec2 v0 = *p++;
+
+    for (uint64_t num = point_array.count - 1; num > 0; num--) {
+        Vec2 v1 = *p++ - v0;
+        result += v1.length();
+        v0 += v1;
+    }
+    result += (point_array.items[0] - point_array.items[point_array.count-1]).length();
+    if (repetition.type != RepetitionType::None) result *= repetition.get_count();
+    return result;
+}
+
+
+
 // Based on algorithm 7 from: Kai Hormann, Alexander Agathos, “The point in
 // polygon problem for arbitrary polygons,” Computational Geometry, Volume 20,
 // Issue 3, 2001, Pages 131-144, ISSN 0925-7721.


Test patch:
diff --git a/tests/polygon_test.py b/tests/polygon_test.py
index dc352a45b..914b4d701 100644
--- a/tests/polygon_test.py
+++ b/tests/polygon_test.py
@@ -34,6 +34,27 @@ def test_area_size():
     numpy.testing.assert_array_equal(poly.points, [[0, 0], [1, 0], [1, 1], [0, 1]])
 
 
+def test_perimeter_size():
+    poly1 = gdstk.Polygon([(0, 0), (2, 0), 2 + 2j, 2j])
+    assert poly1.layer == 0
+    assert poly1.datatype == 0
+    assert poly1.size == 4
+    assert poly1.perimeter() == 8.0
+    numpy.testing.assert_array_equal(poly1.points, [[0, 0], [2, 0], [2, 2], [0, 2]])
+
+    poly2 = gdstk.Polygon([(0, 0), (1, 0), 1 + 1j, 1j], 1, 2)
+    assert poly2.layer == 1
+    assert poly2.datatype == 2
+    assert poly2.size == 4
+    assert poly2.perimeter() == 4.0
+    numpy.testing.assert_array_equal(poly2.points, [[0, 0], [1, 0], [1, 1], [0, 1]])
+
+    poly3 = gdstk.Polygon([(0, 0), (1, 0), 1 + 1j, 1j])
+    assert poly3.size == 4
+    assert poly3.perimeter() == 4.0
+    numpy.testing.assert_array_equal(poly3.points, [[0, 0], [1, 0], [1, 1], [0, 1]])
+
+
 def test_bounding_box():
     poly = gdstk.Polygon([-1 + 0j, -2j, 3 + 0j, 4j])
     assert poly.bounding_box() == ((-1, -2), (3, 4))
@@ -71,6 +92,7 @@ def test_copy():
     numpy.testing.assert_array_equal(p1.points, points)
     numpy.testing.assert_array_equal(p2.points, points)
 
+
 def test_deepcopy():
     points = [[-1, 0], [0, -2], [3, 0], [0, 4]]
     p1 = gdstk.Polygon(points, 5, 6)