From 6644ae94b5d765aea673c0810802cc6d3da1d889 Mon Sep 17 00:00:00 2001 From: janbridley Date: Mon, 29 Jan 2024 15:11:46 -0500 Subject: [PATCH 01/11] Added new to_hoomd method --- coxeter/__init__.py | 2 +- coxeter/shapes/convex_spheropolygon.py | 39 + coxeter/shapes/convex_spheropolyhedron.py | 36 + coxeter/shapes/ellipsoid.py | 42 + coxeter/shapes/polygon.py | 39 + coxeter/shapes/polyhedron.py | 42 + coxeter/shapes/sphere.py | 36 + doc/source/conf.py | 4 +- find_coplanar.ipynb | 540 ++++ pyproject.toml | 2 +- setup.cfg | 2 +- temp.ipynb | 1580 ++++++++++ test.ipynb | 3310 +++++++++++++++++++++ test_families.ipynb | 231 ++ test_sorting.ipynb | 212 ++ tests/test_ellipsoid.py | 17 + tests/test_polygon.py | 18 + tests/test_polyhedron.py | 19 + tests/test_sphere.py | 15 + tests/test_spheropolyhedron.py | 20 +- 20 files changed, 6200 insertions(+), 6 deletions(-) create mode 100644 find_coplanar.ipynb create mode 100644 temp.ipynb create mode 100644 test.ipynb create mode 100644 test_families.ipynb create mode 100644 test_sorting.ipynb diff --git a/coxeter/__init__.py b/coxeter/__init__.py index 15647bfa..b4d82e49 100644 --- a/coxeter/__init__.py +++ b/coxeter/__init__.py @@ -21,4 +21,4 @@ __all__ = ["families", "shapes", "from_gsd_type_shapes"] -__version__ = "0.7.0" +__version__ = "0.8.0" diff --git a/coxeter/shapes/convex_spheropolygon.py b/coxeter/shapes/convex_spheropolygon.py index 7de3ec0c..6bcd3e69 100644 --- a/coxeter/shapes/convex_spheropolygon.py +++ b/coxeter/shapes/convex_spheropolygon.py @@ -267,3 +267,42 @@ def _plato_primitive(self, backend): vertices=verts[:, :2], radius=self.radius, ) + + def to_hoomd(self): + """Get a json-serializable subset of ConvexSpheropolygon properties. + + The json-serializable output of the to_hoomd method can be directly imported + into data management tools like Signac. This data can then be queried for use in + HOOMD simulations. Key naming matches HOOMD integrators: for example, the + moment_inertia key links to data from coxeter's inertia_tensor. + + For a ConvexSpheropolygon, the following properties are stored: + + * vertices (list(list)): + The vertices of the shape. + * centroid (list(float)) + The centroid of the shape. + This is set to [0,0,0] to improve HOOMD performance. + * sweep_radius (float): + The rounding radius of the shape. + * area (float) + The area of the shape. + * moment_inertia (list(list)) + The shape's inertia tensor. + + Returns + ------- + dict + Dict containing a subset of shape properties. + """ + old_centroid = self.centroid + self.centroid = np.array([0, 0, 0]) + hoomd_dict = { + "vertices": self.vertices.tolist(), + "centroid": self.centroid.tolist(), + "sweep_radius": self.radius, + "area": self.area, + "moment_inertia": self.inertia_tensor.tolist(), + } + self.centroid = old_centroid + return hoomd_dict diff --git a/coxeter/shapes/convex_spheropolyhedron.py b/coxeter/shapes/convex_spheropolyhedron.py index bd112740..04b083b8 100644 --- a/coxeter/shapes/convex_spheropolyhedron.py +++ b/coxeter/shapes/convex_spheropolyhedron.py @@ -304,3 +304,39 @@ def _plato_primitive(self, backend): vertices=self.vertices, radius=self.radius, ) + + def to_hoomd(self): + """Get a json-serializable subset of ConvexSpheropolyhedron properties. + + The json-serializable output of the to_hoomd method can be directly imported + into data management tools like Signac. This data can then be queried for use in + HOOMD simulations. Key naming matches HOOMD integrators: for example, the + moment_inertia key links to data from coxeter's inertia_tensor. + + For a ConvexSpheropolyhedron, the following properties are stored: + + * vertices (list(list)): + The vertices of the shape. + * centroid (list(float)) + The centroid of the shape. + This is set to [0,0,0] to improve HOOMD performance. + * sweep_radius (float): + The rounding radius of the shape. + * volume (float) + The volume of the shape. + + Returns + ------- + dict + Dict containing a subset of shape properties. + """ + old_centroid = self._polyhedron.centroid + self._polyhedron.centroid = np.array([0, 0, 0]) + hoomd_dict = { + "vertices": self.vertices.tolist(), + "centroid": self._polyhedron.centroid.tolist(), + "sweep_radius": self.radius, + "volume": self.volume, + } + self._polyhedron.centroid = old_centroid + return hoomd_dict diff --git a/coxeter/shapes/ellipsoid.py b/coxeter/shapes/ellipsoid.py index 18799e52..8399737e 100644 --- a/coxeter/shapes/ellipsoid.py +++ b/coxeter/shapes/ellipsoid.py @@ -225,3 +225,45 @@ def __repr__(self): f"coxeter.shapes.Ellipsoid(a={self.a}, b={self.b}, c={self.c}, " f"center={self.centroid.tolist()})" ) + + def to_hoomd(self): + """Get a json-serializable subset of Ellipsoid properties. + + The json-serializable output of the to_hoomd method can be directly imported + into data management tools like Signac. This data can then be queried for use in + HOOMD simulations. Key naming matches HOOMD integrators: for example, the + moment_inertia key links to data from coxeter's inertia_tensor. + + For an Ellipsoid, the following properties are stored: + + * a (float): + half axis of ellipsoid in the x direction + * b (float): + half axis of ellipsoid in the y direction + * c (float): + half axis of ellipsoid in the z direction + * centroid (list(float)) + The centroid of the shape. + This is set to [0,0,0] to improve HOOMD performance. + * volume (float) + The volume of the shape. + * moment_inertia (list(list)) + The shape's inertia tensor. + + Returns + ------- + dict + Dict containing a subset of shape properties. + """ + old_centroid = self.centroid + self.centroid = np.array([0, 0, 0]) + hoomd_dict = { + "a": self.a, + "b": self.b, + "c": self.c, + "centroid": self.centroid.tolist(), + "volume": self.volume, + "moment_inertia": self.inertia_tensor.tolist(), + } + self.centroid = old_centroid + return hoomd_dict diff --git a/coxeter/shapes/polygon.py b/coxeter/shapes/polygon.py index 0e16f759..b23feb89 100644 --- a/coxeter/shapes/polygon.py +++ b/coxeter/shapes/polygon.py @@ -761,3 +761,42 @@ def _plato_primitive(self, backend): colors=np.array([[0.5, 0.5, 0.5, 1]]), vertices=verts[:, :2], ) + + def to_hoomd(self): + """Get a json-serializable subset of Polygon properties. + + The json-serializable output of the to_hoomd method can be directly imported + into data management tools like Signac. This data can then be queried for use in + HOOMD simulations. Key naming matches HOOMD integrators: for example, the + moment_inertia key links to data from coxeter's inertia_tensor. + + For a Polygon or ConvexPolygon, the following properties are stored: + + * vertices (list(list)): + The vertices of the shape. + * centroid (list(float)) + The centroid of the shape. + This is set to [0,0,0] to improve HOOMD performance. + * sweep_radius (float): + The rounding radius of the shape (0.0). + * area (float) + The area of the shape. + * moment_inertia (list(list)) + The shape's inertia tensor. + + Returns + ------- + dict + Dict containing a subset of shape properties. + """ + old_centroid = self.centroid + self.centroid = np.array([0, 0, 0]) + hoomd_dict = { + "vertices": self.vertices.tolist(), + "centroid": self.centroid.tolist(), + "sweep_radius": 0.0, + "area": self.area, + "moment_inertia": self.inertia_tensor.tolist(), + } + self.centroid = old_centroid + return hoomd_dict diff --git a/coxeter/shapes/polyhedron.py b/coxeter/shapes/polyhedron.py index 155fae7b..2b92558e 100644 --- a/coxeter/shapes/polyhedron.py +++ b/coxeter/shapes/polyhedron.py @@ -973,3 +973,45 @@ def _plato_primitive(self, backend): indices=self.faces, shape_colors=np.array([[0.5, 0.5, 0.5, 1]]), ) + + def to_hoomd(self): + """Get a json-serializable subset of Polyhedron properties. + + The json-serializable output of the to_hoomd method can be directly imported + into data management tools like Signac. This data can then be queried for use in + HOOMD simulations. Key naming matches HOOMD integrators: for example, the + moment_inertia key links to data from coxeter's inertia_tensor. + + For a Polyhedron or ConvexPolyhedron, the following properties are stored: + + * vertices (list(list)): + The vertices of the shape. + * faces (list(list)): + The faces of the shape. + * centroid (list(float)) + The centroid of the shape. + This is set to [0,0,0] to improve HOOMD performance. + * sweep_radius (float): + The rounding radius of the shape (0.0). + * volume (float) + The volume of the shape. + * moment_inertia (list(list)) + The shape's inertia tensor. + + Returns + ------- + dict + Dict containing a subset of shape properties. + """ + old_centroid = self.centroid + self.centroid = np.array([0, 0, 0]) + hoomd_dict = { + "vertices": self.vertices.tolist(), + "faces": [face.tolist() for face in self.faces], + "centroid": self.centroid.tolist(), + "sweep_radius": 0.0, + "volume": self.volume, + "moment_inertia": self.inertia_tensor.tolist(), + } + self.centroid = old_centroid + return hoomd_dict diff --git a/coxeter/shapes/sphere.py b/coxeter/shapes/sphere.py index f0f92c59..74d16c47 100644 --- a/coxeter/shapes/sphere.py +++ b/coxeter/shapes/sphere.py @@ -202,3 +202,39 @@ def _plato_primitive(self, backend): colors=np.array([[0.5, 0.5, 0.5, 1]]), radii=[self.radius], ) + + def to_hoomd(self): + """Get a dict of json-serializable subset of Sphere properties. + + The json-serializable output of the to_hoomd method can be directly imported + into data management tools like Signac. This data can then be queried for use in + HOOMD simulations. Key naming matches HOOMD integrators: for example, the + moment_inertia key links to data from coxeter's inertia_tensor. + + For a Sphere, the following properties are stored: + + * diameter (float): + The diameter of the sphere, equal to twice the radius. + * centroid (list(float)) + The centroid of the shape. + This is set to [0,0,0] to improve HOOMD performance. + * volume (float) + The volume of the shape. + * moment_inertia (list(list)) + The shape's inertia tensor. + + Returns + ------- + dict + Dict containing a subset of shape properties. + """ + old_centroid = self.centroid + self.centroid = np.array([0, 0, 0]) + hoomd_dict = { + "diameter": self.radius * 2, + "centroid": self.centroid.tolist(), + "volume": self.volume, + "moment_inertia": self.inertia_tensor.tolist(), + } + self.centroid = old_centroid + return hoomd_dict diff --git a/doc/source/conf.py b/doc/source/conf.py index 89cd9283..b4c3151a 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -22,8 +22,8 @@ author = "Vyas Ramasubramani" # The full version, including alpha/beta/rc tags -version = "0.7.0" -release = "0.7.0" +version = "0.8.0" +release = "0.8.0" # -- General configuration --------------------------------------------------- diff --git a/find_coplanar.ipynb b/find_coplanar.ipynb new file mode 100644 index 00000000..292612ce --- /dev/null +++ b/find_coplanar.ipynb @@ -0,0 +1,540 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from scipy.spatial import ConvexHull as ch\n", + "from scipy.spatial import Delaunay\n", + "\n", + "from coxeter.families import (\n", + " ArchimedeanFamily as archfam,\n", + ")\n", + "from coxeter.families import (\n", + " JohnsonFamily as johnfam,\n", + ")\n", + "from coxeter.families import (\n", + " PlatonicFamily as platfam,\n", + ")\n", + "from coxeter.shapes import ConvexPolyhedron" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# DISCUSSION : find coplanar is pretty close to optimal. What I have written is an O(N) algorithm, so even though it\n", + "# does not use numpy it is very fast." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "cube = platfam.get_shape(\"Cube\")\n", + "tetr = platfam.get_shape(\"Tetrahedron\")\n", + "mbri = johnfam.get_shape(\"Metabidiminished Rhombicosidodecahedron\")\n", + "icosi = archfam.get_shape(\"Icosidodecahedron\")\n", + "poly = mbri" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ True, False, False, ..., False, False, False],\n", + " [False, True, False, ..., False, False, False],\n", + " [False, False, True, ..., False, False, False],\n", + " ...,\n", + " [False, False, False, ..., True, False, False],\n", + " [False, False, False, ..., False, True, True],\n", + " [False, False, False, ..., False, True, True]])" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hull = ch(poly.vertices, \"\")\n", + "normals = hull.equations[:, :3]\n", + "boolean_coplanars = np.all(normals[:, None] == normals, axis=-1)\n", + "boolean_coplanars" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "rows = np.arange(0, hull.nsimplex)\n", + "coplanarray = np.tile(np.arange(0, hull.nsimplex), (hull.nsimplex, 1))\n", + "# coplanarray" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "271 µs ± 14.5 µs per loop (mean ± std. dev. of 10 runs, 1,000 loops each)\n" + ] + } + ], + "source": [ + "%%timeit -r 10 -n 1000\n", + "boolean_coplanars = np.all(normals[:, None] == normals, axis=-1)\n", + "rows = np.arange(0, hull.nsimplex)\n", + "coplanar_simplices = {tuple(rows[boolean_coplanars[i]]) for i in range(hull.nsimplex)}\n", + "# Do we need to guarantee that the order of coplanars is the same\n", + "coplanar_simplices" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(96,\n", + " [array([0]),\n", + " array([1]),\n", + " array([2]),\n", + " array([3]),\n", + " array([4]),\n", + " array([5]),\n", + " array([6]),\n", + " array([7]),\n", + " array([8]),\n", + " array([9]),\n", + " array([10, 11]),\n", + " array([10, 11]),\n", + " array([12, 13]),\n", + " array([12, 13]),\n", + " array([14, 15, 16]),\n", + " array([14, 15, 16]),\n", + " array([14, 15, 16]),\n", + " array([17, 18]),\n", + " array([17, 18]),\n", + " array([19, 20]),\n", + " array([19, 20]),\n", + " array([21, 22]),\n", + " array([21, 22]),\n", + " array([23, 24]),\n", + " array([23, 24]),\n", + " array([25, 26]),\n", + " array([25, 26]),\n", + " array([27, 28, 29]),\n", + " array([27, 28, 29]),\n", + " array([27, 28, 29]),\n", + " array([30, 31]),\n", + " array([30, 31]),\n", + " array([32, 33, 34]),\n", + " array([32, 33, 34]),\n", + " array([32, 33, 34]),\n", + " array([35, 36]),\n", + " array([35, 36]),\n", + " array([37, 38, 39]),\n", + " array([37, 38, 39]),\n", + " array([37, 38, 39]),\n", + " array([40, 41]),\n", + " array([40, 41]),\n", + " array([42, 43]),\n", + " array([42, 43]),\n", + " array([44, 45, 46]),\n", + " array([44, 45, 46]),\n", + " array([44, 45, 46]),\n", + " array([47, 48]),\n", + " array([47, 48]),\n", + " array([49, 50, 51]),\n", + " array([49, 50, 51]),\n", + " array([49, 50, 51]),\n", + " array([52, 53]),\n", + " array([52, 53]),\n", + " array([54, 55]),\n", + " array([54, 55]),\n", + " array([56, 57, 58]),\n", + " array([56, 57, 58]),\n", + " array([56, 57, 58]),\n", + " array([59, 60]),\n", + " array([59, 60]),\n", + " array([61, 62, 63]),\n", + " array([61, 62, 63]),\n", + " array([61, 62, 63]),\n", + " array([64, 65]),\n", + " array([64, 65]),\n", + " array([66, 67]),\n", + " array([66, 67]),\n", + " array([68, 69]),\n", + " array([68, 69]),\n", + " array([70, 71, 72, 73, 74, 75, 76, 77]),\n", + " array([70, 71, 72, 73, 74, 75, 76, 77]),\n", + " array([70, 71, 72, 73, 74, 75, 76, 77]),\n", + " array([70, 71, 72, 73, 74, 75, 76, 77]),\n", + " array([70, 71, 72, 73, 74, 75, 76, 77]),\n", + " array([70, 71, 72, 73, 74, 75, 76, 77]),\n", + " array([70, 71, 72, 73, 74, 75, 76, 77]),\n", + " array([70, 71, 72, 73, 74, 75, 76, 77]),\n", + " array([78, 79, 80]),\n", + " array([78, 79, 80]),\n", + " array([78, 79, 80]),\n", + " array([81, 82]),\n", + " array([81, 82]),\n", + " array([83, 84, 85, 86, 87, 88, 89, 90]),\n", + " array([83, 84, 85, 86, 87, 88, 89, 90]),\n", + " array([83, 84, 85, 86, 87, 88, 89, 90]),\n", + " array([83, 84, 85, 86, 87, 88, 89, 90]),\n", + " array([83, 84, 85, 86, 87, 88, 89, 90]),\n", + " array([83, 84, 85, 86, 87, 88, 89, 90]),\n", + " array([83, 84, 85, 86, 87, 88, 89, 90]),\n", + " array([83, 84, 85, 86, 87, 88, 89, 90]),\n", + " array([91, 92, 93]),\n", + " array([91, 92, 93]),\n", + " array([91, 92, 93]),\n", + " array([94, 95]),\n", + " array([94, 95])])" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "boolean_coplanars = np.all(normals[:, None] == normals, axis=-1)\n", + "simplex_indices = np.arange(0, hull.nsimplex)\n", + "coplanar_simplices = [\n", + " simplex_indices[boolean_coplanars[i]] for i in range(hull.nsimplex)\n", + "]\n", + "# We need to guarantee that the ORDER of coplanar simplices is identical to the original order\n", + "len(coplanar_simplices), coplanar_simplices" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ True, False, False, ..., False, False, False],\n", + " [False, True, False, ..., False, False, False],\n", + " [False, False, True, ..., False, False, False],\n", + " ...,\n", + " [False, False, False, ..., True, False, False],\n", + " [False, False, False, ..., False, True, True],\n", + " [False, False, False, ..., False, True, True]])" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "boolean_coplanars" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "# simplex_indices[:, None][boolean_coplanars]" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "nanarray = np.full_like(boolean_coplanars, np.nan, dtype=float)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[ 0. nan nan ... nan nan nan]\n", + " [nan 1. nan ... nan nan nan]\n", + " [nan nan 2. ... nan nan nan]\n", + " ...\n", + " [nan nan nan ... 93. nan nan]\n", + " [nan nan nan ... nan 94. 95.]\n", + " [nan nan nan ... nan 94. 95.]]\n" + ] + } + ], + "source": [ + "print(np.where(boolean_coplanars, coplanarray, np.nan))" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "6.75 µs ± 224 ns per loop (mean ± std. dev. of 10 runs, 100,000 loops each)\n" + ] + } + ], + "source": [ + "%timeit -r 10 -n 100000 np.where(boolean_coplanars, coplanarray, np.nan)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[-1, -1, -1, ..., -1, 94, 95],\n", + " [-1, -1, -1, ..., 93, -1, -1],\n", + " [-1, -1, -1, ..., -1, -1, -1],\n", + " ...,\n", + " [-1, -1, 2, ..., -1, -1, -1],\n", + " [-1, 1, -1, ..., -1, -1, -1],\n", + " [ 0, -1, -1, ..., -1, -1, -1]])" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "nancop = np.where(boolean_coplanars, coplanarray, -1)\n", + "\n", + "nancop = np.unique(nancop, axis=0)\n", + "nancop" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "# %timeit -r 10 -n 10000\n", + "# [set(nancop[i]) - {-1} for i in range(hull.nsimplex)]" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 0, 0, 0, ..., 0, 0, 0],\n", + " [ 0, 1, 0, ..., 0, 0, 0],\n", + " [ 0, 0, 2, ..., 0, 0, 0],\n", + " ...,\n", + " [ 0, 0, 0, ..., 93, 0, 0],\n", + " [ 0, 0, 0, ..., 0, 94, 95],\n", + " [ 0, 0, 0, ..., 0, 94, 95]])" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "boolean_coplanars * simplex_indices" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,\n", + " 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,\n", + " 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,\n", + " 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67,\n", + " 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84,\n", + " 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95])" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "simplex_indices" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 1, 0, 0, ..., 0, 0, 0],\n", + " [ 0, 2, 0, ..., 0, 0, 0],\n", + " [ 0, 0, 3, ..., 0, 0, 0],\n", + " ...,\n", + " [ 0, 0, 0, ..., 94, 0, 0],\n", + " [ 0, 0, 0, ..., 0, 95, 96],\n", + " [ 0, 0, 0, ..., 0, 95, 96]])" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(simplex_indices + 1) * boolean_coplanars" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "811 µs ± 58.2 µs per loop (mean ± std. dev. of 10 runs, 1,000 loops each)\n" + ] + } + ], + "source": [ + "%%timeit -r 10 -n 1000\n", + "_, idx = np.unique(boolean_coplanars, axis=0, return_index=True)\n", + "boolean_coplanars[np.sort(idx)]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "209 µs ± 2.23 µs per loop (mean ± std. dev. of 10 runs, 1,000 loops each)\n" + ] + } + ], + "source": [ + "%%timeit -r 10 -n 1000\n", + "mbri._find_coplanar_simplices()" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "ename": "TypeError", + "evalue": "'float' object cannot be interpreted as an integer", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m/Users/jenbradley/github/coxeter/find_coplanar.ipynb Cell 21\u001b[0m line \u001b[0;36m1\n\u001b[0;32m----> 1\u001b[0m cube\u001b[39m.\u001b[39;49mvertices\u001b[39m.\u001b[39;49mround(\u001b[39m1e-15\u001b[39;49m)\n", + "\u001b[0;31mTypeError\u001b[0m: 'float' object cannot be interpreted as an integer" + ] + } + ], + "source": [ + "cube.vertices.round(1e-15)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/pyproject.toml b/pyproject.toml index c371a743..d9d107c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "coxeter" -version = "0.7.0" +version = "0.8.0" requires-python = ">=3.8" description = "Tools for creating and manipulating shapes." readme = "README.rst" diff --git a/setup.cfg b/setup.cfg index 6c561a6b..cbee67bd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.7.0 +current_version = 0.8.0 commit = False tag = True message = Bump up to version {new_version}. diff --git a/temp.ipynb b/temp.ipynb new file mode 100644 index 00000000..90210eb0 --- /dev/null +++ b/temp.ipynb @@ -0,0 +1,1580 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from pyutils.polyutils import *\n", + "from scipy.spatial import ConvexHull as ch\n", + "from scipy.spatial import Delaunay\n", + "\n", + "import coxeter\n", + "from coxeter.families import (\n", + " ArchimedeanFamily as archfam,\n", + ")\n", + "from coxeter.families import (\n", + " JohnsonFamily as johnfam,\n", + ")\n", + "from coxeter.families import (\n", + " PlatonicFamily as platfam,\n", + ")\n", + "from coxeter.shapes import ConvexPolyhedron, Polyhedron\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "cube = platfam.get_shape(\"Cube\")\n", + "# tetr = platfam.get_shape(\"Tetrahedron\")\n", + "mbri = johnfam.get_shape(\"Metabidiminished Rhombicosidodecahedron\")\n", + "# icosi = archfam.get_shape(\"Icosidodecahedron\")\n", + "# poly = cube" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "archs = get_shortcodes()\n", + "# archs" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[-0. , -0. , -1. , -0.5],\n", + " [ 0. , -1. , 0. , -0.5],\n", + " [ 1. , -0. , -0. , -0.5],\n", + " [-1. , -0. , -0. , -0.5],\n", + " [ 0. , 1. , -0. , -0.5],\n", + " [-0. , -0. , 1. , -0.5]])" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cube.equations" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'self' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m/Users/jenbradley/github/coxeter/temp.ipynb Cell 4\u001b[0m line \u001b[0;36m1\n\u001b[0;32m----> 1\u001b[0m simplex_normals \u001b[39m=\u001b[39m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39m_simplex_equations[:, :\u001b[39m3\u001b[39m]\n\u001b[1;32m 2\u001b[0m \u001b[39m# Generate boolean array checking which simplices share a normal (within tolerance)\u001b[39;00m\n\u001b[1;32m 3\u001b[0m is_coplanar \u001b[39m=\u001b[39m np\u001b[39m.\u001b[39mall(\n\u001b[1;32m 4\u001b[0m np\u001b[39m.\u001b[39mabs(simplex_normals[:, \u001b[39mNone\u001b[39;00m] \u001b[39m-\u001b[39m simplex_normals) \u001b[39m<\u001b[39m tol, axis\u001b[39m=\u001b[39m\u001b[39m2\u001b[39m\n\u001b[1;32m 5\u001b[0m )\n", + "\u001b[0;31mNameError\u001b[0m: name 'self' is not defined" + ] + } + ], + "source": [ + "simplex_normals = self._simplex_equations[:, :3]\n", + "# Generate boolean array checking which simplices share a normal (within tolerance)\n", + "is_coplanar = np.all(np.abs(simplex_normals[:, None] - simplex_normals) < tol, axis=2)\n", + "# Convert boolean array into ragged list of coplanar simplices\n", + "coplanar_simplices = list({tuple(np.where(coplanar)[0]) for coplanar in is_coplanar})\n", + "faces = []\n", + "ordered_simplices = []\n", + "simplex_equation_indices = []\n", + "for face_simplex_indices in coplanar_simplices:\n", + " # Get unique vertex indices from faces and convert to numpy array\n", + " faces.append(\n", + " np.fromiter(set(self.simplices[[face_simplex_indices]].flat), np.int32)\n", + " )\n", + " # Add simplex indices back into list that matches face ordering\n", + " ordered_simplices.extend(face_simplex_indices)\n", + " simplex_equation_indices.append(face_simplex_indices[0])\n", + "\n", + "# Assign faces and associated data to variables\n", + "self._faces = faces\n", + "self._coplanar_simplices = coplanar_simplices\n", + "self._equations = self._simplex_equations[simplex_equation_indices]\n", + "\n", + "# Reorder list of simplices (and related properties) to match the list of faces\n", + "self._simplices = self._simplices[ordered_simplices]\n", + "self._simplex_equations = self._simplex_equations[ordered_simplices]\n", + "self._simplex_neighbors = self._simplex_neighbors[ordered_simplices]" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[81, 82, 2, 5, 35, 36, 8, 12, 13, 21, 22, 27, 28, 29, 40, 41, 61, 62, 63, 17, 18, 70, 71, 72, 73, 74, 75, 76, 77, 49, 50, 51, 68, 69, 78, 79, 80, 54, 55, 64, 65, 83, 84, 85, 86, 87, 88, 89, 90, 4, 1, 7, 59, 60, 56, 57, 58, 23, 24, 10, 11, 19, 20, 14, 15, 16, 47, 48, 0, 42, 43, 32, 33, 34, 37, 38, 39, 3, 9, 44, 45, 46, 6, 52, 53, 91, 92, 93, 94, 95, 66, 67, 30, 31, 25, 26]\n" + ] + } + ], + "source": [ + "poly = mbri\n", + "# for poly, name in [findpoly(a) for a in archs]:\n", + "# poly = mbri\n", + "tol = 1e-14\n", + "simplex_normals = poly._simplex_equations\n", + "# Generate boolean array checking which simplices share a normal (within tolerance)\n", + "is_coplanar = np.all(np.abs(simplex_normals[:, None] - simplex_normals) < tol, axis=2)\n", + "# Convert boolean array into ragged list of coplanar simplices\n", + "coplanar_simplices = list({tuple(np.where(coplanar)[0]) for coplanar in is_coplanar})\n", + "faces = []\n", + "ordered_simplices = []\n", + "simplex_equation_indices = []\n", + "for face_simplex_indices in coplanar_simplices:\n", + " # Get unique vertex indices from faces and convert to numpy array\n", + " faces.append(\n", + " np.fromiter(set(poly.simplices[[face_simplex_indices]].flat), np.int32)\n", + " )\n", + " # Add simplex indices back into list that matches face ordering\n", + " ordered_simplices.extend(face_simplex_indices)\n", + " simplex_equation_indices.append(face_simplex_indices[0])\n", + "assert {tuple(sorted(i)) for i in faces} == {tuple(sorted(i)) for i in poly.faces}\n", + "print(ordered_simplices)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 0, 0],\n", + " [ 1, 1],\n", + " [ 2, 2],\n", + " [ 3, 3],\n", + " [ 4, 4],\n", + " [ 5, 5],\n", + " [ 6, 6],\n", + " [ 7, 7],\n", + " [ 8, 8],\n", + " [ 9, 9],\n", + " [10, 10],\n", + " [10, 11],\n", + " [11, 10],\n", + " [11, 11],\n", + " [12, 12],\n", + " [12, 13],\n", + " [13, 12],\n", + " [13, 13],\n", + " [14, 14],\n", + " [14, 15],\n", + " [14, 16],\n", + " [15, 14],\n", + " [15, 15],\n", + " [15, 16],\n", + " [16, 14],\n", + " [16, 15],\n", + " [16, 16],\n", + " [17, 17],\n", + " [17, 18],\n", + " [18, 17],\n", + " [18, 18],\n", + " [19, 19],\n", + " [19, 20],\n", + " [20, 19],\n", + " [20, 20],\n", + " [21, 21],\n", + " [21, 22],\n", + " [22, 21],\n", + " [22, 22],\n", + " [23, 23],\n", + " [23, 24],\n", + " [24, 23],\n", + " [24, 24],\n", + " [25, 25],\n", + " [25, 26],\n", + " [26, 25],\n", + " [26, 26],\n", + " [27, 27],\n", + " [27, 28],\n", + " [27, 29],\n", + " [28, 27],\n", + " [28, 28],\n", + " [28, 29],\n", + " [29, 27],\n", + " [29, 28],\n", + " [29, 29],\n", + " [30, 30],\n", + " [30, 31],\n", + " [31, 30],\n", + " [31, 31],\n", + " [32, 32],\n", + " [32, 33],\n", + " [32, 34],\n", + " [33, 32],\n", + " [33, 33],\n", + " [33, 34],\n", + " [34, 32],\n", + " [34, 33],\n", + " [34, 34],\n", + " [35, 35],\n", + " [35, 36],\n", + " [36, 35],\n", + " [36, 36],\n", + " [37, 37],\n", + " [37, 38],\n", + " [37, 39],\n", + " [38, 37],\n", + " [38, 38],\n", + " [38, 39],\n", + " [39, 37],\n", + " [39, 38],\n", + " [39, 39],\n", + " [40, 40],\n", + " [40, 41],\n", + " [41, 40],\n", + " [41, 41],\n", + " [42, 42],\n", + " [42, 43],\n", + " [43, 42],\n", + " [43, 43],\n", + " [44, 44],\n", + " [44, 45],\n", + " [44, 46],\n", + " [45, 44],\n", + " [45, 45],\n", + " [45, 46],\n", + " [46, 44],\n", + " [46, 45],\n", + " [46, 46],\n", + " [47, 47],\n", + " [47, 48],\n", + " [48, 47],\n", + " [48, 48],\n", + " [49, 49],\n", + " [49, 50],\n", + " [49, 51],\n", + " [50, 49],\n", + " [50, 50],\n", + " [50, 51],\n", + " [51, 49],\n", + " [51, 50],\n", + " [51, 51],\n", + " [52, 52],\n", + " [52, 53],\n", + " [53, 52],\n", + " [53, 53],\n", + " [54, 54],\n", + " [54, 55],\n", + " [55, 54],\n", + " [55, 55],\n", + " [56, 56],\n", + " [56, 57],\n", + " [56, 58],\n", + " [57, 56],\n", + " [57, 57],\n", + " [57, 58],\n", + " [58, 56],\n", + " [58, 57],\n", + " [58, 58],\n", + " [59, 59],\n", + " [59, 60],\n", + " [60, 59],\n", + " [60, 60],\n", + " [61, 61],\n", + " [61, 62],\n", + " [61, 63],\n", + " [62, 61],\n", + " [62, 62],\n", + " [62, 63],\n", + " [63, 61],\n", + " [63, 62],\n", + " [63, 63],\n", + " [64, 64],\n", + " [64, 65],\n", + " [65, 64],\n", + " [65, 65],\n", + " [66, 66],\n", + " [66, 67],\n", + " [67, 66],\n", + " [67, 67],\n", + " [68, 68],\n", + " [68, 69],\n", + " [69, 68],\n", + " [69, 69],\n", + " [70, 70],\n", + " [70, 71],\n", + " [70, 72],\n", + " [70, 73],\n", + " [70, 74],\n", + " [70, 75],\n", + " [70, 76],\n", + " [70, 77],\n", + " [71, 70],\n", + " [71, 71],\n", + " [71, 72],\n", + " [71, 73],\n", + " [71, 74],\n", + " [71, 75],\n", + " [71, 76],\n", + " [71, 77],\n", + " [72, 70],\n", + " [72, 71],\n", + " [72, 72],\n", + " [72, 73],\n", + " [72, 74],\n", + " [72, 75],\n", + " [72, 76],\n", + " [72, 77],\n", + " [73, 70],\n", + " [73, 71],\n", + " [73, 72],\n", + " [73, 73],\n", + " [73, 74],\n", + " [73, 75],\n", + " [73, 76],\n", + " [73, 77],\n", + " [74, 70],\n", + " [74, 71],\n", + " [74, 72],\n", + " [74, 73],\n", + " [74, 74],\n", + " [74, 75],\n", + " [74, 76],\n", + " [74, 77],\n", + " [75, 70],\n", + " [75, 71],\n", + " [75, 72],\n", + " [75, 73],\n", + " [75, 74],\n", + " [75, 75],\n", + " [75, 76],\n", + " [75, 77],\n", + " [76, 70],\n", + " [76, 71],\n", + " [76, 72],\n", + " [76, 73],\n", + " [76, 74],\n", + " [76, 75],\n", + " [76, 76],\n", + " [76, 77],\n", + " [77, 70],\n", + " [77, 71],\n", + " [77, 72],\n", + " [77, 73],\n", + " [77, 74],\n", + " [77, 75],\n", + " [77, 76],\n", + " [77, 77],\n", + " [78, 78],\n", + " [78, 79],\n", + " [78, 80],\n", + " [79, 78],\n", + " [79, 79],\n", + " [79, 80],\n", + " [80, 78],\n", + " [80, 79],\n", + " [80, 80],\n", + " [81, 81],\n", + " [81, 82],\n", + " [82, 81],\n", + " [82, 82],\n", + " [83, 83],\n", + " [83, 84],\n", + " [83, 85],\n", + " [83, 86],\n", + " [83, 87],\n", + " [83, 88],\n", + " [83, 89],\n", + " [83, 90],\n", + " [84, 83],\n", + " [84, 84],\n", + " [84, 85],\n", + " [84, 86],\n", + " [84, 87],\n", + " [84, 88],\n", + " [84, 89],\n", + " [84, 90],\n", + " [85, 83],\n", + " [85, 84],\n", + " [85, 85],\n", + " [85, 86],\n", + " [85, 87],\n", + " [85, 88],\n", + " [85, 89],\n", + " [85, 90],\n", + " [86, 83],\n", + " [86, 84],\n", + " [86, 85],\n", + " [86, 86],\n", + " [86, 87],\n", + " [86, 88],\n", + " [86, 89],\n", + " [86, 90],\n", + " [87, 83],\n", + " [87, 84],\n", + " [87, 85],\n", + " [87, 86],\n", + " [87, 87],\n", + " [87, 88],\n", + " [87, 89],\n", + " [87, 90],\n", + " [88, 83],\n", + " [88, 84],\n", + " [88, 85],\n", + " [88, 86],\n", + " [88, 87],\n", + " [88, 88],\n", + " [88, 89],\n", + " [88, 90],\n", + " [89, 83],\n", + " [89, 84],\n", + " [89, 85],\n", + " [89, 86],\n", + " [89, 87],\n", + " [89, 88],\n", + " [89, 89],\n", + " [89, 90],\n", + " [90, 83],\n", + " [90, 84],\n", + " [90, 85],\n", + " [90, 86],\n", + " [90, 87],\n", + " [90, 88],\n", + " [90, 89],\n", + " [90, 90],\n", + " [91, 91],\n", + " [91, 92],\n", + " [91, 93],\n", + " [92, 91],\n", + " [92, 92],\n", + " [92, 93],\n", + " [93, 91],\n", + " [93, 92],\n", + " [93, 93],\n", + " [94, 94],\n", + " [94, 95],\n", + " [95, 94],\n", + " [95, 95]])" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.argwhere(is_coplanar)\n", + "# now, we index this back into a grid" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[[0],\n", + " [1],\n", + " [2],\n", + " [3],\n", + " [4],\n", + " [5],\n", + " [6],\n", + " [7],\n", + " [8],\n", + " [9],\n", + " [10, 11],\n", + " [10, 11],\n", + " [12, 13],\n", + " [12, 13],\n", + " [14, 15, 16],\n", + " [14, 15, 16],\n", + " [14, 15, 16],\n", + " [17, 18],\n", + " [17, 18],\n", + " [19, 20],\n", + " [19, 20],\n", + " [21, 22],\n", + " [21, 22],\n", + " [23, 24],\n", + " [23, 24],\n", + " [25, 26],\n", + " [25, 26],\n", + " [27, 28, 29],\n", + " [27, 28, 29],\n", + " [27, 28, 29],\n", + " [30, 31],\n", + " [30, 31],\n", + " [32, 33, 34],\n", + " [32, 33, 34],\n", + " [32, 33, 34],\n", + " [35, 36],\n", + " [35, 36],\n", + " [37, 38, 39],\n", + " [37, 38, 39],\n", + " [37, 38, 39],\n", + " [40, 41],\n", + " [40, 41],\n", + " [42, 43],\n", + " [42, 43],\n", + " [44, 45, 46],\n", + " [44, 45, 46],\n", + " [44, 45, 46],\n", + " [47, 48],\n", + " [47, 48],\n", + " [49, 50, 51],\n", + " [49, 50, 51],\n", + " [49, 50, 51],\n", + " [52, 53],\n", + " [52, 53],\n", + " [54, 55],\n", + " [54, 55],\n", + " [56, 57, 58],\n", + " [56, 57, 58],\n", + " [56, 57, 58],\n", + " [59, 60],\n", + " [59, 60],\n", + " [61, 62, 63],\n", + " [61, 62, 63],\n", + " [61, 62, 63],\n", + " [64, 65],\n", + " [64, 65],\n", + " [66, 67],\n", + " [66, 67],\n", + " [68, 69],\n", + " [68, 69],\n", + " [70, 71, 72, 73, 74, 75, 76, 77],\n", + " [70, 71, 72, 73, 74, 75, 76, 77],\n", + " [70, 71, 72, 73, 74, 75, 76, 77],\n", + " [70, 71, 72, 73, 74, 75, 76, 77],\n", + " [70, 71, 72, 73, 74, 75, 76, 77],\n", + " [70, 71, 72, 73, 74, 75, 76, 77],\n", + " [70, 71, 72, 73, 74, 75, 76, 77],\n", + " [70, 71, 72, 73, 74, 75, 76, 77],\n", + " [78, 79, 80],\n", + " [78, 79, 80],\n", + " [78, 79, 80],\n", + " [81, 82],\n", + " [81, 82],\n", + " [83, 84, 85, 86, 87, 88, 89, 90],\n", + " [83, 84, 85, 86, 87, 88, 89, 90],\n", + " [83, 84, 85, 86, 87, 88, 89, 90],\n", + " [83, 84, 85, 86, 87, 88, 89, 90],\n", + " [83, 84, 85, 86, 87, 88, 89, 90],\n", + " [83, 84, 85, 86, 87, 88, 89, 90],\n", + " [83, 84, 85, 86, 87, 88, 89, 90],\n", + " [83, 84, 85, 86, 87, 88, 89, 90],\n", + " [91, 92, 93],\n", + " [91, 92, 93],\n", + " [91, 92, 93],\n", + " [94, 95],\n", + " [94, 95]]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "nsimplices = len(poly.simplices)\n", + "outarr = [[] for _ in range(nsimplices)]\n", + "for row, coplanar in zip(*is_coplanar.nonzero()):\n", + " outarr[row].append(coplanar)\n", + "outarr" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "57.1 µs ± 5.61 µs per loop (mean ± std. dev. of 50 runs, 10,000 loops each)\n" + ] + } + ], + "source": [ + "%%timeit -r 50 -n 10000\n", + "coplanar_indices = [[] for _ in range(nsimplices)]\n", + "for face, index in zip(*is_coplanar.nonzero()):\n", + " coplanar_indices[face].append(index)\n", + "# outarr = list(set(map(tuple, outarr)))\n", + "coplanar_indices = list(set(map(tuple, coplanar_indices)))\n", + "coplanar_indices\n", + "# So now we have our coplanar simplex indices. Extract back the" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "57.7 µs ± 1.58 µs per loop (mean ± std. dev. of 50 runs, 10,000 loops each)\n" + ] + } + ], + "source": [ + "%%timeit -r 50 -n 10000\n", + "coplanar_indices = [[] for _ in range(nsimplices)]\n", + "\n", + "# Iterate over coplanar indices to build face lists\n", + "for face, index in zip(*is_coplanar.nonzero()):\n", + " coplanar_indices[face].append(index)\n", + "\n", + "# Remove duplicate faces, then sort the face indices by their minimum value\n", + "coplanar_indices = sorted(set(map(tuple, coplanar_indices)), key=lambda x: x[0])\n", + "\n", + "# So now we have our coplanar simplex indices. Extract back the faces\n", + "\n", + "coplanar_indices" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'coplanar_indices' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m/Users/jenbradley/github/coxeter/temp.ipynb Cell 12\u001b[0m line \u001b[0;36m2\n\u001b[1;32m 1\u001b[0m \u001b[39msorted\u001b[39m(\n\u001b[0;32m----> 2\u001b[0m [np\u001b[39m.\u001b[39marray(\u001b[39msorted\u001b[39m(\u001b[39mset\u001b[39m(poly\u001b[39m.\u001b[39msimplices[[ind]]\u001b[39m.\u001b[39mflat))) \u001b[39mfor\u001b[39;00m ind \u001b[39min\u001b[39;00m coplanar_indices],\n\u001b[1;32m 3\u001b[0m key\u001b[39m=\u001b[39m\u001b[39mlambda\u001b[39;00m x: x[\u001b[39m0\u001b[39m],\n\u001b[1;32m 4\u001b[0m )\n", + "\u001b[0;31mNameError\u001b[0m: name 'coplanar_indices' is not defined" + ] + } + ], + "source": [ + "sorted(\n", + " [np.array(sorted(set(poly.simplices[[ind]].flat))) for ind in coplanar_indices],\n", + " key=lambda x: x[0],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "89.9 µs ± 6.84 µs per loop (mean ± std. dev. of 100 runs, 10,000 loops each)\n" + ] + } + ], + "source": [ + "%%timeit -r 100 -n 10000\n", + "[np.fromiter(set(poly.simplices[[ind]].flat), np.int32) for ind in coplanar_indices]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "102 µs ± 12.6 µs per loop (mean ± std. dev. of 100 runs, 10,000 loops each)\n" + ] + } + ], + "source": [ + "%%timeit -r 100 -n 10000\n", + "[np.array(sorted(set(poly.simplices[[ind]].flat))) for ind in coplanar_indices]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "220 µs ± 14.3 µs per loop (mean ± std. dev. of 100 runs, 10,000 loops each)\n" + ] + } + ], + "source": [ + "%%timeit -r 100 -n 10000\n", + "[np.unique(poly.simplices[[ind]].flat) for ind in coplanar_indices]\n", + "# Upside of this (other than clarity) is the numbers are \"mostly\" sorted\n", + "# I think this is only true because the input lists are sorted, but maybe this doesnt matter?\n", + "# In any case, sorting above is cheap so we should keep doing that\n", + "## 200us without .flat, and faster without - oof" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "185 µs ± 14.4 µs per loop (mean ± std. dev. of 100 runs, 10,000 loops each)\n" + ] + } + ], + "source": [ + "%%timeit -r 100 -n 10000\n", + "[np.unique(poly.simplices[[ind]]) for ind in coplanar_indices]\n", + "# Upside of this (other than clarity) is the numbers are \"mostly\" sorted\n", + "# I think this is only true because the input lists are sorted, but maybe this doesnt matter?\n", + "# In any case, sorting above is cheap so we should keep doing that\n", + "## 200us without .flat" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[array([38, 40, 48], dtype=int32),\n", + " array([14, 22, 24], dtype=int32),\n", + " array([15, 23, 25], dtype=int32),\n", + " array([ 2, 7, 10], dtype=int32),\n", + " array([0, 6, 8], dtype=int32),\n", + " array([39, 41, 49], dtype=int32),\n", + " array([33, 36, 45], dtype=int32),\n", + " array([17, 20, 29], dtype=int32),\n", + " array([32, 34, 43], dtype=int32),\n", + " array([16, 18, 27], dtype=int32),\n", + " array([15, 21, 25, 30], dtype=int32),\n", + " array([36, 40, 45, 48], dtype=int32),\n", + " array([ 1, 3, 15, 28, 30], dtype=int32),\n", + " array([22, 23, 24, 25], dtype=int32),\n", + " array([15, 19, 23, 28], dtype=int32),\n", + " array([34, 38, 43, 48], dtype=int32),\n", + " array([ 1, 3, 9, 11], dtype=int32),\n", + " array([14, 18, 22, 27], dtype=int32),\n", + " array([18, 19, 22, 23, 26], dtype=int32),\n", + " array([ 2, 7, 17, 29], dtype=int32),\n", + " array([ 0, 2, 14, 27, 29], dtype=int32),\n", + " array([ 0, 2, 8, 10], dtype=int32),\n", + " array([ 8, 10, 43, 45, 48], dtype=int32),\n", + " array([35, 39, 44, 49], dtype=int32),\n", + " array([38, 39, 40, 41], dtype=int32),\n", + " array([36, 37, 40, 41, 47], dtype=int32),\n", + " array([ 7, 10, 33, 45], dtype=int32),\n", + " array([ 5, 7, 13, 17, 33], dtype=int32),\n", + " array([13, 33, 36, 47], dtype=int32),\n", + " array([14, 20, 24, 29], dtype=int32),\n", + " array([34, 35, 38, 39, 42], dtype=int32),\n", + " array([ 6, 8, 32, 43], dtype=int32),\n", + " array([ 9, 11, 44, 46, 49], dtype=int32),\n", + " array([37, 41, 46, 49], dtype=int32),\n", + " array([ 4, 16, 18, 26], dtype=int32),\n", + " array([ 0, 6, 16, 27], dtype=int32),\n", + " array([ 3, 5, 11, 13, 21, 30, 31, 37, 46, 47], dtype=int32),\n", + " array([20, 21, 24, 25, 31], dtype=int32),\n", + " array([ 5, 17, 20, 31], dtype=int32),\n", + " array([ 1, 4, 9, 12, 19, 26, 28, 35, 42, 44], dtype=int32),\n", + " array([ 4, 6, 12, 16, 32], dtype=int32),\n", + " array([12, 32, 34, 42], dtype=int32)]" + ] + }, + "execution_count": 225, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "[np.unique(poly.simplices[[ind]]) for ind in coplanar_indices]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "ename": "ValueError", + "evalue": "setting an array element with a sequence. The requested array has an inhomogeneous shape after 1 dimensions. The detected shape was (42,) + inhomogeneous part.", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m/Users/jenbradley/github/coxeter/temp.ipynb Cell 16\u001b[0m line \u001b[0;36m1\n\u001b[0;32m----> 1\u001b[0m poly\u001b[39m.\u001b[39;49msimplices[coplanar_indices]\n", + "\u001b[0;31mValueError\u001b[0m: setting an array element with a sequence. The requested array has an inhomogeneous shape after 1 dimensions. The detected shape was (42,) + inhomogeneous part." + ] + } + ], + "source": [ + "poly.simplices[coplanar_indices]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{(0, 27, 14, 29, 2),\n", + " (1, 3, 30, 15, 28),\n", + " (1, 28, 19, 26, 4, 12, 42, 35, 44, 9),\n", + " (2, 7, 10),\n", + " (3, 1, 9, 11),\n", + " (3, 11, 46, 37, 47, 13, 5, 31, 21, 30),\n", + " (8, 0, 2, 10),\n", + " (8, 6, 0),\n", + " (8, 10, 45, 48, 43),\n", + " (9, 44, 49, 46, 11),\n", + " (16, 4, 26, 18),\n", + " (16, 18, 27),\n", + " (16, 27, 0, 6),\n", + " (17, 7, 2, 29),\n", + " (17, 20, 31, 5),\n", + " (17, 29, 20),\n", + " (18, 22, 14, 27),\n", + " (18, 26, 19, 23, 22),\n", + " (19, 28, 15, 23),\n", + " (20, 24, 25, 21, 31),\n", + " (24, 14, 22),\n", + " (24, 20, 29, 14),\n", + " (24, 22, 23, 25),\n", + " (25, 15, 30, 21),\n", + " (25, 23, 15),\n", + " (32, 6, 8, 43),\n", + " (32, 12, 4, 16, 6),\n", + " (32, 34, 42, 12),\n", + " (32, 43, 34),\n", + " (33, 7, 17, 5, 13),\n", + " (33, 13, 47, 36),\n", + " (33, 36, 45),\n", + " (33, 45, 10, 7),\n", + " (34, 38, 39, 35, 42),\n", + " (36, 47, 37, 41, 40),\n", + " (40, 41, 39, 38),\n", + " (41, 37, 46, 49),\n", + " (41, 49, 39),\n", + " (48, 38, 34, 43),\n", + " (48, 40, 38),\n", + " (48, 45, 36, 40),\n", + " (49, 44, 35, 39)}" + ] + }, + "execution_count": 240, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "set(map(tuple, mbri.faces))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 250, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "set(\n", + " map(tuple, [np.unique(poly.simplices[[ind]].flat) for ind in coplanar_indices])\n", + ") == set(map(tuple, map(sorted, mbri.faces)))" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[array([38, 40, 48], dtype=int32),\n", + " array([14, 22, 24], dtype=int32),\n", + " array([15, 23, 25], dtype=int32),\n", + " array([ 2, 7, 10], dtype=int32),\n", + " array([0, 6, 8], dtype=int32),\n", + " array([39, 41, 49], dtype=int32),\n", + " array([33, 36, 45], dtype=int32),\n", + " array([17, 20, 29], dtype=int32),\n", + " array([32, 34, 43], dtype=int32),\n", + " array([16, 18, 27], dtype=int32),\n", + " array([15, 21, 25, 30], dtype=int32),\n", + " array([36, 40, 45, 48], dtype=int32),\n", + " array([ 1, 3, 15, 28, 30], dtype=int32),\n", + " array([22, 23, 24, 25], dtype=int32),\n", + " array([15, 19, 23, 28], dtype=int32),\n", + " array([34, 38, 43, 48], dtype=int32),\n", + " array([ 1, 3, 9, 11], dtype=int32),\n", + " array([14, 18, 22, 27], dtype=int32),\n", + " array([18, 19, 22, 23, 26], dtype=int32),\n", + " array([ 2, 7, 17, 29], dtype=int32),\n", + " array([ 0, 2, 14, 27, 29], dtype=int32),\n", + " array([ 0, 2, 8, 10], dtype=int32),\n", + " array([ 8, 10, 43, 45, 48], dtype=int32),\n", + " array([35, 39, 44, 49], dtype=int32),\n", + " array([38, 39, 40, 41], dtype=int32),\n", + " array([36, 37, 40, 41, 47], dtype=int32),\n", + " array([ 7, 10, 33, 45], dtype=int32),\n", + " array([ 5, 7, 13, 17, 33], dtype=int32),\n", + " array([13, 33, 36, 47], dtype=int32),\n", + " array([14, 20, 24, 29], dtype=int32),\n", + " array([34, 35, 38, 39, 42], dtype=int32),\n", + " array([ 6, 8, 32, 43], dtype=int32),\n", + " array([ 9, 11, 44, 46, 49], dtype=int32),\n", + " array([37, 41, 46, 49], dtype=int32),\n", + " array([ 4, 16, 18, 26], dtype=int32),\n", + " array([ 0, 6, 16, 27], dtype=int32),\n", + " array([ 3, 5, 11, 13, 21, 30, 31, 37, 46, 47], dtype=int32),\n", + " array([20, 21, 24, 25, 31], dtype=int32),\n", + " array([ 5, 17, 20, 31], dtype=int32),\n", + " array([ 1, 4, 9, 12, 19, 26, 28, 35, 42, 44], dtype=int32),\n", + " array([ 4, 6, 12, 16, 32], dtype=int32),\n", + " array([12, 32, 34, 42], dtype=int32)]" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# %%timeit -r 10 -n 100\n", + "# lets compile everything together now:\n", + "poly = mbri\n", + "tol = 1e-15\n", + "nsimplices = len(poly._simplices)\n", + "is_coplanar = np.all(\n", + " np.abs(poly._simplex_equations[:, None] - poly._simplex_equations) < tol, axis=2\n", + ")\n", + "coplanar_indices = [[] for _ in range(nsimplices)]\n", + "\n", + "# Iterate over coplanar indices to build face index lists\n", + "for face, index in zip(*is_coplanar.nonzero()):\n", + " coplanar_indices[face].append(index)\n", + "\n", + "# Remove duplicate faces, then sort the face indices by their minimum value\n", + "coplanar_indices = sorted(set(map(tuple, coplanar_indices)), key=lambda x: x[0])\n", + "\n", + "# Set method is faster: but let's try this\n", + "# Extract vertex indices from simplex indices and remove duplicates\n", + "faces = [np.unique(poly.simplices[[ind]]) for ind in coplanar_indices]\n", + "faces" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3.04 µs ± 145 ns per loop (mean ± std. dev. of 10 runs, 10,000 loops each)\n" + ] + } + ], + "source": [ + "%%timeit -r 10 -n 10000\n", + "poly._simplex_equations[[equation_index[0] for equation_index in coplanar_indices]]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "300 µs ± 7.5 µs per loop (mean ± std. dev. of 10 runs, 10,000 loops each)\n" + ] + } + ], + "source": [ + "%%timeit -r 10 -n 10000\n", + "# lets compile everything together now:\n", + "is_coplanar = np.all(\n", + " np.abs(poly._simplex_equations[:, None] - poly._simplex_equations) < tol, axis=2\n", + ")\n", + "coplanar_indices = [[] for _ in range(nsimplices)]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "'tuple' object has no attribute 'append'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m/Users/jenbradley/github/coxeter/temp.ipynb Cell 23\u001b[0m line \u001b[0;36m1\n\u001b[0;32m----> 1\u001b[0m get_ipython()\u001b[39m.\u001b[39;49mrun_cell_magic(\u001b[39m'\u001b[39;49m\u001b[39mtimeit\u001b[39;49m\u001b[39m'\u001b[39;49m, \u001b[39m'\u001b[39;49m\u001b[39m-r 10 -n 10000\u001b[39;49m\u001b[39m'\u001b[39;49m, \u001b[39m'\u001b[39;49m\u001b[39m# Iterate over coplanar indices to build face index lists\u001b[39;49m\u001b[39m\\n\u001b[39;49;00m\u001b[39mfor face, index in zip(*is_coplanar.nonzero()):\u001b[39;49m\u001b[39m\\n\u001b[39;49;00m\u001b[39m coplanar_indices[face].append(index)\u001b[39;49m\u001b[39m\\n\u001b[39;49;00m\u001b[39m'\u001b[39;49m)\n", + "File \u001b[0;32m~/micromamba/envs/690py311/lib/python3.11/site-packages/IPython/core/interactiveshell.py:2493\u001b[0m, in \u001b[0;36mInteractiveShell.run_cell_magic\u001b[0;34m(self, magic_name, line, cell)\u001b[0m\n\u001b[1;32m 2491\u001b[0m \u001b[39mwith\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mbuiltin_trap:\n\u001b[1;32m 2492\u001b[0m args \u001b[39m=\u001b[39m (magic_arg_s, cell)\n\u001b[0;32m-> 2493\u001b[0m result \u001b[39m=\u001b[39m fn(\u001b[39m*\u001b[39;49margs, \u001b[39m*\u001b[39;49m\u001b[39m*\u001b[39;49mkwargs)\n\u001b[1;32m 2495\u001b[0m \u001b[39m# The code below prevents the output from being displayed\u001b[39;00m\n\u001b[1;32m 2496\u001b[0m \u001b[39m# when using magics with decorator @output_can_be_silenced\u001b[39;00m\n\u001b[1;32m 2497\u001b[0m \u001b[39m# when the last Python token in the expression is a ';'.\u001b[39;00m\n\u001b[1;32m 2498\u001b[0m \u001b[39mif\u001b[39;00m \u001b[39mgetattr\u001b[39m(fn, magic\u001b[39m.\u001b[39mMAGIC_OUTPUT_CAN_BE_SILENCED, \u001b[39mFalse\u001b[39;00m):\n", + "File \u001b[0;32m~/micromamba/envs/690py311/lib/python3.11/site-packages/IPython/core/magics/execution.py:1189\u001b[0m, in \u001b[0;36mExecutionMagics.timeit\u001b[0;34m(self, line, cell, local_ns)\u001b[0m\n\u001b[1;32m 1186\u001b[0m \u001b[39mif\u001b[39;00m time_number \u001b[39m>\u001b[39m\u001b[39m=\u001b[39m \u001b[39m0.2\u001b[39m:\n\u001b[1;32m 1187\u001b[0m \u001b[39mbreak\u001b[39;00m\n\u001b[0;32m-> 1189\u001b[0m all_runs \u001b[39m=\u001b[39m timer\u001b[39m.\u001b[39;49mrepeat(repeat, number)\n\u001b[1;32m 1190\u001b[0m best \u001b[39m=\u001b[39m \u001b[39mmin\u001b[39m(all_runs) \u001b[39m/\u001b[39m number\n\u001b[1;32m 1191\u001b[0m worst \u001b[39m=\u001b[39m \u001b[39mmax\u001b[39m(all_runs) \u001b[39m/\u001b[39m number\n", + "File \u001b[0;32m~/micromamba/envs/690py311/lib/python3.11/timeit.py:206\u001b[0m, in \u001b[0;36mTimer.repeat\u001b[0;34m(self, repeat, number)\u001b[0m\n\u001b[1;32m 204\u001b[0m r \u001b[39m=\u001b[39m []\n\u001b[1;32m 205\u001b[0m \u001b[39mfor\u001b[39;00m i \u001b[39min\u001b[39;00m \u001b[39mrange\u001b[39m(repeat):\n\u001b[0;32m--> 206\u001b[0m t \u001b[39m=\u001b[39m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mtimeit(number)\n\u001b[1;32m 207\u001b[0m r\u001b[39m.\u001b[39mappend(t)\n\u001b[1;32m 208\u001b[0m \u001b[39mreturn\u001b[39;00m r\n", + "File \u001b[0;32m~/micromamba/envs/690py311/lib/python3.11/site-packages/IPython/core/magics/execution.py:173\u001b[0m, in \u001b[0;36mTimer.timeit\u001b[0;34m(self, number)\u001b[0m\n\u001b[1;32m 171\u001b[0m gc\u001b[39m.\u001b[39mdisable()\n\u001b[1;32m 172\u001b[0m \u001b[39mtry\u001b[39;00m:\n\u001b[0;32m--> 173\u001b[0m timing \u001b[39m=\u001b[39m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49minner(it, \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mtimer)\n\u001b[1;32m 174\u001b[0m \u001b[39mfinally\u001b[39;00m:\n\u001b[1;32m 175\u001b[0m \u001b[39mif\u001b[39;00m gcold:\n", + "File \u001b[0;32m:3\u001b[0m, in \u001b[0;36minner\u001b[0;34m(_it, _timer)\u001b[0m\n", + "\u001b[0;31mAttributeError\u001b[0m: 'tuple' object has no attribute 'append'" + ] + } + ], + "source": [ + "%%timeit -r 10 -n 10000\n", + "# Iterate over coplanar indices to build face index lists\n", + "for face, index in zip(*is_coplanar.nonzero()):\n", + " coplanar_indices[face].append(index)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%timeit -r 10 -n 10000\n", + "# Remove duplicate faces, then sort the face indices by their minimum value\n", + "coplanar_indices = sorted(set(map(tuple, coplanar_indices)), key=lambda x: x[0])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%timeit -r 10 -n 10000\n", + "# Set method is faster: but let's try this\n", + "# Extract vertex indices from simplex indices and remove duplicates\n", + "faces = [np.unique(poly.simplices[[ind]]) for ind in coplanar_indices]\n", + "faces" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'self' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m/Users/jenbradley/github/coxeter/temp.ipynb Cell 26\u001b[0m line \u001b[0;36m3\n\u001b[1;32m 1\u001b[0m tol \u001b[39m=\u001b[39m \u001b[39m1e-15\u001b[39m\n\u001b[1;32m 2\u001b[0m is_coplanar \u001b[39m=\u001b[39m np\u001b[39m.\u001b[39mall(\n\u001b[0;32m----> 3\u001b[0m np\u001b[39m.\u001b[39mabs(\u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39m_simplex_equations[:, \u001b[39mNone\u001b[39;00m] \u001b[39m-\u001b[39m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_simplex_equations) \u001b[39m<\u001b[39m tol,\n\u001b[1;32m 4\u001b[0m axis\u001b[39m=\u001b[39m\u001b[39m2\u001b[39m,\n\u001b[1;32m 5\u001b[0m )\n\u001b[1;32m 6\u001b[0m coplanar_indices \u001b[39m=\u001b[39m [[] \u001b[39mfor\u001b[39;00m _ \u001b[39min\u001b[39;00m \u001b[39mrange\u001b[39m(\u001b[39mlen\u001b[39m(\u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_simplices))]\n\u001b[1;32m 8\u001b[0m \u001b[39m# Iterate over coplanar indices to build face index lists\u001b[39;00m\n", + "\u001b[0;31mNameError\u001b[0m: name 'self' is not defined" + ] + } + ], + "source": [ + "tol = 1e-15\n", + "is_coplanar = np.all(\n", + " np.abs(self._simplex_equations[:, None] - self._simplex_equations) < tol,\n", + " axis=2,\n", + ")\n", + "coplanar_indices = [[] for _ in range(len(self._simplices))]\n", + "\n", + "# Iterate over coplanar indices to build face index lists\n", + "for face, index in zip(*is_coplanar.nonzero()):\n", + " coplanar_indices[face].append(index)\n", + "\n", + "# Remove duplicate faces, then sort the face indices by their minimum value\n", + "coplanar_indices = sorted(set(map(tuple, coplanar_indices)), key=lambda x: x[0])\n", + "\n", + "# Set method is faster: but let's try this\n", + "# Extract vertex indices from simplex indices and remove duplicates\n", + "faces = [np.unique(self.simplices[[ind]]) for ind in coplanar_indices]\n", + "self._faces = faces\n", + "# self._equations = np.array(list(equation_groups.keys()))" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "13.4 µs ± 929 ns per loop (mean ± std. dev. of 20 runs, 100,000 loops each)\n", + "11.6 µs ± 140 ns per loop (mean ± std. dev. of 20 runs, 100,000 loops each)\n" + ] + } + ], + "source": [ + "coplanar_indices\n", + "%timeit -r 20 -n 100000 [np.array(coplanar) for coplanar in coplanar_indices]\n", + "%timeit -r 20 -n 100000 list(map(np.array,coplanar_indices))" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[array([0]),\n", + " array([1]),\n", + " array([2]),\n", + " array([3]),\n", + " array([4]),\n", + " array([5]),\n", + " array([6]),\n", + " array([7]),\n", + " array([8]),\n", + " array([9]),\n", + " array([10, 11]),\n", + " array([12, 13]),\n", + " array([14, 15, 16]),\n", + " array([17, 18]),\n", + " array([19, 20]),\n", + " array([21, 22]),\n", + " array([23, 24]),\n", + " array([25, 26]),\n", + " array([27, 28, 29]),\n", + " array([30, 31]),\n", + " array([32, 33, 34]),\n", + " array([35, 36]),\n", + " array([37, 38, 39]),\n", + " array([40, 41]),\n", + " array([42, 43]),\n", + " array([44, 45, 46]),\n", + " array([47, 48]),\n", + " array([49, 50, 51]),\n", + " array([52, 53]),\n", + " array([54, 55]),\n", + " array([56, 57, 58]),\n", + " array([59, 60]),\n", + " array([61, 62, 63]),\n", + " array([64, 65]),\n", + " array([66, 67]),\n", + " array([68, 69]),\n", + " array([70, 71, 72, 73, 74, 75, 76, 77]),\n", + " array([78, 79, 80]),\n", + " array([81, 82]),\n", + " array([83, 84, 85, 86, 87, 88, 89, 90]),\n", + " array([91, 92, 93]),\n", + " array([94, 95])]" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "list(map(np.array, coplanar_indices))" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[array([0], dtype=int32),\n", + " array([1], dtype=int32),\n", + " array([2], dtype=int32),\n", + " array([3], dtype=int32),\n", + " array([4], dtype=int32),\n", + " array([5], dtype=int32),\n", + " array([6], dtype=int32),\n", + " array([7], dtype=int32),\n", + " array([8], dtype=int32),\n", + " array([9], dtype=int32),\n", + " array([10, 11], dtype=int32),\n", + " array([12], dtype=int32),\n", + " array([13], dtype=int32),\n", + " array([14], dtype=int32),\n", + " array([16, 15], dtype=int32),\n", + " array([17, 18], dtype=int32),\n", + " array([19], dtype=int32),\n", + " array([20], dtype=int32),\n", + " array([21], dtype=int32),\n", + " array([22], dtype=int32),\n", + " array([24, 23], dtype=int32),\n", + " array([25], dtype=int32),\n", + " array([26], dtype=int32),\n", + " array([27, 28, 29], dtype=int32),\n", + " array([30, 31], dtype=int32),\n", + " array([32, 33, 34], dtype=int32),\n", + " array([35, 36], dtype=int32),\n", + " array([37], dtype=int32),\n", + " array([38, 39], dtype=int32),\n", + " array([40], dtype=int32),\n", + " array([41], dtype=int32),\n", + " array([42, 43], dtype=int32),\n", + " array([44, 45, 46], dtype=int32),\n", + " array([47], dtype=int32),\n", + " array([48], dtype=int32),\n", + " array([49, 50], dtype=int32),\n", + " array([51], dtype=int32),\n", + " array([52], dtype=int32),\n", + " array([53], dtype=int32),\n", + " array([54, 55], dtype=int32),\n", + " array([56, 57, 58], dtype=int32),\n", + " array([59], dtype=int32),\n", + " array([60], dtype=int32),\n", + " array([61, 62, 63], dtype=int32),\n", + " array([64], dtype=int32),\n", + " array([65], dtype=int32),\n", + " array([66], dtype=int32),\n", + " array([67], dtype=int32),\n", + " array([68], dtype=int32),\n", + " array([69], dtype=int32),\n", + " array([76, 70], dtype=int32),\n", + " array([71, 73, 74, 75, 77], dtype=int32),\n", + " array([72], dtype=int32),\n", + " array([80, 78], dtype=int32),\n", + " array([79], dtype=int32),\n", + " array([81], dtype=int32),\n", + " array([82], dtype=int32),\n", + " array([83], dtype=int32),\n", + " array([84, 86, 87, 88, 90], dtype=int32),\n", + " array([89, 85], dtype=int32),\n", + " array([91, 92, 93], dtype=int32),\n", + " array([94, 95], dtype=int32)]" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "poly._coplanar_simplices" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[array([0], dtype=int32),\n", + " array([1], dtype=int32),\n", + " array([2], dtype=int32),\n", + " array([3], dtype=int32),\n", + " array([4], dtype=int32),\n", + " array([5], dtype=int32),\n", + " array([6], dtype=int32),\n", + " array([7], dtype=int32),\n", + " array([8], dtype=int32),\n", + " array([9], dtype=int32)]" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "poly._coplanar_simplices[0:10]" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [], + "source": [ + "verts = johnfam.get_shape(\"Metabidiminished Rhombicosidodecahedron\").vertices\n", + "verts = cube.vertices" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5.08 ms ± 543 µs per loop (mean ± std. dev. of 10 runs, 100 loops each)\n" + ] + } + ], + "source": [ + "%%timeit -r 10 -n 100\n", + "ConvexPolyhedron(mbri.vertices)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "659 µs ± 8.77 µs per loop (mean ± std. dev. of 10 runs, 1,000 loops each)\n" + ] + } + ], + "source": [ + "%%timeit -r 10 -n 1000\n", + "ConvexPolyhedron(cube.vertices)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[array([0]),\n", + " array([1]),\n", + " array([2]),\n", + " array([3]),\n", + " array([4]),\n", + " array([5]),\n", + " array([6]),\n", + " array([7]),\n", + " array([8]),\n", + " array([9]),\n", + " array([10, 11]),\n", + " array([12, 13]),\n", + " array([14, 15, 16]),\n", + " array([17, 18]),\n", + " array([19, 20]),\n", + " array([21, 22]),\n", + " array([23, 24]),\n", + " array([25, 26]),\n", + " array([27, 28, 29]),\n", + " array([30, 31]),\n", + " array([32, 33, 34]),\n", + " array([35, 36]),\n", + " array([37, 38, 39]),\n", + " array([40, 41]),\n", + " array([42, 43]),\n", + " array([44, 45, 46]),\n", + " array([47, 48]),\n", + " array([49, 50, 51]),\n", + " array([52, 53]),\n", + " array([54, 55]),\n", + " array([56, 57, 58]),\n", + " array([59, 60]),\n", + " array([61, 62, 63]),\n", + " array([64, 65]),\n", + " array([66, 67]),\n", + " array([68, 69]),\n", + " array([70, 71, 72, 73, 74, 75, 76, 77]),\n", + " array([78, 79, 80]),\n", + " array([81, 82]),\n", + " array([83, 84, 85, 86, 87, 88, 89, 90]),\n", + " array([91, 92, 93]),\n", + " array([94, 95])]" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "poly = mbri\n", + "tol = 1e-15\n", + "is_coplanar = np.all(\n", + " np.abs(poly._simplex_equations[:, None] - poly._simplex_equations) < tol,\n", + " axis=2,\n", + ")\n", + "coplanar_indices = [[] for _ in range(len(poly._simplices))]\n", + "\n", + "# Iterate over coplanar indices to build face index lists\n", + "for face, index in zip(*is_coplanar.nonzero()):\n", + " coplanar_indices[face].append(index)\n", + "\n", + "# Remove duplicate faces, then sort the face indices by their minimum value\n", + "coplanar_indices = sorted(set(map(tuple, coplanar_indices)), key=lambda x: x[0])\n", + "\n", + "# Extract vertex indices from simplex indices and remove duplicates\n", + "faces = [np.unique(poly.simplices[[ind]]) for ind in coplanar_indices]\n", + "\n", + "# Copy the simplex equation for one of the simplices on each face\n", + "poly._simplex_equations[[equation_index[0] for equation_index in coplanar_indices]]\n", + "# Convert the simplex indices to numpy arrays and save\n", + "list(map(np.array, coplanar_indices))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "177 µs ± 5.02 µs per loop (mean ± std. dev. of 10 runs, 10,000 loops each)\n" + ] + } + ], + "source": [ + "%%timeit -r 10 -n 10000\n", + "[np.unique(poly.simplices[[ind]]) for ind in coplanar_indices]" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1.07 ms ± 114 µs per loop (mean ± std. dev. of 10 runs, 100 loops each)\n" + ] + } + ], + "source": [ + "%%timeit -r 10 -n 100\n", + "# [np.fromiter(set(poly.simplices[[ind]].flat), np.int32) for ind in coplanar_indices]\n", + "mbri._sort_simplices()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1.33 ms ± 242 µs per loop (mean ± std. dev. of 10 runs, 1,000 loops each)\n" + ] + } + ], + "source": [ + "%%timeit -r 10 -n 1000\n", + "# [np.fromiter(set(poly.simplices[[ind]].flat), np.int32) for ind in coplanar_indices]\n", + "mbri._sort_simplices()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/test.ipynb b/test.ipynb new file mode 100644 index 00000000..8809b451 --- /dev/null +++ b/test.ipynb @@ -0,0 +1,3310 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from scipy.spatial import ConvexHull as ch\n", + "from scipy.spatial import Delaunay\n", + "\n", + "from coxeter.families import (\n", + " ArchimedeanFamily as archfam,\n", + ")\n", + "from coxeter.families import (\n", + " JohnsonFamily as johnfam,\n", + ")\n", + "from coxeter.families import (\n", + " PlatonicFamily as platfam,\n", + ")\n", + "from coxeter.shapes import ConvexPolyhedron" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "cube = platfam.get_shape(\"Cube\")\n", + "# tetr = platfam.get_shape(\"Tetrahedron\")\n", + "# mbri = johnfam.get_shape(\"Metabidiminished Rhombicosidodecahedron\")\n", + "# icosi = archfam.get_shape(\"Icosidodecahedron\")\n", + "# poly = cube" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(42, 42)" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mbri.faces.__len__(), mbri._coplanar_simplices.__len__()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "hull = ch(mbri.vertices, qhull_options=\"Q2\")\n", + "# hull.equations\n", + "# hull.neighbors" + ] + }, + { + "cell_type": "code", + "execution_count": 249, + "metadata": {}, + "outputs": [], + "source": [ + "def _find_coplanar_simplices(self, rounding: int = 15):\n", + " \"\"\"\n", + " Get lists of simplex indices for coplanar simplices.\n", + "\n", + " Args:\n", + " rounding (int, optional):\n", + " Integer number of decimal places to round equations to.\n", + " (Default value: 15).\n", + "\n", + "\n", + " \"\"\"\n", + " # Combine simplex indices\n", + " equation_groups = defaultdict(list)\n", + "\n", + " # Iterate over all simplex equations\n", + " for i, equation in enumerate(self._simplex_equations):\n", + " # Convert equation into hashable tuple\n", + " equation_key = tuple(equation.round(rounding))\n", + " equation_groups[equation_key].append(i)\n", + " ragged_coplanar_indices = [\n", + " np.fromiter(set(group), np.int32) for group in equation_groups.values()\n", + " ]\n", + "\n", + " self._coplanar_simplices = ragged_coplanar_indices" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "# hull.simplices" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[48 40] [40 38]\n" + ] + }, + { + "data": { + "text/plain": [ + "array([[48, 40],\n", + " [40, 38]], dtype=int32)" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "((96, 2, 2, 3),\n", + " array([[[ 0.54304647, 0. , -0.39295212],\n", + " [ 0.63580989, 0.15009435, -0.15009435]],\n", + " \n", + " [[ 0.63580989, 0.15009435, -0.15009435],\n", + " [ 0.63580989, -0.15009435, -0.15009435]]]))" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# First - compute normals of each triangle\n", + "# triangle_edges = np.dstack([hull.simplices[:, :-1], hull.simplices[:, 1:]])\n", + "print(hull.simplices[:, :-1][0], hull.simplices[:, 1:][0])\n", + "triangle_edges = np.dstack([hull.simplices[:, :-1], hull.simplices[:, 1:]]).transpose(\n", + " 0, 2, 1\n", + ")\n", + "# %timeit -r 10 -n 100000\n", + "display(triangle_edges[0])\n", + "triangle_edge_endpoints = hull.points[triangle_edges]\n", + "triangle_edge_endpoints.shape, triangle_edge_endpoints[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 130, + "metadata": {}, + "outputs": [], + "source": [ + "# hull.points" + ] + }, + { + "cell_type": "code", + "execution_count": 131, + "metadata": {}, + "outputs": [], + "source": [ + "# poly._coplanar_simplices" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 132, + "metadata": {}, + "outputs": [], + "source": [ + "triangle_edge_vectors = (\n", + " triangle_edge_endpoints[..., 0, :] - triangle_edge_endpoints[..., 1, :]\n", + ")\n", + "# np.diff(triangle_edge_endpoints, axis=2).squeeze()\n", + "\n", + "# triangle_edge_vectors.shape, triangle_edge_vectors" + ] + }, + { + "cell_type": "code", + "execution_count": 133, + "metadata": {}, + "outputs": [], + "source": [ + "normals = np.cross(triangle_edge_vectors[:, 1, :], triangle_edge_vectors[:, 0, :])\n", + "normals / np.linalg.norm(normals, axis=1)[:, None]\n", + "nnormals = normals / np.linalg.norm(normals, axis=1)[:, None]\n", + "nnormals[1::2] *= -1\n", + "# nnormals\n", + "# I think there is something about the sorting of triangles from convexhull that result in\n", + "# odd triangles having opposite orientations in the first half, and evens having opposite in the second" + ] + }, + { + "cell_type": "code", + "execution_count": 134, + "metadata": {}, + "outputs": [], + "source": [ + "# ok - so I can just use this.\n", + "# hull.equations[:, :3]" + ] + }, + { + "cell_type": "code", + "execution_count": 135, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 0, 6, 1, 8],\n", + " [ 1, 2, 0, 4],\n", + " [ 2, 1, 3, 4],\n", + " [ 3, 7, 2, 10],\n", + " [ 4, 1, 2, 5],\n", + " [ 5, 9, 11, 4],\n", + " [ 6, 0, 7, 8],\n", + " [ 7, 3, 6, 10],\n", + " [ 8, 0, 6, 9],\n", + " [ 9, 5, 11, 8],\n", + " [10, 3, 7, 11],\n", + " [11, 5, 9, 10]])" + ] + }, + "execution_count": 135, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# 0 element of array is current simplex. next trhee are neighbors\n", + "simplex_indices = np.hstack(\n", + " [np.arange(0, hull.neighbors.shape[0], 1)[:, None], hull.neighbors]\n", + ")\n", + "simplex_indices" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[False, False, False],\n", + " [False, False, False],\n", + " [False, False, False],\n", + " [False, False, False],\n", + " [False, False, False],\n", + " [False, False, False],\n", + " [False, False, False],\n", + " [False, False, False],\n", + " [False, False, False],\n", + " [False, False, False],\n", + " [False, False, True],\n", + " [False, False, True],\n", + " [False, False, True],\n", + " [False, False, True],\n", + " [False, False, True],\n", + " [False, True, True],\n", + " [False, False, True],\n", + " [False, False, True],\n", + " [False, False, True],\n", + " [False, False, True],\n", + " [False, False, True],\n", + " [False, False, True],\n", + " [False, False, True],\n", + " [False, True, False],\n", + " [False, True, False],\n", + " [False, True, False],\n", + " [False, True, False],\n", + " [False, False, True],\n", + " [False, True, False],\n", + " [False, True, True],\n", + " [False, False, True],\n", + " [False, False, True],\n", + " [False, False, True],\n", + " [False, False, True],\n", + " [False, True, True],\n", + " [False, False, True],\n", + " [False, False, True],\n", + " [False, True, True],\n", + " [False, True, False],\n", + " [False, False, True],\n", + " [False, False, True],\n", + " [False, False, True],\n", + " [False, True, False],\n", + " [False, False, True],\n", + " [False, True, False],\n", + " [False, False, True],\n", + " [False, True, True],\n", + " [False, False, True],\n", + " [False, False, True],\n", + " [False, True, False],\n", + " [False, True, True],\n", + " [False, False, True],\n", + " [False, False, True],\n", + " [False, False, True],\n", + " [False, False, True],\n", + " [False, True, False],\n", + " [False, True, False],\n", + " [False, False, True],\n", + " [False, True, True],\n", + " [False, False, True],\n", + " [False, False, True],\n", + " [False, True, True],\n", + " [False, True, False],\n", + " [False, False, True],\n", + " [False, False, True],\n", + " [False, False, True],\n", + " [False, False, True],\n", + " [False, False, True],\n", + " [False, True, False],\n", + " [False, True, False],\n", + " [False, False, True],\n", + " [False, True, True],\n", + " [False, False, True],\n", + " [False, True, True],\n", + " [False, True, True],\n", + " [False, True, True],\n", + " [False, True, True],\n", + " [False, True, True],\n", + " [False, False, True],\n", + " [False, True, True],\n", + " [False, True, False],\n", + " [False, False, True],\n", + " [False, True, False],\n", + " [False, True, True],\n", + " [False, True, True],\n", + " [False, False, True],\n", + " [False, True, True],\n", + " [False, True, True],\n", + " [False, True, True],\n", + " [False, True, True],\n", + " [False, True, False],\n", + " [False, True, False],\n", + " [False, False, True],\n", + " [False, True, True],\n", + " [False, True, False],\n", + " [False, True, False]])" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hull_normals = hull.equations[:, :3]\n", + "# hull_normals[simplex_indices]\n", + "coplanar_simplex_array = np.all(\n", + " hull_normals[np.arange(0, hull.nsimplex, 1)][:, None]\n", + " == hull_normals[hull.neighbors],\n", + " axis=2,\n", + ")\n", + "\n", + "# This is right! We get the correct coplanar simplices. How do we index back in?\n", + "coplanar_simplex_array" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "96" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hull.neighbors[\n", + " coplanar_simplex_array\n", + "] # Direct indexing returns a flattened list - not useful\n", + "[\n", + " [*hull.neighbors[i][coplanar_simplex_array[i]], i] for i in range(hull.nsimplex)\n", + "].__len__()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0]\n", + "[1]\n", + "[2]\n", + "[3]\n", + "[4]\n", + "[5]\n", + "[6]\n", + "[7]\n", + "[8]\n", + "[9]\n", + "[11, 10]\n", + "[10, 11]\n", + "[13, 12]\n", + "[12, 13]\n", + "[15, 14]\n", + "[16, 14, 15]\n", + "[15, 16]\n", + "[18, 17]\n", + "[17, 18]\n", + "[20, 19]\n", + "[19, 20]\n", + "[22, 21]\n", + "[21, 22]\n", + "[24, 23]\n", + "[23, 24]\n", + "[26, 25]\n", + "[25, 26]\n", + "[29, 27]\n", + "[29, 28]\n", + "[28, 27, 29]\n", + "[31, 30]\n", + "[30, 31]\n", + "[34, 32]\n", + "[34, 33]\n", + "[33, 32, 34]\n", + "[36, 35]\n", + "[35, 36]\n", + "[38, 39, 37]\n", + "[37, 38]\n", + "[37, 39]\n", + "[41, 40]\n", + "[40, 41]\n", + "[43, 42]\n", + "[42, 43]\n", + "[46, 44]\n", + "[46, 45]\n", + "[44, 45, 46]\n", + "[48, 47]\n", + "[47, 48]\n", + "[50, 49]\n", + "[49, 51, 50]\n", + "[50, 51]\n", + "[53, 52]\n", + "[52, 53]\n", + "[55, 54]\n", + "[54, 55]\n", + "[58, 56]\n", + "[58, 57]\n", + "[56, 57, 58]\n", + "[60, 59]\n", + "[59, 60]\n", + "[62, 63, 61]\n", + "[61, 62]\n", + "[61, 63]\n", + "[65, 64]\n", + "[64, 65]\n", + "[67, 66]\n", + "[66, 67]\n", + "[69, 68]\n", + "[68, 69]\n", + "[76, 70]\n", + "[73, 77, 71]\n", + "[74, 72]\n", + "[71, 76, 73]\n", + "[75, 72, 74]\n", + "[74, 77, 75]\n", + "[70, 73, 76]\n", + "[75, 71, 77]\n", + "[79, 78]\n", + "[78, 80, 79]\n", + "[79, 80]\n", + "[82, 81]\n", + "[81, 82]\n", + "[90, 88, 83]\n", + "[86, 88, 84]\n", + "[89, 85]\n", + "[84, 87, 86]\n", + "[89, 86, 87]\n", + "[84, 83, 88]\n", + "[87, 85, 89]\n", + "[83, 90]\n", + "[93, 91]\n", + "[93, 92]\n", + "[91, 92, 93]\n", + "[95, 94]\n", + "[94, 95]\n" + ] + } + ], + "source": [ + "# What does the above do?\n", + "for i in range(hull.nsimplex):\n", + " print([*hull.neighbors[i][coplanar_simplex_array[i]], i])\n", + " # first, we get the coplanar simplices and heighbors for simplex i\n", + " # then, index in the coplanar neighbors and dump them into an array\n", + " # we then add i, as the indexed simplex is also coplanar" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(0.0017318337087434132,\n", + " 0.0020163124217437156,\n", + " 0.0035447480247632335,\n", + " 0.00394259414847542,\n", + " 0.006609278010620279,\n", + " 0.00853404435978633,\n", + " 0.009010762298194974,\n", + " 0.009899284064141756,\n", + " 0.010115961801518636,\n", + " 0.012404473627683599,\n", + " 0.012447484935732667,\n", + " 0.012974861536379279,\n", + " 0.013309849813057273,\n", + " 0.01387163413332293,\n", + " 0.014703931931117853,\n", + " 0.014808475350076455,\n", + " 0.018428864919364796,\n", + " 0.018672799765176995,\n", + " 0.019334288844548464,\n", + " 0.021070590262666555,\n", + " 0.02110502873856468,\n", + " 0.02129166678763994,\n", + " 0.021528721783741545,\n", + " 0.021807986622800035,\n", + " 0.022052899647635127,\n", + " 0.02208917208035044,\n", + " 0.023289882081194424,\n", + " 0.024778974093295214,\n", + " 0.024975308660231677,\n", + " 0.029590825098955165,\n", + " 0.031197292029556123,\n", + " 0.031204465611203602,\n", + " 0.033693294520900086,\n", + " 0.03415956062156722,\n", + " 0.035767986888116554,\n", + " 0.03622266558646581,\n", + " 0.036248777345704974,\n", + " 0.03717957413822137,\n", + " 0.03824200878882533,\n", + " 0.039367799521233926,\n", + " 0.041576934354527806,\n", + " 0.04162211069230026,\n", + " 0.04232261923436931,\n", + " 0.04279335787077321,\n", + " 0.04337995558972951,\n", + " 0.0436185855028739,\n", + " 0.04503056594013033,\n", + " 0.04554938340502823,\n", + " 0.046600675424599114,\n", + " 0.04682370581726858,\n", + " 0.04697496269889989,\n", + " 0.04728911363562571,\n", + " 0.04743570032957245,\n", + " 0.047668527682826256,\n", + " 0.04796450153608056,\n", + " 0.04879183687370359,\n", + " 0.04882164965671976,\n", + " 0.048922739933007664,\n", + " 0.0490309246596935,\n", + " 0.04955161262765606,\n", + " 0.05195998688897807,\n", + " 0.054858635042310144,\n", + " 0.06065540337249775,\n", + " 0.060741806710776336,\n", + " 0.061246862548058445,\n", + " 0.061290154875117686,\n", + " 0.06198979660056314,\n", + " 0.0626103771615164,\n", + " 0.06283930775259372,\n", + " 0.06373528093932801,\n", + " 0.06399943846015921,\n", + " 0.06434235343368755,\n", + " 0.06544235289055411,\n", + " 0.06564962046666056,\n", + " 0.06633136821678975,\n", + " 0.06791013926730971,\n", + " 0.06845367158053817,\n", + " 0.07018917307849848,\n", + " 0.07150486263149591,\n", + " 0.07206897155681857,\n", + " 0.07267489859277232,\n", + " 0.07353405291616566,\n", + " 0.07375557781356579,\n", + " 0.07451514637524581,\n", + " 0.0759550143069544,\n", + " 0.07626557252434252,\n", + " 0.07632692606374714,\n", + " 0.07633292307530737,\n", + " 0.0771118027618557,\n", + " 0.07779369669399872,\n", + " 0.07820960962677725,\n", + " 0.08087579048076732,\n", + " 0.08210092983828621,\n", + " 0.08278563864904265,\n", + " 0.08353085402349059,\n", + " 0.08392664291086527,\n", + " 0.08446481122864813,\n", + " 0.08516786760083628,\n", + " 0.08527409832409338,\n", + " 0.0876451526634382,\n", + " 0.08879153059212863,\n", + " 0.08921883809639986,\n", + " 0.08944287651863003,\n", + " 0.08947004614303489,\n", + " 0.09020475781777826,\n", + " 0.09034342490536307,\n", + " 0.0915350172031294,\n", + " 0.09206330228486603,\n", + " 0.09266669117946502,\n", + " 0.09371286431894066,\n", + " 0.09390675883687472,\n", + " 0.09510836747401663,\n", + " 0.09543611113027495,\n", + " 0.0983175947515661,\n", + " 0.09871433902648774,\n", + " 0.09904463198785052,\n", + " 0.09926123261504927,\n", + " 0.10130578043035487,\n", + " 0.1057062078358959,\n", + " 0.10615100452844062,\n", + " 0.10889038794077255,\n", + " 0.11225091494330519,\n", + " 0.11309801745105086,\n", + " 0.11310964190789663,\n", + " 0.11362824695101759,\n", + " 0.11393471309278458,\n", + " 0.11577716976981967,\n", + " 0.11648156223646,\n", + " 0.11699986906155835,\n", + " 0.11702932655484877,\n", + " 0.11839348496608193,\n", + " 0.11915755658348837,\n", + " 0.11970509237610383,\n", + " 0.12020292514308373,\n", + " 0.12069181897447778,\n", + " 0.12101114277921454,\n", + " 0.12157221623063852,\n", + " 0.12422387897670406,\n", + " 0.12607972596227968,\n", + " 0.12706345312738032,\n", + " 0.12979043820819802,\n", + " 0.13020966545749268,\n", + " 0.13195832814559594,\n", + " 0.13334042105977206,\n", + " 0.13422052468051426,\n", + " 0.1360702775875623,\n", + " 0.13625378876752436,\n", + " 0.13760457073620147,\n", + " 0.13852434814524017,\n", + " 0.13912283037126727,\n", + " 0.13997465745057103,\n", + " 0.1409364452747326,\n", + " 0.1431212811618362,\n", + " 0.14336179695489493,\n", + " 0.14384884267821363,\n", + " 0.1446173713796447,\n", + " 0.14507265384145018,\n", + " 0.14522838687132544,\n", + " 0.14625865075542455,\n", + " 0.14875412007511835,\n", + " 0.1490799293066345,\n", + " 0.1501836558746269,\n", + " 0.15024049696919317,\n", + " 0.15258750320143843,\n", + " 0.1543892384382347,\n", + " 0.15542287735408955,\n", + " 0.1562795798291411,\n", + " 0.15672090001256167,\n", + " 0.15835450845597965,\n", + " 0.15885558237045605,\n", + " 0.15916683197346893,\n", + " 0.16128592233735117,\n", + " 0.16176260574045143,\n", + " 0.1625689679776966,\n", + " 0.1640033302756848,\n", + " 0.16428224723518692,\n", + " 0.1646108946315311,\n", + " 0.16519659583027135,\n", + " 0.16686728385993066,\n", + " 0.17083591871787485,\n", + " 0.17086548334009344,\n", + " 0.17184192733550785,\n", + " 0.173196270811397,\n", + " 0.1738182562856646,\n", + " 0.17392939661484452,\n", + " 0.1743878644990864,\n", + " 0.1755944550799181,\n", + " 0.1755987311148095,\n", + " 0.1761517518744068,\n", + " 0.17978336530128602,\n", + " 0.18142197523537484,\n", + " 0.18193857903019073,\n", + " 0.18343907674996252,\n", + " 0.18405849214612302,\n", + " 0.18639217320458523,\n", + " 0.18778247530377268,\n", + " 0.18844195171864653,\n", + " 0.19146236325380506,\n", + " 0.19259512352276398,\n", + " 0.19382009930180055,\n", + " 0.19491557970592266,\n", + " 0.19573521178604103,\n", + " 0.19597759438959828,\n", + " 0.19658963498205273,\n", + " 0.197357491688532,\n", + " 0.19902552491177128,\n", + " 0.19984213182415256,\n", + " 0.20032201494662394,\n", + " 0.20036598382789594,\n", + " 0.20100597798491715,\n", + " 0.20313787976321207,\n", + " 0.20376749861696353,\n", + " 0.2050304503621968,\n", + " 0.20628518210328228,\n", + " 0.20706397928656173,\n", + " 0.2077117059419803,\n", + " 0.2080483398093258,\n", + " 0.20931228237497213,\n", + " 0.20933429094758804,\n", + " 0.21006582224508907,\n", + " 0.21139514482142086,\n", + " 0.21172846241190912,\n", + " 0.213041897864376,\n", + " 0.21393253802800694,\n", + " 0.21495061074075017,\n", + " 0.2150180436839999,\n", + " 0.2150227833214905,\n", + " 0.21527313954954486,\n", + " 0.21745549980914047,\n", + " 0.21798480762541628,\n", + " 0.22112824906097217,\n", + " 0.2213165098192843,\n", + " 0.2225069791823141,\n", + " 0.2234287962822562,\n", + " 0.2239602132532641,\n", + " 0.2241044689489926,\n", + " 0.22469892418389437,\n", + " 0.2250010370980503,\n", + " 0.22525997630394123,\n", + " 0.22613353689490412,\n", + " 0.22621289370202935,\n", + " 0.22649234356054937,\n", + " 0.22657887876359106,\n", + " 0.2289104829453915,\n", + " 0.2289258875474921,\n", + " 0.22962773095859146,\n", + " 0.22991441127585766,\n", + " 0.23061963345939152,\n", + " 0.2327835638543322,\n", + " 0.2335813431057503,\n", + " 0.23598738350126336,\n", + " 0.2377190638825466,\n", + " 0.23772857723276897,\n", + " 0.2409644542067777,\n", + " 0.24193443760187738,\n", + " 0.24266624066074172,\n", + " 0.24477530681145188,\n", + " 0.24492192767472287,\n", + " 0.24540730482958095,\n", + " 0.24581932517381044,\n", + " 0.24622853765853447,\n", + " 0.24629855513647103,\n", + " 0.24728703733301227,\n", + " 0.251937457408962,\n", + " 0.2522953265193757,\n", + " 0.2527938188424861,\n", + " 0.2539936200986932,\n", + " 0.25657153541353117,\n", + " 0.2623976312092874,\n", + " 0.26376559746492356,\n", + " 0.2655534869207212,\n", + " 0.26818740207454805,\n", + " 0.26880379561174716,\n", + " 0.2689638803390203,\n", + " 0.2694146795139064,\n", + " 0.2698980642610357,\n", + " 0.26991337645100466,\n", + " 0.2703954442128468,\n", + " 0.27042257295282235,\n", + " 0.2718438340112861,\n", + " 0.2725801894777097,\n", + " 0.2726460826494519,\n", + " 0.2727350425493835,\n", + " 0.2738700149958927,\n", + " 0.2744249698593041,\n", + " 0.27470516707142867,\n", + " 0.2748434762498124,\n", + " 0.2753480848521488,\n", + " 0.2771428616028684,\n", + " 0.2773304285760102,\n", + " 0.2774187380514108,\n", + " 0.2815008188319805,\n", + " 0.2818961156975073,\n", + " 0.2825031460832935,\n", + " 0.28339231391103514,\n", + " 0.28390681005429885,\n", + " 0.2840362643979768,\n", + " 0.2851504062186031,\n", + " 0.28519697987876336,\n", + " 0.2879404006568673,\n", + " 0.2882647145928189,\n", + " 0.2894115576441685,\n", + " 0.2897178421348965,\n", + " 0.29078381533111564,\n", + " 0.29269394716741903,\n", + " 0.29289878288861027,\n", + " 0.29296222593695065,\n", + " 0.29457197951092984,\n", + " 0.2950193987565687,\n", + " 0.2958771448234342,\n", + " 0.29613678410363287,\n", + " 0.29719252608419644,\n", + " 0.29792769035852107,\n", + " 0.29924804789123327,\n", + " 0.2996306403465836,\n", + " 0.3002353582383869,\n", + " 0.3011733796410101,\n", + " 0.3014253642025765,\n", + " 0.30148381428385473,\n", + " 0.30222409934718697,\n", + " 0.30302293725644946,\n", + " 0.303384152437963,\n", + " 0.30438044990745017,\n", + " 0.30446289895869627,\n", + " 0.3047713593394179,\n", + " 0.30533492324887923,\n", + " 0.30759341292963394,\n", + " 0.3079895891000084,\n", + " 0.31027622731015214,\n", + " 0.3106259936464205,\n", + " 0.31228365685858517,\n", + " 0.31373446618386713,\n", + " 0.3146581537887806,\n", + " 0.3165201156090204,\n", + " 0.3173050860943959,\n", + " 0.31736311969000963,\n", + " 0.3186545546394014,\n", + " 0.31920526839665697,\n", + " 0.31979874620684423,\n", + " 0.3201108772890495,\n", + " 0.3214729043953435,\n", + " 0.3215723538416375,\n", + " 0.32251629974443485,\n", + " 0.3230211269289174,\n", + " 0.3231937877282306,\n", + " 0.32320098025357535,\n", + " 0.32646242445471185,\n", + " 0.32888382887491807,\n", + " 0.32903430363868547,\n", + " 0.32957799937042587,\n", + " 0.33006006774592356,\n", + " 0.33069586535718964,\n", + " 0.3318803309471582,\n", + " 0.3319491238165069,\n", + " 0.33201952611476593,\n", + " 0.33227261556719356,\n", + " 0.3341717770194724,\n", + " 0.33502812146243033,\n", + " 0.3350865258380781,\n", + " 0.3351923785682117,\n", + " 0.3356458733463127,\n", + " 0.33693187026392657,\n", + " 0.3381201622838067,\n", + " 0.338893645248199,\n", + " 0.3397673433972799,\n", + " 0.3398019741194217,\n", + " 0.3409627794568184,\n", + " 0.34163480060181994,\n", + " 0.3420234464604043,\n", + " 0.3422086123136834,\n", + " 0.34260760982028204,\n", + " 0.34318070016126323,\n", + " 0.34318744412953817,\n", + " 0.3463931391807529,\n", + " 0.34734857457497803,\n", + " 0.3487257129724425,\n", + " 0.3487656028971219,\n", + " 0.3490224050510077,\n", + " 0.35090031858542203,\n", + " 0.3540231244224501,\n", + " 0.35612024486657834,\n", + " 0.359700436512684,\n", + " 0.36035349126449834,\n", + " 0.36096303665109,\n", + " 0.3609721396252491,\n", + " 0.3609965324844564,\n", + " 0.3642079730049166,\n", + " 0.3642766237177696,\n", + " 0.3655986476806531,\n", + " 0.36579335613388275,\n", + " 0.3670745855106351,\n", + " 0.36718658948699967,\n", + " 0.36793019445350506,\n", + " 0.3685813859580468,\n", + " 0.3686858452704782,\n", + " 0.3702056628147493,\n", + " 0.3721391447849598,\n", + " 0.37224877222959407,\n", + " 0.37488077609551884,\n", + " 0.37826153984308275,\n", + " 0.3784609830209543,\n", + " 0.3830038238658926,\n", + " 0.38354518035449725,\n", + " 0.3839381870692038,\n", + " 0.38559293427210484,\n", + " 0.3862788418417622,\n", + " 0.38702126387394575,\n", + " 0.3902805919669198,\n", + " 0.39168248459395727,\n", + " 0.39209796654894635,\n", + " 0.3924721502060944,\n", + " 0.39524397556637625,\n", + " 0.3955464509331692,\n", + " 0.3994984438160434,\n", + " 0.4015548540854227,\n", + " 0.40349399813249953,\n", + " 0.40398184570731355,\n", + " 0.4046058709572422,\n", + " 0.4061359794165864,\n", + " 0.40669253933291094,\n", + " 0.4069287803527428,\n", + " 0.4078699321218693,\n", + " 0.4096896667694705,\n", + " 0.41127642116593444,\n", + " 0.41132016736260224,\n", + " 0.4114466832337782,\n", + " 0.41297013392248016,\n", + " 0.4131852738513141,\n", + " 0.413955072315066,\n", + " 0.4151754325589744,\n", + " 0.41759656217509356,\n", + " 0.4178329112653013,\n", + " 0.41833503857831467,\n", + " 0.42132520458576384,\n", + " 0.421869363988447,\n", + " 0.42296202632284063,\n", + " 0.42397664238566934,\n", + " 0.4241386776293773,\n", + " 0.42463226505351526,\n", + " 0.4248473357532826,\n", + " 0.4248802234854424,\n", + " 0.4249406393174283,\n", + " 0.42656104379365,\n", + " 0.4288660479433638,\n", + " 0.42898888252375944,\n", + " 0.43035004663232024,\n", + " 0.4304925278927392,\n", + " 0.43085122262158115,\n", + " 0.4322949494317947,\n", + " 0.4325987560745109,\n", + " 0.4331322113250635,\n", + " 0.43419316548053377,\n", + " 0.434494285624762,\n", + " 0.4345597477720148,\n", + " 0.43594574707406575,\n", + " 0.43604313993678046,\n", + " 0.4383326685339004,\n", + " 0.43903459696089475,\n", + " 0.44319275309272244,\n", + " 0.44328434490982827,\n", + " 0.4433997577790556,\n", + " 0.44443018472155804,\n", + " 0.4474687964494354,\n", + " 0.44756620457824714,\n", + " 0.4475684687188618,\n", + " 0.44919516734939025,\n", + " 0.4494438649292968,\n", + " 0.45066213114841147,\n", + " 0.45112983442386645,\n", + " 0.45126854199966215,\n", + " 0.45151470079875644,\n", + " 0.45298141541676595,\n", + " 0.4531507980203662,\n", + " 0.4534059616627919,\n", + " 0.454345304507597,\n", + " 0.45508936278476475,\n", + " 0.45794978856445856,\n", + " 0.4639728982841209,\n", + " 0.46418369804557347,\n", + " 0.46573468314645805,\n", + " 0.4657962536634507,\n", + " 0.46585140271307846,\n", + " 0.4662896871973118,\n", + " 0.4670637930611341,\n", + " 0.46866547778109247,\n", + " 0.46872946303098184,\n", + " 0.4709556752564361,\n", + " 0.47106794769399196,\n", + " 0.4712283259893091,\n", + " 0.47182548591299167,\n", + " 0.47240831847715337,\n", + " 0.4724386810280179,\n", + " 0.4731713972698349,\n", + " 0.4741469415451376,\n", + " 0.47426924858085273,\n", + " 0.4773623412757162,\n", + " 0.47835965664062563,\n", + " 0.47846285307187686,\n", + " 0.48147785234031626,\n", + " 0.4815490058046151,\n", + " 0.4830370929359752,\n", + " 0.48355340548086534,\n", + " 0.48436368114394635,\n", + " 0.49234663530282463,\n", + " 0.4925622194838086,\n", + " 0.4928607922583056,\n", + " 0.4929278548345497,\n", + " 0.49371279562088133,\n", + " 0.4981914629363716,\n", + " 0.4989523350245948,\n", + " 0.4995127137897363,\n", + " 0.5018234540925802,\n", + " 0.5034805568076962,\n", + " 0.5052257203378888,\n", + " 0.5056625181717007,\n", + " 0.5131861021579847,\n", + " 0.5133489973148581,\n", + " 0.5141676647104568,\n", + " 0.5146366310836915,\n", + " 0.5161843673900794,\n", + " 0.516212051421584,\n", + " 0.5164873405306739,\n", + " 0.5171987285389105,\n", + " 0.5201150217361008,\n", + " 0.5203009992352942,\n", + " 0.521626491911722,\n", + " 0.5252579158144539,\n", + " 0.5270337766705395,\n", + " 0.5275196427727605,\n", + " 0.5277186587977098,\n", + " 0.5279571674405147,\n", + " 0.5288214406476862,\n", + " 0.5290168371188084,\n", + " 0.5298635635399771,\n", + " 0.531365343632835,\n", + " 0.5323851790497115,\n", + " 0.532411863384256,\n", + " 0.5324916825828468,\n", + " 0.5343531301272328,\n", + " 0.5345886379608727,\n", + " 0.5358827519792342,\n", + " 0.5374751794100243,\n", + " 0.538929935762309,\n", + " 0.5395720104874221,\n", + " 0.5402912155948718,\n", + " 0.541206390510491,\n", + " 0.542214039234039,\n", + " 0.5440376384789697,\n", + " 0.5441603134096432,\n", + " 0.5448353726659675,\n", + " 0.5452091923328296,\n", + " 0.5482626451053983,\n", + " 0.5508772740236291,\n", + " 0.5521464600195365,\n", + " 0.5527011604949804,\n", + " 0.555433113218722,\n", + " 0.5569464544657843,\n", + " 0.5578424556881069,\n", + " 0.5605800772588564,\n", + " 0.5610416270651907,\n", + " 0.5619463459071875,\n", + " 0.5663937462133342,\n", + " 0.5665142295823579,\n", + " 0.5668784872007242,\n", + " 0.5670973859772207,\n", + " 0.567220718116592,\n", + " 0.5674641923935309,\n", + " 0.5699560902879853,\n", + " 0.5704526823725057,\n", + " 0.5713294809115436,\n", + " 0.5717613601107112,\n", + " 0.5721748221970422,\n", + " 0.5731214874107099,\n", + " 0.5764031634707573,\n", + " 0.577083013649053,\n", + " 0.5788954769505547,\n", + " 0.5808882640628719,\n", + " 0.5823788714490341,\n", + " 0.58393532424283,\n", + " 0.5853069931491429,\n", + " 0.5861719541405012,\n", + " 0.586260604455198,\n", + " 0.5864527560454786,\n", + " 0.5867325920361051,\n", + " 0.5878747495410683,\n", + " 0.588425105013239,\n", + " 0.5888403168462132,\n", + " 0.5898362294132522,\n", + " 0.5908943990428708,\n", + " 0.5911327583700652,\n", + " 0.5916914766189256,\n", + " 0.5922160863958118,\n", + " 0.5925162403192972,\n", + " 0.5933223119867499,\n", + " 0.5958782210376558,\n", + " 0.5963509734387221,\n", + " 0.5965423148438698,\n", + " 0.5966285272688691,\n", + " 0.5972531692894347,\n", + " 0.5987510402508917,\n", + " 0.5989050517636052,\n", + " 0.5989854074418631,\n", + " 0.5991875332924592,\n", + " 0.5995336797044957,\n", + " 0.5995841137695194,\n", + " 0.6022996816527698,\n", + " 0.6051694078552863,\n", + " 0.605495378640489,\n", + " 0.6084111389746149,\n", + " 0.609023052460295,\n", + " 0.6095300188777706,\n", + " 0.6115421269425185,\n", + " 0.6146000989541851,\n", + " 0.6148799386374123,\n", + " 0.616022711068745,\n", + " 0.6161666817249483,\n", + " 0.616446775089544,\n", + " 0.6180945057355842,\n", + " 0.6189093819359845,\n", + " 0.6189989124062417,\n", + " 0.6207284447223983,\n", + " 0.6219453597269589,\n", + " 0.6219579976281282,\n", + " 0.6244091274690841,\n", + " 0.6248605451324224,\n", + " 0.6248684467182025,\n", + " 0.6249810574488789,\n", + " 0.625003572104589,\n", + " 0.6272286274129104,\n", + " 0.627443382512834,\n", + " 0.6282820084195593,\n", + " 0.6317063258167573,\n", + " 0.6319261036389178,\n", + " 0.6326554113139327,\n", + " 0.632674582482594,\n", + " 0.6330225162794573,\n", + " 0.6337186581954308,\n", + " 0.6351846082245701,\n", + " 0.6377762229103271,\n", + " 0.6380562572439947,\n", + " 0.6384409954616111,\n", + " 0.6388699683391781,\n", + " 0.6458196024949021,\n", + " 0.6462234038560144,\n", + " 0.6484371929052909,\n", + " 0.6496183325677178,\n", + " 0.6498901159391679,\n", + " 0.6506615976936588,\n", + " 0.6518082779180436,\n", + " 0.6526195846036325,\n", + " 0.6526227077362098,\n", + " 0.6545777790175734,\n", + " 0.6553645898107151,\n", + " 0.6563994620621694,\n", + " 0.656663064995839,\n", + " 0.6586776652485704,\n", + " 0.6589838515579817,\n", + " 0.6590658482725277,\n", + " 0.6591832701820634,\n", + " 0.6608640533489334,\n", + " 0.6610952202877582,\n", + " 0.6618513422415427,\n", + " 0.6629358490620881,\n", + " 0.6634054186095141,\n", + " 0.6645463735085086,\n", + " 0.6654166572652109,\n", + " 0.6686987914714826,\n", + " 0.6693473962485036,\n", + " 0.6706020597659939,\n", + " 0.6709705100800764,\n", + " 0.6717084778402611,\n", + " 0.6724828486148766,\n", + " 0.6724906593674672,\n", + " 0.675741808662794,\n", + " 0.6766053415994165,\n", + " 0.6775276810953021,\n", + " 0.6780099452375963,\n", + " 0.6782226837586124,\n", + " 0.6788456264260829,\n", + " 0.6816996275873363,\n", + " 0.682168054486859,\n", + " 0.6837431492066041,\n", + " 0.683823608645679,\n", + " 0.6839177965321879,\n", + " 0.6840725936963146,\n", + " 0.6858748031215338,\n", + " 0.6865288172641009,\n", + " 0.687001739158991,\n", + " 0.687094229155063,\n", + " 0.6885366677976457,\n", + " 0.6886244767370505,\n", + " 0.68946654578105,\n", + " 0.6895433320583686,\n", + " 0.6921627538821671,\n", + " 0.6932977290087255,\n", + " 0.6933668659389108,\n", + " 0.6938352914030828,\n", + " 0.6939640414315045,\n", + " 0.6948709533875934,\n", + " 0.6951758485451573,\n", + " 0.6968917640014154,\n", + " 0.6973800161267699,\n", + " 0.6989696382432962,\n", + " 0.6997026290248549,\n", + " 0.7001626522350871,\n", + " 0.7008677802880492,\n", + " 0.7048688057441729,\n", + " 0.7049890058354797,\n", + " 0.7050195332760512,\n", + " 0.7056350806101372,\n", + " 0.7066024370925158,\n", + " 0.706857889784513,\n", + " 0.7079278333522699,\n", + " 0.7080900818469954,\n", + " 0.7091907266516967,\n", + " 0.7093730725517341,\n", + " 0.7105530402465497,\n", + " 0.7105860603115497,\n", + " 0.7108427169975953,\n", + " 0.710958941816638,\n", + " 0.7119183508079874,\n", + " 0.712473922240254,\n", + " 0.7130857128316854,\n", + " 0.7143735687307076,\n", + " 0.7156535693731784,\n", + " 0.7167044758712661,\n", + " 0.7171971976174101,\n", + " 0.7173591166112153,\n", + " 0.7180122963467511,\n", + " 0.7182547768985454,\n", + " 0.7186503638143066,\n", + " 0.7199706143473819,\n", + " 0.7233082375375243,\n", + " 0.7239011487153489,\n", + " 0.7242404448483999,\n", + " 0.7244070670283783,\n", + " 0.7270800957754413,\n", + " 0.7273828972992467,\n", + " 0.7278698916103851,\n", + " 0.7279001489325277,\n", + " 0.7286691379731557,\n", + " 0.7290190167242739,\n", + " 0.7309908810487125,\n", + " 0.7326820039199539,\n", + " 0.7329090517759312,\n", + " 0.739940921055405,\n", + " 0.7434099431208323,\n", + " 0.7466478217794823,\n", + " 0.7476975521388607,\n", + " 0.7482384371917541,\n", + " 0.7489193206002432,\n", + " 0.7510700727730789,\n", + " 0.751168415348455,\n", + " 0.7513344525466453,\n", + " 0.7530838441205541,\n", + " 0.7561479450556158,\n", + " 0.7589382023303526,\n", + " 0.7591723511860086,\n", + " 0.7596873869687933,\n", + " 0.7604656847419421,\n", + " 0.7613223536829636,\n", + " 0.7651786557756509,\n", + " 0.765289337995715,\n", + " 0.7672956742772928,\n", + " 0.7673533636761452,\n", + " 0.767860014375011,\n", + " 0.7679593417231557,\n", + " 0.7686754737065615,\n", + " 0.7723743769694924,\n", + " 0.7738576335626756,\n", + " 0.7752593808743791,\n", + " 0.7762420437397328,\n", + " 0.7785389296302156,\n", + " 0.7786513118789642,\n", + " 0.7824737739069422,\n", + " 0.7828458158695082,\n", + " 0.7832963409396819,\n", + " 0.7841102167276137,\n", + " 0.7841743140878821,\n", + " 0.786346530470324,\n", + " 0.786745257198348,\n", + " 0.7876078132295328,\n", + " 0.7877903861892426,\n", + " 0.7880663743956936,\n", + " 0.7889421450421804,\n", + " 0.790079152236495,\n", + " 0.7901899265548301,\n", + " 0.7911841751640999,\n", + " 0.7914794033211856,\n", + " 0.7918014732787095,\n", + " 0.7920188842930745,\n", + " 0.7931485253636771,\n", + " 0.7934685062336554,\n", + " 0.7937162787969917,\n", + " 0.7948047537275847,\n", + " 0.7957713438937825,\n", + " 0.7970347955467577,\n", + " 0.7985062066147556,\n", + " 0.798559640661481,\n", + " 0.7988328923937563,\n", + " 0.7999832070711188,\n", + " 0.802479135164161,\n", + " 0.8037822018412717,\n", + " 0.8041398874952114,\n", + " 0.8059849661420606,\n", + " 0.8063320314723228,\n", + " 0.8068094143803295,\n", + " 0.8070624779145962,\n", + " 0.8074867779441165,\n", + " 0.8096171744387134,\n", + " 0.8110306680244982,\n", + " 0.8114369216867047,\n", + " 0.8117419041250775,\n", + " 0.811752076440983,\n", + " 0.8128343061976084,\n", + " 0.8128608729237652,\n", + " 0.8138963074526002,\n", + " 0.8141290792413814,\n", + " 0.8141830507072457,\n", + " 0.814433174940653,\n", + " 0.8148250267316691,\n", + " 0.8149382866951798,\n", + " 0.8152603955746031,\n", + " 0.8152945471875449,\n", + " 0.8158350971248133,\n", + " 0.8168592246024563,\n", + " 0.8186602155454856,\n", + " 0.8192136320759589,\n", + " 0.8193948886315071,\n", + " 0.8196723394397735,\n", + " 0.819683388474866,\n", + " 0.8223955878513909,\n", + " 0.8224427045961231,\n", + " 0.8227417945462477,\n", + " 0.8232460099219688,\n", + " 0.8239276617347282,\n", + " 0.825427184811981,\n", + " 0.8257244431505741,\n", + " 0.8267201640239498,\n", + " 0.8269931007749239,\n", + " 0.8304237087661536,\n", + " 0.8315863178336268,\n", + " 0.8319525210480346,\n", + " 0.8326772466260077,\n", + " 0.8351142780964415,\n", + " 0.83825390661194,\n", + " 0.8397877239750948,\n", + " 0.840744751995548,\n", + " 0.8427488927306527,\n", + " 0.8445167324082414,\n", + " 0.845185141478426,\n", + " 0.8466896139688487,\n", + " 0.8483482985559312,\n", + " 0.8485679151458629,\n", + " 0.8532066212612518,\n", + " 0.8562437882360899,\n", + " 0.8562935893759662,\n", + " 0.8575871646118248,\n", + " 0.857644814415513,\n", + " 0.8582119597276281,\n", + " 0.858694708708258,\n", + " 0.8589709717063778,\n", + " 0.8605621290195696,\n", + " 0.8607744346316882,\n", + " 0.8629913359265646,\n", + " 0.8648230556139784,\n", + " 0.8663882374848827,\n", + " 0.8695776723054177,\n", + " 0.8697369520479535,\n", + " 0.8704051213699109,\n", + " 0.8708881163434653,\n", + " 0.8709551364946905,\n", + " 0.8709745602687864,\n", + " 0.8715834721521957,\n", + " 0.8724111417020128,\n", + " 0.8729489870607494,\n", + " 0.8736889646732721,\n", + " 0.8738783734571005,\n", + " 0.8741320524972753,\n", + " 0.874578760833131,\n", + " 0.87578111908946,\n", + " 0.8765445355294582,\n", + " 0.8802360676510553,\n", + " 0.88176308798502,\n", + " 0.8858626453809116,\n", + " 0.8865089208554938,\n", + " 0.8876483949256709,\n", + " 0.8891008236195337,\n", + " 0.8893800444775953,\n", + " 0.8898280102302075,\n", + " 0.891661821635953,\n", + " 0.8924892027704591,\n", + " 0.8927759074983884,\n", + " 0.8930924571015301,\n", + " 0.8938027188961681,\n", + " 0.8947272722888562,\n", + " 0.896297864212215,\n", + " 0.8975995479971396,\n", + " 0.898125621411599,\n", + " 0.8988463798584126,\n", + " 0.8997407390358053,\n", + " 0.9022287191147113,\n", + " 0.902446504531618,\n", + " 0.9028544988178231,\n", + " 0.9044669790870821,\n", + " 0.9045300894860915,\n", + " 0.9070290539301853,\n", + " 0.9072707973775902,\n", + " 0.9109974211348453,\n", + " 0.9112430301939605,\n", + " 0.9138488445665155,\n", + " 0.9139844449282692,\n", + " 0.9140495145428476,\n", + " 0.9144879361253944,\n", + " 0.9149932664735345,\n", + " 0.9151647097255314,\n", + " 0.9222250279766461,\n", + " 0.9252043283700748,\n", + " 0.9260858477143477,\n", + " 0.929099643236498,\n", + " 0.9293706132507173,\n", + " 0.9296305058548253,\n", + " 0.930718293864877,\n", + " 0.9320243562539431,\n", + " 0.9335823058617141,\n", + " 0.93410476975939,\n", + " 0.9346552334282847,\n", + " 0.9385919819697336,\n", + " 0.9386023489291914,\n", + " 0.9390203563997565,\n", + " 0.9408441239775633,\n", + " 0.9412564300291297,\n", + " 0.9413386657486532,\n", + " 0.9433383329974413,\n", + " 0.9435829852880645,\n", + " 0.9439426859956916,\n", + " 0.9440418846949984,\n", + " 0.944457107147504,\n", + " 0.9445282488366455,\n", + " 0.9446485879108835,\n", + " 0.9447041708238896,\n", + " 0.9507075442088528,\n", + " 0.9516549853366104,\n", + " 0.9522816431638531,\n", + " 0.9523476094574852,\n", + " 0.9524971125587479,\n", + " 0.9526138269599105,\n", + " 0.9526523460383233,\n", + " 0.9533167084354774,\n", + " 0.9537704376521136,\n", + " 0.9538326116275573,\n", + " 0.9546455174766972,\n", + " 0.9547422574465706,\n", + " 0.9553611771028514,\n", + " 0.9555710223311377,\n", + " 0.9564267083365693,\n", + " 0.9571480093742284,\n", + " 0.9574754568551648,\n", + " 0.9576261107170713,\n", + " 0.958603301986054,\n", + " 0.9590048438605638,\n", + " 0.9607915929402436,\n", + " 0.9614895398270192,\n", + " 0.963946747653707,\n", + " 0.9640146312956573,\n", + " 0.9646032337594956,\n", + " 0.9646588086918275,\n", + " 0.9652517383271517,\n", + " 0.9655899982377839,\n", + " 0.9661455661035445,\n", + " 0.9668379624888932,\n", + " 0.9670857249634918,\n", + " 0.9695274880563277,\n", + " 0.9731380024312987,\n", + " 0.9750278875563737,\n", + " 0.9769125114348521,\n", + " 0.9773525933444962,\n", + " 0.9775963757277827,\n", + " 0.9800896104562643,\n", + " 0.9803236936910357,\n", + " 0.9811079278434411,\n", + " 0.9814569641014795,\n", + " 0.9832020498138141,\n", + " 0.9832994030971569,\n", + " 0.9839039601268755,\n", + " 0.9859639959104932,\n", + " 0.9878285351775311,\n", + " 0.9911735805216166,\n", + " 0.9919861584535846,\n", + " 0.9930350454798805,\n", + " 0.9932140669095247,\n", + " 0.9936069085091837,\n", + " 0.9942273196897291,\n", + " 0.994539884025336,\n", + " 0.9946208471066382,\n", + " 0.9953063953349244,\n", + " 0.9971302633362986,\n", + " 0.9974618282678035,\n", + " 0.9975375022645281,\n", + " 0.9982090026363253)" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "func = lambda x: tuple(sorted(x))\n", + "arr = np.random.rand(1000)\n", + "func(arr)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "210 µs ± 32.5 µs per loop (mean ± std. dev. of 10 runs, 1,000 loops each)\n" + ] + } + ], + "source": [ + "%timeit -r 10 -n 1000 [[*hull.neighbors[i][coplanar_simplex_array[i]], i] for i in range(hull.nsimplex)]" + ] + }, + { + "cell_type": "code", + "execution_count": 180, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{0},\n", + " {1},\n", + " {2},\n", + " {3},\n", + " {4},\n", + " {5},\n", + " {6},\n", + " {7},\n", + " {8},\n", + " {9},\n", + " {10, 11},\n", + " {10, 11},\n", + " {12, 13},\n", + " {12, 13},\n", + " {14, 15},\n", + " {14, 15, 16},\n", + " {15, 16},\n", + " {17, 18},\n", + " {17, 18},\n", + " {19, 20},\n", + " {19, 20},\n", + " {21, 22},\n", + " {21, 22},\n", + " {23, 24},\n", + " {23, 24},\n", + " {25, 26},\n", + " {25, 26},\n", + " {27, 29},\n", + " {28, 29},\n", + " {27, 28, 29},\n", + " {30, 31},\n", + " {30, 31},\n", + " {32, 34},\n", + " {33, 34},\n", + " {32, 33, 34},\n", + " {35, 36},\n", + " {35, 36},\n", + " {37, 38, 39},\n", + " {37, 38},\n", + " {37, 39},\n", + " {40, 41},\n", + " {40, 41},\n", + " {42, 43},\n", + " {42, 43},\n", + " {44, 46},\n", + " {45, 46},\n", + " {44, 45, 46},\n", + " {47, 48},\n", + " {47, 48},\n", + " {49, 50},\n", + " {49, 50, 51},\n", + " {50, 51},\n", + " {52, 53},\n", + " {52, 53},\n", + " {54, 55},\n", + " {54, 55},\n", + " {56, 58},\n", + " {57, 58},\n", + " {56, 57, 58},\n", + " {59, 60},\n", + " {59, 60},\n", + " {61, 62, 63},\n", + " {61, 62},\n", + " {61, 63},\n", + " {64, 65},\n", + " {64, 65},\n", + " {66, 67},\n", + " {66, 67},\n", + " {68, 69},\n", + " {68, 69},\n", + " {70, 76},\n", + " {71, 73, 77},\n", + " {72, 74},\n", + " {71, 73, 76},\n", + " {72, 74, 75},\n", + " {74, 75, 77},\n", + " {70, 73, 76},\n", + " {71, 75, 77},\n", + " {78, 79},\n", + " {78, 79, 80},\n", + " {79, 80},\n", + " {81, 82},\n", + " {81, 82},\n", + " {83, 88, 90},\n", + " {84, 86, 88},\n", + " {85, 89},\n", + " {84, 86, 87},\n", + " {86, 87, 89},\n", + " {83, 84, 88},\n", + " {85, 87, 89},\n", + " {83, 90},\n", + " {91, 93},\n", + " {92, 93},\n", + " {91, 92, 93},\n", + " {94, 95},\n", + " {94, 95}]" + ] + }, + "execution_count": 180, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# There can be a variable number of neighbors! If one triangle has multiple coplanar simplices,\n", + "# an array indexing will not work. A list comprehension is probably fine here\n", + "# Take the coplanar neighbors for each simplex and add the current simplex (see above)\n", + "coplanar_simplices = [\n", + " {*hull.neighbors[i][coplanar_simplex_array[i]], i} for i in range(hull.nsimplex)\n", + "]\n", + "coplanar_simplices" + ] + }, + { + "cell_type": "code", + "execution_count": 185, + "metadata": {}, + "outputs": [], + "source": [ + "{0, 1, 2}.issuperset({1, 2})\n", + "# for test_set in coplanar_simplices:\n", + "# What if we cech subset and superset?\n", + "for test_set in coplanar_simplices:\n", + " for other_set in coplanar_simplices:\n", + " pass #\n", + " # Or can we do j>i?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 177, + "metadata": {}, + "outputs": [], + "source": [ + "# coplanar_simplices[20]#coplanar_simplices[21]\n", + "def is_subset(s, sets_list):\n", + " # return any(s.issubset(other_set) for other_set in sets_list if s != other_set)\n", + " return any(s.issubset(other_set) for other_set in sets_list if s != other_set)" + ] + }, + { + "cell_type": "code", + "execution_count": 179, + "metadata": {}, + "outputs": [], + "source": [ + "# %%timeit -r 10 -n 1000\n", + "# %timeit -r 10 -n 1000 coplanar_simplices[20].issubset(coplanar_simplices[21])\n", + "filtered_sets = [s for s in coplanar_simplices if not is_subset(s, coplanar_simplices)]\n", + "# filtered_sets" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "40.3 µs ± 102 ns per loop (mean ± std. dev. of 10 runs, 10,000 loops each)\n" + ] + } + ], + "source": [ + "%timeit -r 10 -n 10000 poly._find_coplanar_simplices()" + ] + }, + { + "cell_type": "code", + "execution_count": 100, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 100, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "is_subset({0, 1}, [{0}, {0, 1}, {0, 1, 2}])" + ] + }, + { + "cell_type": "code", + "execution_count": 108, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 108, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "{0, 1}.issubset({0, 1})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 109, + "metadata": {}, + "outputs": [ + { + "ename": "IndexError", + "evalue": "only integers, slices (`:`), ellipsis (`...`), numpy.newaxis (`None`) and integer or boolean arrays are valid indices", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mIndexError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m/Users/jenbradley/github/coxeter/test.ipynb Cell 23\u001b[0m line \u001b[0;36m2\n\u001b[1;32m 1\u001b[0m i \u001b[39m=\u001b[39m \u001b[39m0\u001b[39m\n\u001b[0;32m----> 2\u001b[0m [\u001b[39m*\u001b[39mhull\u001b[39m.\u001b[39;49mneighbors[i][coplanar_simplices[i]], i]\u001b[39m.\u001b[39msort()\n\u001b[1;32m 3\u001b[0m \u001b[39msorted\u001b[39m([\u001b[39m*\u001b[39mhull\u001b[39m.\u001b[39mneighbors[i][coplanar_simplices[i]], i])\n", + "\u001b[0;31mIndexError\u001b[0m: only integers, slices (`:`), ellipsis (`...`), numpy.newaxis (`None`) and integer or boolean arrays are valid indices" + ] + } + ], + "source": [ + "i = 0\n", + "[*hull.neighbors[i][coplanar_simplices[i]], i].sort()\n", + "sorted([*hull.neighbors[i][coplanar_simplices[i]], i])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,\n", + " 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,\n", + " 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,\n", + " 51, 52, 53, 54, 55]),\n", + " array([[False, False, False, False, False, False, False, False, False,\n", + " False, False, False, False, False, False, False, False, False,\n", + " False, False, False, False, False, False, False, False, False,\n", + " False, False, False, False, False, False, False, False, False,\n", + " False, False, False, False, False, False, False, False, False,\n", + " False, False, False, False, False, False, False, False, False,\n", + " False, False],\n", + " [False, False, False, False, False, False, False, False, False,\n", + " False, False, False, False, False, False, False, False, False,\n", + " False, False, False, True, True, False, True, True, True,\n", + " True, False, False, True, True, True, False, False, True,\n", + " False, True, True, False, True, True, False, True, False,\n", + " True, True, True, False, True, True, False, True, True,\n", + " False, True],\n", + " [False, False, False, False, False, False, False, False, False,\n", + " False, False, False, False, False, False, False, False, False,\n", + " False, False, True, True, False, True, True, False, True,\n", + " False, True, True, True, False, True, True, True, False,\n", + " True, True, True, True, False, True, True, False, True,\n", + " True, False, True, True, False, False, True, True, False,\n", + " True, True]]))" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.arange(hull.nsimplex), coplanar_simplices.T" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[24, 54, 45],\n", + " [22, 30, 37],\n", + " [41, 36, 29],\n", + " [20, 39, 27],\n", + " [21, 31, 26],\n", + " [22, 35, 47],\n", + " [23, 40, 46],\n", + " [44, 32, 55],\n", + " [50, 28, 25],\n", + " [38, 23, 27],\n", + " [42, 51, 29],\n", + " [28, 52, 31],\n", + " [43, 36, 34],\n", + " [48, 33, 35],\n", + " [20, 39, 49],\n", + " [34, 43, 53],\n", + " [33, 48, 44],\n", + " [40, 46, 49],\n", + " [25, 54, 50],\n", + " [42, 51, 53],\n", + " [ 3, 14, 21],\n", + " [ 4, 22, 20],\n", + " [ 1, 21, 5],\n", + " [ 6, 9, 24],\n", + " [ 0, 25, 23],\n", + " [18, 24, 8],\n", + " [ 4, 27, 28],\n", + " [ 3, 26, 9],\n", + " [11, 8, 26],\n", + " [ 2, 10, 30],\n", + " [ 1, 31, 29],\n", + " [ 4, 30, 11],\n", + " [ 7, 33, 34],\n", + " [16, 13, 32],\n", + " [15, 12, 32],\n", + " [ 5, 37, 13],\n", + " [ 2, 12, 37],\n", + " [ 1, 35, 36],\n", + " [ 9, 40, 39],\n", + " [ 3, 14, 38],\n", + " [ 6, 38, 17],\n", + " [ 2, 43, 42],\n", + " [10, 19, 41],\n", + " [12, 41, 15],\n", + " [ 7, 16, 45],\n", + " [ 0, 46, 44],\n", + " [ 6, 45, 17],\n", + " [ 5, 49, 48],\n", + " [13, 16, 47],\n", + " [14, 47, 17],\n", + " [ 8, 52, 18],\n", + " [10, 19, 52],\n", + " [11, 50, 51],\n", + " [15, 55, 19],\n", + " [ 0, 18, 55],\n", + " [ 7, 54, 53]], dtype=int32)" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hull.neighbors" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 59, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[(-0.0, -0.0, -1.0),\n", + " (-0.0, -0.0, -1.0),\n", + " (0.0, -1.0, 0.0),\n", + " (0.0, -1.0, 0.0),\n", + " (1.0, -0.0, -0.0),\n", + " (1.0, -0.0, -0.0),\n", + " (-1.0, -0.0, -0.0),\n", + " (-1.0, -0.0, -0.0),\n", + " (0.0, 1.0, -0.0),\n", + " (0.0, 1.0, -0.0),\n", + " (-0.0, -0.0, 1.0),\n", + " (-0.0, -0.0, 1.0)]" + ] + }, + "execution_count": 59, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# This was close - but I'm thinking about this the wrong way. All we need is equations\n", + "normals = hull.equations[:, :3]\n", + "normals\n", + "\n", + "[tuple(normal) for normal in normals]" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[ True True False False False False False False False False False False]\n", + " [ True True False False False False False False False False False False]\n", + " [False False True True False False False False False False False False]\n", + " [False False True True False False False False False False False False]\n", + " [False False False False True True False False False False False False]\n", + " [False False False False True True False False False False False False]\n", + " [False False False False False False True True False False False False]\n", + " [False False False False False False True True False False False False]\n", + " [False False False False False False False False True True False False]\n", + " [False False False False False False False False True True False False]\n", + " [False False False False False False False False False False True True]\n", + " [False False False False False False False False False False True True]]\n" + ] + } + ], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "mbri.get_face_area().__len__(), len(mbri.faces)\n", + "face_areas = np.array(mbri.get_face_area())" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2.7821089295582024" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sum(face_areas)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5.1295458308488735" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mbri.surface_area" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "62" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mbri._coplanar_simplices.__len__()" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "42" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mbri.faces.__len__()" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,\n", + " 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,\n", + " 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,\n", + " 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67,\n", + " 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84,\n", + " 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95], dtype=int32)" + ] + }, + "execution_count": 58, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.unique([item for row in mbri._coplanar_simplices for item in row])\n", + "# So each item in the coplanar simplices is unique" + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([array([0], dtype=int32), array([1], dtype=int32),\n", + " array([2], dtype=int32), array([3], dtype=int32),\n", + " array([4], dtype=int32), array([5], dtype=int32),\n", + " array([6], dtype=int32), array([7], dtype=int32),\n", + " array([8], dtype=int32), array([9], dtype=int32),\n", + " array([10, 11], dtype=int32), array([12], dtype=int32),\n", + " array([13], dtype=int32), array([14], dtype=int32),\n", + " array([16, 15], dtype=int32), array([17, 18], dtype=int32),\n", + " array([19], dtype=int32), array([20], dtype=int32),\n", + " array([21], dtype=int32), array([22], dtype=int32)], dtype=object)" + ] + }, + "execution_count": 71, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cs = np.array(mbri._coplanar_simplices, dtype=object)\n", + "cs[0:20]" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[array([48, 40, 38], dtype=int32),\n", + " array([24, 14, 22], dtype=int32),\n", + " array([25, 23, 15], dtype=int32),\n", + " array([ 2, 7, 10], dtype=int32),\n", + " array([8, 6, 0], dtype=int32),\n", + " array([41, 49, 39], dtype=int32),\n", + " array([33, 36, 45], dtype=int32),\n", + " array([17, 29, 20], dtype=int32),\n", + " array([32, 43, 34], dtype=int32),\n", + " array([16, 18, 27], dtype=int32),\n", + " array([25, 15, 30, 21], dtype=int32),\n", + " array([48, 45, 36, 40], dtype=int32),\n", + " array([ 1, 3, 30, 15, 28], dtype=int32),\n", + " array([24, 22, 23, 25], dtype=int32),\n", + " array([19, 28, 15, 23], dtype=int32),\n", + " array([48, 38, 34, 43], dtype=int32),\n", + " array([ 3, 1, 9, 11], dtype=int32),\n", + " array([18, 22, 14, 27], dtype=int32),\n", + " array([18, 26, 19, 23, 22], dtype=int32),\n", + " array([17, 7, 2, 29], dtype=int32)]" + ] + }, + "execution_count": 73, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mbri.faces[0:20]" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(62, 42)" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(mbri._coplanar_simplices), len(mbri.faces)" + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[array([0], dtype=int32),\n", + " array([1], dtype=int32),\n", + " array([2], dtype=int32),\n", + " array([3], dtype=int32),\n", + " array([4], dtype=int32),\n", + " array([5], dtype=int32),\n", + " array([6], dtype=int32),\n", + " array([7], dtype=int32),\n", + " array([8], dtype=int32),\n", + " array([9], dtype=int32),\n", + " array([10, 11], dtype=int32),\n", + " array([12], dtype=int32),\n", + " array([13], dtype=int32),\n", + " array([14], dtype=int32),\n", + " array([16, 15], dtype=int32),\n", + " array([17, 18], dtype=int32),\n", + " array([19], dtype=int32),\n", + " array([20], dtype=int32),\n", + " array([21], dtype=int32),\n", + " array([22], dtype=int32),\n", + " array([24, 23], dtype=int32),\n", + " array([25], dtype=int32),\n", + " array([26], dtype=int32),\n", + " array([27, 28, 29], dtype=int32),\n", + " array([30, 31], dtype=int32),\n", + " array([32, 33, 34], dtype=int32),\n", + " array([35, 36], dtype=int32),\n", + " array([37], dtype=int32),\n", + " array([38, 39], dtype=int32),\n", + " array([40], dtype=int32),\n", + " array([41], dtype=int32),\n", + " array([42, 43], dtype=int32),\n", + " array([44, 45, 46], dtype=int32),\n", + " array([47], dtype=int32),\n", + " array([48], dtype=int32),\n", + " array([49, 50], dtype=int32),\n", + " array([51], dtype=int32),\n", + " array([52], dtype=int32),\n", + " array([53], dtype=int32),\n", + " array([54, 55], dtype=int32),\n", + " array([56, 57, 58], dtype=int32),\n", + " array([59], dtype=int32),\n", + " array([60], dtype=int32),\n", + " array([61, 62, 63], dtype=int32),\n", + " array([64], dtype=int32),\n", + " array([65], dtype=int32),\n", + " array([66], dtype=int32),\n", + " array([67], dtype=int32),\n", + " array([68], dtype=int32),\n", + " array([69], dtype=int32),\n", + " array([76, 70], dtype=int32),\n", + " array([71, 73, 74, 75, 77], dtype=int32),\n", + " array([72], dtype=int32),\n", + " array([80, 78], dtype=int32),\n", + " array([79], dtype=int32),\n", + " array([81], dtype=int32),\n", + " array([82], dtype=int32),\n", + " array([83], dtype=int32),\n", + " array([84, 86, 87, 88, 90], dtype=int32),\n", + " array([89, 85], dtype=int32),\n", + " array([91, 92, 93], dtype=int32),\n", + " array([94, 95], dtype=int32)]" + ] + }, + "execution_count": 71, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mbri._coplanar_simplices" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1.12 ms ± 11.8 µs per loop (mean ± std. dev. of 10 runs, 1,000 loops each)\n" + ] + } + ], + "source": [ + "%%timeit -r 10 -n 1000\n", + "# Start with cube\n", + "snorms = mbri._simplex_equations[:, :3]\n", + "tol = 1e-15\n", + "\n", + "coplanar_simplices = [() for i in range(len(snorms))]\n", + "for i, simplex_normal in enumerate(snorms):\n", + " coplanars = np.all(np.abs(simplex_normal - snorms) < tol, axis=1)\n", + " for ind, co in enumerate(coplanars):\n", + " # Iterating means each tuple will have the same length - nice!\n", + " # print(i, co)\n", + " if co:\n", + " coplanar_simplices[i] += (ind,)\n", + "list(set(coplanar_simplices))" + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "345 µs ± 11.8 µs per loop (mean ± std. dev. of 10 runs, 1,000 loops each)\n" + ] + } + ], + "source": [ + "%%timeit -r 10 -n 1000\n", + "mbri._find_coplanar_simplices()" + ] + }, + { + "cell_type": "code", + "execution_count": 112, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "515 µs ± 3.72 µs per loop (mean ± std. dev. of 10 runs, 10,000 loops each)\n" + ] + } + ], + "source": [ + "%%timeit -r 10 -n 10000\n", + "snorms = mbri._simplex_equations[:, :3]\n", + "tol = 1e-15\n", + "coplanars = np.all(np.abs(snorms[:, None] - snorms) < tol, axis=2)\n", + "coplanar_simplices = list({tuple(np.where(co)[0]) for co in coplanars})\n", + "coplanar_simplices" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 113, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "338 µs ± 9.85 µs per loop (mean ± std. dev. of 10 runs, 10,000 loops each)\n" + ] + } + ], + "source": [ + "%%timeit -r 10 -n 10000\n", + "mbri._find_coplanar_simplices()" + ] + }, + { + "cell_type": "code", + "execution_count": 140, + "metadata": {}, + "outputs": [], + "source": [ + "snorms = mbri._simplex_equations[:, :3]\n", + "tol = 1e-15\n", + "coplanars = np.all(np.abs(snorms[:, None] - snorms) < tol, axis=2)\n", + "coplanar_simplices = list({tuple(np.where(co)[0]) for co in coplanars})" + ] + }, + { + "cell_type": "code", + "execution_count": 142, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "788 ns ± 111 ns per loop (mean ± std. dev. of 10 runs, 100,000 loops each)\n", + "474 ns ± 44.5 ns per loop (mean ± std. dev. of 10 runs, 100,000 loops each)\n", + "1 µs ± 21.1 ns per loop (mean ± std. dev. of 10 runs, 100,000 loops each)\n" + ] + } + ], + "source": [ + "for co in coplanars:\n", + " # print(co)\n", + " # display(np.where(co)[0])\n", + " %timeit -r 10 -n 100000 np.where(co)[0]\n", + " cp = np.where(co)[0]\n", + " %timeit -r 10 -n 100000 tuple(cp)\n", + " %timeit -r 10 -n 100000 np.arange(0, len(mbri.simplices))[co]\n", + " break" + ] + }, + { + "cell_type": "code", + "execution_count": 145, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "456 µs ± 115 µs per loop (mean ± std. dev. of 10 runs, 1,000 loops each)\n" + ] + } + ], + "source": [ + "%timeit -r 10 -n 1000 coplanars = np.all(np.abs(snorms[:, None] - snorms) < tol, axis=2)" + ] + }, + { + "cell_type": "code", + "execution_count": 230, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[ True False False ... False False False]\n", + " [False True False ... False False False]\n", + " [False False True ... False False False]\n", + " ...\n", + " [False False False ... True False False]\n", + " [False False False ... False True True]\n", + " [False False False ... False True True]]\n" + ] + } + ], + "source": [ + "snorms = mbri._simplex_equations[:, :3]\n", + "coplanars = np.all(np.abs(snorms[:, None] - snorms) < tol, axis=2)\n", + "coplanar_simplices = list({tuple(np.where(co)[0]) for co in coplanars})\n", + "print(coplanars)" + ] + }, + { + "cell_type": "code", + "execution_count": 257, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[(0,),\n", + " (1,),\n", + " (2,),\n", + " (3,),\n", + " (4,),\n", + " (5,),\n", + " (6,),\n", + " (7,),\n", + " (8,),\n", + " (9,),\n", + " (10, 11),\n", + " (10, 11),\n", + " (12, 13),\n", + " (12, 13),\n", + " (14, 15, 16),\n", + " (14, 15, 16),\n", + " (14, 15, 16),\n", + " (17, 18),\n", + " (17, 18),\n", + " (19, 20),\n", + " (19, 20),\n", + " (21, 22),\n", + " (21, 22),\n", + " (23, 24),\n", + " (23, 24),\n", + " (25, 26),\n", + " (25, 26),\n", + " (27, 28, 29),\n", + " (27, 28, 29),\n", + " (27, 28, 29),\n", + " (30, 31),\n", + " (30, 31),\n", + " (32, 33, 34),\n", + " (32, 33, 34),\n", + " (32, 33, 34),\n", + " (35, 36),\n", + " (35, 36),\n", + " (37, 38, 39),\n", + " (37, 38, 39),\n", + " (37, 38, 39),\n", + " (40, 41),\n", + " (40, 41),\n", + " (42, 43),\n", + " (42, 43),\n", + " (44, 45, 46),\n", + " (44, 45, 46),\n", + " (44, 45, 46),\n", + " (47, 48),\n", + " (47, 48),\n", + " (49, 50, 51),\n", + " (49, 50, 51),\n", + " (49, 50, 51),\n", + " (52, 53),\n", + " (52, 53),\n", + " (54, 55),\n", + " (54, 55),\n", + " (56, 57, 58),\n", + " (56, 57, 58),\n", + " (56, 57, 58),\n", + " (59, 60),\n", + " (59, 60),\n", + " (61, 62, 63),\n", + " (61, 62, 63),\n", + " (61, 62, 63),\n", + " (64, 65),\n", + " (64, 65),\n", + " (66, 67),\n", + " (66, 67),\n", + " (68, 69),\n", + " (68, 69),\n", + " (70, 71, 72, 73, 74, 75, 76, 77),\n", + " (70, 71, 72, 73, 74, 75, 76, 77),\n", + " (70, 71, 72, 73, 74, 75, 76, 77),\n", + " (70, 71, 72, 73, 74, 75, 76, 77),\n", + " (70, 71, 72, 73, 74, 75, 76, 77),\n", + " (70, 71, 72, 73, 74, 75, 76, 77),\n", + " (70, 71, 72, 73, 74, 75, 76, 77),\n", + " (70, 71, 72, 73, 74, 75, 76, 77),\n", + " (78, 79, 80),\n", + " (78, 79, 80),\n", + " (78, 79, 80),\n", + " (81, 82),\n", + " (81, 82),\n", + " (83, 84, 85, 86, 87, 88, 89, 90),\n", + " (83, 84, 85, 86, 87, 88, 89, 90),\n", + " (83, 84, 85, 86, 87, 88, 89, 90),\n", + " (83, 84, 85, 86, 87, 88, 89, 90),\n", + " (83, 84, 85, 86, 87, 88, 89, 90),\n", + " (83, 84, 85, 86, 87, 88, 89, 90),\n", + " (83, 84, 85, 86, 87, 88, 89, 90),\n", + " (83, 84, 85, 86, 87, 88, 89, 90),\n", + " (91, 92, 93),\n", + " (91, 92, 93),\n", + " (91, 92, 93),\n", + " (94, 95),\n", + " (94, 95)]" + ] + }, + "execution_count": 257, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# %%timeit -r 10 -n 1000\n", + "snorms = mbri._simplex_equations[:, :3]\n", + "coplanars = np.all(np.abs(snorms[:, None] - snorms) < tol, axis=2)\n", + "coplanar_simplices = [tuple(np.where(co)[0]) for co in coplanars]\n", + "coplanar_simplices" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 252, + "metadata": {}, + "outputs": [], + "source": [ + "# %%timeit -r 10 -n 1000\n", + "# np.argwhere(coplanars)\n", + "snorms = mbri._simplex_equations[:, :3]\n", + "coplanars = np.all(np.abs(snorms[:, None] - snorms) < tol, axis=2)\n", + "writeto = [() for _ in range(len(coplanars))]\n", + "for i, val in np.argwhere(coplanars):\n", + " writeto[i] += (val,)\n", + "output = set(writeto)\n", + "# output" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "62\n" + ] + } + ], + "source": [ + "from collections import defaultdict\n", + "\n", + "equation_groups = defaultdict(list)\n", + "eqution_keys = []\n", + "# Iterate over all simplex equations\n", + "for i, equation in enumerate(mbri._simplex_equations):\n", + " # Convert to hashable key\n", + " equation_key = tuple(equation.round(15))\n", + "\n", + " # Store vertex indices from the new simplex under the correct equation key\n", + " equation_groups[equation_key].extend(mbri._simplices[i])\n", + " eqution_keys.append(equation_key)\n", + "# Combine elements with the same plan equation and remove duplicate indices\n", + "ragged_faces = [np.fromiter(set(group), np.int32) for group in equation_groups.values()]\n", + "print(set(eqution_keys).__len__())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[(0.0, 0.0, -1.0, -0.5),\n", + " (0.0, 0.0, -1.0, -0.5),\n", + " (0.0, -1.0, -0.0, -0.5),\n", + " (-0.0, -1.0, 0.0, -0.5),\n", + " (1.0, 0.0, 0.0, -0.5),\n", + " (1.0, -0.0, 0.0, -0.5),\n", + " (-1.0, 0.0, 0.0, -0.5),\n", + " (-1.0, -0.0, 0.0, -0.5),\n", + " (0.0, 1.0, 0.0, -0.5),\n", + " (0.0, 1.0, -0.0, -0.5),\n", + " (0.0, 0.0, 1.0, -0.5),\n", + " (0.0, -0.0, 1.0, -0.5)]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x = [tuple(row) for row in cube._simplex_equations]\n", + "x" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[(0, 1), (10, 11), (2, 3), (6, 7), (4, 5), (8, 9)]" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "snorms = cube._simplex_equations[:, :3]\n", + "tol = 1e-15\n", + "coplanars = np.all(np.abs(snorms[:, None] - snorms) < tol, axis=2)\n", + "coplanar_simplices = list({tuple(np.where(co)[0]) for co in coplanars})\n", + "coplanar_simplices\n", + "# So this gets me an array of coplanar simplices. Now, how do I tie back to the faces?" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5])" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cube._simplex_areas = cube._find_triangle_array_area(\n", + " cube._vertices[cube._simplices], sum_result=False\n", + ")\n", + "cube._simplex_areas" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "ename": "TypeError", + "evalue": "'dict_values' object is not subscriptable", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m/Users/jenbradley/github/coxeter/test.ipynb Cell 66\u001b[0m line \u001b[0;36m1\n\u001b[0;32m----> 1\u001b[0m cube\u001b[39m.\u001b[39m_simplex_areas[cube\u001b[39m.\u001b[39;49m_coplanar_simplices[\u001b[39m0\u001b[39;49m]]\n", + "\u001b[0;31mTypeError\u001b[0m: 'dict_values' object is not subscriptable" + ] + } + ], + "source": [ + "cube._simplex_areas[cube._coplanar_simplices[0]]" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "dict_values([[0, 1], [2, 3], [4, 5], [6, 7], [8, 9], [10, 11]])" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cube._coplanar_simplices" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(6409598020480052466, 6409598020480052466)" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hash(mbri._simplex_equations[10].round(15).tobytes()), hash(\n", + " mbri._simplex_equations[11].round(15).tobytes()\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "178 µs ± 2.04 µs per loop (mean ± std. dev. of 10 runs, 10,000 loops each)\n", + "263 µs ± 226 ns per loop (mean ± std. dev. of 10 runs, 10,000 loops each)\n" + ] + } + ], + "source": [ + "%timeit -r 10 -n 10000 mbri._find_coplanar_simplices()\n", + "%timeit -r 10 -n 10000 mbri._combine_simplices()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[array([48, 40, 38], dtype=int32),\n", + " array([24, 14, 22], dtype=int32),\n", + " array([25, 23, 15], dtype=int32),\n", + " array([ 2, 7, 10], dtype=int32),\n", + " array([8, 6, 0], dtype=int32),\n", + " array([41, 49, 39], dtype=int32),\n", + " array([33, 36, 45], dtype=int32),\n", + " array([17, 29, 20], dtype=int32),\n", + " array([32, 43, 34], dtype=int32),\n", + " array([16, 18, 27], dtype=int32)]" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mbri.faces[0:10]" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[38, 48, 40],\n", + " [14, 22, 24],\n", + " [25, 23, 15],\n", + " [ 2, 7, 10],\n", + " [ 8, 6, 0],\n", + " [49, 39, 41],\n", + " [36, 45, 33],\n", + " [17, 29, 20],\n", + " [32, 43, 34],\n", + " [18, 27, 16]], dtype=int32)" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mbri.simplices[0:10]" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "265 µs ± 5.58 µs per loop (mean ± std. dev. of 10 runs, 1,000 loops each)\n" + ] + } + ], + "source": [ + "# So sort_faces never changes the order of faces in the list\n", + "# So as long as combine_simplices and find_coplanar_simplices\n", + "# return the same order, we should be ok!\n", + "%timeit -r 10 -n 1000 mbri._combine_simplices()" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "# %%timeit -r 10 -n 10000\n", + "poly = mbri\n", + "simplex_normals = poly._simplex_equations[:, :3]\n", + "tol = 1e-15\n", + "coplanars = np.all(np.abs(simplex_normals[:, None] - simplex_normals) < tol, axis=2)\n", + "coplanar_simplices = list({tuple(np.where(co)[0]) for co in coplanars})\n", + "# coplanar_simplices[0:10]\n", + "# now - use coplanar simplices to build faces\n", + "# faces = [\n", + "# np.array(list(set(poly.simplices[[co]].flatten()))) for co in coplanar_simplices\n", + "# ]\n", + "\n", + "# Move the simplices out into an array that matches the new sorting\n", + "# ordered_simplices = [simplex for coplanar in coplanar_simplices for simplex in coplanar]" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([10, 20, 10, 2])" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 74, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "651 µs ± 26.5 µs per loop (mean ± std. dev. of 10 runs, 10,000 loops each)\n" + ] + } + ], + "source": [ + "%%timeit -r 10 -n 10000\n", + "poly = mbri\n", + "simplex_normals = poly._simplex_equations[:, :3]\n", + "tol = 1e-15\n", + "# Generate boolean array checking which simplices share a normal (within tolerance)\n", + "is_coplanar = np.all(np.abs(simplex_normals[:, None] - simplex_normals) < tol, axis=2)\n", + "# Convert boolean array into ragged list of coplanar simplices\n", + "coplanar_simplices = list({tuple(np.where(coplanar)[0]) for coplanar in is_coplanar})\n", + "faces = []\n", + "ordered_simplices = []\n", + "for face_simplex_indices in coplanar_simplices:\n", + " # Get unique vertex indices from faces and convert to np array\n", + " faces.append(\n", + " np.fromiter(set(poly.simplices[[face_simplex_indices]].flat), np.int32)\n", + " )\n", + " # Add simplex indices back into list that matches face ordering\n", + " ordered_simplices.extend(face_simplex_indices)\n", + " # faces.append(set(poly.simplices[[face_simplex_indices]].flatten()))\n", + "# faces.__len__(), coplanar_simplices.__len__(), ordered_simplices.__len__()" + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "metadata": {}, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "'ConvexPolyhedron' object has no attribute '_simplex_areas'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m/Users/jenbradley/github/coxeter/test.ipynb Cell 78\u001b[0m line \u001b[0;36m2\n\u001b[1;32m 1\u001b[0m poly\u001b[39m.\u001b[39m_simplex_equations\n\u001b[0;32m----> 2\u001b[0m poly\u001b[39m.\u001b[39;49m_simplex_areas\n", + "\u001b[0;31mAttributeError\u001b[0m: 'ConvexPolyhedron' object has no attribute '_simplex_areas'" + ] + } + ], + "source": [ + "poly._simplex_equations\n", + "poly._simplex_areas" + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([10, 20, 10, 2])" + ] + }, + "execution_count": 63, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.sum(np.array([len(face) for face in faces])[:, None] == [3, 4, 5, 10], axis=0)\n", + "# so we get the right number of vertices and faces!" + ] + }, + { + "cell_type": "code", + "execution_count": 91, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0. , 0. , -1. , -0.63580989])" + ] + }, + "execution_count": 91, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "i = 3\n", + "poly._simplex_equations[coplanar_simplices[i][0]]" + ] + }, + { + "cell_type": "code", + "execution_count": 96, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0. , 0.35682, -0.93417, -0.64751])" + ] + }, + "execution_count": 96, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "poly._simplex_equations.round(5)[3]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 66, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "710 µs ± 14.2 µs per loop (mean ± std. dev. of 10 runs, 10,000 loops each)\n" + ] + } + ], + "source": [ + "%%timeit -r 10 -n 10000\n", + "mbri._combine_simplices()\n", + "mbri._find_coplanar_simplices()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "179 µs ± 1.85 µs per loop (mean ± std. dev. of 10 runs, 10,000 loops each)\n" + ] + } + ], + "source": [ + "%%timeit -r 10 -n 10000\n", + "faces = [\n", + " np.array(list(set(poly.simplices[[co]].flatten()))) for co in coplanar_simplices\n", + "]" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "706 µs ± 6.3 µs per loop (mean ± std. dev. of 10 runs, 10,000 loops each)\n" + ] + } + ], + "source": [ + "%%timeit -r 10 -n 10000\n", + "mbri._combine_simplices()\n", + "mbri._find_coplanar_simplices()" + ] + }, + { + "cell_type": "code", + "execution_count": 96, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3.52 µs ± 229 ns per loop (mean ± std. dev. of 10 runs, 100,000 loops each)\n", + "7.24 µs ± 463 ns per loop (mean ± std. dev. of 10 runs, 100,000 loops each)\n" + ] + } + ], + "source": [ + "%timeit -r 10 -n 100000 [simplex for coplanar in coplanar_simplices for simplex in coplanar]\n", + "%timeit -r 10 -n 100000 list(sum(coplanar_simplices,()))" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([-0.04505663, 0.07290316, -0.02784653])" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "t1 = mbri.vertices[mbri.simplices[81]]\n", + "np.cross(\n", + " np.diff(t1[[0, 1], :], axis=0).squeeze(), np.diff(t1[[1, 2], :], axis=0).squeeze()\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0.24285777, 0.09276341, -0.15009435])" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.diff(t1[[0, 1], :], axis=0).squeeze()" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([-0.5 , 0.80901699, -0.30901699])" + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "nor = np.cross(t1[1, :] - t1[0, :], t1[2, :] - t1[0, :])\n", + "nor /= np.linalg.norm(nor)\n", + "nor # So the normal is right!" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([-0.5 , 0.80901699, -0.30901699, -0.63580989])" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mbri._simplex_equations[81]" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": {}, + "outputs": [], + "source": [ + "# Somehow, we get the correct number and hsape of faces but different than we expect?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/test_families.ipynb b/test_families.ipynb new file mode 100644 index 00000000..565e17d3 --- /dev/null +++ b/test_families.ipynb @@ -0,0 +1,231 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import math\n", + "\n", + "import numpy as np\n", + "from conway.utils.arraymath import _norm3\n", + "from conway.utils.trig import cot, csc, sec\n", + "from scipy.spatial import ConvexHull as ch\n", + "from scipy.spatial import Delaunay\n", + "\n", + "from coxeter.families import (\n", + " ArchimedeanFamily as archfam,\n", + ")\n", + "from coxeter.families import (\n", + " JohnsonFamily as johnfam,\n", + ")\n", + "from coxeter.families import (\n", + " PlatonicFamily as platfam,\n", + ")\n", + "from conway.seeds.infinite_shape_families import *\n", + "\n", + "from co" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "xeter.shapes import ConvexPolygon, ConvexPolyhedron\n", + "from coxeter.shapes import ConvexPolyhedron" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "def _make_ngon(n, z=0, A=1, angle=0):\n", + " \"\"\"\n", + " Make a regular n-gon with area equal to A. The initial vertex lies on the :math:`x`\n", + " axis by default, but can be rotated by the angle parameter.\n", + "\n", + " Args:\n", + " n (int): Number of vertices\n", + " z (int|float): z value for the polygon. Defaults to 0.\n", + " A (int, optional): Area of polygon. Defaults to 1.\n", + " angle (float, optional): Rotation angle, in radians. Defaults to 0.\n", + "\n", + " Returns:\n", + " np.array: n-gon vertices\n", + " \"\"\"\n", + " check_domain(n, [3, np.inf])\n", + " theta = (\n", + " np.arange(0, 2 * np.pi, 2 * np.pi / n) + angle\n", + " ) # Generate angle for each point\n", + " Ao = 0.5 * n * math.sin(2 * np.pi / n) # Area of the shape with circumradius = 1\n", + "\n", + " ngon_vertices = np.array([np.cos(theta), np.sin(theta), np.full_like(theta, z)]).T\n", + " ngon_vertices[:, :2] *= math.sqrt(A / Ao) # Rescale the x and y coords to area A\n", + "\n", + " return ngon_vertices" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "99.00000000000055" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "vertices = make_vertices_trapezohedron(124, 99.0)\n", + "ConvexPolyhedron(vertices).volume" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "# parallelepiped\n", + "# We need three vectors and 4 poitns: OA, OB, OC\n", + "# How can we construct those vectors from angles and lengths?\n", + "# αlpha = angle between b and c\n", + "# beta = angle between a and c\n", + "# gamma = angle between a and b\n", + "a = 1\n", + "b = 1\n", + "c = 1\n", + "alpha, beta, gamma = [π / 2, π / 2, π / 2]\n", + "origin = [0, 0, 0]" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "ename": "DomainError", + "evalue": "Value n=361 is not in domain [180, 360]", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mDomainError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[6], line 9\u001b[0m\n\u001b[1;32m 4\u001b[0m c \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m1\u001b[39m\n\u001b[1;32m 5\u001b[0m alpha, beta, gamma \u001b[38;5;241m=\u001b[39m [\u001b[38;5;241m120\u001b[39m, \u001b[38;5;241m120\u001b[39m, \u001b[38;5;241m121\u001b[39m]\n\u001b[1;32m 8\u001b[0m ConvexPolyhedron(\n\u001b[0;32m----> 9\u001b[0m \u001b[43mmake_vertices_parallelepiped\u001b[49m\u001b[43m(\u001b[49m\u001b[43ma\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mb\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43malpha\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mbeta\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mgamma\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mV\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 10\u001b[0m )\u001b[38;5;241m.\u001b[39mplot()\n", + "File \u001b[0;32m~/github/conway/conway/seeds/infinite_shape_families.py:219\u001b[0m, in \u001b[0;36mmake_vertices_parallelepiped\u001b[0;34m(a, b, c, alpha, beta, gamma, V)\u001b[0m\n\u001b[1;32m 201\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mmake_vertices_parallelepiped\u001b[39m(a\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m1\u001b[39m, b\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m1\u001b[39m, c\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m1\u001b[39m, alpha\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m90\u001b[39m, beta\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m90\u001b[39m, gamma\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m90\u001b[39m, V\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m):\n\u001b[1;32m 202\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 203\u001b[0m \u001b[38;5;124;03m Generate a parallelepiped defined by a,b,c,alpha,beta,and gamma.\u001b[39;00m\n\u001b[1;32m 204\u001b[0m \n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 217\u001b[0m \u001b[38;5;124;03m V (float): Volume of the parallelepiped. Defaults to None. (Keep volume)\u001b[39;00m\n\u001b[1;32m 218\u001b[0m \u001b[38;5;124;03m \"\"\"\u001b[39;00m\n\u001b[0;32m--> 219\u001b[0m \u001b[43mcheck_domain\u001b[49m\u001b[43m(\u001b[49m\u001b[43malpha\u001b[49m\u001b[38;5;241;43m+\u001b[39;49m\u001b[43mbeta\u001b[49m\u001b[38;5;241;43m+\u001b[39;49m\u001b[43mgamma\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m180\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m360\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 221\u001b[0m alpha \u001b[38;5;241m=\u001b[39m math\u001b[38;5;241m.\u001b[39mradians(alpha)\n\u001b[1;32m 222\u001b[0m beta \u001b[38;5;241m=\u001b[39m math\u001b[38;5;241m.\u001b[39mradians(beta)\n", + "File \u001b[0;32m~/github/conway/conway/utils/_errors_and_logging.py:19\u001b[0m, in \u001b[0;36mcheck_domain\u001b[0;34m(value, domain_bounds)\u001b[0m\n\u001b[1;32m 17\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mtype\u001b[39m(domain_bounds) \u001b[38;5;241m==\u001b[39m \u001b[38;5;28mlist\u001b[39m:\n\u001b[1;32m 18\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m (value \u001b[38;5;241m<\u001b[39m\u001b[38;5;241m=\u001b[39m domain_bounds[\u001b[38;5;241m1\u001b[39m] \u001b[38;5;129;01mand\u001b[39;00m value \u001b[38;5;241m>\u001b[39m\u001b[38;5;241m=\u001b[39m domain_bounds[\u001b[38;5;241m0\u001b[39m]):\n\u001b[0;32m---> 19\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m DomainError(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mValue n=\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mvalue\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m is not in domain \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mdomain_bounds\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 20\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m \u001b[38;5;28mtype\u001b[39m(domain_bounds) \u001b[38;5;241m==\u001b[39m \u001b[38;5;28mtuple\u001b[39m:\n\u001b[1;32m 21\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m (value \u001b[38;5;241m<\u001b[39m domain_bounds[\u001b[38;5;241m1\u001b[39m] \u001b[38;5;129;01mand\u001b[39;00m value \u001b[38;5;241m>\u001b[39m domain_bounds[\u001b[38;5;241m0\u001b[39m]):\n", + "\u001b[0;31mDomainError\u001b[0m: Value n=361 is not in domain [180, 360]" + ] + } + ], + "source": [ + "# So what are our points?\n", + "a = 1\n", + "b = 1\n", + "c = 1\n", + "alpha, beta, gamma = [120, 120, 121]\n", + "\n", + "\n", + "ConvexPolyhedron(make_vertices_parallelepiped(a, b, c, alpha, beta, gamma, V=1)).plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'oa' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[7], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m u, v, w \u001b[38;5;241m=\u001b[39m \u001b[43moa\u001b[49m\n\u001b[1;32m 2\u001b[0m x, y, z \u001b[38;5;241m=\u001b[39m ob\n\u001b[1;32m 3\u001b[0m [\n\u001b[1;32m 4\u001b[0m c \u001b[38;5;241m*\u001b[39m cos(alpha) \u001b[38;5;241m+\u001b[39m a \u001b[38;5;241m*\u001b[39m cos(beta) \u001b[38;5;241m*\u001b[39m u,\n\u001b[1;32m 5\u001b[0m c \u001b[38;5;241m*\u001b[39m cos(alpha) \u001b[38;5;241m*\u001b[39m y \u001b[38;5;241m+\u001b[39m a \u001b[38;5;241m*\u001b[39m cos(beta) \u001b[38;5;241m*\u001b[39m v,\n\u001b[1;32m 6\u001b[0m c \u001b[38;5;241m*\u001b[39m cos(alpha) \u001b[38;5;241m*\u001b[39m z \u001b[38;5;241m+\u001b[39m a \u001b[38;5;241m*\u001b[39m cos(beta) \u001b[38;5;241m*\u001b[39m w,\n\u001b[1;32m 7\u001b[0m ]\n", + "\u001b[0;31mNameError\u001b[0m: name 'oa' is not defined" + ] + } + ], + "source": [ + "u, v, w = oa\n", + "x, y, z = ob\n", + "[\n", + " c * cos(alpha) + a * cos(beta) * u,\n", + " c * cos(alpha) * y + a * cos(beta) * v,\n", + " c * cos(alpha) * z + a * cos(beta) * w,\n", + "]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([6.123234e-17, 6.123234e-17, 0.000000e+00])" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/test_sorting.ipynb b/test_sorting.ipynb new file mode 100644 index 00000000..0e41d12e --- /dev/null +++ b/test_sorting.ipynb @@ -0,0 +1,212 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from scipy.spatial import ConvexHull as ch\n", + "from scipy.spatial import Delaunay\n", + "\n", + "from coxeter.families import (\n", + " ArchimedeanFamily as archfam,\n", + ")\n", + "from coxeter.families import (\n", + " JohnsonFamily as johnfam,\n", + ")\n", + "from coxeter.families import (\n", + " PlatonicFamily as platfam,\n", + ")\n", + "from coxeter.shapes import ConvexPolyhedron" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 0.20800838, 0. , 0. ],\n", + " [ 0. , 0.20800838, -0. ],\n", + " [ 0. , -0. , 0.20800838]])" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "tetr = platfam.get_shape(\"Tetrahedron\")\n", + "tetr.plot(label_verts=True)\n", + "tetr.inertia_tensor.round(15)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[2, 3, 1],\n", + " [0, 3, 1],\n", + " [0, 2, 1],\n", + " [0, 2, 3]], dtype=int32)" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ch(tetr.vertices).simplices" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "i = 2\n", + "tetr._simplices = tetr._simplices[::-1]" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 0. , 0. , 1.24902477],\n", + " [-0.58879592, -1.01982445, -0.41634159],\n", + " [-0.58879592, 1.01982445, -0.41634159],\n", + " [ 1.17759184, 0. , -0.41634159]])" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tetr.vertices\n", + "tetr" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(array([[-0.03466806, -0.03002342, 0.02451402],\n", + " [-0.03002342, -0.10400419, 0.04245953],\n", + " [ 0.02451402, 0.04245953, -0.06933613]]),\n", + " array([ 0., 0., -0.]))" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tetr.inertia_tensor.round(15), tetr.centroid.round(15)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[array([1, 2, 3], dtype=int32),\n", + " array([0, 1, 3], dtype=int32),\n", + " array([0, 2, 1], dtype=int32),\n", + " array([0, 3, 2], dtype=int32)]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tetr.faces" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'n' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[12], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mn\u001b[49m\u001b[38;5;241m*\u001b[39mb\n", + "\u001b[0;31mNameError\u001b[0m: name 'n' is not defined" + ] + } + ], + "source": [ + "n * b" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tests/test_ellipsoid.py b/tests/test_ellipsoid.py index 90274754..350b7e30 100644 --- a/tests/test_ellipsoid.py +++ b/tests/test_ellipsoid.py @@ -252,3 +252,20 @@ def test_get_set_minimal_centered_bounding_circle_radius(a, b, c, center): def test_repr(): ellipsoid = Ellipsoid(1, 2, 3, [1, 2, 3]) assert str(ellipsoid), str(eval(repr(ellipsoid))) + + +@given(floats(0.1, 1000), floats(0.1, 1000), floats(0.1, 1000)) +def test_to_hoomd(a, b, c): + ellipsoid = Ellipsoid(a, b, c) + dict_keys = ["a", "b", "c", "centroid", "volume", "moment_inertia"] + dict_vals = [ + ellipsoid.a, + ellipsoid.b, + ellipsoid.c, + [0, 0, 0], + ellipsoid.volume, + ellipsoid.inertia_tensor, + ] + hoomd_dict = ellipsoid.to_hoomd() + for key, val in zip(dict_keys, dict_vals): + assert np.allclose(hoomd_dict[key], val), f"{key}" diff --git a/tests/test_polygon.py b/tests/test_polygon.py index 4db0d233..b451637a 100644 --- a/tests/test_polygon.py +++ b/tests/test_polygon.py @@ -595,3 +595,21 @@ def test_repr_nonconvex(square): def test_repr_convex(convex_square): assert str(convex_square), str(eval(repr(convex_square))) + + +@given(EllipseSurfaceStrategy) +def test_to_hoomd(points): + hull = ConvexHull(points) + poly = polygon_from_hull(points[hull.vertices]) + poly.centroid = [0, 0, 0] + dict_keys = ["vertices", "centroid", "sweep_radius", "area", "moment_inertia"] + dict_vals = [ + poly.vertices, + [0, 0, 0], + 0, + poly.area, + poly.inertia_tensor, + ] + hoomd_dict = poly.to_hoomd() + for key, val in zip(dict_keys, dict_vals): + assert np.allclose(hoomd_dict[key], val), f"{key}" diff --git a/tests/test_polyhedron.py b/tests/test_polyhedron.py index 25cd2a8b..4be7ccf1 100644 --- a/tests/test_polyhedron.py +++ b/tests/test_polyhedron.py @@ -934,3 +934,22 @@ def test_find_equations_and_normals(poly): ppoly._find_equations() assert np.allclose(poly.equations, ppoly._equations) assert np.allclose(poly.normals, ppoly.normals) + + +@named_solids_mark +def test_to_hoomd(poly): + poly.centroid = [0, 0, 0] + dict_keys = ["vertices", "centroid", "sweep_radius", "volume", "moment_inertia"] + dict_vals = [ + poly.vertices, + [0, 0, 0], + 0, + poly.volume, + poly.inertia_tensor, + ] + hoomd_dict = poly.to_hoomd() + for key, val in zip(dict_keys, dict_vals): + assert np.allclose(hoomd_dict[key], val), f"{key}" + + for i, face in enumerate(poly.faces): + assert np.allclose(face, hoomd_dict["faces"][i]) diff --git a/tests/test_sphere.py b/tests/test_sphere.py index aed3b4d1..f8ac2032 100644 --- a/tests/test_sphere.py +++ b/tests/test_sphere.py @@ -230,3 +230,18 @@ def test_get_set_minimal_centered_bounding_circle_radius(r, center): def test_repr(): sphere = Sphere(1, [1, 2, 3]) assert str(sphere), str(eval(repr(sphere))) + + +@given(floats(0.1, 1000)) +def test_to_hoomd(r): + sphere = Sphere(r) + dict_keys = ["diameter", "centroid", "volume", "moment_inertia"] + dict_vals = [ + sphere.radius * 2, + [0, 0, 0], + sphere.volume, + sphere.inertia_tensor, + ] + hoomd_dict = sphere.to_hoomd() + for key, val in zip(dict_keys, dict_vals): + assert np.allclose(hoomd_dict[key], val), f"{key}" diff --git a/tests/test_spheropolyhedron.py b/tests/test_spheropolyhedron.py index c744c22e..fd295d19 100644 --- a/tests/test_spheropolyhedron.py +++ b/tests/test_spheropolyhedron.py @@ -7,7 +7,8 @@ from hypothesis.strategies import floats from pytest import approx -from conftest import make_sphero_cube +from conftest import make_sphero_cube, named_catalan_mark +from coxeter.shapes import ConvexSpheropolyhedron @given(radius=floats(0.1, 1)) @@ -120,3 +121,20 @@ def test_inside_boundaries(): def test_repr(): sphero_cube = make_sphero_cube(radius=1) assert str(sphero_cube), str(eval(repr(sphero_cube))) + + +@given(r=floats(0.01, 1)) +@named_catalan_mark +def test_to_hoomd(poly, r): + poly.centroid = [0, 0, 0] + poly = ConvexSpheropolyhedron(poly.vertices, r) + dict_keys = ["vertices", "centroid", "sweep_radius", "volume"] + dict_vals = [ + poly.vertices, + [0, 0, 0], + poly.radius, + poly.volume, + ] + hoomd_dict = poly.to_hoomd() + for key, val in zip(dict_keys, dict_vals): + assert np.allclose(hoomd_dict[key], val), f"{key}" From 0e760526a893574b6cafbb01e44cae0c9090b087 Mon Sep 17 00:00:00 2001 From: janbridley Date: Tue, 30 Jan 2024 10:29:23 -0500 Subject: [PATCH 02/11] Updated changelog and credits --- ChangeLog.rst | 1 + Credits.rst | 1 + 2 files changed, 2 insertions(+) diff --git a/ChangeLog.rst b/ChangeLog.rst index d2e70122..a83f6055 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -13,6 +13,7 @@ Added - ``simplices``, ``equations``, and ``face_centroids`` properties for the ConvexPolyhedron class. - Additional pytests for surface area, volume, centroid, moment of inertia, and equations properties. +- Added ``to_hoomd`` export method for use with simulation tools Changed ~~~~~~~ diff --git a/Credits.rst b/Credits.rst index 476fb38b..4bc7a188 100644 --- a/Credits.rst +++ b/Credits.rst @@ -123,6 +123,7 @@ Jen Bradley * Added ``simplices``, ``equations``, and ``face_centroids`` properties to the ConvexPolyhedron class. * Optimized pytest configurations for more efficient use of local and remote resources. +* Added ``to_hoomd`` export method for use with simulation tools. Domagoj Fijan From ab452325010d73881af17a39b96c661927efee33 Mon Sep 17 00:00:00 2001 From: janbridley Date: Tue, 30 Jan 2024 12:49:59 -0500 Subject: [PATCH 03/11] Removed working tempfiles and rolled back bumpversion --- coxeter/__init__.py | 2 +- doc/source/conf.py | 4 +- find_coplanar.ipynb | 540 ------- pyproject.toml | 2 +- setup.cfg | 2 +- temp.ipynb | 1580 --------------------- test.ipynb | 3310 ------------------------------------------- test_families.ipynb | 231 --- test_sorting.ipynb | 212 --- 9 files changed, 5 insertions(+), 5878 deletions(-) delete mode 100644 find_coplanar.ipynb delete mode 100644 temp.ipynb delete mode 100644 test.ipynb delete mode 100644 test_families.ipynb delete mode 100644 test_sorting.ipynb diff --git a/coxeter/__init__.py b/coxeter/__init__.py index b4d82e49..15647bfa 100644 --- a/coxeter/__init__.py +++ b/coxeter/__init__.py @@ -21,4 +21,4 @@ __all__ = ["families", "shapes", "from_gsd_type_shapes"] -__version__ = "0.8.0" +__version__ = "0.7.0" diff --git a/doc/source/conf.py b/doc/source/conf.py index b4c3151a..89cd9283 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -22,8 +22,8 @@ author = "Vyas Ramasubramani" # The full version, including alpha/beta/rc tags -version = "0.8.0" -release = "0.8.0" +version = "0.7.0" +release = "0.7.0" # -- General configuration --------------------------------------------------- diff --git a/find_coplanar.ipynb b/find_coplanar.ipynb deleted file mode 100644 index 292612ce..00000000 --- a/find_coplanar.ipynb +++ /dev/null @@ -1,540 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "from scipy.spatial import ConvexHull as ch\n", - "from scipy.spatial import Delaunay\n", - "\n", - "from coxeter.families import (\n", - " ArchimedeanFamily as archfam,\n", - ")\n", - "from coxeter.families import (\n", - " JohnsonFamily as johnfam,\n", - ")\n", - "from coxeter.families import (\n", - " PlatonicFamily as platfam,\n", - ")\n", - "from coxeter.shapes import ConvexPolyhedron" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# DISCUSSION : find coplanar is pretty close to optimal. What I have written is an O(N) algorithm, so even though it\n", - "# does not use numpy it is very fast." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "cube = platfam.get_shape(\"Cube\")\n", - "tetr = platfam.get_shape(\"Tetrahedron\")\n", - "mbri = johnfam.get_shape(\"Metabidiminished Rhombicosidodecahedron\")\n", - "icosi = archfam.get_shape(\"Icosidodecahedron\")\n", - "poly = mbri" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[ True, False, False, ..., False, False, False],\n", - " [False, True, False, ..., False, False, False],\n", - " [False, False, True, ..., False, False, False],\n", - " ...,\n", - " [False, False, False, ..., True, False, False],\n", - " [False, False, False, ..., False, True, True],\n", - " [False, False, False, ..., False, True, True]])" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "hull = ch(poly.vertices, \"\")\n", - "normals = hull.equations[:, :3]\n", - "boolean_coplanars = np.all(normals[:, None] == normals, axis=-1)\n", - "boolean_coplanars" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "rows = np.arange(0, hull.nsimplex)\n", - "coplanarray = np.tile(np.arange(0, hull.nsimplex), (hull.nsimplex, 1))\n", - "# coplanarray" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "271 µs ± 14.5 µs per loop (mean ± std. dev. of 10 runs, 1,000 loops each)\n" - ] - } - ], - "source": [ - "%%timeit -r 10 -n 1000\n", - "boolean_coplanars = np.all(normals[:, None] == normals, axis=-1)\n", - "rows = np.arange(0, hull.nsimplex)\n", - "coplanar_simplices = {tuple(rows[boolean_coplanars[i]]) for i in range(hull.nsimplex)}\n", - "# Do we need to guarantee that the order of coplanars is the same\n", - "coplanar_simplices" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(96,\n", - " [array([0]),\n", - " array([1]),\n", - " array([2]),\n", - " array([3]),\n", - " array([4]),\n", - " array([5]),\n", - " array([6]),\n", - " array([7]),\n", - " array([8]),\n", - " array([9]),\n", - " array([10, 11]),\n", - " array([10, 11]),\n", - " array([12, 13]),\n", - " array([12, 13]),\n", - " array([14, 15, 16]),\n", - " array([14, 15, 16]),\n", - " array([14, 15, 16]),\n", - " array([17, 18]),\n", - " array([17, 18]),\n", - " array([19, 20]),\n", - " array([19, 20]),\n", - " array([21, 22]),\n", - " array([21, 22]),\n", - " array([23, 24]),\n", - " array([23, 24]),\n", - " array([25, 26]),\n", - " array([25, 26]),\n", - " array([27, 28, 29]),\n", - " array([27, 28, 29]),\n", - " array([27, 28, 29]),\n", - " array([30, 31]),\n", - " array([30, 31]),\n", - " array([32, 33, 34]),\n", - " array([32, 33, 34]),\n", - " array([32, 33, 34]),\n", - " array([35, 36]),\n", - " array([35, 36]),\n", - " array([37, 38, 39]),\n", - " array([37, 38, 39]),\n", - " array([37, 38, 39]),\n", - " array([40, 41]),\n", - " array([40, 41]),\n", - " array([42, 43]),\n", - " array([42, 43]),\n", - " array([44, 45, 46]),\n", - " array([44, 45, 46]),\n", - " array([44, 45, 46]),\n", - " array([47, 48]),\n", - " array([47, 48]),\n", - " array([49, 50, 51]),\n", - " array([49, 50, 51]),\n", - " array([49, 50, 51]),\n", - " array([52, 53]),\n", - " array([52, 53]),\n", - " array([54, 55]),\n", - " array([54, 55]),\n", - " array([56, 57, 58]),\n", - " array([56, 57, 58]),\n", - " array([56, 57, 58]),\n", - " array([59, 60]),\n", - " array([59, 60]),\n", - " array([61, 62, 63]),\n", - " array([61, 62, 63]),\n", - " array([61, 62, 63]),\n", - " array([64, 65]),\n", - " array([64, 65]),\n", - " array([66, 67]),\n", - " array([66, 67]),\n", - " array([68, 69]),\n", - " array([68, 69]),\n", - " array([70, 71, 72, 73, 74, 75, 76, 77]),\n", - " array([70, 71, 72, 73, 74, 75, 76, 77]),\n", - " array([70, 71, 72, 73, 74, 75, 76, 77]),\n", - " array([70, 71, 72, 73, 74, 75, 76, 77]),\n", - " array([70, 71, 72, 73, 74, 75, 76, 77]),\n", - " array([70, 71, 72, 73, 74, 75, 76, 77]),\n", - " array([70, 71, 72, 73, 74, 75, 76, 77]),\n", - " array([70, 71, 72, 73, 74, 75, 76, 77]),\n", - " array([78, 79, 80]),\n", - " array([78, 79, 80]),\n", - " array([78, 79, 80]),\n", - " array([81, 82]),\n", - " array([81, 82]),\n", - " array([83, 84, 85, 86, 87, 88, 89, 90]),\n", - " array([83, 84, 85, 86, 87, 88, 89, 90]),\n", - " array([83, 84, 85, 86, 87, 88, 89, 90]),\n", - " array([83, 84, 85, 86, 87, 88, 89, 90]),\n", - " array([83, 84, 85, 86, 87, 88, 89, 90]),\n", - " array([83, 84, 85, 86, 87, 88, 89, 90]),\n", - " array([83, 84, 85, 86, 87, 88, 89, 90]),\n", - " array([83, 84, 85, 86, 87, 88, 89, 90]),\n", - " array([91, 92, 93]),\n", - " array([91, 92, 93]),\n", - " array([91, 92, 93]),\n", - " array([94, 95]),\n", - " array([94, 95])])" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "boolean_coplanars = np.all(normals[:, None] == normals, axis=-1)\n", - "simplex_indices = np.arange(0, hull.nsimplex)\n", - "coplanar_simplices = [\n", - " simplex_indices[boolean_coplanars[i]] for i in range(hull.nsimplex)\n", - "]\n", - "# We need to guarantee that the ORDER of coplanar simplices is identical to the original order\n", - "len(coplanar_simplices), coplanar_simplices" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[ True, False, False, ..., False, False, False],\n", - " [False, True, False, ..., False, False, False],\n", - " [False, False, True, ..., False, False, False],\n", - " ...,\n", - " [False, False, False, ..., True, False, False],\n", - " [False, False, False, ..., False, True, True],\n", - " [False, False, False, ..., False, True, True]])" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "boolean_coplanars" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "# simplex_indices[:, None][boolean_coplanars]" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "nanarray = np.full_like(boolean_coplanars, np.nan, dtype=float)" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[[ 0. nan nan ... nan nan nan]\n", - " [nan 1. nan ... nan nan nan]\n", - " [nan nan 2. ... nan nan nan]\n", - " ...\n", - " [nan nan nan ... 93. nan nan]\n", - " [nan nan nan ... nan 94. 95.]\n", - " [nan nan nan ... nan 94. 95.]]\n" - ] - } - ], - "source": [ - "print(np.where(boolean_coplanars, coplanarray, np.nan))" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "6.75 µs ± 224 ns per loop (mean ± std. dev. of 10 runs, 100,000 loops each)\n" - ] - } - ], - "source": [ - "%timeit -r 10 -n 100000 np.where(boolean_coplanars, coplanarray, np.nan)" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[-1, -1, -1, ..., -1, 94, 95],\n", - " [-1, -1, -1, ..., 93, -1, -1],\n", - " [-1, -1, -1, ..., -1, -1, -1],\n", - " ...,\n", - " [-1, -1, 2, ..., -1, -1, -1],\n", - " [-1, 1, -1, ..., -1, -1, -1],\n", - " [ 0, -1, -1, ..., -1, -1, -1]])" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "nancop = np.where(boolean_coplanars, coplanarray, -1)\n", - "\n", - "nancop = np.unique(nancop, axis=0)\n", - "nancop" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [], - "source": [ - "# %timeit -r 10 -n 10000\n", - "# [set(nancop[i]) - {-1} for i in range(hull.nsimplex)]" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[ 0, 0, 0, ..., 0, 0, 0],\n", - " [ 0, 1, 0, ..., 0, 0, 0],\n", - " [ 0, 0, 2, ..., 0, 0, 0],\n", - " ...,\n", - " [ 0, 0, 0, ..., 93, 0, 0],\n", - " [ 0, 0, 0, ..., 0, 94, 95],\n", - " [ 0, 0, 0, ..., 0, 94, 95]])" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "boolean_coplanars * simplex_indices" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,\n", - " 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,\n", - " 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,\n", - " 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67,\n", - " 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84,\n", - " 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95])" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "simplex_indices" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[ 1, 0, 0, ..., 0, 0, 0],\n", - " [ 0, 2, 0, ..., 0, 0, 0],\n", - " [ 0, 0, 3, ..., 0, 0, 0],\n", - " ...,\n", - " [ 0, 0, 0, ..., 94, 0, 0],\n", - " [ 0, 0, 0, ..., 0, 95, 96],\n", - " [ 0, 0, 0, ..., 0, 95, 96]])" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "(simplex_indices + 1) * boolean_coplanars" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "811 µs ± 58.2 µs per loop (mean ± std. dev. of 10 runs, 1,000 loops each)\n" - ] - } - ], - "source": [ - "%%timeit -r 10 -n 1000\n", - "_, idx = np.unique(boolean_coplanars, axis=0, return_index=True)\n", - "boolean_coplanars[np.sort(idx)]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "209 µs ± 2.23 µs per loop (mean ± std. dev. of 10 runs, 1,000 loops each)\n" - ] - } - ], - "source": [ - "%%timeit -r 10 -n 1000\n", - "mbri._find_coplanar_simplices()" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [ - { - "ename": "TypeError", - "evalue": "'float' object cannot be interpreted as an integer", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m/Users/jenbradley/github/coxeter/find_coplanar.ipynb Cell 21\u001b[0m line \u001b[0;36m1\n\u001b[0;32m----> 1\u001b[0m cube\u001b[39m.\u001b[39;49mvertices\u001b[39m.\u001b[39;49mround(\u001b[39m1e-15\u001b[39;49m)\n", - "\u001b[0;31mTypeError\u001b[0m: 'float' object cannot be interpreted as an integer" - ] - } - ], - "source": [ - "cube.vertices.round(1e-15)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.5" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/pyproject.toml b/pyproject.toml index d9d107c5..c371a743 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "coxeter" -version = "0.8.0" +version = "0.7.0" requires-python = ">=3.8" description = "Tools for creating and manipulating shapes." readme = "README.rst" diff --git a/setup.cfg b/setup.cfg index cbee67bd..6c561a6b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.8.0 +current_version = 0.7.0 commit = False tag = True message = Bump up to version {new_version}. diff --git a/temp.ipynb b/temp.ipynb deleted file mode 100644 index 90210eb0..00000000 --- a/temp.ipynb +++ /dev/null @@ -1,1580 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "from pyutils.polyutils import *\n", - "from scipy.spatial import ConvexHull as ch\n", - "from scipy.spatial import Delaunay\n", - "\n", - "import coxeter\n", - "from coxeter.families import (\n", - " ArchimedeanFamily as archfam,\n", - ")\n", - "from coxeter.families import (\n", - " JohnsonFamily as johnfam,\n", - ")\n", - "from coxeter.families import (\n", - " PlatonicFamily as platfam,\n", - ")\n", - "from coxeter.shapes import ConvexPolyhedron, Polyhedron\n" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "cube = platfam.get_shape(\"Cube\")\n", - "# tetr = platfam.get_shape(\"Tetrahedron\")\n", - "mbri = johnfam.get_shape(\"Metabidiminished Rhombicosidodecahedron\")\n", - "# icosi = archfam.get_shape(\"Icosidodecahedron\")\n", - "# poly = cube" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "archs = get_shortcodes()\n", - "# archs" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[-0. , -0. , -1. , -0.5],\n", - " [ 0. , -1. , 0. , -0.5],\n", - " [ 1. , -0. , -0. , -0.5],\n", - " [-1. , -0. , -0. , -0.5],\n", - " [ 0. , 1. , -0. , -0.5],\n", - " [-0. , -0. , 1. , -0.5]])" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cube.equations" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "ename": "NameError", - "evalue": "name 'self' is not defined", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m/Users/jenbradley/github/coxeter/temp.ipynb Cell 4\u001b[0m line \u001b[0;36m1\n\u001b[0;32m----> 1\u001b[0m simplex_normals \u001b[39m=\u001b[39m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39m_simplex_equations[:, :\u001b[39m3\u001b[39m]\n\u001b[1;32m 2\u001b[0m \u001b[39m# Generate boolean array checking which simplices share a normal (within tolerance)\u001b[39;00m\n\u001b[1;32m 3\u001b[0m is_coplanar \u001b[39m=\u001b[39m np\u001b[39m.\u001b[39mall(\n\u001b[1;32m 4\u001b[0m np\u001b[39m.\u001b[39mabs(simplex_normals[:, \u001b[39mNone\u001b[39;00m] \u001b[39m-\u001b[39m simplex_normals) \u001b[39m<\u001b[39m tol, axis\u001b[39m=\u001b[39m\u001b[39m2\u001b[39m\n\u001b[1;32m 5\u001b[0m )\n", - "\u001b[0;31mNameError\u001b[0m: name 'self' is not defined" - ] - } - ], - "source": [ - "simplex_normals = self._simplex_equations[:, :3]\n", - "# Generate boolean array checking which simplices share a normal (within tolerance)\n", - "is_coplanar = np.all(np.abs(simplex_normals[:, None] - simplex_normals) < tol, axis=2)\n", - "# Convert boolean array into ragged list of coplanar simplices\n", - "coplanar_simplices = list({tuple(np.where(coplanar)[0]) for coplanar in is_coplanar})\n", - "faces = []\n", - "ordered_simplices = []\n", - "simplex_equation_indices = []\n", - "for face_simplex_indices in coplanar_simplices:\n", - " # Get unique vertex indices from faces and convert to numpy array\n", - " faces.append(\n", - " np.fromiter(set(self.simplices[[face_simplex_indices]].flat), np.int32)\n", - " )\n", - " # Add simplex indices back into list that matches face ordering\n", - " ordered_simplices.extend(face_simplex_indices)\n", - " simplex_equation_indices.append(face_simplex_indices[0])\n", - "\n", - "# Assign faces and associated data to variables\n", - "self._faces = faces\n", - "self._coplanar_simplices = coplanar_simplices\n", - "self._equations = self._simplex_equations[simplex_equation_indices]\n", - "\n", - "# Reorder list of simplices (and related properties) to match the list of faces\n", - "self._simplices = self._simplices[ordered_simplices]\n", - "self._simplex_equations = self._simplex_equations[ordered_simplices]\n", - "self._simplex_neighbors = self._simplex_neighbors[ordered_simplices]" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[81, 82, 2, 5, 35, 36, 8, 12, 13, 21, 22, 27, 28, 29, 40, 41, 61, 62, 63, 17, 18, 70, 71, 72, 73, 74, 75, 76, 77, 49, 50, 51, 68, 69, 78, 79, 80, 54, 55, 64, 65, 83, 84, 85, 86, 87, 88, 89, 90, 4, 1, 7, 59, 60, 56, 57, 58, 23, 24, 10, 11, 19, 20, 14, 15, 16, 47, 48, 0, 42, 43, 32, 33, 34, 37, 38, 39, 3, 9, 44, 45, 46, 6, 52, 53, 91, 92, 93, 94, 95, 66, 67, 30, 31, 25, 26]\n" - ] - } - ], - "source": [ - "poly = mbri\n", - "# for poly, name in [findpoly(a) for a in archs]:\n", - "# poly = mbri\n", - "tol = 1e-14\n", - "simplex_normals = poly._simplex_equations\n", - "# Generate boolean array checking which simplices share a normal (within tolerance)\n", - "is_coplanar = np.all(np.abs(simplex_normals[:, None] - simplex_normals) < tol, axis=2)\n", - "# Convert boolean array into ragged list of coplanar simplices\n", - "coplanar_simplices = list({tuple(np.where(coplanar)[0]) for coplanar in is_coplanar})\n", - "faces = []\n", - "ordered_simplices = []\n", - "simplex_equation_indices = []\n", - "for face_simplex_indices in coplanar_simplices:\n", - " # Get unique vertex indices from faces and convert to numpy array\n", - " faces.append(\n", - " np.fromiter(set(poly.simplices[[face_simplex_indices]].flat), np.int32)\n", - " )\n", - " # Add simplex indices back into list that matches face ordering\n", - " ordered_simplices.extend(face_simplex_indices)\n", - " simplex_equation_indices.append(face_simplex_indices[0])\n", - "assert {tuple(sorted(i)) for i in faces} == {tuple(sorted(i)) for i in poly.faces}\n", - "print(ordered_simplices)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[ 0, 0],\n", - " [ 1, 1],\n", - " [ 2, 2],\n", - " [ 3, 3],\n", - " [ 4, 4],\n", - " [ 5, 5],\n", - " [ 6, 6],\n", - " [ 7, 7],\n", - " [ 8, 8],\n", - " [ 9, 9],\n", - " [10, 10],\n", - " [10, 11],\n", - " [11, 10],\n", - " [11, 11],\n", - " [12, 12],\n", - " [12, 13],\n", - " [13, 12],\n", - " [13, 13],\n", - " [14, 14],\n", - " [14, 15],\n", - " [14, 16],\n", - " [15, 14],\n", - " [15, 15],\n", - " [15, 16],\n", - " [16, 14],\n", - " [16, 15],\n", - " [16, 16],\n", - " [17, 17],\n", - " [17, 18],\n", - " [18, 17],\n", - " [18, 18],\n", - " [19, 19],\n", - " [19, 20],\n", - " [20, 19],\n", - " [20, 20],\n", - " [21, 21],\n", - " [21, 22],\n", - " [22, 21],\n", - " [22, 22],\n", - " [23, 23],\n", - " [23, 24],\n", - " [24, 23],\n", - " [24, 24],\n", - " [25, 25],\n", - " [25, 26],\n", - " [26, 25],\n", - " [26, 26],\n", - " [27, 27],\n", - " [27, 28],\n", - " [27, 29],\n", - " [28, 27],\n", - " [28, 28],\n", - " [28, 29],\n", - " [29, 27],\n", - " [29, 28],\n", - " [29, 29],\n", - " [30, 30],\n", - " [30, 31],\n", - " [31, 30],\n", - " [31, 31],\n", - " [32, 32],\n", - " [32, 33],\n", - " [32, 34],\n", - " [33, 32],\n", - " [33, 33],\n", - " [33, 34],\n", - " [34, 32],\n", - " [34, 33],\n", - " [34, 34],\n", - " [35, 35],\n", - " [35, 36],\n", - " [36, 35],\n", - " [36, 36],\n", - " [37, 37],\n", - " [37, 38],\n", - " [37, 39],\n", - " [38, 37],\n", - " [38, 38],\n", - " [38, 39],\n", - " [39, 37],\n", - " [39, 38],\n", - " [39, 39],\n", - " [40, 40],\n", - " [40, 41],\n", - " [41, 40],\n", - " [41, 41],\n", - " [42, 42],\n", - " [42, 43],\n", - " [43, 42],\n", - " [43, 43],\n", - " [44, 44],\n", - " [44, 45],\n", - " [44, 46],\n", - " [45, 44],\n", - " [45, 45],\n", - " [45, 46],\n", - " [46, 44],\n", - " [46, 45],\n", - " [46, 46],\n", - " [47, 47],\n", - " [47, 48],\n", - " [48, 47],\n", - " [48, 48],\n", - " [49, 49],\n", - " [49, 50],\n", - " [49, 51],\n", - " [50, 49],\n", - " [50, 50],\n", - " [50, 51],\n", - " [51, 49],\n", - " [51, 50],\n", - " [51, 51],\n", - " [52, 52],\n", - " [52, 53],\n", - " [53, 52],\n", - " [53, 53],\n", - " [54, 54],\n", - " [54, 55],\n", - " [55, 54],\n", - " [55, 55],\n", - " [56, 56],\n", - " [56, 57],\n", - " [56, 58],\n", - " [57, 56],\n", - " [57, 57],\n", - " [57, 58],\n", - " [58, 56],\n", - " [58, 57],\n", - " [58, 58],\n", - " [59, 59],\n", - " [59, 60],\n", - " [60, 59],\n", - " [60, 60],\n", - " [61, 61],\n", - " [61, 62],\n", - " [61, 63],\n", - " [62, 61],\n", - " [62, 62],\n", - " [62, 63],\n", - " [63, 61],\n", - " [63, 62],\n", - " [63, 63],\n", - " [64, 64],\n", - " [64, 65],\n", - " [65, 64],\n", - " [65, 65],\n", - " [66, 66],\n", - " [66, 67],\n", - " [67, 66],\n", - " [67, 67],\n", - " [68, 68],\n", - " [68, 69],\n", - " [69, 68],\n", - " [69, 69],\n", - " [70, 70],\n", - " [70, 71],\n", - " [70, 72],\n", - " [70, 73],\n", - " [70, 74],\n", - " [70, 75],\n", - " [70, 76],\n", - " [70, 77],\n", - " [71, 70],\n", - " [71, 71],\n", - " [71, 72],\n", - " [71, 73],\n", - " [71, 74],\n", - " [71, 75],\n", - " [71, 76],\n", - " [71, 77],\n", - " [72, 70],\n", - " [72, 71],\n", - " [72, 72],\n", - " [72, 73],\n", - " [72, 74],\n", - " [72, 75],\n", - " [72, 76],\n", - " [72, 77],\n", - " [73, 70],\n", - " [73, 71],\n", - " [73, 72],\n", - " [73, 73],\n", - " [73, 74],\n", - " [73, 75],\n", - " [73, 76],\n", - " [73, 77],\n", - " [74, 70],\n", - " [74, 71],\n", - " [74, 72],\n", - " [74, 73],\n", - " [74, 74],\n", - " [74, 75],\n", - " [74, 76],\n", - " [74, 77],\n", - " [75, 70],\n", - " [75, 71],\n", - " [75, 72],\n", - " [75, 73],\n", - " [75, 74],\n", - " [75, 75],\n", - " [75, 76],\n", - " [75, 77],\n", - " [76, 70],\n", - " [76, 71],\n", - " [76, 72],\n", - " [76, 73],\n", - " [76, 74],\n", - " [76, 75],\n", - " [76, 76],\n", - " [76, 77],\n", - " [77, 70],\n", - " [77, 71],\n", - " [77, 72],\n", - " [77, 73],\n", - " [77, 74],\n", - " [77, 75],\n", - " [77, 76],\n", - " [77, 77],\n", - " [78, 78],\n", - " [78, 79],\n", - " [78, 80],\n", - " [79, 78],\n", - " [79, 79],\n", - " [79, 80],\n", - " [80, 78],\n", - " [80, 79],\n", - " [80, 80],\n", - " [81, 81],\n", - " [81, 82],\n", - " [82, 81],\n", - " [82, 82],\n", - " [83, 83],\n", - " [83, 84],\n", - " [83, 85],\n", - " [83, 86],\n", - " [83, 87],\n", - " [83, 88],\n", - " [83, 89],\n", - " [83, 90],\n", - " [84, 83],\n", - " [84, 84],\n", - " [84, 85],\n", - " [84, 86],\n", - " [84, 87],\n", - " [84, 88],\n", - " [84, 89],\n", - " [84, 90],\n", - " [85, 83],\n", - " [85, 84],\n", - " [85, 85],\n", - " [85, 86],\n", - " [85, 87],\n", - " [85, 88],\n", - " [85, 89],\n", - " [85, 90],\n", - " [86, 83],\n", - " [86, 84],\n", - " [86, 85],\n", - " [86, 86],\n", - " [86, 87],\n", - " [86, 88],\n", - " [86, 89],\n", - " [86, 90],\n", - " [87, 83],\n", - " [87, 84],\n", - " [87, 85],\n", - " [87, 86],\n", - " [87, 87],\n", - " [87, 88],\n", - " [87, 89],\n", - " [87, 90],\n", - " [88, 83],\n", - " [88, 84],\n", - " [88, 85],\n", - " [88, 86],\n", - " [88, 87],\n", - " [88, 88],\n", - " [88, 89],\n", - " [88, 90],\n", - " [89, 83],\n", - " [89, 84],\n", - " [89, 85],\n", - " [89, 86],\n", - " [89, 87],\n", - " [89, 88],\n", - " [89, 89],\n", - " [89, 90],\n", - " [90, 83],\n", - " [90, 84],\n", - " [90, 85],\n", - " [90, 86],\n", - " [90, 87],\n", - " [90, 88],\n", - " [90, 89],\n", - " [90, 90],\n", - " [91, 91],\n", - " [91, 92],\n", - " [91, 93],\n", - " [92, 91],\n", - " [92, 92],\n", - " [92, 93],\n", - " [93, 91],\n", - " [93, 92],\n", - " [93, 93],\n", - " [94, 94],\n", - " [94, 95],\n", - " [95, 94],\n", - " [95, 95]])" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "np.argwhere(is_coplanar)\n", - "# now, we index this back into a grid" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[[0],\n", - " [1],\n", - " [2],\n", - " [3],\n", - " [4],\n", - " [5],\n", - " [6],\n", - " [7],\n", - " [8],\n", - " [9],\n", - " [10, 11],\n", - " [10, 11],\n", - " [12, 13],\n", - " [12, 13],\n", - " [14, 15, 16],\n", - " [14, 15, 16],\n", - " [14, 15, 16],\n", - " [17, 18],\n", - " [17, 18],\n", - " [19, 20],\n", - " [19, 20],\n", - " [21, 22],\n", - " [21, 22],\n", - " [23, 24],\n", - " [23, 24],\n", - " [25, 26],\n", - " [25, 26],\n", - " [27, 28, 29],\n", - " [27, 28, 29],\n", - " [27, 28, 29],\n", - " [30, 31],\n", - " [30, 31],\n", - " [32, 33, 34],\n", - " [32, 33, 34],\n", - " [32, 33, 34],\n", - " [35, 36],\n", - " [35, 36],\n", - " [37, 38, 39],\n", - " [37, 38, 39],\n", - " [37, 38, 39],\n", - " [40, 41],\n", - " [40, 41],\n", - " [42, 43],\n", - " [42, 43],\n", - " [44, 45, 46],\n", - " [44, 45, 46],\n", - " [44, 45, 46],\n", - " [47, 48],\n", - " [47, 48],\n", - " [49, 50, 51],\n", - " [49, 50, 51],\n", - " [49, 50, 51],\n", - " [52, 53],\n", - " [52, 53],\n", - " [54, 55],\n", - " [54, 55],\n", - " [56, 57, 58],\n", - " [56, 57, 58],\n", - " [56, 57, 58],\n", - " [59, 60],\n", - " [59, 60],\n", - " [61, 62, 63],\n", - " [61, 62, 63],\n", - " [61, 62, 63],\n", - " [64, 65],\n", - " [64, 65],\n", - " [66, 67],\n", - " [66, 67],\n", - " [68, 69],\n", - " [68, 69],\n", - " [70, 71, 72, 73, 74, 75, 76, 77],\n", - " [70, 71, 72, 73, 74, 75, 76, 77],\n", - " [70, 71, 72, 73, 74, 75, 76, 77],\n", - " [70, 71, 72, 73, 74, 75, 76, 77],\n", - " [70, 71, 72, 73, 74, 75, 76, 77],\n", - " [70, 71, 72, 73, 74, 75, 76, 77],\n", - " [70, 71, 72, 73, 74, 75, 76, 77],\n", - " [70, 71, 72, 73, 74, 75, 76, 77],\n", - " [78, 79, 80],\n", - " [78, 79, 80],\n", - " [78, 79, 80],\n", - " [81, 82],\n", - " [81, 82],\n", - " [83, 84, 85, 86, 87, 88, 89, 90],\n", - " [83, 84, 85, 86, 87, 88, 89, 90],\n", - " [83, 84, 85, 86, 87, 88, 89, 90],\n", - " [83, 84, 85, 86, 87, 88, 89, 90],\n", - " [83, 84, 85, 86, 87, 88, 89, 90],\n", - " [83, 84, 85, 86, 87, 88, 89, 90],\n", - " [83, 84, 85, 86, 87, 88, 89, 90],\n", - " [83, 84, 85, 86, 87, 88, 89, 90],\n", - " [91, 92, 93],\n", - " [91, 92, 93],\n", - " [91, 92, 93],\n", - " [94, 95],\n", - " [94, 95]]" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "nsimplices = len(poly.simplices)\n", - "outarr = [[] for _ in range(nsimplices)]\n", - "for row, coplanar in zip(*is_coplanar.nonzero()):\n", - " outarr[row].append(coplanar)\n", - "outarr" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "57.1 µs ± 5.61 µs per loop (mean ± std. dev. of 50 runs, 10,000 loops each)\n" - ] - } - ], - "source": [ - "%%timeit -r 50 -n 10000\n", - "coplanar_indices = [[] for _ in range(nsimplices)]\n", - "for face, index in zip(*is_coplanar.nonzero()):\n", - " coplanar_indices[face].append(index)\n", - "# outarr = list(set(map(tuple, outarr)))\n", - "coplanar_indices = list(set(map(tuple, coplanar_indices)))\n", - "coplanar_indices\n", - "# So now we have our coplanar simplex indices. Extract back the" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "57.7 µs ± 1.58 µs per loop (mean ± std. dev. of 50 runs, 10,000 loops each)\n" - ] - } - ], - "source": [ - "%%timeit -r 50 -n 10000\n", - "coplanar_indices = [[] for _ in range(nsimplices)]\n", - "\n", - "# Iterate over coplanar indices to build face lists\n", - "for face, index in zip(*is_coplanar.nonzero()):\n", - " coplanar_indices[face].append(index)\n", - "\n", - "# Remove duplicate faces, then sort the face indices by their minimum value\n", - "coplanar_indices = sorted(set(map(tuple, coplanar_indices)), key=lambda x: x[0])\n", - "\n", - "# So now we have our coplanar simplex indices. Extract back the faces\n", - "\n", - "coplanar_indices" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "ename": "NameError", - "evalue": "name 'coplanar_indices' is not defined", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m/Users/jenbradley/github/coxeter/temp.ipynb Cell 12\u001b[0m line \u001b[0;36m2\n\u001b[1;32m 1\u001b[0m \u001b[39msorted\u001b[39m(\n\u001b[0;32m----> 2\u001b[0m [np\u001b[39m.\u001b[39marray(\u001b[39msorted\u001b[39m(\u001b[39mset\u001b[39m(poly\u001b[39m.\u001b[39msimplices[[ind]]\u001b[39m.\u001b[39mflat))) \u001b[39mfor\u001b[39;00m ind \u001b[39min\u001b[39;00m coplanar_indices],\n\u001b[1;32m 3\u001b[0m key\u001b[39m=\u001b[39m\u001b[39mlambda\u001b[39;00m x: x[\u001b[39m0\u001b[39m],\n\u001b[1;32m 4\u001b[0m )\n", - "\u001b[0;31mNameError\u001b[0m: name 'coplanar_indices' is not defined" - ] - } - ], - "source": [ - "sorted(\n", - " [np.array(sorted(set(poly.simplices[[ind]].flat))) for ind in coplanar_indices],\n", - " key=lambda x: x[0],\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "89.9 µs ± 6.84 µs per loop (mean ± std. dev. of 100 runs, 10,000 loops each)\n" - ] - } - ], - "source": [ - "%%timeit -r 100 -n 10000\n", - "[np.fromiter(set(poly.simplices[[ind]].flat), np.int32) for ind in coplanar_indices]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "102 µs ± 12.6 µs per loop (mean ± std. dev. of 100 runs, 10,000 loops each)\n" - ] - } - ], - "source": [ - "%%timeit -r 100 -n 10000\n", - "[np.array(sorted(set(poly.simplices[[ind]].flat))) for ind in coplanar_indices]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "220 µs ± 14.3 µs per loop (mean ± std. dev. of 100 runs, 10,000 loops each)\n" - ] - } - ], - "source": [ - "%%timeit -r 100 -n 10000\n", - "[np.unique(poly.simplices[[ind]].flat) for ind in coplanar_indices]\n", - "# Upside of this (other than clarity) is the numbers are \"mostly\" sorted\n", - "# I think this is only true because the input lists are sorted, but maybe this doesnt matter?\n", - "# In any case, sorting above is cheap so we should keep doing that\n", - "## 200us without .flat, and faster without - oof" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "185 µs ± 14.4 µs per loop (mean ± std. dev. of 100 runs, 10,000 loops each)\n" - ] - } - ], - "source": [ - "%%timeit -r 100 -n 10000\n", - "[np.unique(poly.simplices[[ind]]) for ind in coplanar_indices]\n", - "# Upside of this (other than clarity) is the numbers are \"mostly\" sorted\n", - "# I think this is only true because the input lists are sorted, but maybe this doesnt matter?\n", - "# In any case, sorting above is cheap so we should keep doing that\n", - "## 200us without .flat" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[array([38, 40, 48], dtype=int32),\n", - " array([14, 22, 24], dtype=int32),\n", - " array([15, 23, 25], dtype=int32),\n", - " array([ 2, 7, 10], dtype=int32),\n", - " array([0, 6, 8], dtype=int32),\n", - " array([39, 41, 49], dtype=int32),\n", - " array([33, 36, 45], dtype=int32),\n", - " array([17, 20, 29], dtype=int32),\n", - " array([32, 34, 43], dtype=int32),\n", - " array([16, 18, 27], dtype=int32),\n", - " array([15, 21, 25, 30], dtype=int32),\n", - " array([36, 40, 45, 48], dtype=int32),\n", - " array([ 1, 3, 15, 28, 30], dtype=int32),\n", - " array([22, 23, 24, 25], dtype=int32),\n", - " array([15, 19, 23, 28], dtype=int32),\n", - " array([34, 38, 43, 48], dtype=int32),\n", - " array([ 1, 3, 9, 11], dtype=int32),\n", - " array([14, 18, 22, 27], dtype=int32),\n", - " array([18, 19, 22, 23, 26], dtype=int32),\n", - " array([ 2, 7, 17, 29], dtype=int32),\n", - " array([ 0, 2, 14, 27, 29], dtype=int32),\n", - " array([ 0, 2, 8, 10], dtype=int32),\n", - " array([ 8, 10, 43, 45, 48], dtype=int32),\n", - " array([35, 39, 44, 49], dtype=int32),\n", - " array([38, 39, 40, 41], dtype=int32),\n", - " array([36, 37, 40, 41, 47], dtype=int32),\n", - " array([ 7, 10, 33, 45], dtype=int32),\n", - " array([ 5, 7, 13, 17, 33], dtype=int32),\n", - " array([13, 33, 36, 47], dtype=int32),\n", - " array([14, 20, 24, 29], dtype=int32),\n", - " array([34, 35, 38, 39, 42], dtype=int32),\n", - " array([ 6, 8, 32, 43], dtype=int32),\n", - " array([ 9, 11, 44, 46, 49], dtype=int32),\n", - " array([37, 41, 46, 49], dtype=int32),\n", - " array([ 4, 16, 18, 26], dtype=int32),\n", - " array([ 0, 6, 16, 27], dtype=int32),\n", - " array([ 3, 5, 11, 13, 21, 30, 31, 37, 46, 47], dtype=int32),\n", - " array([20, 21, 24, 25, 31], dtype=int32),\n", - " array([ 5, 17, 20, 31], dtype=int32),\n", - " array([ 1, 4, 9, 12, 19, 26, 28, 35, 42, 44], dtype=int32),\n", - " array([ 4, 6, 12, 16, 32], dtype=int32),\n", - " array([12, 32, 34, 42], dtype=int32)]" - ] - }, - "execution_count": 225, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "[np.unique(poly.simplices[[ind]]) for ind in coplanar_indices]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "ename": "ValueError", - "evalue": "setting an array element with a sequence. The requested array has an inhomogeneous shape after 1 dimensions. The detected shape was (42,) + inhomogeneous part.", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m/Users/jenbradley/github/coxeter/temp.ipynb Cell 16\u001b[0m line \u001b[0;36m1\n\u001b[0;32m----> 1\u001b[0m poly\u001b[39m.\u001b[39;49msimplices[coplanar_indices]\n", - "\u001b[0;31mValueError\u001b[0m: setting an array element with a sequence. The requested array has an inhomogeneous shape after 1 dimensions. The detected shape was (42,) + inhomogeneous part." - ] - } - ], - "source": [ - "poly.simplices[coplanar_indices]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{(0, 27, 14, 29, 2),\n", - " (1, 3, 30, 15, 28),\n", - " (1, 28, 19, 26, 4, 12, 42, 35, 44, 9),\n", - " (2, 7, 10),\n", - " (3, 1, 9, 11),\n", - " (3, 11, 46, 37, 47, 13, 5, 31, 21, 30),\n", - " (8, 0, 2, 10),\n", - " (8, 6, 0),\n", - " (8, 10, 45, 48, 43),\n", - " (9, 44, 49, 46, 11),\n", - " (16, 4, 26, 18),\n", - " (16, 18, 27),\n", - " (16, 27, 0, 6),\n", - " (17, 7, 2, 29),\n", - " (17, 20, 31, 5),\n", - " (17, 29, 20),\n", - " (18, 22, 14, 27),\n", - " (18, 26, 19, 23, 22),\n", - " (19, 28, 15, 23),\n", - " (20, 24, 25, 21, 31),\n", - " (24, 14, 22),\n", - " (24, 20, 29, 14),\n", - " (24, 22, 23, 25),\n", - " (25, 15, 30, 21),\n", - " (25, 23, 15),\n", - " (32, 6, 8, 43),\n", - " (32, 12, 4, 16, 6),\n", - " (32, 34, 42, 12),\n", - " (32, 43, 34),\n", - " (33, 7, 17, 5, 13),\n", - " (33, 13, 47, 36),\n", - " (33, 36, 45),\n", - " (33, 45, 10, 7),\n", - " (34, 38, 39, 35, 42),\n", - " (36, 47, 37, 41, 40),\n", - " (40, 41, 39, 38),\n", - " (41, 37, 46, 49),\n", - " (41, 49, 39),\n", - " (48, 38, 34, 43),\n", - " (48, 40, 38),\n", - " (48, 45, 36, 40),\n", - " (49, 44, 35, 39)}" - ] - }, - "execution_count": 240, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "set(map(tuple, mbri.faces))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 250, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "set(\n", - " map(tuple, [np.unique(poly.simplices[[ind]].flat) for ind in coplanar_indices])\n", - ") == set(map(tuple, map(sorted, mbri.faces)))" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[array([38, 40, 48], dtype=int32),\n", - " array([14, 22, 24], dtype=int32),\n", - " array([15, 23, 25], dtype=int32),\n", - " array([ 2, 7, 10], dtype=int32),\n", - " array([0, 6, 8], dtype=int32),\n", - " array([39, 41, 49], dtype=int32),\n", - " array([33, 36, 45], dtype=int32),\n", - " array([17, 20, 29], dtype=int32),\n", - " array([32, 34, 43], dtype=int32),\n", - " array([16, 18, 27], dtype=int32),\n", - " array([15, 21, 25, 30], dtype=int32),\n", - " array([36, 40, 45, 48], dtype=int32),\n", - " array([ 1, 3, 15, 28, 30], dtype=int32),\n", - " array([22, 23, 24, 25], dtype=int32),\n", - " array([15, 19, 23, 28], dtype=int32),\n", - " array([34, 38, 43, 48], dtype=int32),\n", - " array([ 1, 3, 9, 11], dtype=int32),\n", - " array([14, 18, 22, 27], dtype=int32),\n", - " array([18, 19, 22, 23, 26], dtype=int32),\n", - " array([ 2, 7, 17, 29], dtype=int32),\n", - " array([ 0, 2, 14, 27, 29], dtype=int32),\n", - " array([ 0, 2, 8, 10], dtype=int32),\n", - " array([ 8, 10, 43, 45, 48], dtype=int32),\n", - " array([35, 39, 44, 49], dtype=int32),\n", - " array([38, 39, 40, 41], dtype=int32),\n", - " array([36, 37, 40, 41, 47], dtype=int32),\n", - " array([ 7, 10, 33, 45], dtype=int32),\n", - " array([ 5, 7, 13, 17, 33], dtype=int32),\n", - " array([13, 33, 36, 47], dtype=int32),\n", - " array([14, 20, 24, 29], dtype=int32),\n", - " array([34, 35, 38, 39, 42], dtype=int32),\n", - " array([ 6, 8, 32, 43], dtype=int32),\n", - " array([ 9, 11, 44, 46, 49], dtype=int32),\n", - " array([37, 41, 46, 49], dtype=int32),\n", - " array([ 4, 16, 18, 26], dtype=int32),\n", - " array([ 0, 6, 16, 27], dtype=int32),\n", - " array([ 3, 5, 11, 13, 21, 30, 31, 37, 46, 47], dtype=int32),\n", - " array([20, 21, 24, 25, 31], dtype=int32),\n", - " array([ 5, 17, 20, 31], dtype=int32),\n", - " array([ 1, 4, 9, 12, 19, 26, 28, 35, 42, 44], dtype=int32),\n", - " array([ 4, 6, 12, 16, 32], dtype=int32),\n", - " array([12, 32, 34, 42], dtype=int32)]" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# %%timeit -r 10 -n 100\n", - "# lets compile everything together now:\n", - "poly = mbri\n", - "tol = 1e-15\n", - "nsimplices = len(poly._simplices)\n", - "is_coplanar = np.all(\n", - " np.abs(poly._simplex_equations[:, None] - poly._simplex_equations) < tol, axis=2\n", - ")\n", - "coplanar_indices = [[] for _ in range(nsimplices)]\n", - "\n", - "# Iterate over coplanar indices to build face index lists\n", - "for face, index in zip(*is_coplanar.nonzero()):\n", - " coplanar_indices[face].append(index)\n", - "\n", - "# Remove duplicate faces, then sort the face indices by their minimum value\n", - "coplanar_indices = sorted(set(map(tuple, coplanar_indices)), key=lambda x: x[0])\n", - "\n", - "# Set method is faster: but let's try this\n", - "# Extract vertex indices from simplex indices and remove duplicates\n", - "faces = [np.unique(poly.simplices[[ind]]) for ind in coplanar_indices]\n", - "faces" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "3.04 µs ± 145 ns per loop (mean ± std. dev. of 10 runs, 10,000 loops each)\n" - ] - } - ], - "source": [ - "%%timeit -r 10 -n 10000\n", - "poly._simplex_equations[[equation_index[0] for equation_index in coplanar_indices]]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "300 µs ± 7.5 µs per loop (mean ± std. dev. of 10 runs, 10,000 loops each)\n" - ] - } - ], - "source": [ - "%%timeit -r 10 -n 10000\n", - "# lets compile everything together now:\n", - "is_coplanar = np.all(\n", - " np.abs(poly._simplex_equations[:, None] - poly._simplex_equations) < tol, axis=2\n", - ")\n", - "coplanar_indices = [[] for _ in range(nsimplices)]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "ename": "AttributeError", - "evalue": "'tuple' object has no attribute 'append'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m/Users/jenbradley/github/coxeter/temp.ipynb Cell 23\u001b[0m line \u001b[0;36m1\n\u001b[0;32m----> 1\u001b[0m get_ipython()\u001b[39m.\u001b[39;49mrun_cell_magic(\u001b[39m'\u001b[39;49m\u001b[39mtimeit\u001b[39;49m\u001b[39m'\u001b[39;49m, \u001b[39m'\u001b[39;49m\u001b[39m-r 10 -n 10000\u001b[39;49m\u001b[39m'\u001b[39;49m, \u001b[39m'\u001b[39;49m\u001b[39m# Iterate over coplanar indices to build face index lists\u001b[39;49m\u001b[39m\\n\u001b[39;49;00m\u001b[39mfor face, index in zip(*is_coplanar.nonzero()):\u001b[39;49m\u001b[39m\\n\u001b[39;49;00m\u001b[39m coplanar_indices[face].append(index)\u001b[39;49m\u001b[39m\\n\u001b[39;49;00m\u001b[39m'\u001b[39;49m)\n", - "File \u001b[0;32m~/micromamba/envs/690py311/lib/python3.11/site-packages/IPython/core/interactiveshell.py:2493\u001b[0m, in \u001b[0;36mInteractiveShell.run_cell_magic\u001b[0;34m(self, magic_name, line, cell)\u001b[0m\n\u001b[1;32m 2491\u001b[0m \u001b[39mwith\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mbuiltin_trap:\n\u001b[1;32m 2492\u001b[0m args \u001b[39m=\u001b[39m (magic_arg_s, cell)\n\u001b[0;32m-> 2493\u001b[0m result \u001b[39m=\u001b[39m fn(\u001b[39m*\u001b[39;49margs, \u001b[39m*\u001b[39;49m\u001b[39m*\u001b[39;49mkwargs)\n\u001b[1;32m 2495\u001b[0m \u001b[39m# The code below prevents the output from being displayed\u001b[39;00m\n\u001b[1;32m 2496\u001b[0m \u001b[39m# when using magics with decorator @output_can_be_silenced\u001b[39;00m\n\u001b[1;32m 2497\u001b[0m \u001b[39m# when the last Python token in the expression is a ';'.\u001b[39;00m\n\u001b[1;32m 2498\u001b[0m \u001b[39mif\u001b[39;00m \u001b[39mgetattr\u001b[39m(fn, magic\u001b[39m.\u001b[39mMAGIC_OUTPUT_CAN_BE_SILENCED, \u001b[39mFalse\u001b[39;00m):\n", - "File \u001b[0;32m~/micromamba/envs/690py311/lib/python3.11/site-packages/IPython/core/magics/execution.py:1189\u001b[0m, in \u001b[0;36mExecutionMagics.timeit\u001b[0;34m(self, line, cell, local_ns)\u001b[0m\n\u001b[1;32m 1186\u001b[0m \u001b[39mif\u001b[39;00m time_number \u001b[39m>\u001b[39m\u001b[39m=\u001b[39m \u001b[39m0.2\u001b[39m:\n\u001b[1;32m 1187\u001b[0m \u001b[39mbreak\u001b[39;00m\n\u001b[0;32m-> 1189\u001b[0m all_runs \u001b[39m=\u001b[39m timer\u001b[39m.\u001b[39;49mrepeat(repeat, number)\n\u001b[1;32m 1190\u001b[0m best \u001b[39m=\u001b[39m \u001b[39mmin\u001b[39m(all_runs) \u001b[39m/\u001b[39m number\n\u001b[1;32m 1191\u001b[0m worst \u001b[39m=\u001b[39m \u001b[39mmax\u001b[39m(all_runs) \u001b[39m/\u001b[39m number\n", - "File \u001b[0;32m~/micromamba/envs/690py311/lib/python3.11/timeit.py:206\u001b[0m, in \u001b[0;36mTimer.repeat\u001b[0;34m(self, repeat, number)\u001b[0m\n\u001b[1;32m 204\u001b[0m r \u001b[39m=\u001b[39m []\n\u001b[1;32m 205\u001b[0m \u001b[39mfor\u001b[39;00m i \u001b[39min\u001b[39;00m \u001b[39mrange\u001b[39m(repeat):\n\u001b[0;32m--> 206\u001b[0m t \u001b[39m=\u001b[39m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mtimeit(number)\n\u001b[1;32m 207\u001b[0m r\u001b[39m.\u001b[39mappend(t)\n\u001b[1;32m 208\u001b[0m \u001b[39mreturn\u001b[39;00m r\n", - "File \u001b[0;32m~/micromamba/envs/690py311/lib/python3.11/site-packages/IPython/core/magics/execution.py:173\u001b[0m, in \u001b[0;36mTimer.timeit\u001b[0;34m(self, number)\u001b[0m\n\u001b[1;32m 171\u001b[0m gc\u001b[39m.\u001b[39mdisable()\n\u001b[1;32m 172\u001b[0m \u001b[39mtry\u001b[39;00m:\n\u001b[0;32m--> 173\u001b[0m timing \u001b[39m=\u001b[39m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49minner(it, \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mtimer)\n\u001b[1;32m 174\u001b[0m \u001b[39mfinally\u001b[39;00m:\n\u001b[1;32m 175\u001b[0m \u001b[39mif\u001b[39;00m gcold:\n", - "File \u001b[0;32m:3\u001b[0m, in \u001b[0;36minner\u001b[0;34m(_it, _timer)\u001b[0m\n", - "\u001b[0;31mAttributeError\u001b[0m: 'tuple' object has no attribute 'append'" - ] - } - ], - "source": [ - "%%timeit -r 10 -n 10000\n", - "# Iterate over coplanar indices to build face index lists\n", - "for face, index in zip(*is_coplanar.nonzero()):\n", - " coplanar_indices[face].append(index)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%timeit -r 10 -n 10000\n", - "# Remove duplicate faces, then sort the face indices by their minimum value\n", - "coplanar_indices = sorted(set(map(tuple, coplanar_indices)), key=lambda x: x[0])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%timeit -r 10 -n 10000\n", - "# Set method is faster: but let's try this\n", - "# Extract vertex indices from simplex indices and remove duplicates\n", - "faces = [np.unique(poly.simplices[[ind]]) for ind in coplanar_indices]\n", - "faces" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "ename": "NameError", - "evalue": "name 'self' is not defined", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m/Users/jenbradley/github/coxeter/temp.ipynb Cell 26\u001b[0m line \u001b[0;36m3\n\u001b[1;32m 1\u001b[0m tol \u001b[39m=\u001b[39m \u001b[39m1e-15\u001b[39m\n\u001b[1;32m 2\u001b[0m is_coplanar \u001b[39m=\u001b[39m np\u001b[39m.\u001b[39mall(\n\u001b[0;32m----> 3\u001b[0m np\u001b[39m.\u001b[39mabs(\u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39m_simplex_equations[:, \u001b[39mNone\u001b[39;00m] \u001b[39m-\u001b[39m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_simplex_equations) \u001b[39m<\u001b[39m tol,\n\u001b[1;32m 4\u001b[0m axis\u001b[39m=\u001b[39m\u001b[39m2\u001b[39m,\n\u001b[1;32m 5\u001b[0m )\n\u001b[1;32m 6\u001b[0m coplanar_indices \u001b[39m=\u001b[39m [[] \u001b[39mfor\u001b[39;00m _ \u001b[39min\u001b[39;00m \u001b[39mrange\u001b[39m(\u001b[39mlen\u001b[39m(\u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_simplices))]\n\u001b[1;32m 8\u001b[0m \u001b[39m# Iterate over coplanar indices to build face index lists\u001b[39;00m\n", - "\u001b[0;31mNameError\u001b[0m: name 'self' is not defined" - ] - } - ], - "source": [ - "tol = 1e-15\n", - "is_coplanar = np.all(\n", - " np.abs(self._simplex_equations[:, None] - self._simplex_equations) < tol,\n", - " axis=2,\n", - ")\n", - "coplanar_indices = [[] for _ in range(len(self._simplices))]\n", - "\n", - "# Iterate over coplanar indices to build face index lists\n", - "for face, index in zip(*is_coplanar.nonzero()):\n", - " coplanar_indices[face].append(index)\n", - "\n", - "# Remove duplicate faces, then sort the face indices by their minimum value\n", - "coplanar_indices = sorted(set(map(tuple, coplanar_indices)), key=lambda x: x[0])\n", - "\n", - "# Set method is faster: but let's try this\n", - "# Extract vertex indices from simplex indices and remove duplicates\n", - "faces = [np.unique(self.simplices[[ind]]) for ind in coplanar_indices]\n", - "self._faces = faces\n", - "# self._equations = np.array(list(equation_groups.keys()))" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "13.4 µs ± 929 ns per loop (mean ± std. dev. of 20 runs, 100,000 loops each)\n", - "11.6 µs ± 140 ns per loop (mean ± std. dev. of 20 runs, 100,000 loops each)\n" - ] - } - ], - "source": [ - "coplanar_indices\n", - "%timeit -r 20 -n 100000 [np.array(coplanar) for coplanar in coplanar_indices]\n", - "%timeit -r 20 -n 100000 list(map(np.array,coplanar_indices))" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[array([0]),\n", - " array([1]),\n", - " array([2]),\n", - " array([3]),\n", - " array([4]),\n", - " array([5]),\n", - " array([6]),\n", - " array([7]),\n", - " array([8]),\n", - " array([9]),\n", - " array([10, 11]),\n", - " array([12, 13]),\n", - " array([14, 15, 16]),\n", - " array([17, 18]),\n", - " array([19, 20]),\n", - " array([21, 22]),\n", - " array([23, 24]),\n", - " array([25, 26]),\n", - " array([27, 28, 29]),\n", - " array([30, 31]),\n", - " array([32, 33, 34]),\n", - " array([35, 36]),\n", - " array([37, 38, 39]),\n", - " array([40, 41]),\n", - " array([42, 43]),\n", - " array([44, 45, 46]),\n", - " array([47, 48]),\n", - " array([49, 50, 51]),\n", - " array([52, 53]),\n", - " array([54, 55]),\n", - " array([56, 57, 58]),\n", - " array([59, 60]),\n", - " array([61, 62, 63]),\n", - " array([64, 65]),\n", - " array([66, 67]),\n", - " array([68, 69]),\n", - " array([70, 71, 72, 73, 74, 75, 76, 77]),\n", - " array([78, 79, 80]),\n", - " array([81, 82]),\n", - " array([83, 84, 85, 86, 87, 88, 89, 90]),\n", - " array([91, 92, 93]),\n", - " array([94, 95])]" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "list(map(np.array, coplanar_indices))" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[array([0], dtype=int32),\n", - " array([1], dtype=int32),\n", - " array([2], dtype=int32),\n", - " array([3], dtype=int32),\n", - " array([4], dtype=int32),\n", - " array([5], dtype=int32),\n", - " array([6], dtype=int32),\n", - " array([7], dtype=int32),\n", - " array([8], dtype=int32),\n", - " array([9], dtype=int32),\n", - " array([10, 11], dtype=int32),\n", - " array([12], dtype=int32),\n", - " array([13], dtype=int32),\n", - " array([14], dtype=int32),\n", - " array([16, 15], dtype=int32),\n", - " array([17, 18], dtype=int32),\n", - " array([19], dtype=int32),\n", - " array([20], dtype=int32),\n", - " array([21], dtype=int32),\n", - " array([22], dtype=int32),\n", - " array([24, 23], dtype=int32),\n", - " array([25], dtype=int32),\n", - " array([26], dtype=int32),\n", - " array([27, 28, 29], dtype=int32),\n", - " array([30, 31], dtype=int32),\n", - " array([32, 33, 34], dtype=int32),\n", - " array([35, 36], dtype=int32),\n", - " array([37], dtype=int32),\n", - " array([38, 39], dtype=int32),\n", - " array([40], dtype=int32),\n", - " array([41], dtype=int32),\n", - " array([42, 43], dtype=int32),\n", - " array([44, 45, 46], dtype=int32),\n", - " array([47], dtype=int32),\n", - " array([48], dtype=int32),\n", - " array([49, 50], dtype=int32),\n", - " array([51], dtype=int32),\n", - " array([52], dtype=int32),\n", - " array([53], dtype=int32),\n", - " array([54, 55], dtype=int32),\n", - " array([56, 57, 58], dtype=int32),\n", - " array([59], dtype=int32),\n", - " array([60], dtype=int32),\n", - " array([61, 62, 63], dtype=int32),\n", - " array([64], dtype=int32),\n", - " array([65], dtype=int32),\n", - " array([66], dtype=int32),\n", - " array([67], dtype=int32),\n", - " array([68], dtype=int32),\n", - " array([69], dtype=int32),\n", - " array([76, 70], dtype=int32),\n", - " array([71, 73, 74, 75, 77], dtype=int32),\n", - " array([72], dtype=int32),\n", - " array([80, 78], dtype=int32),\n", - " array([79], dtype=int32),\n", - " array([81], dtype=int32),\n", - " array([82], dtype=int32),\n", - " array([83], dtype=int32),\n", - " array([84, 86, 87, 88, 90], dtype=int32),\n", - " array([89, 85], dtype=int32),\n", - " array([91, 92, 93], dtype=int32),\n", - " array([94, 95], dtype=int32)]" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "poly._coplanar_simplices" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[array([0], dtype=int32),\n", - " array([1], dtype=int32),\n", - " array([2], dtype=int32),\n", - " array([3], dtype=int32),\n", - " array([4], dtype=int32),\n", - " array([5], dtype=int32),\n", - " array([6], dtype=int32),\n", - " array([7], dtype=int32),\n", - " array([8], dtype=int32),\n", - " array([9], dtype=int32)]" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "poly._coplanar_simplices[0:10]" - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "metadata": {}, - "outputs": [], - "source": [ - "verts = johnfam.get_shape(\"Metabidiminished Rhombicosidodecahedron\").vertices\n", - "verts = cube.vertices" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "5.08 ms ± 543 µs per loop (mean ± std. dev. of 10 runs, 100 loops each)\n" - ] - } - ], - "source": [ - "%%timeit -r 10 -n 100\n", - "ConvexPolyhedron(mbri.vertices)" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "659 µs ± 8.77 µs per loop (mean ± std. dev. of 10 runs, 1,000 loops each)\n" - ] - } - ], - "source": [ - "%%timeit -r 10 -n 1000\n", - "ConvexPolyhedron(cube.vertices)" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[array([0]),\n", - " array([1]),\n", - " array([2]),\n", - " array([3]),\n", - " array([4]),\n", - " array([5]),\n", - " array([6]),\n", - " array([7]),\n", - " array([8]),\n", - " array([9]),\n", - " array([10, 11]),\n", - " array([12, 13]),\n", - " array([14, 15, 16]),\n", - " array([17, 18]),\n", - " array([19, 20]),\n", - " array([21, 22]),\n", - " array([23, 24]),\n", - " array([25, 26]),\n", - " array([27, 28, 29]),\n", - " array([30, 31]),\n", - " array([32, 33, 34]),\n", - " array([35, 36]),\n", - " array([37, 38, 39]),\n", - " array([40, 41]),\n", - " array([42, 43]),\n", - " array([44, 45, 46]),\n", - " array([47, 48]),\n", - " array([49, 50, 51]),\n", - " array([52, 53]),\n", - " array([54, 55]),\n", - " array([56, 57, 58]),\n", - " array([59, 60]),\n", - " array([61, 62, 63]),\n", - " array([64, 65]),\n", - " array([66, 67]),\n", - " array([68, 69]),\n", - " array([70, 71, 72, 73, 74, 75, 76, 77]),\n", - " array([78, 79, 80]),\n", - " array([81, 82]),\n", - " array([83, 84, 85, 86, 87, 88, 89, 90]),\n", - " array([91, 92, 93]),\n", - " array([94, 95])]" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "poly = mbri\n", - "tol = 1e-15\n", - "is_coplanar = np.all(\n", - " np.abs(poly._simplex_equations[:, None] - poly._simplex_equations) < tol,\n", - " axis=2,\n", - ")\n", - "coplanar_indices = [[] for _ in range(len(poly._simplices))]\n", - "\n", - "# Iterate over coplanar indices to build face index lists\n", - "for face, index in zip(*is_coplanar.nonzero()):\n", - " coplanar_indices[face].append(index)\n", - "\n", - "# Remove duplicate faces, then sort the face indices by their minimum value\n", - "coplanar_indices = sorted(set(map(tuple, coplanar_indices)), key=lambda x: x[0])\n", - "\n", - "# Extract vertex indices from simplex indices and remove duplicates\n", - "faces = [np.unique(poly.simplices[[ind]]) for ind in coplanar_indices]\n", - "\n", - "# Copy the simplex equation for one of the simplices on each face\n", - "poly._simplex_equations[[equation_index[0] for equation_index in coplanar_indices]]\n", - "# Convert the simplex indices to numpy arrays and save\n", - "list(map(np.array, coplanar_indices))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 28, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "177 µs ± 5.02 µs per loop (mean ± std. dev. of 10 runs, 10,000 loops each)\n" - ] - } - ], - "source": [ - "%%timeit -r 10 -n 10000\n", - "[np.unique(poly.simplices[[ind]]) for ind in coplanar_indices]" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1.07 ms ± 114 µs per loop (mean ± std. dev. of 10 runs, 100 loops each)\n" - ] - } - ], - "source": [ - "%%timeit -r 10 -n 100\n", - "# [np.fromiter(set(poly.simplices[[ind]].flat), np.int32) for ind in coplanar_indices]\n", - "mbri._sort_simplices()" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1.33 ms ± 242 µs per loop (mean ± std. dev. of 10 runs, 1,000 loops each)\n" - ] - } - ], - "source": [ - "%%timeit -r 10 -n 1000\n", - "# [np.fromiter(set(poly.simplices[[ind]].flat), np.int32) for ind in coplanar_indices]\n", - "mbri._sort_simplices()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.5" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/test.ipynb b/test.ipynb deleted file mode 100644 index 8809b451..00000000 --- a/test.ipynb +++ /dev/null @@ -1,3310 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "from scipy.spatial import ConvexHull as ch\n", - "from scipy.spatial import Delaunay\n", - "\n", - "from coxeter.families import (\n", - " ArchimedeanFamily as archfam,\n", - ")\n", - "from coxeter.families import (\n", - " JohnsonFamily as johnfam,\n", - ")\n", - "from coxeter.families import (\n", - " PlatonicFamily as platfam,\n", - ")\n", - "from coxeter.shapes import ConvexPolyhedron" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "cube = platfam.get_shape(\"Cube\")\n", - "# tetr = platfam.get_shape(\"Tetrahedron\")\n", - "# mbri = johnfam.get_shape(\"Metabidiminished Rhombicosidodecahedron\")\n", - "# icosi = archfam.get_shape(\"Icosidodecahedron\")\n", - "# poly = cube" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(42, 42)" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "mbri.faces.__len__(), mbri._coplanar_simplices.__len__()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "hull = ch(mbri.vertices, qhull_options=\"Q2\")\n", - "# hull.equations\n", - "# hull.neighbors" - ] - }, - { - "cell_type": "code", - "execution_count": 249, - "metadata": {}, - "outputs": [], - "source": [ - "def _find_coplanar_simplices(self, rounding: int = 15):\n", - " \"\"\"\n", - " Get lists of simplex indices for coplanar simplices.\n", - "\n", - " Args:\n", - " rounding (int, optional):\n", - " Integer number of decimal places to round equations to.\n", - " (Default value: 15).\n", - "\n", - "\n", - " \"\"\"\n", - " # Combine simplex indices\n", - " equation_groups = defaultdict(list)\n", - "\n", - " # Iterate over all simplex equations\n", - " for i, equation in enumerate(self._simplex_equations):\n", - " # Convert equation into hashable tuple\n", - " equation_key = tuple(equation.round(rounding))\n", - " equation_groups[equation_key].append(i)\n", - " ragged_coplanar_indices = [\n", - " np.fromiter(set(group), np.int32) for group in equation_groups.values()\n", - " ]\n", - "\n", - " self._coplanar_simplices = ragged_coplanar_indices" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "# hull.simplices" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[48 40] [40 38]\n" - ] - }, - { - "data": { - "text/plain": [ - "array([[48, 40],\n", - " [40, 38]], dtype=int32)" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "((96, 2, 2, 3),\n", - " array([[[ 0.54304647, 0. , -0.39295212],\n", - " [ 0.63580989, 0.15009435, -0.15009435]],\n", - " \n", - " [[ 0.63580989, 0.15009435, -0.15009435],\n", - " [ 0.63580989, -0.15009435, -0.15009435]]]))" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# First - compute normals of each triangle\n", - "# triangle_edges = np.dstack([hull.simplices[:, :-1], hull.simplices[:, 1:]])\n", - "print(hull.simplices[:, :-1][0], hull.simplices[:, 1:][0])\n", - "triangle_edges = np.dstack([hull.simplices[:, :-1], hull.simplices[:, 1:]]).transpose(\n", - " 0, 2, 1\n", - ")\n", - "# %timeit -r 10 -n 100000\n", - "display(triangle_edges[0])\n", - "triangle_edge_endpoints = hull.points[triangle_edges]\n", - "triangle_edge_endpoints.shape, triangle_edge_endpoints[0]" - ] - }, - { - "cell_type": "code", - "execution_count": 130, - "metadata": {}, - "outputs": [], - "source": [ - "# hull.points" - ] - }, - { - "cell_type": "code", - "execution_count": 131, - "metadata": {}, - "outputs": [], - "source": [ - "# poly._coplanar_simplices" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 132, - "metadata": {}, - "outputs": [], - "source": [ - "triangle_edge_vectors = (\n", - " triangle_edge_endpoints[..., 0, :] - triangle_edge_endpoints[..., 1, :]\n", - ")\n", - "# np.diff(triangle_edge_endpoints, axis=2).squeeze()\n", - "\n", - "# triangle_edge_vectors.shape, triangle_edge_vectors" - ] - }, - { - "cell_type": "code", - "execution_count": 133, - "metadata": {}, - "outputs": [], - "source": [ - "normals = np.cross(triangle_edge_vectors[:, 1, :], triangle_edge_vectors[:, 0, :])\n", - "normals / np.linalg.norm(normals, axis=1)[:, None]\n", - "nnormals = normals / np.linalg.norm(normals, axis=1)[:, None]\n", - "nnormals[1::2] *= -1\n", - "# nnormals\n", - "# I think there is something about the sorting of triangles from convexhull that result in\n", - "# odd triangles having opposite orientations in the first half, and evens having opposite in the second" - ] - }, - { - "cell_type": "code", - "execution_count": 134, - "metadata": {}, - "outputs": [], - "source": [ - "# ok - so I can just use this.\n", - "# hull.equations[:, :3]" - ] - }, - { - "cell_type": "code", - "execution_count": 135, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[ 0, 6, 1, 8],\n", - " [ 1, 2, 0, 4],\n", - " [ 2, 1, 3, 4],\n", - " [ 3, 7, 2, 10],\n", - " [ 4, 1, 2, 5],\n", - " [ 5, 9, 11, 4],\n", - " [ 6, 0, 7, 8],\n", - " [ 7, 3, 6, 10],\n", - " [ 8, 0, 6, 9],\n", - " [ 9, 5, 11, 8],\n", - " [10, 3, 7, 11],\n", - " [11, 5, 9, 10]])" - ] - }, - "execution_count": 135, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# 0 element of array is current simplex. next trhee are neighbors\n", - "simplex_indices = np.hstack(\n", - " [np.arange(0, hull.neighbors.shape[0], 1)[:, None], hull.neighbors]\n", - ")\n", - "simplex_indices" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[False, False, False],\n", - " [False, False, False],\n", - " [False, False, False],\n", - " [False, False, False],\n", - " [False, False, False],\n", - " [False, False, False],\n", - " [False, False, False],\n", - " [False, False, False],\n", - " [False, False, False],\n", - " [False, False, False],\n", - " [False, False, True],\n", - " [False, False, True],\n", - " [False, False, True],\n", - " [False, False, True],\n", - " [False, False, True],\n", - " [False, True, True],\n", - " [False, False, True],\n", - " [False, False, True],\n", - " [False, False, True],\n", - " [False, False, True],\n", - " [False, False, True],\n", - " [False, False, True],\n", - " [False, False, True],\n", - " [False, True, False],\n", - " [False, True, False],\n", - " [False, True, False],\n", - " [False, True, False],\n", - " [False, False, True],\n", - " [False, True, False],\n", - " [False, True, True],\n", - " [False, False, True],\n", - " [False, False, True],\n", - " [False, False, True],\n", - " [False, False, True],\n", - " [False, True, True],\n", - " [False, False, True],\n", - " [False, False, True],\n", - " [False, True, True],\n", - " [False, True, False],\n", - " [False, False, True],\n", - " [False, False, True],\n", - " [False, False, True],\n", - " [False, True, False],\n", - " [False, False, True],\n", - " [False, True, False],\n", - " [False, False, True],\n", - " [False, True, True],\n", - " [False, False, True],\n", - " [False, False, True],\n", - " [False, True, False],\n", - " [False, True, True],\n", - " [False, False, True],\n", - " [False, False, True],\n", - " [False, False, True],\n", - " [False, False, True],\n", - " [False, True, False],\n", - " [False, True, False],\n", - " [False, False, True],\n", - " [False, True, True],\n", - " [False, False, True],\n", - " [False, False, True],\n", - " [False, True, True],\n", - " [False, True, False],\n", - " [False, False, True],\n", - " [False, False, True],\n", - " [False, False, True],\n", - " [False, False, True],\n", - " [False, False, True],\n", - " [False, True, False],\n", - " [False, True, False],\n", - " [False, False, True],\n", - " [False, True, True],\n", - " [False, False, True],\n", - " [False, True, True],\n", - " [False, True, True],\n", - " [False, True, True],\n", - " [False, True, True],\n", - " [False, True, True],\n", - " [False, False, True],\n", - " [False, True, True],\n", - " [False, True, False],\n", - " [False, False, True],\n", - " [False, True, False],\n", - " [False, True, True],\n", - " [False, True, True],\n", - " [False, False, True],\n", - " [False, True, True],\n", - " [False, True, True],\n", - " [False, True, True],\n", - " [False, True, True],\n", - " [False, True, False],\n", - " [False, True, False],\n", - " [False, False, True],\n", - " [False, True, True],\n", - " [False, True, False],\n", - " [False, True, False]])" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "hull_normals = hull.equations[:, :3]\n", - "# hull_normals[simplex_indices]\n", - "coplanar_simplex_array = np.all(\n", - " hull_normals[np.arange(0, hull.nsimplex, 1)][:, None]\n", - " == hull_normals[hull.neighbors],\n", - " axis=2,\n", - ")\n", - "\n", - "# This is right! We get the correct coplanar simplices. How do we index back in?\n", - "coplanar_simplex_array" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "96" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "hull.neighbors[\n", - " coplanar_simplex_array\n", - "] # Direct indexing returns a flattened list - not useful\n", - "[\n", - " [*hull.neighbors[i][coplanar_simplex_array[i]], i] for i in range(hull.nsimplex)\n", - "].__len__()" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[0]\n", - "[1]\n", - "[2]\n", - "[3]\n", - "[4]\n", - "[5]\n", - "[6]\n", - "[7]\n", - "[8]\n", - "[9]\n", - "[11, 10]\n", - "[10, 11]\n", - "[13, 12]\n", - "[12, 13]\n", - "[15, 14]\n", - "[16, 14, 15]\n", - "[15, 16]\n", - "[18, 17]\n", - "[17, 18]\n", - "[20, 19]\n", - "[19, 20]\n", - "[22, 21]\n", - "[21, 22]\n", - "[24, 23]\n", - "[23, 24]\n", - "[26, 25]\n", - "[25, 26]\n", - "[29, 27]\n", - "[29, 28]\n", - "[28, 27, 29]\n", - "[31, 30]\n", - "[30, 31]\n", - "[34, 32]\n", - "[34, 33]\n", - "[33, 32, 34]\n", - "[36, 35]\n", - "[35, 36]\n", - "[38, 39, 37]\n", - "[37, 38]\n", - "[37, 39]\n", - "[41, 40]\n", - "[40, 41]\n", - "[43, 42]\n", - "[42, 43]\n", - "[46, 44]\n", - "[46, 45]\n", - "[44, 45, 46]\n", - "[48, 47]\n", - "[47, 48]\n", - "[50, 49]\n", - "[49, 51, 50]\n", - "[50, 51]\n", - "[53, 52]\n", - "[52, 53]\n", - "[55, 54]\n", - "[54, 55]\n", - "[58, 56]\n", - "[58, 57]\n", - "[56, 57, 58]\n", - "[60, 59]\n", - "[59, 60]\n", - "[62, 63, 61]\n", - "[61, 62]\n", - "[61, 63]\n", - "[65, 64]\n", - "[64, 65]\n", - "[67, 66]\n", - "[66, 67]\n", - "[69, 68]\n", - "[68, 69]\n", - "[76, 70]\n", - "[73, 77, 71]\n", - "[74, 72]\n", - "[71, 76, 73]\n", - "[75, 72, 74]\n", - "[74, 77, 75]\n", - "[70, 73, 76]\n", - "[75, 71, 77]\n", - "[79, 78]\n", - "[78, 80, 79]\n", - "[79, 80]\n", - "[82, 81]\n", - "[81, 82]\n", - "[90, 88, 83]\n", - "[86, 88, 84]\n", - "[89, 85]\n", - "[84, 87, 86]\n", - "[89, 86, 87]\n", - "[84, 83, 88]\n", - "[87, 85, 89]\n", - "[83, 90]\n", - "[93, 91]\n", - "[93, 92]\n", - "[91, 92, 93]\n", - "[95, 94]\n", - "[94, 95]\n" - ] - } - ], - "source": [ - "# What does the above do?\n", - "for i in range(hull.nsimplex):\n", - " print([*hull.neighbors[i][coplanar_simplex_array[i]], i])\n", - " # first, we get the coplanar simplices and heighbors for simplex i\n", - " # then, index in the coplanar neighbors and dump them into an array\n", - " # we then add i, as the indexed simplex is also coplanar" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(0.0017318337087434132,\n", - " 0.0020163124217437156,\n", - " 0.0035447480247632335,\n", - " 0.00394259414847542,\n", - " 0.006609278010620279,\n", - " 0.00853404435978633,\n", - " 0.009010762298194974,\n", - " 0.009899284064141756,\n", - " 0.010115961801518636,\n", - " 0.012404473627683599,\n", - " 0.012447484935732667,\n", - " 0.012974861536379279,\n", - " 0.013309849813057273,\n", - " 0.01387163413332293,\n", - " 0.014703931931117853,\n", - " 0.014808475350076455,\n", - " 0.018428864919364796,\n", - " 0.018672799765176995,\n", - " 0.019334288844548464,\n", - " 0.021070590262666555,\n", - " 0.02110502873856468,\n", - " 0.02129166678763994,\n", - " 0.021528721783741545,\n", - " 0.021807986622800035,\n", - " 0.022052899647635127,\n", - " 0.02208917208035044,\n", - " 0.023289882081194424,\n", - " 0.024778974093295214,\n", - " 0.024975308660231677,\n", - " 0.029590825098955165,\n", - " 0.031197292029556123,\n", - " 0.031204465611203602,\n", - " 0.033693294520900086,\n", - " 0.03415956062156722,\n", - " 0.035767986888116554,\n", - " 0.03622266558646581,\n", - " 0.036248777345704974,\n", - " 0.03717957413822137,\n", - " 0.03824200878882533,\n", - " 0.039367799521233926,\n", - " 0.041576934354527806,\n", - " 0.04162211069230026,\n", - " 0.04232261923436931,\n", - " 0.04279335787077321,\n", - " 0.04337995558972951,\n", - " 0.0436185855028739,\n", - " 0.04503056594013033,\n", - " 0.04554938340502823,\n", - " 0.046600675424599114,\n", - " 0.04682370581726858,\n", - " 0.04697496269889989,\n", - " 0.04728911363562571,\n", - " 0.04743570032957245,\n", - " 0.047668527682826256,\n", - " 0.04796450153608056,\n", - " 0.04879183687370359,\n", - " 0.04882164965671976,\n", - " 0.048922739933007664,\n", - " 0.0490309246596935,\n", - " 0.04955161262765606,\n", - " 0.05195998688897807,\n", - " 0.054858635042310144,\n", - " 0.06065540337249775,\n", - " 0.060741806710776336,\n", - " 0.061246862548058445,\n", - " 0.061290154875117686,\n", - " 0.06198979660056314,\n", - " 0.0626103771615164,\n", - " 0.06283930775259372,\n", - " 0.06373528093932801,\n", - " 0.06399943846015921,\n", - " 0.06434235343368755,\n", - " 0.06544235289055411,\n", - " 0.06564962046666056,\n", - " 0.06633136821678975,\n", - " 0.06791013926730971,\n", - " 0.06845367158053817,\n", - " 0.07018917307849848,\n", - " 0.07150486263149591,\n", - " 0.07206897155681857,\n", - " 0.07267489859277232,\n", - " 0.07353405291616566,\n", - " 0.07375557781356579,\n", - " 0.07451514637524581,\n", - " 0.0759550143069544,\n", - " 0.07626557252434252,\n", - " 0.07632692606374714,\n", - " 0.07633292307530737,\n", - " 0.0771118027618557,\n", - " 0.07779369669399872,\n", - " 0.07820960962677725,\n", - " 0.08087579048076732,\n", - " 0.08210092983828621,\n", - " 0.08278563864904265,\n", - " 0.08353085402349059,\n", - " 0.08392664291086527,\n", - " 0.08446481122864813,\n", - " 0.08516786760083628,\n", - " 0.08527409832409338,\n", - " 0.0876451526634382,\n", - " 0.08879153059212863,\n", - " 0.08921883809639986,\n", - " 0.08944287651863003,\n", - " 0.08947004614303489,\n", - " 0.09020475781777826,\n", - " 0.09034342490536307,\n", - " 0.0915350172031294,\n", - " 0.09206330228486603,\n", - " 0.09266669117946502,\n", - " 0.09371286431894066,\n", - " 0.09390675883687472,\n", - " 0.09510836747401663,\n", - " 0.09543611113027495,\n", - " 0.0983175947515661,\n", - " 0.09871433902648774,\n", - " 0.09904463198785052,\n", - " 0.09926123261504927,\n", - " 0.10130578043035487,\n", - " 0.1057062078358959,\n", - " 0.10615100452844062,\n", - " 0.10889038794077255,\n", - " 0.11225091494330519,\n", - " 0.11309801745105086,\n", - " 0.11310964190789663,\n", - " 0.11362824695101759,\n", - " 0.11393471309278458,\n", - " 0.11577716976981967,\n", - " 0.11648156223646,\n", - " 0.11699986906155835,\n", - " 0.11702932655484877,\n", - " 0.11839348496608193,\n", - " 0.11915755658348837,\n", - " 0.11970509237610383,\n", - " 0.12020292514308373,\n", - " 0.12069181897447778,\n", - " 0.12101114277921454,\n", - " 0.12157221623063852,\n", - " 0.12422387897670406,\n", - " 0.12607972596227968,\n", - " 0.12706345312738032,\n", - " 0.12979043820819802,\n", - " 0.13020966545749268,\n", - " 0.13195832814559594,\n", - " 0.13334042105977206,\n", - " 0.13422052468051426,\n", - " 0.1360702775875623,\n", - " 0.13625378876752436,\n", - " 0.13760457073620147,\n", - " 0.13852434814524017,\n", - " 0.13912283037126727,\n", - " 0.13997465745057103,\n", - " 0.1409364452747326,\n", - " 0.1431212811618362,\n", - " 0.14336179695489493,\n", - " 0.14384884267821363,\n", - " 0.1446173713796447,\n", - " 0.14507265384145018,\n", - " 0.14522838687132544,\n", - " 0.14625865075542455,\n", - " 0.14875412007511835,\n", - " 0.1490799293066345,\n", - " 0.1501836558746269,\n", - " 0.15024049696919317,\n", - " 0.15258750320143843,\n", - " 0.1543892384382347,\n", - " 0.15542287735408955,\n", - " 0.1562795798291411,\n", - " 0.15672090001256167,\n", - " 0.15835450845597965,\n", - " 0.15885558237045605,\n", - " 0.15916683197346893,\n", - " 0.16128592233735117,\n", - " 0.16176260574045143,\n", - " 0.1625689679776966,\n", - " 0.1640033302756848,\n", - " 0.16428224723518692,\n", - " 0.1646108946315311,\n", - " 0.16519659583027135,\n", - " 0.16686728385993066,\n", - " 0.17083591871787485,\n", - " 0.17086548334009344,\n", - " 0.17184192733550785,\n", - " 0.173196270811397,\n", - " 0.1738182562856646,\n", - " 0.17392939661484452,\n", - " 0.1743878644990864,\n", - " 0.1755944550799181,\n", - " 0.1755987311148095,\n", - " 0.1761517518744068,\n", - " 0.17978336530128602,\n", - " 0.18142197523537484,\n", - " 0.18193857903019073,\n", - " 0.18343907674996252,\n", - " 0.18405849214612302,\n", - " 0.18639217320458523,\n", - " 0.18778247530377268,\n", - " 0.18844195171864653,\n", - " 0.19146236325380506,\n", - " 0.19259512352276398,\n", - " 0.19382009930180055,\n", - " 0.19491557970592266,\n", - " 0.19573521178604103,\n", - " 0.19597759438959828,\n", - " 0.19658963498205273,\n", - " 0.197357491688532,\n", - " 0.19902552491177128,\n", - " 0.19984213182415256,\n", - " 0.20032201494662394,\n", - " 0.20036598382789594,\n", - " 0.20100597798491715,\n", - " 0.20313787976321207,\n", - " 0.20376749861696353,\n", - " 0.2050304503621968,\n", - " 0.20628518210328228,\n", - " 0.20706397928656173,\n", - " 0.2077117059419803,\n", - " 0.2080483398093258,\n", - " 0.20931228237497213,\n", - " 0.20933429094758804,\n", - " 0.21006582224508907,\n", - " 0.21139514482142086,\n", - " 0.21172846241190912,\n", - " 0.213041897864376,\n", - " 0.21393253802800694,\n", - " 0.21495061074075017,\n", - " 0.2150180436839999,\n", - " 0.2150227833214905,\n", - " 0.21527313954954486,\n", - " 0.21745549980914047,\n", - " 0.21798480762541628,\n", - " 0.22112824906097217,\n", - " 0.2213165098192843,\n", - " 0.2225069791823141,\n", - " 0.2234287962822562,\n", - " 0.2239602132532641,\n", - " 0.2241044689489926,\n", - " 0.22469892418389437,\n", - " 0.2250010370980503,\n", - " 0.22525997630394123,\n", - " 0.22613353689490412,\n", - " 0.22621289370202935,\n", - " 0.22649234356054937,\n", - " 0.22657887876359106,\n", - " 0.2289104829453915,\n", - " 0.2289258875474921,\n", - " 0.22962773095859146,\n", - " 0.22991441127585766,\n", - " 0.23061963345939152,\n", - " 0.2327835638543322,\n", - " 0.2335813431057503,\n", - " 0.23598738350126336,\n", - " 0.2377190638825466,\n", - " 0.23772857723276897,\n", - " 0.2409644542067777,\n", - " 0.24193443760187738,\n", - " 0.24266624066074172,\n", - " 0.24477530681145188,\n", - " 0.24492192767472287,\n", - " 0.24540730482958095,\n", - " 0.24581932517381044,\n", - " 0.24622853765853447,\n", - " 0.24629855513647103,\n", - " 0.24728703733301227,\n", - " 0.251937457408962,\n", - " 0.2522953265193757,\n", - " 0.2527938188424861,\n", - " 0.2539936200986932,\n", - " 0.25657153541353117,\n", - " 0.2623976312092874,\n", - " 0.26376559746492356,\n", - " 0.2655534869207212,\n", - " 0.26818740207454805,\n", - " 0.26880379561174716,\n", - " 0.2689638803390203,\n", - " 0.2694146795139064,\n", - " 0.2698980642610357,\n", - " 0.26991337645100466,\n", - " 0.2703954442128468,\n", - " 0.27042257295282235,\n", - " 0.2718438340112861,\n", - " 0.2725801894777097,\n", - " 0.2726460826494519,\n", - " 0.2727350425493835,\n", - " 0.2738700149958927,\n", - " 0.2744249698593041,\n", - " 0.27470516707142867,\n", - " 0.2748434762498124,\n", - " 0.2753480848521488,\n", - " 0.2771428616028684,\n", - " 0.2773304285760102,\n", - " 0.2774187380514108,\n", - " 0.2815008188319805,\n", - " 0.2818961156975073,\n", - " 0.2825031460832935,\n", - " 0.28339231391103514,\n", - " 0.28390681005429885,\n", - " 0.2840362643979768,\n", - " 0.2851504062186031,\n", - " 0.28519697987876336,\n", - " 0.2879404006568673,\n", - " 0.2882647145928189,\n", - " 0.2894115576441685,\n", - " 0.2897178421348965,\n", - " 0.29078381533111564,\n", - " 0.29269394716741903,\n", - " 0.29289878288861027,\n", - " 0.29296222593695065,\n", - " 0.29457197951092984,\n", - " 0.2950193987565687,\n", - " 0.2958771448234342,\n", - " 0.29613678410363287,\n", - " 0.29719252608419644,\n", - " 0.29792769035852107,\n", - " 0.29924804789123327,\n", - " 0.2996306403465836,\n", - " 0.3002353582383869,\n", - " 0.3011733796410101,\n", - " 0.3014253642025765,\n", - " 0.30148381428385473,\n", - " 0.30222409934718697,\n", - " 0.30302293725644946,\n", - " 0.303384152437963,\n", - " 0.30438044990745017,\n", - " 0.30446289895869627,\n", - " 0.3047713593394179,\n", - " 0.30533492324887923,\n", - " 0.30759341292963394,\n", - " 0.3079895891000084,\n", - " 0.31027622731015214,\n", - " 0.3106259936464205,\n", - " 0.31228365685858517,\n", - " 0.31373446618386713,\n", - " 0.3146581537887806,\n", - " 0.3165201156090204,\n", - " 0.3173050860943959,\n", - " 0.31736311969000963,\n", - " 0.3186545546394014,\n", - " 0.31920526839665697,\n", - " 0.31979874620684423,\n", - " 0.3201108772890495,\n", - " 0.3214729043953435,\n", - " 0.3215723538416375,\n", - " 0.32251629974443485,\n", - " 0.3230211269289174,\n", - " 0.3231937877282306,\n", - " 0.32320098025357535,\n", - " 0.32646242445471185,\n", - " 0.32888382887491807,\n", - " 0.32903430363868547,\n", - " 0.32957799937042587,\n", - " 0.33006006774592356,\n", - " 0.33069586535718964,\n", - " 0.3318803309471582,\n", - " 0.3319491238165069,\n", - " 0.33201952611476593,\n", - " 0.33227261556719356,\n", - " 0.3341717770194724,\n", - " 0.33502812146243033,\n", - " 0.3350865258380781,\n", - " 0.3351923785682117,\n", - " 0.3356458733463127,\n", - " 0.33693187026392657,\n", - " 0.3381201622838067,\n", - " 0.338893645248199,\n", - " 0.3397673433972799,\n", - " 0.3398019741194217,\n", - " 0.3409627794568184,\n", - " 0.34163480060181994,\n", - " 0.3420234464604043,\n", - " 0.3422086123136834,\n", - " 0.34260760982028204,\n", - " 0.34318070016126323,\n", - " 0.34318744412953817,\n", - " 0.3463931391807529,\n", - " 0.34734857457497803,\n", - " 0.3487257129724425,\n", - " 0.3487656028971219,\n", - " 0.3490224050510077,\n", - " 0.35090031858542203,\n", - " 0.3540231244224501,\n", - " 0.35612024486657834,\n", - " 0.359700436512684,\n", - " 0.36035349126449834,\n", - " 0.36096303665109,\n", - " 0.3609721396252491,\n", - " 0.3609965324844564,\n", - " 0.3642079730049166,\n", - " 0.3642766237177696,\n", - " 0.3655986476806531,\n", - " 0.36579335613388275,\n", - " 0.3670745855106351,\n", - " 0.36718658948699967,\n", - " 0.36793019445350506,\n", - " 0.3685813859580468,\n", - " 0.3686858452704782,\n", - " 0.3702056628147493,\n", - " 0.3721391447849598,\n", - " 0.37224877222959407,\n", - " 0.37488077609551884,\n", - " 0.37826153984308275,\n", - " 0.3784609830209543,\n", - " 0.3830038238658926,\n", - " 0.38354518035449725,\n", - " 0.3839381870692038,\n", - " 0.38559293427210484,\n", - " 0.3862788418417622,\n", - " 0.38702126387394575,\n", - " 0.3902805919669198,\n", - " 0.39168248459395727,\n", - " 0.39209796654894635,\n", - " 0.3924721502060944,\n", - " 0.39524397556637625,\n", - " 0.3955464509331692,\n", - " 0.3994984438160434,\n", - " 0.4015548540854227,\n", - " 0.40349399813249953,\n", - " 0.40398184570731355,\n", - " 0.4046058709572422,\n", - " 0.4061359794165864,\n", - " 0.40669253933291094,\n", - " 0.4069287803527428,\n", - " 0.4078699321218693,\n", - " 0.4096896667694705,\n", - " 0.41127642116593444,\n", - " 0.41132016736260224,\n", - " 0.4114466832337782,\n", - " 0.41297013392248016,\n", - " 0.4131852738513141,\n", - " 0.413955072315066,\n", - " 0.4151754325589744,\n", - " 0.41759656217509356,\n", - " 0.4178329112653013,\n", - " 0.41833503857831467,\n", - " 0.42132520458576384,\n", - " 0.421869363988447,\n", - " 0.42296202632284063,\n", - " 0.42397664238566934,\n", - " 0.4241386776293773,\n", - " 0.42463226505351526,\n", - " 0.4248473357532826,\n", - " 0.4248802234854424,\n", - " 0.4249406393174283,\n", - " 0.42656104379365,\n", - " 0.4288660479433638,\n", - " 0.42898888252375944,\n", - " 0.43035004663232024,\n", - " 0.4304925278927392,\n", - " 0.43085122262158115,\n", - " 0.4322949494317947,\n", - " 0.4325987560745109,\n", - " 0.4331322113250635,\n", - " 0.43419316548053377,\n", - " 0.434494285624762,\n", - " 0.4345597477720148,\n", - " 0.43594574707406575,\n", - " 0.43604313993678046,\n", - " 0.4383326685339004,\n", - " 0.43903459696089475,\n", - " 0.44319275309272244,\n", - " 0.44328434490982827,\n", - " 0.4433997577790556,\n", - " 0.44443018472155804,\n", - " 0.4474687964494354,\n", - " 0.44756620457824714,\n", - " 0.4475684687188618,\n", - " 0.44919516734939025,\n", - " 0.4494438649292968,\n", - " 0.45066213114841147,\n", - " 0.45112983442386645,\n", - " 0.45126854199966215,\n", - " 0.45151470079875644,\n", - " 0.45298141541676595,\n", - " 0.4531507980203662,\n", - " 0.4534059616627919,\n", - " 0.454345304507597,\n", - " 0.45508936278476475,\n", - " 0.45794978856445856,\n", - " 0.4639728982841209,\n", - " 0.46418369804557347,\n", - " 0.46573468314645805,\n", - " 0.4657962536634507,\n", - " 0.46585140271307846,\n", - " 0.4662896871973118,\n", - " 0.4670637930611341,\n", - " 0.46866547778109247,\n", - " 0.46872946303098184,\n", - " 0.4709556752564361,\n", - " 0.47106794769399196,\n", - " 0.4712283259893091,\n", - " 0.47182548591299167,\n", - " 0.47240831847715337,\n", - " 0.4724386810280179,\n", - " 0.4731713972698349,\n", - " 0.4741469415451376,\n", - " 0.47426924858085273,\n", - " 0.4773623412757162,\n", - " 0.47835965664062563,\n", - " 0.47846285307187686,\n", - " 0.48147785234031626,\n", - " 0.4815490058046151,\n", - " 0.4830370929359752,\n", - " 0.48355340548086534,\n", - " 0.48436368114394635,\n", - " 0.49234663530282463,\n", - " 0.4925622194838086,\n", - " 0.4928607922583056,\n", - " 0.4929278548345497,\n", - " 0.49371279562088133,\n", - " 0.4981914629363716,\n", - " 0.4989523350245948,\n", - " 0.4995127137897363,\n", - " 0.5018234540925802,\n", - " 0.5034805568076962,\n", - " 0.5052257203378888,\n", - " 0.5056625181717007,\n", - " 0.5131861021579847,\n", - " 0.5133489973148581,\n", - " 0.5141676647104568,\n", - " 0.5146366310836915,\n", - " 0.5161843673900794,\n", - " 0.516212051421584,\n", - " 0.5164873405306739,\n", - " 0.5171987285389105,\n", - " 0.5201150217361008,\n", - " 0.5203009992352942,\n", - " 0.521626491911722,\n", - " 0.5252579158144539,\n", - " 0.5270337766705395,\n", - " 0.5275196427727605,\n", - " 0.5277186587977098,\n", - " 0.5279571674405147,\n", - " 0.5288214406476862,\n", - " 0.5290168371188084,\n", - " 0.5298635635399771,\n", - " 0.531365343632835,\n", - " 0.5323851790497115,\n", - " 0.532411863384256,\n", - " 0.5324916825828468,\n", - " 0.5343531301272328,\n", - " 0.5345886379608727,\n", - " 0.5358827519792342,\n", - " 0.5374751794100243,\n", - " 0.538929935762309,\n", - " 0.5395720104874221,\n", - " 0.5402912155948718,\n", - " 0.541206390510491,\n", - " 0.542214039234039,\n", - " 0.5440376384789697,\n", - " 0.5441603134096432,\n", - " 0.5448353726659675,\n", - " 0.5452091923328296,\n", - " 0.5482626451053983,\n", - " 0.5508772740236291,\n", - " 0.5521464600195365,\n", - " 0.5527011604949804,\n", - " 0.555433113218722,\n", - " 0.5569464544657843,\n", - " 0.5578424556881069,\n", - " 0.5605800772588564,\n", - " 0.5610416270651907,\n", - " 0.5619463459071875,\n", - " 0.5663937462133342,\n", - " 0.5665142295823579,\n", - " 0.5668784872007242,\n", - " 0.5670973859772207,\n", - " 0.567220718116592,\n", - " 0.5674641923935309,\n", - " 0.5699560902879853,\n", - " 0.5704526823725057,\n", - " 0.5713294809115436,\n", - " 0.5717613601107112,\n", - " 0.5721748221970422,\n", - " 0.5731214874107099,\n", - " 0.5764031634707573,\n", - " 0.577083013649053,\n", - " 0.5788954769505547,\n", - " 0.5808882640628719,\n", - " 0.5823788714490341,\n", - " 0.58393532424283,\n", - " 0.5853069931491429,\n", - " 0.5861719541405012,\n", - " 0.586260604455198,\n", - " 0.5864527560454786,\n", - " 0.5867325920361051,\n", - " 0.5878747495410683,\n", - " 0.588425105013239,\n", - " 0.5888403168462132,\n", - " 0.5898362294132522,\n", - " 0.5908943990428708,\n", - " 0.5911327583700652,\n", - " 0.5916914766189256,\n", - " 0.5922160863958118,\n", - " 0.5925162403192972,\n", - " 0.5933223119867499,\n", - " 0.5958782210376558,\n", - " 0.5963509734387221,\n", - " 0.5965423148438698,\n", - " 0.5966285272688691,\n", - " 0.5972531692894347,\n", - " 0.5987510402508917,\n", - " 0.5989050517636052,\n", - " 0.5989854074418631,\n", - " 0.5991875332924592,\n", - " 0.5995336797044957,\n", - " 0.5995841137695194,\n", - " 0.6022996816527698,\n", - " 0.6051694078552863,\n", - " 0.605495378640489,\n", - " 0.6084111389746149,\n", - " 0.609023052460295,\n", - " 0.6095300188777706,\n", - " 0.6115421269425185,\n", - " 0.6146000989541851,\n", - " 0.6148799386374123,\n", - " 0.616022711068745,\n", - " 0.6161666817249483,\n", - " 0.616446775089544,\n", - " 0.6180945057355842,\n", - " 0.6189093819359845,\n", - " 0.6189989124062417,\n", - " 0.6207284447223983,\n", - " 0.6219453597269589,\n", - " 0.6219579976281282,\n", - " 0.6244091274690841,\n", - " 0.6248605451324224,\n", - " 0.6248684467182025,\n", - " 0.6249810574488789,\n", - " 0.625003572104589,\n", - " 0.6272286274129104,\n", - " 0.627443382512834,\n", - " 0.6282820084195593,\n", - " 0.6317063258167573,\n", - " 0.6319261036389178,\n", - " 0.6326554113139327,\n", - " 0.632674582482594,\n", - " 0.6330225162794573,\n", - " 0.6337186581954308,\n", - " 0.6351846082245701,\n", - " 0.6377762229103271,\n", - " 0.6380562572439947,\n", - " 0.6384409954616111,\n", - " 0.6388699683391781,\n", - " 0.6458196024949021,\n", - " 0.6462234038560144,\n", - " 0.6484371929052909,\n", - " 0.6496183325677178,\n", - " 0.6498901159391679,\n", - " 0.6506615976936588,\n", - " 0.6518082779180436,\n", - " 0.6526195846036325,\n", - " 0.6526227077362098,\n", - " 0.6545777790175734,\n", - " 0.6553645898107151,\n", - " 0.6563994620621694,\n", - " 0.656663064995839,\n", - " 0.6586776652485704,\n", - " 0.6589838515579817,\n", - " 0.6590658482725277,\n", - " 0.6591832701820634,\n", - " 0.6608640533489334,\n", - " 0.6610952202877582,\n", - " 0.6618513422415427,\n", - " 0.6629358490620881,\n", - " 0.6634054186095141,\n", - " 0.6645463735085086,\n", - " 0.6654166572652109,\n", - " 0.6686987914714826,\n", - " 0.6693473962485036,\n", - " 0.6706020597659939,\n", - " 0.6709705100800764,\n", - " 0.6717084778402611,\n", - " 0.6724828486148766,\n", - " 0.6724906593674672,\n", - " 0.675741808662794,\n", - " 0.6766053415994165,\n", - " 0.6775276810953021,\n", - " 0.6780099452375963,\n", - " 0.6782226837586124,\n", - " 0.6788456264260829,\n", - " 0.6816996275873363,\n", - " 0.682168054486859,\n", - " 0.6837431492066041,\n", - " 0.683823608645679,\n", - " 0.6839177965321879,\n", - " 0.6840725936963146,\n", - " 0.6858748031215338,\n", - " 0.6865288172641009,\n", - " 0.687001739158991,\n", - " 0.687094229155063,\n", - " 0.6885366677976457,\n", - " 0.6886244767370505,\n", - " 0.68946654578105,\n", - " 0.6895433320583686,\n", - " 0.6921627538821671,\n", - " 0.6932977290087255,\n", - " 0.6933668659389108,\n", - " 0.6938352914030828,\n", - " 0.6939640414315045,\n", - " 0.6948709533875934,\n", - " 0.6951758485451573,\n", - " 0.6968917640014154,\n", - " 0.6973800161267699,\n", - " 0.6989696382432962,\n", - " 0.6997026290248549,\n", - " 0.7001626522350871,\n", - " 0.7008677802880492,\n", - " 0.7048688057441729,\n", - " 0.7049890058354797,\n", - " 0.7050195332760512,\n", - " 0.7056350806101372,\n", - " 0.7066024370925158,\n", - " 0.706857889784513,\n", - " 0.7079278333522699,\n", - " 0.7080900818469954,\n", - " 0.7091907266516967,\n", - " 0.7093730725517341,\n", - " 0.7105530402465497,\n", - " 0.7105860603115497,\n", - " 0.7108427169975953,\n", - " 0.710958941816638,\n", - " 0.7119183508079874,\n", - " 0.712473922240254,\n", - " 0.7130857128316854,\n", - " 0.7143735687307076,\n", - " 0.7156535693731784,\n", - " 0.7167044758712661,\n", - " 0.7171971976174101,\n", - " 0.7173591166112153,\n", - " 0.7180122963467511,\n", - " 0.7182547768985454,\n", - " 0.7186503638143066,\n", - " 0.7199706143473819,\n", - " 0.7233082375375243,\n", - " 0.7239011487153489,\n", - " 0.7242404448483999,\n", - " 0.7244070670283783,\n", - " 0.7270800957754413,\n", - " 0.7273828972992467,\n", - " 0.7278698916103851,\n", - " 0.7279001489325277,\n", - " 0.7286691379731557,\n", - " 0.7290190167242739,\n", - " 0.7309908810487125,\n", - " 0.7326820039199539,\n", - " 0.7329090517759312,\n", - " 0.739940921055405,\n", - " 0.7434099431208323,\n", - " 0.7466478217794823,\n", - " 0.7476975521388607,\n", - " 0.7482384371917541,\n", - " 0.7489193206002432,\n", - " 0.7510700727730789,\n", - " 0.751168415348455,\n", - " 0.7513344525466453,\n", - " 0.7530838441205541,\n", - " 0.7561479450556158,\n", - " 0.7589382023303526,\n", - " 0.7591723511860086,\n", - " 0.7596873869687933,\n", - " 0.7604656847419421,\n", - " 0.7613223536829636,\n", - " 0.7651786557756509,\n", - " 0.765289337995715,\n", - " 0.7672956742772928,\n", - " 0.7673533636761452,\n", - " 0.767860014375011,\n", - " 0.7679593417231557,\n", - " 0.7686754737065615,\n", - " 0.7723743769694924,\n", - " 0.7738576335626756,\n", - " 0.7752593808743791,\n", - " 0.7762420437397328,\n", - " 0.7785389296302156,\n", - " 0.7786513118789642,\n", - " 0.7824737739069422,\n", - " 0.7828458158695082,\n", - " 0.7832963409396819,\n", - " 0.7841102167276137,\n", - " 0.7841743140878821,\n", - " 0.786346530470324,\n", - " 0.786745257198348,\n", - " 0.7876078132295328,\n", - " 0.7877903861892426,\n", - " 0.7880663743956936,\n", - " 0.7889421450421804,\n", - " 0.790079152236495,\n", - " 0.7901899265548301,\n", - " 0.7911841751640999,\n", - " 0.7914794033211856,\n", - " 0.7918014732787095,\n", - " 0.7920188842930745,\n", - " 0.7931485253636771,\n", - " 0.7934685062336554,\n", - " 0.7937162787969917,\n", - " 0.7948047537275847,\n", - " 0.7957713438937825,\n", - " 0.7970347955467577,\n", - " 0.7985062066147556,\n", - " 0.798559640661481,\n", - " 0.7988328923937563,\n", - " 0.7999832070711188,\n", - " 0.802479135164161,\n", - " 0.8037822018412717,\n", - " 0.8041398874952114,\n", - " 0.8059849661420606,\n", - " 0.8063320314723228,\n", - " 0.8068094143803295,\n", - " 0.8070624779145962,\n", - " 0.8074867779441165,\n", - " 0.8096171744387134,\n", - " 0.8110306680244982,\n", - " 0.8114369216867047,\n", - " 0.8117419041250775,\n", - " 0.811752076440983,\n", - " 0.8128343061976084,\n", - " 0.8128608729237652,\n", - " 0.8138963074526002,\n", - " 0.8141290792413814,\n", - " 0.8141830507072457,\n", - " 0.814433174940653,\n", - " 0.8148250267316691,\n", - " 0.8149382866951798,\n", - " 0.8152603955746031,\n", - " 0.8152945471875449,\n", - " 0.8158350971248133,\n", - " 0.8168592246024563,\n", - " 0.8186602155454856,\n", - " 0.8192136320759589,\n", - " 0.8193948886315071,\n", - " 0.8196723394397735,\n", - " 0.819683388474866,\n", - " 0.8223955878513909,\n", - " 0.8224427045961231,\n", - " 0.8227417945462477,\n", - " 0.8232460099219688,\n", - " 0.8239276617347282,\n", - " 0.825427184811981,\n", - " 0.8257244431505741,\n", - " 0.8267201640239498,\n", - " 0.8269931007749239,\n", - " 0.8304237087661536,\n", - " 0.8315863178336268,\n", - " 0.8319525210480346,\n", - " 0.8326772466260077,\n", - " 0.8351142780964415,\n", - " 0.83825390661194,\n", - " 0.8397877239750948,\n", - " 0.840744751995548,\n", - " 0.8427488927306527,\n", - " 0.8445167324082414,\n", - " 0.845185141478426,\n", - " 0.8466896139688487,\n", - " 0.8483482985559312,\n", - " 0.8485679151458629,\n", - " 0.8532066212612518,\n", - " 0.8562437882360899,\n", - " 0.8562935893759662,\n", - " 0.8575871646118248,\n", - " 0.857644814415513,\n", - " 0.8582119597276281,\n", - " 0.858694708708258,\n", - " 0.8589709717063778,\n", - " 0.8605621290195696,\n", - " 0.8607744346316882,\n", - " 0.8629913359265646,\n", - " 0.8648230556139784,\n", - " 0.8663882374848827,\n", - " 0.8695776723054177,\n", - " 0.8697369520479535,\n", - " 0.8704051213699109,\n", - " 0.8708881163434653,\n", - " 0.8709551364946905,\n", - " 0.8709745602687864,\n", - " 0.8715834721521957,\n", - " 0.8724111417020128,\n", - " 0.8729489870607494,\n", - " 0.8736889646732721,\n", - " 0.8738783734571005,\n", - " 0.8741320524972753,\n", - " 0.874578760833131,\n", - " 0.87578111908946,\n", - " 0.8765445355294582,\n", - " 0.8802360676510553,\n", - " 0.88176308798502,\n", - " 0.8858626453809116,\n", - " 0.8865089208554938,\n", - " 0.8876483949256709,\n", - " 0.8891008236195337,\n", - " 0.8893800444775953,\n", - " 0.8898280102302075,\n", - " 0.891661821635953,\n", - " 0.8924892027704591,\n", - " 0.8927759074983884,\n", - " 0.8930924571015301,\n", - " 0.8938027188961681,\n", - " 0.8947272722888562,\n", - " 0.896297864212215,\n", - " 0.8975995479971396,\n", - " 0.898125621411599,\n", - " 0.8988463798584126,\n", - " 0.8997407390358053,\n", - " 0.9022287191147113,\n", - " 0.902446504531618,\n", - " 0.9028544988178231,\n", - " 0.9044669790870821,\n", - " 0.9045300894860915,\n", - " 0.9070290539301853,\n", - " 0.9072707973775902,\n", - " 0.9109974211348453,\n", - " 0.9112430301939605,\n", - " 0.9138488445665155,\n", - " 0.9139844449282692,\n", - " 0.9140495145428476,\n", - " 0.9144879361253944,\n", - " 0.9149932664735345,\n", - " 0.9151647097255314,\n", - " 0.9222250279766461,\n", - " 0.9252043283700748,\n", - " 0.9260858477143477,\n", - " 0.929099643236498,\n", - " 0.9293706132507173,\n", - " 0.9296305058548253,\n", - " 0.930718293864877,\n", - " 0.9320243562539431,\n", - " 0.9335823058617141,\n", - " 0.93410476975939,\n", - " 0.9346552334282847,\n", - " 0.9385919819697336,\n", - " 0.9386023489291914,\n", - " 0.9390203563997565,\n", - " 0.9408441239775633,\n", - " 0.9412564300291297,\n", - " 0.9413386657486532,\n", - " 0.9433383329974413,\n", - " 0.9435829852880645,\n", - " 0.9439426859956916,\n", - " 0.9440418846949984,\n", - " 0.944457107147504,\n", - " 0.9445282488366455,\n", - " 0.9446485879108835,\n", - " 0.9447041708238896,\n", - " 0.9507075442088528,\n", - " 0.9516549853366104,\n", - " 0.9522816431638531,\n", - " 0.9523476094574852,\n", - " 0.9524971125587479,\n", - " 0.9526138269599105,\n", - " 0.9526523460383233,\n", - " 0.9533167084354774,\n", - " 0.9537704376521136,\n", - " 0.9538326116275573,\n", - " 0.9546455174766972,\n", - " 0.9547422574465706,\n", - " 0.9553611771028514,\n", - " 0.9555710223311377,\n", - " 0.9564267083365693,\n", - " 0.9571480093742284,\n", - " 0.9574754568551648,\n", - " 0.9576261107170713,\n", - " 0.958603301986054,\n", - " 0.9590048438605638,\n", - " 0.9607915929402436,\n", - " 0.9614895398270192,\n", - " 0.963946747653707,\n", - " 0.9640146312956573,\n", - " 0.9646032337594956,\n", - " 0.9646588086918275,\n", - " 0.9652517383271517,\n", - " 0.9655899982377839,\n", - " 0.9661455661035445,\n", - " 0.9668379624888932,\n", - " 0.9670857249634918,\n", - " 0.9695274880563277,\n", - " 0.9731380024312987,\n", - " 0.9750278875563737,\n", - " 0.9769125114348521,\n", - " 0.9773525933444962,\n", - " 0.9775963757277827,\n", - " 0.9800896104562643,\n", - " 0.9803236936910357,\n", - " 0.9811079278434411,\n", - " 0.9814569641014795,\n", - " 0.9832020498138141,\n", - " 0.9832994030971569,\n", - " 0.9839039601268755,\n", - " 0.9859639959104932,\n", - " 0.9878285351775311,\n", - " 0.9911735805216166,\n", - " 0.9919861584535846,\n", - " 0.9930350454798805,\n", - " 0.9932140669095247,\n", - " 0.9936069085091837,\n", - " 0.9942273196897291,\n", - " 0.994539884025336,\n", - " 0.9946208471066382,\n", - " 0.9953063953349244,\n", - " 0.9971302633362986,\n", - " 0.9974618282678035,\n", - " 0.9975375022645281,\n", - " 0.9982090026363253)" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "func = lambda x: tuple(sorted(x))\n", - "arr = np.random.rand(1000)\n", - "func(arr)" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "210 µs ± 32.5 µs per loop (mean ± std. dev. of 10 runs, 1,000 loops each)\n" - ] - } - ], - "source": [ - "%timeit -r 10 -n 1000 [[*hull.neighbors[i][coplanar_simplex_array[i]], i] for i in range(hull.nsimplex)]" - ] - }, - { - "cell_type": "code", - "execution_count": 180, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[{0},\n", - " {1},\n", - " {2},\n", - " {3},\n", - " {4},\n", - " {5},\n", - " {6},\n", - " {7},\n", - " {8},\n", - " {9},\n", - " {10, 11},\n", - " {10, 11},\n", - " {12, 13},\n", - " {12, 13},\n", - " {14, 15},\n", - " {14, 15, 16},\n", - " {15, 16},\n", - " {17, 18},\n", - " {17, 18},\n", - " {19, 20},\n", - " {19, 20},\n", - " {21, 22},\n", - " {21, 22},\n", - " {23, 24},\n", - " {23, 24},\n", - " {25, 26},\n", - " {25, 26},\n", - " {27, 29},\n", - " {28, 29},\n", - " {27, 28, 29},\n", - " {30, 31},\n", - " {30, 31},\n", - " {32, 34},\n", - " {33, 34},\n", - " {32, 33, 34},\n", - " {35, 36},\n", - " {35, 36},\n", - " {37, 38, 39},\n", - " {37, 38},\n", - " {37, 39},\n", - " {40, 41},\n", - " {40, 41},\n", - " {42, 43},\n", - " {42, 43},\n", - " {44, 46},\n", - " {45, 46},\n", - " {44, 45, 46},\n", - " {47, 48},\n", - " {47, 48},\n", - " {49, 50},\n", - " {49, 50, 51},\n", - " {50, 51},\n", - " {52, 53},\n", - " {52, 53},\n", - " {54, 55},\n", - " {54, 55},\n", - " {56, 58},\n", - " {57, 58},\n", - " {56, 57, 58},\n", - " {59, 60},\n", - " {59, 60},\n", - " {61, 62, 63},\n", - " {61, 62},\n", - " {61, 63},\n", - " {64, 65},\n", - " {64, 65},\n", - " {66, 67},\n", - " {66, 67},\n", - " {68, 69},\n", - " {68, 69},\n", - " {70, 76},\n", - " {71, 73, 77},\n", - " {72, 74},\n", - " {71, 73, 76},\n", - " {72, 74, 75},\n", - " {74, 75, 77},\n", - " {70, 73, 76},\n", - " {71, 75, 77},\n", - " {78, 79},\n", - " {78, 79, 80},\n", - " {79, 80},\n", - " {81, 82},\n", - " {81, 82},\n", - " {83, 88, 90},\n", - " {84, 86, 88},\n", - " {85, 89},\n", - " {84, 86, 87},\n", - " {86, 87, 89},\n", - " {83, 84, 88},\n", - " {85, 87, 89},\n", - " {83, 90},\n", - " {91, 93},\n", - " {92, 93},\n", - " {91, 92, 93},\n", - " {94, 95},\n", - " {94, 95}]" - ] - }, - "execution_count": 180, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# There can be a variable number of neighbors! If one triangle has multiple coplanar simplices,\n", - "# an array indexing will not work. A list comprehension is probably fine here\n", - "# Take the coplanar neighbors for each simplex and add the current simplex (see above)\n", - "coplanar_simplices = [\n", - " {*hull.neighbors[i][coplanar_simplex_array[i]], i} for i in range(hull.nsimplex)\n", - "]\n", - "coplanar_simplices" - ] - }, - { - "cell_type": "code", - "execution_count": 185, - "metadata": {}, - "outputs": [], - "source": [ - "{0, 1, 2}.issuperset({1, 2})\n", - "# for test_set in coplanar_simplices:\n", - "# What if we cech subset and superset?\n", - "for test_set in coplanar_simplices:\n", - " for other_set in coplanar_simplices:\n", - " pass #\n", - " # Or can we do j>i?" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 177, - "metadata": {}, - "outputs": [], - "source": [ - "# coplanar_simplices[20]#coplanar_simplices[21]\n", - "def is_subset(s, sets_list):\n", - " # return any(s.issubset(other_set) for other_set in sets_list if s != other_set)\n", - " return any(s.issubset(other_set) for other_set in sets_list if s != other_set)" - ] - }, - { - "cell_type": "code", - "execution_count": 179, - "metadata": {}, - "outputs": [], - "source": [ - "# %%timeit -r 10 -n 1000\n", - "# %timeit -r 10 -n 1000 coplanar_simplices[20].issubset(coplanar_simplices[21])\n", - "filtered_sets = [s for s in coplanar_simplices if not is_subset(s, coplanar_simplices)]\n", - "# filtered_sets" - ] - }, - { - "cell_type": "code", - "execution_count": 45, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "40.3 µs ± 102 ns per loop (mean ± std. dev. of 10 runs, 10,000 loops each)\n" - ] - } - ], - "source": [ - "%timeit -r 10 -n 10000 poly._find_coplanar_simplices()" - ] - }, - { - "cell_type": "code", - "execution_count": 100, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 100, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "is_subset({0, 1}, [{0}, {0, 1}, {0, 1, 2}])" - ] - }, - { - "cell_type": "code", - "execution_count": 108, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 108, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "{0, 1}.issubset({0, 1})" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 109, - "metadata": {}, - "outputs": [ - { - "ename": "IndexError", - "evalue": "only integers, slices (`:`), ellipsis (`...`), numpy.newaxis (`None`) and integer or boolean arrays are valid indices", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mIndexError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m/Users/jenbradley/github/coxeter/test.ipynb Cell 23\u001b[0m line \u001b[0;36m2\n\u001b[1;32m 1\u001b[0m i \u001b[39m=\u001b[39m \u001b[39m0\u001b[39m\n\u001b[0;32m----> 2\u001b[0m [\u001b[39m*\u001b[39mhull\u001b[39m.\u001b[39;49mneighbors[i][coplanar_simplices[i]], i]\u001b[39m.\u001b[39msort()\n\u001b[1;32m 3\u001b[0m \u001b[39msorted\u001b[39m([\u001b[39m*\u001b[39mhull\u001b[39m.\u001b[39mneighbors[i][coplanar_simplices[i]], i])\n", - "\u001b[0;31mIndexError\u001b[0m: only integers, slices (`:`), ellipsis (`...`), numpy.newaxis (`None`) and integer or boolean arrays are valid indices" - ] - } - ], - "source": [ - "i = 0\n", - "[*hull.neighbors[i][coplanar_simplices[i]], i].sort()\n", - "sorted([*hull.neighbors[i][coplanar_simplices[i]], i])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,\n", - " 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,\n", - " 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,\n", - " 51, 52, 53, 54, 55]),\n", - " array([[False, False, False, False, False, False, False, False, False,\n", - " False, False, False, False, False, False, False, False, False,\n", - " False, False, False, False, False, False, False, False, False,\n", - " False, False, False, False, False, False, False, False, False,\n", - " False, False, False, False, False, False, False, False, False,\n", - " False, False, False, False, False, False, False, False, False,\n", - " False, False],\n", - " [False, False, False, False, False, False, False, False, False,\n", - " False, False, False, False, False, False, False, False, False,\n", - " False, False, False, True, True, False, True, True, True,\n", - " True, False, False, True, True, True, False, False, True,\n", - " False, True, True, False, True, True, False, True, False,\n", - " True, True, True, False, True, True, False, True, True,\n", - " False, True],\n", - " [False, False, False, False, False, False, False, False, False,\n", - " False, False, False, False, False, False, False, False, False,\n", - " False, False, True, True, False, True, True, False, True,\n", - " False, True, True, True, False, True, True, True, False,\n", - " True, True, True, True, False, True, True, False, True,\n", - " True, False, True, True, False, False, True, True, False,\n", - " True, True]]))" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "np.arange(hull.nsimplex), coplanar_simplices.T" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[24, 54, 45],\n", - " [22, 30, 37],\n", - " [41, 36, 29],\n", - " [20, 39, 27],\n", - " [21, 31, 26],\n", - " [22, 35, 47],\n", - " [23, 40, 46],\n", - " [44, 32, 55],\n", - " [50, 28, 25],\n", - " [38, 23, 27],\n", - " [42, 51, 29],\n", - " [28, 52, 31],\n", - " [43, 36, 34],\n", - " [48, 33, 35],\n", - " [20, 39, 49],\n", - " [34, 43, 53],\n", - " [33, 48, 44],\n", - " [40, 46, 49],\n", - " [25, 54, 50],\n", - " [42, 51, 53],\n", - " [ 3, 14, 21],\n", - " [ 4, 22, 20],\n", - " [ 1, 21, 5],\n", - " [ 6, 9, 24],\n", - " [ 0, 25, 23],\n", - " [18, 24, 8],\n", - " [ 4, 27, 28],\n", - " [ 3, 26, 9],\n", - " [11, 8, 26],\n", - " [ 2, 10, 30],\n", - " [ 1, 31, 29],\n", - " [ 4, 30, 11],\n", - " [ 7, 33, 34],\n", - " [16, 13, 32],\n", - " [15, 12, 32],\n", - " [ 5, 37, 13],\n", - " [ 2, 12, 37],\n", - " [ 1, 35, 36],\n", - " [ 9, 40, 39],\n", - " [ 3, 14, 38],\n", - " [ 6, 38, 17],\n", - " [ 2, 43, 42],\n", - " [10, 19, 41],\n", - " [12, 41, 15],\n", - " [ 7, 16, 45],\n", - " [ 0, 46, 44],\n", - " [ 6, 45, 17],\n", - " [ 5, 49, 48],\n", - " [13, 16, 47],\n", - " [14, 47, 17],\n", - " [ 8, 52, 18],\n", - " [10, 19, 52],\n", - " [11, 50, 51],\n", - " [15, 55, 19],\n", - " [ 0, 18, 55],\n", - " [ 7, 54, 53]], dtype=int32)" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "hull.neighbors" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 59, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[(-0.0, -0.0, -1.0),\n", - " (-0.0, -0.0, -1.0),\n", - " (0.0, -1.0, 0.0),\n", - " (0.0, -1.0, 0.0),\n", - " (1.0, -0.0, -0.0),\n", - " (1.0, -0.0, -0.0),\n", - " (-1.0, -0.0, -0.0),\n", - " (-1.0, -0.0, -0.0),\n", - " (0.0, 1.0, -0.0),\n", - " (0.0, 1.0, -0.0),\n", - " (-0.0, -0.0, 1.0),\n", - " (-0.0, -0.0, 1.0)]" - ] - }, - "execution_count": 59, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# This was close - but I'm thinking about this the wrong way. All we need is equations\n", - "normals = hull.equations[:, :3]\n", - "normals\n", - "\n", - "[tuple(normal) for normal in normals]" - ] - }, - { - "cell_type": "code", - "execution_count": 66, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[[ True True False False False False False False False False False False]\n", - " [ True True False False False False False False False False False False]\n", - " [False False True True False False False False False False False False]\n", - " [False False True True False False False False False False False False]\n", - " [False False False False True True False False False False False False]\n", - " [False False False False True True False False False False False False]\n", - " [False False False False False False True True False False False False]\n", - " [False False False False False False True True False False False False]\n", - " [False False False False False False False False True True False False]\n", - " [False False False False False False False False True True False False]\n", - " [False False False False False False False False False False True True]\n", - " [False False False False False False False False False False True True]]\n" - ] - } - ], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [], - "source": [ - "mbri.get_face_area().__len__(), len(mbri.faces)\n", - "face_areas = np.array(mbri.get_face_area())" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "2.7821089295582024" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "sum(face_areas)" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "5.1295458308488735" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "mbri.surface_area" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "62" - ] - }, - "execution_count": 31, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "mbri._coplanar_simplices.__len__()" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "42" - ] - }, - "execution_count": 32, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "mbri.faces.__len__()" - ] - }, - { - "cell_type": "code", - "execution_count": 58, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,\n", - " 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,\n", - " 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,\n", - " 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67,\n", - " 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84,\n", - " 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95], dtype=int32)" - ] - }, - "execution_count": 58, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "np.unique([item for row in mbri._coplanar_simplices for item in row])\n", - "# So each item in the coplanar simplices is unique" - ] - }, - { - "cell_type": "code", - "execution_count": 71, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([array([0], dtype=int32), array([1], dtype=int32),\n", - " array([2], dtype=int32), array([3], dtype=int32),\n", - " array([4], dtype=int32), array([5], dtype=int32),\n", - " array([6], dtype=int32), array([7], dtype=int32),\n", - " array([8], dtype=int32), array([9], dtype=int32),\n", - " array([10, 11], dtype=int32), array([12], dtype=int32),\n", - " array([13], dtype=int32), array([14], dtype=int32),\n", - " array([16, 15], dtype=int32), array([17, 18], dtype=int32),\n", - " array([19], dtype=int32), array([20], dtype=int32),\n", - " array([21], dtype=int32), array([22], dtype=int32)], dtype=object)" - ] - }, - "execution_count": 71, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cs = np.array(mbri._coplanar_simplices, dtype=object)\n", - "cs[0:20]" - ] - }, - { - "cell_type": "code", - "execution_count": 73, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[array([48, 40, 38], dtype=int32),\n", - " array([24, 14, 22], dtype=int32),\n", - " array([25, 23, 15], dtype=int32),\n", - " array([ 2, 7, 10], dtype=int32),\n", - " array([8, 6, 0], dtype=int32),\n", - " array([41, 49, 39], dtype=int32),\n", - " array([33, 36, 45], dtype=int32),\n", - " array([17, 29, 20], dtype=int32),\n", - " array([32, 43, 34], dtype=int32),\n", - " array([16, 18, 27], dtype=int32),\n", - " array([25, 15, 30, 21], dtype=int32),\n", - " array([48, 45, 36, 40], dtype=int32),\n", - " array([ 1, 3, 30, 15, 28], dtype=int32),\n", - " array([24, 22, 23, 25], dtype=int32),\n", - " array([19, 28, 15, 23], dtype=int32),\n", - " array([48, 38, 34, 43], dtype=int32),\n", - " array([ 3, 1, 9, 11], dtype=int32),\n", - " array([18, 22, 14, 27], dtype=int32),\n", - " array([18, 26, 19, 23, 22], dtype=int32),\n", - " array([17, 7, 2, 29], dtype=int32)]" - ] - }, - "execution_count": 73, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "mbri.faces[0:20]" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(62, 42)" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "len(mbri._coplanar_simplices), len(mbri.faces)" - ] - }, - { - "cell_type": "code", - "execution_count": 71, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[array([0], dtype=int32),\n", - " array([1], dtype=int32),\n", - " array([2], dtype=int32),\n", - " array([3], dtype=int32),\n", - " array([4], dtype=int32),\n", - " array([5], dtype=int32),\n", - " array([6], dtype=int32),\n", - " array([7], dtype=int32),\n", - " array([8], dtype=int32),\n", - " array([9], dtype=int32),\n", - " array([10, 11], dtype=int32),\n", - " array([12], dtype=int32),\n", - " array([13], dtype=int32),\n", - " array([14], dtype=int32),\n", - " array([16, 15], dtype=int32),\n", - " array([17, 18], dtype=int32),\n", - " array([19], dtype=int32),\n", - " array([20], dtype=int32),\n", - " array([21], dtype=int32),\n", - " array([22], dtype=int32),\n", - " array([24, 23], dtype=int32),\n", - " array([25], dtype=int32),\n", - " array([26], dtype=int32),\n", - " array([27, 28, 29], dtype=int32),\n", - " array([30, 31], dtype=int32),\n", - " array([32, 33, 34], dtype=int32),\n", - " array([35, 36], dtype=int32),\n", - " array([37], dtype=int32),\n", - " array([38, 39], dtype=int32),\n", - " array([40], dtype=int32),\n", - " array([41], dtype=int32),\n", - " array([42, 43], dtype=int32),\n", - " array([44, 45, 46], dtype=int32),\n", - " array([47], dtype=int32),\n", - " array([48], dtype=int32),\n", - " array([49, 50], dtype=int32),\n", - " array([51], dtype=int32),\n", - " array([52], dtype=int32),\n", - " array([53], dtype=int32),\n", - " array([54, 55], dtype=int32),\n", - " array([56, 57, 58], dtype=int32),\n", - " array([59], dtype=int32),\n", - " array([60], dtype=int32),\n", - " array([61, 62, 63], dtype=int32),\n", - " array([64], dtype=int32),\n", - " array([65], dtype=int32),\n", - " array([66], dtype=int32),\n", - " array([67], dtype=int32),\n", - " array([68], dtype=int32),\n", - " array([69], dtype=int32),\n", - " array([76, 70], dtype=int32),\n", - " array([71, 73, 74, 75, 77], dtype=int32),\n", - " array([72], dtype=int32),\n", - " array([80, 78], dtype=int32),\n", - " array([79], dtype=int32),\n", - " array([81], dtype=int32),\n", - " array([82], dtype=int32),\n", - " array([83], dtype=int32),\n", - " array([84, 86, 87, 88, 90], dtype=int32),\n", - " array([89, 85], dtype=int32),\n", - " array([91, 92, 93], dtype=int32),\n", - " array([94, 95], dtype=int32)]" - ] - }, - "execution_count": 71, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "mbri._coplanar_simplices" - ] - }, - { - "cell_type": "code", - "execution_count": 73, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1.12 ms ± 11.8 µs per loop (mean ± std. dev. of 10 runs, 1,000 loops each)\n" - ] - } - ], - "source": [ - "%%timeit -r 10 -n 1000\n", - "# Start with cube\n", - "snorms = mbri._simplex_equations[:, :3]\n", - "tol = 1e-15\n", - "\n", - "coplanar_simplices = [() for i in range(len(snorms))]\n", - "for i, simplex_normal in enumerate(snorms):\n", - " coplanars = np.all(np.abs(simplex_normal - snorms) < tol, axis=1)\n", - " for ind, co in enumerate(coplanars):\n", - " # Iterating means each tuple will have the same length - nice!\n", - " # print(i, co)\n", - " if co:\n", - " coplanar_simplices[i] += (ind,)\n", - "list(set(coplanar_simplices))" - ] - }, - { - "cell_type": "code", - "execution_count": 77, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "345 µs ± 11.8 µs per loop (mean ± std. dev. of 10 runs, 1,000 loops each)\n" - ] - } - ], - "source": [ - "%%timeit -r 10 -n 1000\n", - "mbri._find_coplanar_simplices()" - ] - }, - { - "cell_type": "code", - "execution_count": 112, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "515 µs ± 3.72 µs per loop (mean ± std. dev. of 10 runs, 10,000 loops each)\n" - ] - } - ], - "source": [ - "%%timeit -r 10 -n 10000\n", - "snorms = mbri._simplex_equations[:, :3]\n", - "tol = 1e-15\n", - "coplanars = np.all(np.abs(snorms[:, None] - snorms) < tol, axis=2)\n", - "coplanar_simplices = list({tuple(np.where(co)[0]) for co in coplanars})\n", - "coplanar_simplices" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 113, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "338 µs ± 9.85 µs per loop (mean ± std. dev. of 10 runs, 10,000 loops each)\n" - ] - } - ], - "source": [ - "%%timeit -r 10 -n 10000\n", - "mbri._find_coplanar_simplices()" - ] - }, - { - "cell_type": "code", - "execution_count": 140, - "metadata": {}, - "outputs": [], - "source": [ - "snorms = mbri._simplex_equations[:, :3]\n", - "tol = 1e-15\n", - "coplanars = np.all(np.abs(snorms[:, None] - snorms) < tol, axis=2)\n", - "coplanar_simplices = list({tuple(np.where(co)[0]) for co in coplanars})" - ] - }, - { - "cell_type": "code", - "execution_count": 142, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "788 ns ± 111 ns per loop (mean ± std. dev. of 10 runs, 100,000 loops each)\n", - "474 ns ± 44.5 ns per loop (mean ± std. dev. of 10 runs, 100,000 loops each)\n", - "1 µs ± 21.1 ns per loop (mean ± std. dev. of 10 runs, 100,000 loops each)\n" - ] - } - ], - "source": [ - "for co in coplanars:\n", - " # print(co)\n", - " # display(np.where(co)[0])\n", - " %timeit -r 10 -n 100000 np.where(co)[0]\n", - " cp = np.where(co)[0]\n", - " %timeit -r 10 -n 100000 tuple(cp)\n", - " %timeit -r 10 -n 100000 np.arange(0, len(mbri.simplices))[co]\n", - " break" - ] - }, - { - "cell_type": "code", - "execution_count": 145, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "456 µs ± 115 µs per loop (mean ± std. dev. of 10 runs, 1,000 loops each)\n" - ] - } - ], - "source": [ - "%timeit -r 10 -n 1000 coplanars = np.all(np.abs(snorms[:, None] - snorms) < tol, axis=2)" - ] - }, - { - "cell_type": "code", - "execution_count": 230, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[[ True False False ... False False False]\n", - " [False True False ... False False False]\n", - " [False False True ... False False False]\n", - " ...\n", - " [False False False ... True False False]\n", - " [False False False ... False True True]\n", - " [False False False ... False True True]]\n" - ] - } - ], - "source": [ - "snorms = mbri._simplex_equations[:, :3]\n", - "coplanars = np.all(np.abs(snorms[:, None] - snorms) < tol, axis=2)\n", - "coplanar_simplices = list({tuple(np.where(co)[0]) for co in coplanars})\n", - "print(coplanars)" - ] - }, - { - "cell_type": "code", - "execution_count": 257, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[(0,),\n", - " (1,),\n", - " (2,),\n", - " (3,),\n", - " (4,),\n", - " (5,),\n", - " (6,),\n", - " (7,),\n", - " (8,),\n", - " (9,),\n", - " (10, 11),\n", - " (10, 11),\n", - " (12, 13),\n", - " (12, 13),\n", - " (14, 15, 16),\n", - " (14, 15, 16),\n", - " (14, 15, 16),\n", - " (17, 18),\n", - " (17, 18),\n", - " (19, 20),\n", - " (19, 20),\n", - " (21, 22),\n", - " (21, 22),\n", - " (23, 24),\n", - " (23, 24),\n", - " (25, 26),\n", - " (25, 26),\n", - " (27, 28, 29),\n", - " (27, 28, 29),\n", - " (27, 28, 29),\n", - " (30, 31),\n", - " (30, 31),\n", - " (32, 33, 34),\n", - " (32, 33, 34),\n", - " (32, 33, 34),\n", - " (35, 36),\n", - " (35, 36),\n", - " (37, 38, 39),\n", - " (37, 38, 39),\n", - " (37, 38, 39),\n", - " (40, 41),\n", - " (40, 41),\n", - " (42, 43),\n", - " (42, 43),\n", - " (44, 45, 46),\n", - " (44, 45, 46),\n", - " (44, 45, 46),\n", - " (47, 48),\n", - " (47, 48),\n", - " (49, 50, 51),\n", - " (49, 50, 51),\n", - " (49, 50, 51),\n", - " (52, 53),\n", - " (52, 53),\n", - " (54, 55),\n", - " (54, 55),\n", - " (56, 57, 58),\n", - " (56, 57, 58),\n", - " (56, 57, 58),\n", - " (59, 60),\n", - " (59, 60),\n", - " (61, 62, 63),\n", - " (61, 62, 63),\n", - " (61, 62, 63),\n", - " (64, 65),\n", - " (64, 65),\n", - " (66, 67),\n", - " (66, 67),\n", - " (68, 69),\n", - " (68, 69),\n", - " (70, 71, 72, 73, 74, 75, 76, 77),\n", - " (70, 71, 72, 73, 74, 75, 76, 77),\n", - " (70, 71, 72, 73, 74, 75, 76, 77),\n", - " (70, 71, 72, 73, 74, 75, 76, 77),\n", - " (70, 71, 72, 73, 74, 75, 76, 77),\n", - " (70, 71, 72, 73, 74, 75, 76, 77),\n", - " (70, 71, 72, 73, 74, 75, 76, 77),\n", - " (70, 71, 72, 73, 74, 75, 76, 77),\n", - " (78, 79, 80),\n", - " (78, 79, 80),\n", - " (78, 79, 80),\n", - " (81, 82),\n", - " (81, 82),\n", - " (83, 84, 85, 86, 87, 88, 89, 90),\n", - " (83, 84, 85, 86, 87, 88, 89, 90),\n", - " (83, 84, 85, 86, 87, 88, 89, 90),\n", - " (83, 84, 85, 86, 87, 88, 89, 90),\n", - " (83, 84, 85, 86, 87, 88, 89, 90),\n", - " (83, 84, 85, 86, 87, 88, 89, 90),\n", - " (83, 84, 85, 86, 87, 88, 89, 90),\n", - " (83, 84, 85, 86, 87, 88, 89, 90),\n", - " (91, 92, 93),\n", - " (91, 92, 93),\n", - " (91, 92, 93),\n", - " (94, 95),\n", - " (94, 95)]" - ] - }, - "execution_count": 257, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# %%timeit -r 10 -n 1000\n", - "snorms = mbri._simplex_equations[:, :3]\n", - "coplanars = np.all(np.abs(snorms[:, None] - snorms) < tol, axis=2)\n", - "coplanar_simplices = [tuple(np.where(co)[0]) for co in coplanars]\n", - "coplanar_simplices" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 252, - "metadata": {}, - "outputs": [], - "source": [ - "# %%timeit -r 10 -n 1000\n", - "# np.argwhere(coplanars)\n", - "snorms = mbri._simplex_equations[:, :3]\n", - "coplanars = np.all(np.abs(snorms[:, None] - snorms) < tol, axis=2)\n", - "writeto = [() for _ in range(len(coplanars))]\n", - "for i, val in np.argwhere(coplanars):\n", - " writeto[i] += (val,)\n", - "output = set(writeto)\n", - "# output" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "62\n" - ] - } - ], - "source": [ - "from collections import defaultdict\n", - "\n", - "equation_groups = defaultdict(list)\n", - "eqution_keys = []\n", - "# Iterate over all simplex equations\n", - "for i, equation in enumerate(mbri._simplex_equations):\n", - " # Convert to hashable key\n", - " equation_key = tuple(equation.round(15))\n", - "\n", - " # Store vertex indices from the new simplex under the correct equation key\n", - " equation_groups[equation_key].extend(mbri._simplices[i])\n", - " eqution_keys.append(equation_key)\n", - "# Combine elements with the same plan equation and remove duplicate indices\n", - "ragged_faces = [np.fromiter(set(group), np.int32) for group in equation_groups.values()]\n", - "print(set(eqution_keys).__len__())" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[(0.0, 0.0, -1.0, -0.5),\n", - " (0.0, 0.0, -1.0, -0.5),\n", - " (0.0, -1.0, -0.0, -0.5),\n", - " (-0.0, -1.0, 0.0, -0.5),\n", - " (1.0, 0.0, 0.0, -0.5),\n", - " (1.0, -0.0, 0.0, -0.5),\n", - " (-1.0, 0.0, 0.0, -0.5),\n", - " (-1.0, -0.0, 0.0, -0.5),\n", - " (0.0, 1.0, 0.0, -0.5),\n", - " (0.0, 1.0, -0.0, -0.5),\n", - " (0.0, 0.0, 1.0, -0.5),\n", - " (0.0, -0.0, 1.0, -0.5)]" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "x = [tuple(row) for row in cube._simplex_equations]\n", - "x" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[(0, 1), (10, 11), (2, 3), (6, 7), (4, 5), (8, 9)]" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "snorms = cube._simplex_equations[:, :3]\n", - "tol = 1e-15\n", - "coplanars = np.all(np.abs(snorms[:, None] - snorms) < tol, axis=2)\n", - "coplanar_simplices = list({tuple(np.where(co)[0]) for co in coplanars})\n", - "coplanar_simplices\n", - "# So this gets me an array of coplanar simplices. Now, how do I tie back to the faces?" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5])" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cube._simplex_areas = cube._find_triangle_array_area(\n", - " cube._vertices[cube._simplices], sum_result=False\n", - ")\n", - "cube._simplex_areas" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [ - { - "ename": "TypeError", - "evalue": "'dict_values' object is not subscriptable", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m/Users/jenbradley/github/coxeter/test.ipynb Cell 66\u001b[0m line \u001b[0;36m1\n\u001b[0;32m----> 1\u001b[0m cube\u001b[39m.\u001b[39m_simplex_areas[cube\u001b[39m.\u001b[39;49m_coplanar_simplices[\u001b[39m0\u001b[39;49m]]\n", - "\u001b[0;31mTypeError\u001b[0m: 'dict_values' object is not subscriptable" - ] - } - ], - "source": [ - "cube._simplex_areas[cube._coplanar_simplices[0]]" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "dict_values([[0, 1], [2, 3], [4, 5], [6, 7], [8, 9], [10, 11]])" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cube._coplanar_simplices" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(6409598020480052466, 6409598020480052466)" - ] - }, - "execution_count": 30, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "hash(mbri._simplex_equations[10].round(15).tobytes()), hash(\n", - " mbri._simplex_equations[11].round(15).tobytes()\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "178 µs ± 2.04 µs per loop (mean ± std. dev. of 10 runs, 10,000 loops each)\n", - "263 µs ± 226 ns per loop (mean ± std. dev. of 10 runs, 10,000 loops each)\n" - ] - } - ], - "source": [ - "%timeit -r 10 -n 10000 mbri._find_coplanar_simplices()\n", - "%timeit -r 10 -n 10000 mbri._combine_simplices()" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[array([48, 40, 38], dtype=int32),\n", - " array([24, 14, 22], dtype=int32),\n", - " array([25, 23, 15], dtype=int32),\n", - " array([ 2, 7, 10], dtype=int32),\n", - " array([8, 6, 0], dtype=int32),\n", - " array([41, 49, 39], dtype=int32),\n", - " array([33, 36, 45], dtype=int32),\n", - " array([17, 29, 20], dtype=int32),\n", - " array([32, 43, 34], dtype=int32),\n", - " array([16, 18, 27], dtype=int32)]" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "mbri.faces[0:10]" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[38, 48, 40],\n", - " [14, 22, 24],\n", - " [25, 23, 15],\n", - " [ 2, 7, 10],\n", - " [ 8, 6, 0],\n", - " [49, 39, 41],\n", - " [36, 45, 33],\n", - " [17, 29, 20],\n", - " [32, 43, 34],\n", - " [18, 27, 16]], dtype=int32)" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "mbri.simplices[0:10]" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "265 µs ± 5.58 µs per loop (mean ± std. dev. of 10 runs, 1,000 loops each)\n" - ] - } - ], - "source": [ - "# So sort_faces never changes the order of faces in the list\n", - "# So as long as combine_simplices and find_coplanar_simplices\n", - "# return the same order, we should be ok!\n", - "%timeit -r 10 -n 1000 mbri._combine_simplices()" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [], - "source": [ - "# %%timeit -r 10 -n 10000\n", - "poly = mbri\n", - "simplex_normals = poly._simplex_equations[:, :3]\n", - "tol = 1e-15\n", - "coplanars = np.all(np.abs(simplex_normals[:, None] - simplex_normals) < tol, axis=2)\n", - "coplanar_simplices = list({tuple(np.where(co)[0]) for co in coplanars})\n", - "# coplanar_simplices[0:10]\n", - "# now - use coplanar simplices to build faces\n", - "# faces = [\n", - "# np.array(list(set(poly.simplices[[co]].flatten()))) for co in coplanar_simplices\n", - "# ]\n", - "\n", - "# Move the simplices out into an array that matches the new sorting\n", - "# ordered_simplices = [simplex for coplanar in coplanar_simplices for simplex in coplanar]" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([10, 20, 10, 2])" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 74, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "651 µs ± 26.5 µs per loop (mean ± std. dev. of 10 runs, 10,000 loops each)\n" - ] - } - ], - "source": [ - "%%timeit -r 10 -n 10000\n", - "poly = mbri\n", - "simplex_normals = poly._simplex_equations[:, :3]\n", - "tol = 1e-15\n", - "# Generate boolean array checking which simplices share a normal (within tolerance)\n", - "is_coplanar = np.all(np.abs(simplex_normals[:, None] - simplex_normals) < tol, axis=2)\n", - "# Convert boolean array into ragged list of coplanar simplices\n", - "coplanar_simplices = list({tuple(np.where(coplanar)[0]) for coplanar in is_coplanar})\n", - "faces = []\n", - "ordered_simplices = []\n", - "for face_simplex_indices in coplanar_simplices:\n", - " # Get unique vertex indices from faces and convert to np array\n", - " faces.append(\n", - " np.fromiter(set(poly.simplices[[face_simplex_indices]].flat), np.int32)\n", - " )\n", - " # Add simplex indices back into list that matches face ordering\n", - " ordered_simplices.extend(face_simplex_indices)\n", - " # faces.append(set(poly.simplices[[face_simplex_indices]].flatten()))\n", - "# faces.__len__(), coplanar_simplices.__len__(), ordered_simplices.__len__()" - ] - }, - { - "cell_type": "code", - "execution_count": 82, - "metadata": {}, - "outputs": [ - { - "ename": "AttributeError", - "evalue": "'ConvexPolyhedron' object has no attribute '_simplex_areas'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m/Users/jenbradley/github/coxeter/test.ipynb Cell 78\u001b[0m line \u001b[0;36m2\n\u001b[1;32m 1\u001b[0m poly\u001b[39m.\u001b[39m_simplex_equations\n\u001b[0;32m----> 2\u001b[0m poly\u001b[39m.\u001b[39;49m_simplex_areas\n", - "\u001b[0;31mAttributeError\u001b[0m: 'ConvexPolyhedron' object has no attribute '_simplex_areas'" - ] - } - ], - "source": [ - "poly._simplex_equations\n", - "poly._simplex_areas" - ] - }, - { - "cell_type": "code", - "execution_count": 63, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([10, 20, 10, 2])" - ] - }, - "execution_count": 63, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "np.sum(np.array([len(face) for face in faces])[:, None] == [3, 4, 5, 10], axis=0)\n", - "# so we get the right number of vertices and faces!" - ] - }, - { - "cell_type": "code", - "execution_count": 91, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([ 0. , 0. , -1. , -0.63580989])" - ] - }, - "execution_count": 91, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "i = 3\n", - "poly._simplex_equations[coplanar_simplices[i][0]]" - ] - }, - { - "cell_type": "code", - "execution_count": 96, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([ 0. , 0.35682, -0.93417, -0.64751])" - ] - }, - "execution_count": 96, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "poly._simplex_equations.round(5)[3]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 66, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "710 µs ± 14.2 µs per loop (mean ± std. dev. of 10 runs, 10,000 loops each)\n" - ] - } - ], - "source": [ - "%%timeit -r 10 -n 10000\n", - "mbri._combine_simplices()\n", - "mbri._find_coplanar_simplices()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "179 µs ± 1.85 µs per loop (mean ± std. dev. of 10 runs, 10,000 loops each)\n" - ] - } - ], - "source": [ - "%%timeit -r 10 -n 10000\n", - "faces = [\n", - " np.array(list(set(poly.simplices[[co]].flatten()))) for co in coplanar_simplices\n", - "]" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "706 µs ± 6.3 µs per loop (mean ± std. dev. of 10 runs, 10,000 loops each)\n" - ] - } - ], - "source": [ - "%%timeit -r 10 -n 10000\n", - "mbri._combine_simplices()\n", - "mbri._find_coplanar_simplices()" - ] - }, - { - "cell_type": "code", - "execution_count": 96, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "3.52 µs ± 229 ns per loop (mean ± std. dev. of 10 runs, 100,000 loops each)\n", - "7.24 µs ± 463 ns per loop (mean ± std. dev. of 10 runs, 100,000 loops each)\n" - ] - } - ], - "source": [ - "%timeit -r 10 -n 100000 [simplex for coplanar in coplanar_simplices for simplex in coplanar]\n", - "%timeit -r 10 -n 100000 list(sum(coplanar_simplices,()))" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([-0.04505663, 0.07290316, -0.02784653])" - ] - }, - "execution_count": 30, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "t1 = mbri.vertices[mbri.simplices[81]]\n", - "np.cross(\n", - " np.diff(t1[[0, 1], :], axis=0).squeeze(), np.diff(t1[[1, 2], :], axis=0).squeeze()\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([ 0.24285777, 0.09276341, -0.15009435])" - ] - }, - "execution_count": 31, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "np.diff(t1[[0, 1], :], axis=0).squeeze()" - ] - }, - { - "cell_type": "code", - "execution_count": 42, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([-0.5 , 0.80901699, -0.30901699])" - ] - }, - "execution_count": 42, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "nor = np.cross(t1[1, :] - t1[0, :], t1[2, :] - t1[0, :])\n", - "nor /= np.linalg.norm(nor)\n", - "nor # So the normal is right!" - ] - }, - { - "cell_type": "code", - "execution_count": 38, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([-0.5 , 0.80901699, -0.30901699, -0.63580989])" - ] - }, - "execution_count": 38, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "mbri._simplex_equations[81]" - ] - }, - { - "cell_type": "code", - "execution_count": 43, - "metadata": {}, - "outputs": [], - "source": [ - "# Somehow, we get the correct number and hsape of faces but different than we expect?" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.5" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/test_families.ipynb b/test_families.ipynb deleted file mode 100644 index 565e17d3..00000000 --- a/test_families.ipynb +++ /dev/null @@ -1,231 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import math\n", - "\n", - "import numpy as np\n", - "from conway.utils.arraymath import _norm3\n", - "from conway.utils.trig import cot, csc, sec\n", - "from scipy.spatial import ConvexHull as ch\n", - "from scipy.spatial import Delaunay\n", - "\n", - "from coxeter.families import (\n", - " ArchimedeanFamily as archfam,\n", - ")\n", - "from coxeter.families import (\n", - " JohnsonFamily as johnfam,\n", - ")\n", - "from coxeter.families import (\n", - " PlatonicFamily as platfam,\n", - ")\n", - "from conway.seeds.infinite_shape_families import *\n", - "\n", - "from co" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "xeter.shapes import ConvexPolygon, ConvexPolyhedron\n", - "from coxeter.shapes import ConvexPolyhedron" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "def _make_ngon(n, z=0, A=1, angle=0):\n", - " \"\"\"\n", - " Make a regular n-gon with area equal to A. The initial vertex lies on the :math:`x`\n", - " axis by default, but can be rotated by the angle parameter.\n", - "\n", - " Args:\n", - " n (int): Number of vertices\n", - " z (int|float): z value for the polygon. Defaults to 0.\n", - " A (int, optional): Area of polygon. Defaults to 1.\n", - " angle (float, optional): Rotation angle, in radians. Defaults to 0.\n", - "\n", - " Returns:\n", - " np.array: n-gon vertices\n", - " \"\"\"\n", - " check_domain(n, [3, np.inf])\n", - " theta = (\n", - " np.arange(0, 2 * np.pi, 2 * np.pi / n) + angle\n", - " ) # Generate angle for each point\n", - " Ao = 0.5 * n * math.sin(2 * np.pi / n) # Area of the shape with circumradius = 1\n", - "\n", - " ngon_vertices = np.array([np.cos(theta), np.sin(theta), np.full_like(theta, z)]).T\n", - " ngon_vertices[:, :2] *= math.sqrt(A / Ao) # Rescale the x and y coords to area A\n", - "\n", - " return ngon_vertices" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "99.00000000000055" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "vertices = make_vertices_trapezohedron(124, 99.0)\n", - "ConvexPolyhedron(vertices).volume" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "# parallelepiped\n", - "# We need three vectors and 4 poitns: OA, OB, OC\n", - "# How can we construct those vectors from angles and lengths?\n", - "# αlpha = angle between b and c\n", - "# beta = angle between a and c\n", - "# gamma = angle between a and b\n", - "a = 1\n", - "b = 1\n", - "c = 1\n", - "alpha, beta, gamma = [π / 2, π / 2, π / 2]\n", - "origin = [0, 0, 0]" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "ename": "DomainError", - "evalue": "Value n=361 is not in domain [180, 360]", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mDomainError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[6], line 9\u001b[0m\n\u001b[1;32m 4\u001b[0m c \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m1\u001b[39m\n\u001b[1;32m 5\u001b[0m alpha, beta, gamma \u001b[38;5;241m=\u001b[39m [\u001b[38;5;241m120\u001b[39m, \u001b[38;5;241m120\u001b[39m, \u001b[38;5;241m121\u001b[39m]\n\u001b[1;32m 8\u001b[0m ConvexPolyhedron(\n\u001b[0;32m----> 9\u001b[0m \u001b[43mmake_vertices_parallelepiped\u001b[49m\u001b[43m(\u001b[49m\u001b[43ma\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mb\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43malpha\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mbeta\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mgamma\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mV\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 10\u001b[0m )\u001b[38;5;241m.\u001b[39mplot()\n", - "File \u001b[0;32m~/github/conway/conway/seeds/infinite_shape_families.py:219\u001b[0m, in \u001b[0;36mmake_vertices_parallelepiped\u001b[0;34m(a, b, c, alpha, beta, gamma, V)\u001b[0m\n\u001b[1;32m 201\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mmake_vertices_parallelepiped\u001b[39m(a\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m1\u001b[39m, b\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m1\u001b[39m, c\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m1\u001b[39m, alpha\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m90\u001b[39m, beta\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m90\u001b[39m, gamma\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m90\u001b[39m, V\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m):\n\u001b[1;32m 202\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 203\u001b[0m \u001b[38;5;124;03m Generate a parallelepiped defined by a,b,c,alpha,beta,and gamma.\u001b[39;00m\n\u001b[1;32m 204\u001b[0m \n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 217\u001b[0m \u001b[38;5;124;03m V (float): Volume of the parallelepiped. Defaults to None. (Keep volume)\u001b[39;00m\n\u001b[1;32m 218\u001b[0m \u001b[38;5;124;03m \"\"\"\u001b[39;00m\n\u001b[0;32m--> 219\u001b[0m \u001b[43mcheck_domain\u001b[49m\u001b[43m(\u001b[49m\u001b[43malpha\u001b[49m\u001b[38;5;241;43m+\u001b[39;49m\u001b[43mbeta\u001b[49m\u001b[38;5;241;43m+\u001b[39;49m\u001b[43mgamma\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m180\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m360\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 221\u001b[0m alpha \u001b[38;5;241m=\u001b[39m math\u001b[38;5;241m.\u001b[39mradians(alpha)\n\u001b[1;32m 222\u001b[0m beta \u001b[38;5;241m=\u001b[39m math\u001b[38;5;241m.\u001b[39mradians(beta)\n", - "File \u001b[0;32m~/github/conway/conway/utils/_errors_and_logging.py:19\u001b[0m, in \u001b[0;36mcheck_domain\u001b[0;34m(value, domain_bounds)\u001b[0m\n\u001b[1;32m 17\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mtype\u001b[39m(domain_bounds) \u001b[38;5;241m==\u001b[39m \u001b[38;5;28mlist\u001b[39m:\n\u001b[1;32m 18\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m (value \u001b[38;5;241m<\u001b[39m\u001b[38;5;241m=\u001b[39m domain_bounds[\u001b[38;5;241m1\u001b[39m] \u001b[38;5;129;01mand\u001b[39;00m value \u001b[38;5;241m>\u001b[39m\u001b[38;5;241m=\u001b[39m domain_bounds[\u001b[38;5;241m0\u001b[39m]):\n\u001b[0;32m---> 19\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m DomainError(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mValue n=\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mvalue\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m is not in domain \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mdomain_bounds\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 20\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m \u001b[38;5;28mtype\u001b[39m(domain_bounds) \u001b[38;5;241m==\u001b[39m \u001b[38;5;28mtuple\u001b[39m:\n\u001b[1;32m 21\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m (value \u001b[38;5;241m<\u001b[39m domain_bounds[\u001b[38;5;241m1\u001b[39m] \u001b[38;5;129;01mand\u001b[39;00m value \u001b[38;5;241m>\u001b[39m domain_bounds[\u001b[38;5;241m0\u001b[39m]):\n", - "\u001b[0;31mDomainError\u001b[0m: Value n=361 is not in domain [180, 360]" - ] - } - ], - "source": [ - "# So what are our points?\n", - "a = 1\n", - "b = 1\n", - "c = 1\n", - "alpha, beta, gamma = [120, 120, 121]\n", - "\n", - "\n", - "ConvexPolyhedron(make_vertices_parallelepiped(a, b, c, alpha, beta, gamma, V=1)).plot()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "ename": "NameError", - "evalue": "name 'oa' is not defined", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[7], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m u, v, w \u001b[38;5;241m=\u001b[39m \u001b[43moa\u001b[49m\n\u001b[1;32m 2\u001b[0m x, y, z \u001b[38;5;241m=\u001b[39m ob\n\u001b[1;32m 3\u001b[0m [\n\u001b[1;32m 4\u001b[0m c \u001b[38;5;241m*\u001b[39m cos(alpha) \u001b[38;5;241m+\u001b[39m a \u001b[38;5;241m*\u001b[39m cos(beta) \u001b[38;5;241m*\u001b[39m u,\n\u001b[1;32m 5\u001b[0m c \u001b[38;5;241m*\u001b[39m cos(alpha) \u001b[38;5;241m*\u001b[39m y \u001b[38;5;241m+\u001b[39m a \u001b[38;5;241m*\u001b[39m cos(beta) \u001b[38;5;241m*\u001b[39m v,\n\u001b[1;32m 6\u001b[0m c \u001b[38;5;241m*\u001b[39m cos(alpha) \u001b[38;5;241m*\u001b[39m z \u001b[38;5;241m+\u001b[39m a \u001b[38;5;241m*\u001b[39m cos(beta) \u001b[38;5;241m*\u001b[39m w,\n\u001b[1;32m 7\u001b[0m ]\n", - "\u001b[0;31mNameError\u001b[0m: name 'oa' is not defined" - ] - } - ], - "source": [ - "u, v, w = oa\n", - "x, y, z = ob\n", - "[\n", - " c * cos(alpha) + a * cos(beta) * u,\n", - " c * cos(alpha) * y + a * cos(beta) * v,\n", - " c * cos(alpha) * z + a * cos(beta) * w,\n", - "]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([6.123234e-17, 6.123234e-17, 0.000000e+00])" - ] - }, - "execution_count": 49, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.5" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/test_sorting.ipynb b/test_sorting.ipynb deleted file mode 100644 index 0e41d12e..00000000 --- a/test_sorting.ipynb +++ /dev/null @@ -1,212 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "from scipy.spatial import ConvexHull as ch\n", - "from scipy.spatial import Delaunay\n", - "\n", - "from coxeter.families import (\n", - " ArchimedeanFamily as archfam,\n", - ")\n", - "from coxeter.families import (\n", - " JohnsonFamily as johnfam,\n", - ")\n", - "from coxeter.families import (\n", - " PlatonicFamily as platfam,\n", - ")\n", - "from coxeter.shapes import ConvexPolyhedron" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[ 0.20800838, 0. , 0. ],\n", - " [ 0. , 0.20800838, -0. ],\n", - " [ 0. , -0. , 0.20800838]])" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "tetr = platfam.get_shape(\"Tetrahedron\")\n", - "tetr.plot(label_verts=True)\n", - "tetr.inertia_tensor.round(15)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[2, 3, 1],\n", - " [0, 3, 1],\n", - " [0, 2, 1],\n", - " [0, 2, 3]], dtype=int32)" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "ch(tetr.vertices).simplices" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "i = 2\n", - "tetr._simplices = tetr._simplices[::-1]" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[ 0. , 0. , 1.24902477],\n", - " [-0.58879592, -1.01982445, -0.41634159],\n", - " [-0.58879592, 1.01982445, -0.41634159],\n", - " [ 1.17759184, 0. , -0.41634159]])" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "tetr.vertices\n", - "tetr" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(array([[-0.03466806, -0.03002342, 0.02451402],\n", - " [-0.03002342, -0.10400419, 0.04245953],\n", - " [ 0.02451402, 0.04245953, -0.06933613]]),\n", - " array([ 0., 0., -0.]))" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "tetr.inertia_tensor.round(15), tetr.centroid.round(15)" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[array([1, 2, 3], dtype=int32),\n", - " array([0, 1, 3], dtype=int32),\n", - " array([0, 2, 1], dtype=int32),\n", - " array([0, 3, 2], dtype=int32)]" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "tetr.faces" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "ename": "NameError", - "evalue": "name 'n' is not defined", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[12], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mn\u001b[49m\u001b[38;5;241m*\u001b[39mb\n", - "\u001b[0;31mNameError\u001b[0m: name 'n' is not defined" - ] - } - ], - "source": [ - "n * b" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.5" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} From 60f222c8b057c0c5b0823486eb37c16f182654fb Mon Sep 17 00:00:00 2001 From: janbridley Date: Tue, 20 Feb 2024 10:53:28 -0500 Subject: [PATCH 04/11] Added to_json method to base class --- coxeter/shapes/base_classes.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/coxeter/shapes/base_classes.py b/coxeter/shapes/base_classes.py index e66506d2..03c60a49 100644 --- a/coxeter/shapes/base_classes.py +++ b/coxeter/shapes/base_classes.py @@ -197,6 +197,30 @@ def to_plato_scene(self, backend="matplotlib", scene=None, scene_kwargs=None): scene.add_primitive(prim) return scene + def to_json(self, attributes: list): + """Get a JSON-serializable subset of shape properties. + + Args: + attributes (list): + List of attributes to export. Each element must be a valid attribute + of the class. + + Returns + ------- + dict + A dict containing the requested attributes. + + Raises + ------ + AttributeError: + If any keys in the input list are invalid. + """ + export = {} + for attribute in attributes: + # If an invalid key is passed, this will raise an attribute error + export.update({attribute: getattr(self, attribute)}) + return export + class Shape2D(Shape): """An abstract representation of a shape in 2 dimensions.""" From 74a9d73d9c0da0aa8dd27587cbf90b3a0d523ad3 Mon Sep 17 00:00:00 2001 From: janbridley Date: Tue, 20 Feb 2024 10:53:44 -0500 Subject: [PATCH 05/11] Added mapping functions to utils --- coxeter/shapes/utils.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/coxeter/shapes/utils.py b/coxeter/shapes/utils.py index 71e74ef0..dcff789a 100644 --- a/coxeter/shapes/utils.py +++ b/coxeter/shapes/utils.py @@ -90,3 +90,27 @@ def _set_3d_axes_equal(ax, limits=None): ax.set_ylim3d([origin[1] - radius, origin[1] + radius]) ax.set_zlim3d([origin[2] - radius, origin[2] + radius]) return ax + + +def _map_dict_keys(data, key_mapping): + """Rename a dict's keys based on a mapping dict. + + If an instance of :class:`matplotlib.axes.Axes` is provided, it will be + passed through. + + Args: + data (dict): + A dict with keys to be remapped + key_mapping (dict): + A dict of keys that should be renamed. The keys of this dict should + correspond with the keys of data that are to be changed, and the values + should correspond with the desired new keys. + + Returns + ------- + dict: A dict with the select keys renamed to the mapped values. + """ + return {key_mapping.get(key, key): value for key, value in data.items()} + + +_hoomd_dict_mapping = {"inertia_tensor": "moment_inertia", "radius": "sweep_radius"} From c43d999d66d21813e14e2bed8f36fdcb8b05226d Mon Sep 17 00:00:00 2001 From: janbridley Date: Tue, 20 Feb 2024 10:54:23 -0500 Subject: [PATCH 06/11] Migrated to_hoomd functions to use internal to_json method --- coxeter/shapes/convex_spheropolygon.py | 28 +++++++++---------- coxeter/shapes/convex_spheropolyhedron.py | 20 +++++++------- coxeter/shapes/ellipsoid.py | 20 ++++++-------- coxeter/shapes/polygon.py | 22 ++++++++------- coxeter/shapes/polyhedron.py | 33 +++++++++++++---------- coxeter/shapes/sphere.py | 30 ++++++++++++++------- 6 files changed, 82 insertions(+), 71 deletions(-) diff --git a/coxeter/shapes/convex_spheropolygon.py b/coxeter/shapes/convex_spheropolygon.py index 6bcd3e69..45e77f6d 100644 --- a/coxeter/shapes/convex_spheropolygon.py +++ b/coxeter/shapes/convex_spheropolygon.py @@ -12,6 +12,7 @@ from .base_classes import Shape2D from .convex_polygon import ConvexPolygon, _is_convex from .polygon import _align_points_by_normal +from .utils import _hoomd_dict_mapping, _map_dict_keys class ConvexSpheropolygon(Shape2D): @@ -269,12 +270,13 @@ def _plato_primitive(self, backend): ) def to_hoomd(self): - """Get a json-serializable subset of ConvexSpheropolygon properties. + """Get a JSON-serializable subset of ConvexSpheropolygon properties. - The json-serializable output of the to_hoomd method can be directly imported - into data management tools like Signac. This data can then be queried for use in + The JSON-serializable output of the to_hoomd method can be directly imported + into data management tools like signac. This data can then be queried for use in HOOMD simulations. Key naming matches HOOMD integrators: for example, the - moment_inertia key links to data from coxeter's inertia_tensor. + moment_inertia key links to data from coxeter's inertia_tensor. Stored values + are based on the shape with its centroid at the origin. For a ConvexSpheropolygon, the following properties are stored: @@ -287,22 +289,16 @@ def to_hoomd(self): The rounding radius of the shape. * area (float) The area of the shape. - * moment_inertia (list(list)) - The shape's inertia tensor. Returns ------- dict Dict containing a subset of shape properties. """ - old_centroid = self.centroid - self.centroid = np.array([0, 0, 0]) - hoomd_dict = { - "vertices": self.vertices.tolist(), - "centroid": self.centroid.tolist(), - "sweep_radius": self.radius, - "area": self.area, - "moment_inertia": self.inertia_tensor.tolist(), - } - self.centroid = old_centroid + old_centroid = self._polygon.centroid + data = self.to_json(["vertices", "radius", "area"]) + hoomd_dict = _map_dict_keys(data, key_mapping=_hoomd_dict_mapping) + hoomd_dict["centroid"] = [0, 0, 0] + + self._polygon.centroid = old_centroid return hoomd_dict diff --git a/coxeter/shapes/convex_spheropolyhedron.py b/coxeter/shapes/convex_spheropolyhedron.py index 54fc0f44..fef506f2 100644 --- a/coxeter/shapes/convex_spheropolyhedron.py +++ b/coxeter/shapes/convex_spheropolyhedron.py @@ -11,6 +11,7 @@ from .base_classes import Shape3D from .convex_polyhedron import ConvexPolyhedron +from .utils import _hoomd_dict_mapping, _map_dict_keys class ConvexSpheropolyhedron(Shape3D): @@ -330,12 +331,13 @@ def _plato_primitive(self, backend): ) def to_hoomd(self): - """Get a json-serializable subset of ConvexSpheropolyhedron properties. + """Get a JSON-serializable subset of ConvexSpheropolyhedron properties. - The json-serializable output of the to_hoomd method can be directly imported - into data management tools like Signac. This data can then be queried for use in + The JSON-serializable output of the to_hoomd method can be directly imported + into data management tools like signac. This data can then be queried for use in HOOMD simulations. Key naming matches HOOMD integrators: for example, the - moment_inertia key links to data from coxeter's inertia_tensor. + moment_inertia key links to data from coxeter's inertia_tensor. Stored values + are based on the shape with its centroid at the origin. For a ConvexSpheropolyhedron, the following properties are stored: @@ -356,11 +358,9 @@ def to_hoomd(self): """ old_centroid = self._polyhedron.centroid self._polyhedron.centroid = np.array([0, 0, 0]) - hoomd_dict = { - "vertices": self.vertices.tolist(), - "centroid": self._polyhedron.centroid.tolist(), - "sweep_radius": self.radius, - "volume": self.volume, - } + data = self.to_json(["vertices", "radius", "volume"]) + hoomd_dict = _map_dict_keys(data, key_mapping=_hoomd_dict_mapping) + hoomd_dict["centroid"] = [0, 0, 0] + self._polyhedron.centroid = old_centroid return hoomd_dict diff --git a/coxeter/shapes/ellipsoid.py b/coxeter/shapes/ellipsoid.py index 8399737e..71f5fe6f 100644 --- a/coxeter/shapes/ellipsoid.py +++ b/coxeter/shapes/ellipsoid.py @@ -8,7 +8,7 @@ from .base_classes import Shape3D from .sphere import Sphere -from .utils import translate_inertia_tensor +from .utils import _hoomd_dict_mapping, _map_dict_keys, translate_inertia_tensor class Ellipsoid(Shape3D): @@ -227,12 +227,13 @@ def __repr__(self): ) def to_hoomd(self): - """Get a json-serializable subset of Ellipsoid properties. + """Get a JSON-serializable subset of Ellipsoid properties. - The json-serializable output of the to_hoomd method can be directly imported + The JSON-serializable output of the to_hoomd method can be directly imported into data management tools like Signac. This data can then be queried for use in HOOMD simulations. Key naming matches HOOMD integrators: for example, the - moment_inertia key links to data from coxeter's inertia_tensor. + moment_inertia key links to data from coxeter's inertia_tensor. Stored values + are based on the shape with its centroid at the origin. For an Ellipsoid, the following properties are stored: @@ -257,13 +258,8 @@ def to_hoomd(self): """ old_centroid = self.centroid self.centroid = np.array([0, 0, 0]) - hoomd_dict = { - "a": self.a, - "b": self.b, - "c": self.c, - "centroid": self.centroid.tolist(), - "volume": self.volume, - "moment_inertia": self.inertia_tensor.tolist(), - } + data = self.to_json(["a", "b", "c", "centroid", "volume", "inertia_tensor"]) + hoomd_dict = _map_dict_keys(data, key_mapping=_hoomd_dict_mapping) + self.centroid = old_centroid return hoomd_dict diff --git a/coxeter/shapes/polygon.py b/coxeter/shapes/polygon.py index b23feb89..9eeadf3e 100644 --- a/coxeter/shapes/polygon.py +++ b/coxeter/shapes/polygon.py @@ -12,7 +12,13 @@ from ..extern.polytri import polytri from .base_classes import Shape2D from .circle import Circle -from .utils import _generate_ax, rotate_order2_tensor, translate_inertia_tensor +from .utils import ( + _generate_ax, + _hoomd_dict_mapping, + _map_dict_keys, + rotate_order2_tensor, + translate_inertia_tensor, +) try: import miniball @@ -768,7 +774,8 @@ def to_hoomd(self): The json-serializable output of the to_hoomd method can be directly imported into data management tools like Signac. This data can then be queried for use in HOOMD simulations. Key naming matches HOOMD integrators: for example, the - moment_inertia key links to data from coxeter's inertia_tensor. + moment_inertia key links to data from coxeter's inertia_tensor. Stored values + are based on the shape with its centroid at the origin. For a Polygon or ConvexPolygon, the following properties are stored: @@ -791,12 +798,9 @@ def to_hoomd(self): """ old_centroid = self.centroid self.centroid = np.array([0, 0, 0]) - hoomd_dict = { - "vertices": self.vertices.tolist(), - "centroid": self.centroid.tolist(), - "sweep_radius": 0.0, - "area": self.area, - "moment_inertia": self.inertia_tensor.tolist(), - } + data = self.to_json(["vertices", "centroid", "area", "inertia_tensor"]) + hoomd_dict = _map_dict_keys(data, key_mapping=_hoomd_dict_mapping) + hoomd_dict["sweep_radius"] = 0.0 + self.centroid = old_centroid return hoomd_dict diff --git a/coxeter/shapes/polyhedron.py b/coxeter/shapes/polyhedron.py index 2b92558e..ff3fc3e9 100644 --- a/coxeter/shapes/polyhedron.py +++ b/coxeter/shapes/polyhedron.py @@ -15,7 +15,13 @@ from .convex_polygon import ConvexPolygon, _is_convex from .polygon import Polygon, _is_simple from .sphere import Sphere -from .utils import _generate_ax, _set_3d_axes_equal, translate_inertia_tensor +from .utils import ( + _generate_ax, + _hoomd_dict_mapping, + _map_dict_keys, + _set_3d_axes_equal, + translate_inertia_tensor, +) try: import miniball @@ -975,12 +981,13 @@ def _plato_primitive(self, backend): ) def to_hoomd(self): - """Get a json-serializable subset of Polyhedron properties. + """Get a JSON-serializable subset of Polyhedron properties. - The json-serializable output of the to_hoomd method can be directly imported - into data management tools like Signac. This data can then be queried for use in + The JSON-serializable output of the to_hoomd method can be directly imported + into data management tools like signac. This data can then be queried for use in HOOMD simulations. Key naming matches HOOMD integrators: for example, the - moment_inertia key links to data from coxeter's inertia_tensor. + moment_inertia key links to data from coxeter's inertia_tensor. Stored values + are based on the shape with its centroid at the origin. For a Polyhedron or ConvexPolyhedron, the following properties are stored: @@ -1001,17 +1008,15 @@ def to_hoomd(self): Returns ------- dict - Dict containing a subset of shape properties. + Dict containing a subset of shape properties required for HOOMD function. """ old_centroid = self.centroid self.centroid = np.array([0, 0, 0]) - hoomd_dict = { - "vertices": self.vertices.tolist(), - "faces": [face.tolist() for face in self.faces], - "centroid": self.centroid.tolist(), - "sweep_radius": 0.0, - "volume": self.volume, - "moment_inertia": self.inertia_tensor.tolist(), - } + data = self.to_json( + ["vertices", "faces", "centroid", "volume", "inertia_tensor"] + ) + hoomd_dict = _map_dict_keys(data, key_mapping=_hoomd_dict_mapping) + hoomd_dict["sweep_radius"] = 0.0 + self.centroid = old_centroid return hoomd_dict diff --git a/coxeter/shapes/sphere.py b/coxeter/shapes/sphere.py index 74d16c47..573686ca 100644 --- a/coxeter/shapes/sphere.py +++ b/coxeter/shapes/sphere.py @@ -6,7 +6,7 @@ import numpy as np from .base_classes import Shape3D -from .utils import translate_inertia_tensor +from .utils import _hoomd_dict_mapping, _map_dict_keys, translate_inertia_tensor class Sphere(Shape3D): @@ -68,6 +68,18 @@ def radius(self, value): else: raise ValueError("Radius must be greater than zero.") + @property + def diameter(self): + """float: Get or set the radius of the sphere.""" + return 2 * self._radius + + @diameter.setter + def diameter(self, value): + if value > 0: + self._radius = value / 2 + else: + raise ValueError("Diameter must be greater than zero.") + def _rescale(self, scale): """Multiply length scale. @@ -204,12 +216,13 @@ def _plato_primitive(self, backend): ) def to_hoomd(self): - """Get a dict of json-serializable subset of Sphere properties. + """Get a dict of JSON-serializable subset of Sphere properties. - The json-serializable output of the to_hoomd method can be directly imported + The JSON-serializable output of the to_hoomd method can be directly imported into data management tools like Signac. This data can then be queried for use in HOOMD simulations. Key naming matches HOOMD integrators: for example, the - moment_inertia key links to data from coxeter's inertia_tensor. + moment_inertia key links to data from coxeter's inertia_tensor. Stored values + are based on the shape with its centroid at the origin. For a Sphere, the following properties are stored: @@ -230,11 +243,8 @@ def to_hoomd(self): """ old_centroid = self.centroid self.centroid = np.array([0, 0, 0]) - hoomd_dict = { - "diameter": self.radius * 2, - "centroid": self.centroid.tolist(), - "volume": self.volume, - "moment_inertia": self.inertia_tensor.tolist(), - } + data = self.to_json(["diameter", "centroid", "volume", "inertia_tensor"]) + hoomd_dict = _map_dict_keys(data, key_mapping=_hoomd_dict_mapping) + self.centroid = old_centroid return hoomd_dict From fa6032d892d3844d85f8e5042e16b0e5d12e05c2 Mon Sep 17 00:00:00 2001 From: janbridley Date: Tue, 20 Feb 2024 10:54:41 -0500 Subject: [PATCH 07/11] Updated tests --- tests/test_sphere.py | 8 ++++++++ tests/test_spheropolygon.py | 15 +++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/tests/test_sphere.py b/tests/test_sphere.py index f8ac2032..d4d48a4f 100644 --- a/tests/test_sphere.py +++ b/tests/test_sphere.py @@ -21,6 +21,14 @@ def test_radius_getter_setter(r): assert sphere.radius == r + 1 +@given(floats(0.1, 1000)) +def test_diameter_getter_setter(r): + sphere = Sphere(r) + assert sphere.diameter == 2 * r + sphere.diameter = 2 * r + 1 + assert sphere.diameter == 2 * r + 1 + + @given(floats(-1000, -1)) def test_invalid_radius_setter(r): """Test setting an invalid Volume.""" diff --git a/tests/test_spheropolygon.py b/tests/test_spheropolygon.py index a1d34b6e..0d4c18af 100644 --- a/tests/test_spheropolygon.py +++ b/tests/test_spheropolygon.py @@ -219,3 +219,18 @@ def test_inertia(unit_rounded_square): def test_repr(unit_rounded_square): assert str(unit_rounded_square), str(eval(repr(unit_rounded_square))) + + +def test_to_hoomd(unit_rounded_square): + """Test hoomd JSON calculation.""" + shape = unit_rounded_square + dict_keys = ["vertices", "centroid", "sweep_radius", "area"] + dict_vals = [ + shape.vertices, + [0, 0, 0], + 1, + shape.area, + ] + hoomd_dict = shape.to_hoomd() + for key, val in zip(dict_keys, dict_vals): + assert np.allclose(hoomd_dict[key], val), f"{key}" From 77d7ae60d5a5d3e1dd8fbc9b683c79f791f7d20f Mon Sep 17 00:00:00 2001 From: janbridley Date: Tue, 20 Feb 2024 11:01:37 -0500 Subject: [PATCH 08/11] Fixed capitalization errors --- coxeter/shapes/ellipsoid.py | 2 +- coxeter/shapes/polygon.py | 6 +++--- coxeter/shapes/sphere.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/coxeter/shapes/ellipsoid.py b/coxeter/shapes/ellipsoid.py index 71f5fe6f..eef18bee 100644 --- a/coxeter/shapes/ellipsoid.py +++ b/coxeter/shapes/ellipsoid.py @@ -230,7 +230,7 @@ def to_hoomd(self): """Get a JSON-serializable subset of Ellipsoid properties. The JSON-serializable output of the to_hoomd method can be directly imported - into data management tools like Signac. This data can then be queried for use in + into data management tools like signac. This data can then be queried for use in HOOMD simulations. Key naming matches HOOMD integrators: for example, the moment_inertia key links to data from coxeter's inertia_tensor. Stored values are based on the shape with its centroid at the origin. diff --git a/coxeter/shapes/polygon.py b/coxeter/shapes/polygon.py index 9eeadf3e..3c4d1aeb 100644 --- a/coxeter/shapes/polygon.py +++ b/coxeter/shapes/polygon.py @@ -769,10 +769,10 @@ def _plato_primitive(self, backend): ) def to_hoomd(self): - """Get a json-serializable subset of Polygon properties. + """Get a JSON-serializable subset of Polygon properties. - The json-serializable output of the to_hoomd method can be directly imported - into data management tools like Signac. This data can then be queried for use in + The JSON-serializable output of the to_hoomd method can be directly imported + into data management tools like signac. This data can then be queried for use in HOOMD simulations. Key naming matches HOOMD integrators: for example, the moment_inertia key links to data from coxeter's inertia_tensor. Stored values are based on the shape with its centroid at the origin. diff --git a/coxeter/shapes/sphere.py b/coxeter/shapes/sphere.py index 573686ca..8665a621 100644 --- a/coxeter/shapes/sphere.py +++ b/coxeter/shapes/sphere.py @@ -219,7 +219,7 @@ def to_hoomd(self): """Get a dict of JSON-serializable subset of Sphere properties. The JSON-serializable output of the to_hoomd method can be directly imported - into data management tools like Signac. This data can then be queried for use in + into data management tools like signac. This data can then be queried for use in HOOMD simulations. Key naming matches HOOMD integrators: for example, the moment_inertia key links to data from coxeter's inertia_tensor. Stored values are based on the shape with its centroid at the origin. From bc02f9179a37b797eccbde0cdb42764f1c29148d Mon Sep 17 00:00:00 2001 From: janbridley Date: Tue, 20 Feb 2024 11:03:25 -0500 Subject: [PATCH 09/11] Added more detail to return descriptor --- coxeter/shapes/convex_spheropolygon.py | 2 +- coxeter/shapes/convex_spheropolyhedron.py | 2 +- coxeter/shapes/ellipsoid.py | 2 +- coxeter/shapes/polygon.py | 2 +- coxeter/shapes/sphere.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/coxeter/shapes/convex_spheropolygon.py b/coxeter/shapes/convex_spheropolygon.py index 45e77f6d..897dad6f 100644 --- a/coxeter/shapes/convex_spheropolygon.py +++ b/coxeter/shapes/convex_spheropolygon.py @@ -293,7 +293,7 @@ def to_hoomd(self): Returns ------- dict - Dict containing a subset of shape properties. + Dict containing a subset of shape properties required for HOOMD function. """ old_centroid = self._polygon.centroid data = self.to_json(["vertices", "radius", "area"]) diff --git a/coxeter/shapes/convex_spheropolyhedron.py b/coxeter/shapes/convex_spheropolyhedron.py index fef506f2..71a74127 100644 --- a/coxeter/shapes/convex_spheropolyhedron.py +++ b/coxeter/shapes/convex_spheropolyhedron.py @@ -354,7 +354,7 @@ def to_hoomd(self): Returns ------- dict - Dict containing a subset of shape properties. + Dict containing a subset of shape properties required for HOOMD function. """ old_centroid = self._polyhedron.centroid self._polyhedron.centroid = np.array([0, 0, 0]) diff --git a/coxeter/shapes/ellipsoid.py b/coxeter/shapes/ellipsoid.py index eef18bee..35cc6a9d 100644 --- a/coxeter/shapes/ellipsoid.py +++ b/coxeter/shapes/ellipsoid.py @@ -254,7 +254,7 @@ def to_hoomd(self): Returns ------- dict - Dict containing a subset of shape properties. + Dict containing a subset of shape properties required for HOOMD function. """ old_centroid = self.centroid self.centroid = np.array([0, 0, 0]) diff --git a/coxeter/shapes/polygon.py b/coxeter/shapes/polygon.py index 3c4d1aeb..aaff2776 100644 --- a/coxeter/shapes/polygon.py +++ b/coxeter/shapes/polygon.py @@ -794,7 +794,7 @@ def to_hoomd(self): Returns ------- dict - Dict containing a subset of shape properties. + Dict containing a subset of shape properties required for HOOMD function. """ old_centroid = self.centroid self.centroid = np.array([0, 0, 0]) diff --git a/coxeter/shapes/sphere.py b/coxeter/shapes/sphere.py index 8665a621..ce36d97b 100644 --- a/coxeter/shapes/sphere.py +++ b/coxeter/shapes/sphere.py @@ -239,7 +239,7 @@ def to_hoomd(self): Returns ------- dict - Dict containing a subset of shape properties. + Dict containing a subset of shape properties required for HOOMD function. """ old_centroid = self.centroid self.centroid = np.array([0, 0, 0]) From 80b91b3bb69ab1400145b9c8af68022a0edf3a81 Mon Sep 17 00:00:00 2001 From: janbridley Date: Tue, 20 Feb 2024 11:31:12 -0500 Subject: [PATCH 10/11] Updated credits --- Credits.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Credits.rst b/Credits.rst index 4bc7a188..7a72c4b1 100644 --- a/Credits.rst +++ b/Credits.rst @@ -123,7 +123,7 @@ Jen Bradley * Added ``simplices``, ``equations``, and ``face_centroids`` properties to the ConvexPolyhedron class. * Optimized pytest configurations for more efficient use of local and remote resources. -* Added ``to_hoomd`` export method for use with simulation tools. +* Added ``to_json`` and ``to_hoomd`` export methods. Domagoj Fijan From 6e886f4ebebf5abb7add3a52b757e28490a42f68 Mon Sep 17 00:00:00 2001 From: janbridley Date: Tue, 20 Feb 2024 11:44:41 -0500 Subject: [PATCH 11/11] Updated explanation for centering shapes in to_hoomd --- coxeter/shapes/convex_spheropolygon.py | 2 +- coxeter/shapes/convex_spheropolyhedron.py | 2 +- coxeter/shapes/ellipsoid.py | 2 +- coxeter/shapes/polygon.py | 2 +- coxeter/shapes/polyhedron.py | 2 +- coxeter/shapes/sphere.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/coxeter/shapes/convex_spheropolygon.py b/coxeter/shapes/convex_spheropolygon.py index 897dad6f..35652fa3 100644 --- a/coxeter/shapes/convex_spheropolygon.py +++ b/coxeter/shapes/convex_spheropolygon.py @@ -284,7 +284,7 @@ def to_hoomd(self): The vertices of the shape. * centroid (list(float)) The centroid of the shape. - This is set to [0,0,0] to improve HOOMD performance. + This is set to [0,0,0] per HOOMD's spec. * sweep_radius (float): The rounding radius of the shape. * area (float) diff --git a/coxeter/shapes/convex_spheropolyhedron.py b/coxeter/shapes/convex_spheropolyhedron.py index 71a74127..56a52cea 100644 --- a/coxeter/shapes/convex_spheropolyhedron.py +++ b/coxeter/shapes/convex_spheropolyhedron.py @@ -345,7 +345,7 @@ def to_hoomd(self): The vertices of the shape. * centroid (list(float)) The centroid of the shape. - This is set to [0,0,0] to improve HOOMD performance. + This is set to [0,0,0] per HOOMD's spec. * sweep_radius (float): The rounding radius of the shape. * volume (float) diff --git a/coxeter/shapes/ellipsoid.py b/coxeter/shapes/ellipsoid.py index 35cc6a9d..37ace1dc 100644 --- a/coxeter/shapes/ellipsoid.py +++ b/coxeter/shapes/ellipsoid.py @@ -245,7 +245,7 @@ def to_hoomd(self): half axis of ellipsoid in the z direction * centroid (list(float)) The centroid of the shape. - This is set to [0,0,0] to improve HOOMD performance. + This is set to [0,0,0] per HOOMD's spec. * volume (float) The volume of the shape. * moment_inertia (list(list)) diff --git a/coxeter/shapes/polygon.py b/coxeter/shapes/polygon.py index aaff2776..3f0a32d9 100644 --- a/coxeter/shapes/polygon.py +++ b/coxeter/shapes/polygon.py @@ -783,7 +783,7 @@ def to_hoomd(self): The vertices of the shape. * centroid (list(float)) The centroid of the shape. - This is set to [0,0,0] to improve HOOMD performance. + This is set to [0,0,0] per HOOMD's spec. * sweep_radius (float): The rounding radius of the shape (0.0). * area (float) diff --git a/coxeter/shapes/polyhedron.py b/coxeter/shapes/polyhedron.py index ff3fc3e9..6e817aad 100644 --- a/coxeter/shapes/polyhedron.py +++ b/coxeter/shapes/polyhedron.py @@ -997,7 +997,7 @@ def to_hoomd(self): The faces of the shape. * centroid (list(float)) The centroid of the shape. - This is set to [0,0,0] to improve HOOMD performance. + This is set to [0,0,0] per HOOMD's spec. * sweep_radius (float): The rounding radius of the shape (0.0). * volume (float) diff --git a/coxeter/shapes/sphere.py b/coxeter/shapes/sphere.py index ce36d97b..35d9fcf0 100644 --- a/coxeter/shapes/sphere.py +++ b/coxeter/shapes/sphere.py @@ -230,7 +230,7 @@ def to_hoomd(self): The diameter of the sphere, equal to twice the radius. * centroid (list(float)) The centroid of the shape. - This is set to [0,0,0] to improve HOOMD performance. + This is set to [0,0,0] per HOOMD's spec. * volume (float) The volume of the shape. * moment_inertia (list(list))