Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
796 changes: 796 additions & 0 deletions coxeter/shapes/_distance2d.py

Large diffs are not rendered by default.

1,127 changes: 1,127 additions & 0 deletions coxeter/shapes/_distance3d.py

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions coxeter/shapes/convex_polyhedron.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,14 @@ def __init__(self, vertices):
self._sort_simplices()
self.sort_faces()

# For shortest distance functions
self._edge_face_neighbors = None
self._vertex_zones = None
self._edge_zones = None
self._face_zones = None
self._vertex_normals = None
self._edge_normals = None

def _consume_hull(self, hull):
"""Extract data from ConvexHull.

Expand Down
83 changes: 83 additions & 0 deletions coxeter/shapes/convex_spheropolygon.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@

import numpy as np

from ._distance2d import (
spheropolygon_shortest_displacement_to_surface,
)
from .base_classes import Shape2D
from .convex_polygon import ConvexPolygon, _is_convex
from .polygon import _align_points_by_normal
Expand Down Expand Up @@ -57,6 +60,10 @@ def __init__(self, vertices, radius, normal=None):
if not _is_convex(self.vertices, self._polygon.normal):
raise ValueError("The vertices do not define a convex polygon.")

self._vertex_zones = None
self._edge_zones = None
self._face_zones = None

@property
def polygon(self):
""":class:`~coxeter.shapes.ConvexPolygon`: The underlying polygon."""
Expand Down Expand Up @@ -107,6 +114,9 @@ def _rescale(self, scale):
"""
self.polygon._vertices *= scale
self.radius *= scale
self._vertex_zones = None
self._edge_zones = None
self._face_zones = None

@property
def signed_area(self):
Expand Down Expand Up @@ -302,3 +312,76 @@ def to_hoomd(self):

self._polygon.centroid = old_centroid
return hoomd_dict

def shortest_distance_to_surface(
self, points, translation_vector=np.array([0, 0, 0])
):
"""
Solves for the shortest distance (magnitude) between points and
the surface of a polygon.

This function calculates the shortest distance by partitioning
the space around a polygon into zones: vertex, edge, and face.
Determining the zone(s) a point lies in, determines the distance
calculation(s) done. For a vertex zone,the distance is calculated
between a point and the vertex. For an edge zone, the distance is
calculated between a point and the edge. For a face zone, the
distance is calculated between a point and the face. Zones are
allowed to overlap, and points can be in more than one zone. By
taking the minimum of all the calculated distances, the shortest
distances are found.

Args:
points (list or :class:`numpy.ndarray`):
positions of the points [shape = (n_points,3) or (n_points,2)]
translation_vector (list or :class:`numpy.ndarray`):
translation vector of the polygon [shape = (3,) of (2,)]
(Default value: [0,0,0])

Returns
-------
:class:`numpy.ndarray`:
the shortest distance of each point to the surface
[shape = (n_points,)]
"""
return np.linalg.norm(
spheropolygon_shortest_displacement_to_surface(
self._polygon, self.radius, points, translation_vector
),
axis=1,
)

def shortest_displacement_to_surface(
self, points, translation_vector=np.array([0, 0, 0])
):
"""
Solves for the shortest displacement (vector) between points and
the surface of a polygon.

This function calculates the shortest displacement by partitioning
the space around a polygon into zones: vertex, edge, and face.
Determining the zone(s) a point lies in, determines the displacement
calculation(s) done. For a vertex zone, the displacement is
calculated between a point and the vertex. For an edge zone, the
displacement is calculated between a point and the edge. For a face
zone, the displacement is calculated between a point and the face.
Zones are allowed to overlap, and points can be in more than one
zone. By taking the minimum of all the distances of the calculated
displacements, the shortest displacements are found.

Args:
points (list or :class:`numpy.ndarray`):
positions of the points [shape = (n_points,3) or (n_points,2)]
translation_vector (list or :class:`numpy.ndarray`):
translation vector of the polygon [shape = (3,) or (2,)]
(Default value: [0,0,0])

Returns
-------
:class:`numpy.ndarray`:
the shortest displacement of each point to the surface
[shape = (n_points, 3)]
"""
return spheropolygon_shortest_displacement_to_surface(
self._polygon, self.radius, points, translation_vector
)
86 changes: 86 additions & 0 deletions coxeter/shapes/convex_spheropolyhedron.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@

import numpy as np

from ._distance3d import (
shortest_distance_to_surface,
spheropolyhedron_shortest_displacement_to_surface,
)
from .base_classes import Shape3D
from .convex_polyhedron import ConvexPolyhedron
from .utils import _hoomd_dict_mapping, _map_dict_keys
Expand Down Expand Up @@ -69,6 +73,13 @@ def __init__(self, vertices, radius):
self._polyhedron = ConvexPolyhedron(vertices)
self.radius = radius

self._edge_face_neighbors = None
self._vertex_zones = None
self._edge_zones = None
self._face_zones = None
self._vertex_normals = None
self._edge_normals = None

@property
def gsd_shape_spec(self):
"""dict: Get a :ref:`complete GSD specification <gsd:shapes>`.""" # noqa: D401
Expand Down Expand Up @@ -97,6 +108,9 @@ def _rescale(self, scale):
"""
self.polyhedron._rescale(scale)
self.radius *= scale
self._vertex_zones = None
self._edge_zones = None
self._face_zones = None

@property
def volume(self):
Expand Down Expand Up @@ -364,3 +378,75 @@ def to_hoomd(self):

self._polyhedron.centroid = old_centroid
return hoomd_dict

def shortest_distance_to_surface(
self, points, translation_vector=np.array([0, 0, 0])
):
"""
Solves for the shortest distance (magnitude) between points and
the surface of a spheropolyhedron. If the point lies inside the
spheropolyhedron, the distance is negative.

This function calculates the shortest distance by partitioning
the space around a spheropolyhedron into zones: vertex, edge, and face.
Determining the zone(s) a point lies in, determines the distance
calculation(s) done. For a vertex zone,the distance is calculated
between a point and the vertex. For an edge zone, the distance is
calculated between a point and the edge. For a face zone, the
distance is calculated between a point and the face. Zones are
allowed to overlap, and points can be in more than one zone. By
taking the minimum of all the calculated distances, the shortest
distances are found.

Args:
points (list or :class:`numpy.ndarray`):
positions of the points [shape = (n_points, 3)]
translation_vector (list or :class:`numpy.ndarray`):
translation vector of the spheropolyhedron [shape = (3,)]
(Default value: [0,0,0])

Returns
-------
:class:`numpy.ndarray`:
the shortest distance of each point to the surface
[shape = (n_points,)]
"""
return (
shortest_distance_to_surface(self._polyhedron, points, translation_vector)
- self.radius
)

def shortest_displacement_to_surface(
self, points, translation_vector=np.array([0, 0, 0])
):
"""
Solves for the shortest displacement (vector) between points and
the surface of a spheropolyhedron.

This function calculates the shortest displacement by partitioning
the space around a spheropolyhedron into zones: vertex, edge, and face.
Determining the zone(s) a point lies in, determines the displacement
calculation(s) done. For a vertex zone, the displacement is
calculated between a point and the vertex. For an edge zone, the
displacement is calculated between a point and the edge. For a face
zone, the displacement is calculated between a point and the face.
Zones are allowed to overlap, and points can be in more than one
zone. By taking the minimum of all the distances of the calculated
displacements, the shortest displacements are found.

Args:
points (list or :class:`numpy.ndarray`):
positions of the points [shape = (n_points, 3)]
translation_vector (list or :class:`numpy.ndarray`):
translation vector of the spheropolyhedron [shape = (3,)]
(Default value: [0,0,0])

Returns
-------
:class:`numpy.ndarray`:
the shortest displacement of each point to the surface
[shape = (n_points, 3)]
"""
return spheropolyhedron_shortest_displacement_to_surface(
self._polyhedron, self.radius, points, translation_vector
)
126 changes: 126 additions & 0 deletions coxeter/shapes/polygon.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@

from ..extern.bentley_ottmann import poly_point_isect
from ..extern.polytri import polytri
from ._distance2d import (
get_edge_zones,
get_face_zones,
get_vert_zones,
shortest_displacement_to_surface,
shortest_distance_to_surface,
)
from .base_classes import Shape2D
from .circle import Circle
from .utils import (
Expand Down Expand Up @@ -132,6 +139,10 @@ class Polygon(Shape2D):
def __init__(self, vertices, normal=None, planar_tolerance=1e-5, test_simple=True):
vertices = np.array(vertices, dtype=np.float64)

self._vertex_zones = None
self._edge_zones = None
self._face_zones = None

if len(vertices.shape) != 2 or vertices.shape[1] not in (2, 3):
raise ValueError("Vertices must be specified as an Nx2 or Nx3 array.")
if len(vertices) < 3:
Expand Down Expand Up @@ -215,6 +226,9 @@ def _rescale(self, scale):
Scale factor.
"""
self._vertices *= scale
self._vertex_zones = None
self._edge_zones = None
self._face_zones = None

@property
def perimeter(self):
Expand Down Expand Up @@ -418,6 +432,19 @@ def centroid(self):
def centroid(self, value):
self._vertices += np.asarray(value) - self.centroid

if self._vertex_zones is not None:
self._vertex_zones["bounds"] += self._vertex_zones["constraint"] @ (
np.asarray(value) - self.centroid
)
if self._edge_zones is not None:
self._edge_zones["bounds"] += self._edge_zones["constraint"] @ (
np.asarray(value) - self.centroid
)
if self._face_zones is not None:
self._face_zones["bounds"] += self._face_zones["constraint"] @ (
np.asarray(value) - self.centroid
)

@property
def edges(self):
""":class:`numpy.ndarray`: Get the polygon's edges.
Expand Down Expand Up @@ -838,3 +865,102 @@ def to_hoomd(self):

self.centroid = old_centroid
return hoomd_dict

@property
def vertex_zones(self):
"""dict: Get the constraints and bounds needed to partition the
volume surrounding a polygon into zones where the shortest
distance from any point that is within a vertex zone is the
distance between the point and the corresponding vertex.
"""
if self._vertex_zones is None:
self._vertex_zones = get_vert_zones(self)
return self._vertex_zones

@property
def edge_zones(self):
"""dict: Get the constraints and bounds needed to partition
the volume surrounding a polygon into zones where the
shortest distance from any point that is within an edge zone
is the distance between the point and the corresponding edge.
"""
if self._edge_zones is None:
self._edge_zones = get_edge_zones(self)
return self._edge_zones

@property
def face_zones(self):
"""dict: Get the constraints and bounds needed to partition
the volume surrounding a polygon into zones where the shortest
distance from any point that is within the face zone
is the distance between the point and the face of the polygon.
"""
if self._face_zones is None:
self._face_zones = get_face_zones(self)
return self._face_zones

def shortest_distance_to_surface(
self, points, translation_vector=np.array([0, 0, 0])
):
"""
Solves for the shortest distance (magnitude) between points and
the surface of a polygon.

This function calculates the shortest distance by partitioning
the space around a polygon into zones: vertex, edge, and face.
Determining the zone(s) a point lies in, determines the distance
calculation(s) done. For a vertex zone,the distance is calculated
between a point and the vertex. For an edge zone, the distance is
calculated between a point and the edge. For a face zone, the
distance is calculated between a point and the face. Zones are
allowed to overlap, and points can be in more than one zone. By
taking the minimum of all the calculated distances, the shortest
distances are found.

Args:
points (list or :class:`numpy.ndarray`):
positions of the points [shape = (n_points,3) or (n_points,2)]
translation_vector (list or :class:`numpy.ndarray`):
translation vector of the polygon [shape = (3,) of (2,)]
(Default value: [0,0,0])

Returns
-------
:class:`numpy.ndarray`:
the shortest distance of each point to the surface
[shape = (n_points,)]
"""
return shortest_distance_to_surface(self, points, translation_vector)

def shortest_displacement_to_surface(
self, points, translation_vector=np.array([0, 0, 0])
):
"""
Solves for the shortest displacement (vector) between points and
the surface of a polygon.

This function calculates the shortest displacement by partitioning
the space around a polygon into zones: vertex, edge, and face.
Determining the zone(s) a point lies in, determines the displacement
calculation(s) done. For a vertex zone, the displacement is
calculated between a point and the vertex. For an edge zone, the
displacement is calculated between a point and the edge. For a face
zone, the displacement is calculated between a point and the face.
Zones are allowed to overlap, and points can be in more than one
zone. By taking the minimum of all the distances of the calculated
displacements, the shortest displacements are found.

Args:
points (list or :class:`numpy.ndarray`):
positions of the points [shape = (n_points,3) or (n_points,2)]
translation_vector (list or :class:`numpy.ndarray`):
translation vector of the polygon [shape = (3,) or (2,)]
(Default value: [0,0,0])

Returns
-------
:class:`numpy.ndarray`:
the shortest displacement of each point to the surface
[shape = (n_points, 3)]
"""
return shortest_displacement_to_surface(self, points, translation_vector)
Loading
Loading