Source code for conics.geometry

# conics - Python library for dealing with conics
#
# Copyright 2025 Sergiu Deitsch <sergiu.deitsch@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import numpy as np
import numpy.typing as npt


[docs] def hnormalized(p: npt.NDArray[np.floating]) -> npt.NDArray[np.floating]: """Projects points from an :math:`n`-dimensional space onto :math:`n-1`-dimensional one. Parameters ---------- p: numpy.ndarray (n, m) The :math:`n`-dimensional points to be projected stored in :math:`m` columns. Returns ------- numpy.ndarray (n-1, m): The projected points. """ return p[:-1] / p[-1]
[docs] def homogeneous(p: npt.ArrayLike) -> npt.NDArray[np.floating]: """Homogenizes :math:`n`-dimensional points by appending all ones to the last dimension. Parameters ---------- p: numpy.ndarray (n, m) The :math:`n`-dimensional points to be homogenized stored in :math:`m` columns. Returns ------- numpy.ndarray (n+1, m) The homogenized points. """ p = np.atleast_2d(p).T return np.vstack((p, np.ones_like(p[0])))
[docs] def line_through(a: npt.ArrayLike, b: npt.ArrayLike) -> npt.NDArray[np.floating]: """Constructs a homogeneous lines from 2-D points. Parameters ---------- a: array_like (2, ) The start point of the line segment. b: array_like (2, ) The end point of the line segment. Returns ------- numpy.ndarray: The homogeneous line that connects `a` and `b`. """ start = np.append(a, 1) end = np.append(b, 1) return np.cross(start, end)
[docs] def line_intersection(l1: npt.ArrayLike, l2: npt.ArrayLike) -> npt.NDArray[np.floating]: """Computes the intersection between two homogeneous lines. Parameters ---------- l1: array_like (3, ) The first line. l2: array_like (3, ) The second line. Returns ------- numpy.ndarray: The homogeneous intersection point. """ return np.cross(l1, l2)
[docs] def rot2d(alpha: float) -> npt.NDArray[np.float64]: """Constructs a 2-D rotation matrix given a specified angle. Parameters ---------- alpha: float The rotation angle in the plane, in radians. Returns ------- numpy.ndarray (2, 2): The 2-D rotation matrix. """ c = np.cos(alpha) s = np.sin(alpha) return np.array([[c, -s], [s, c]])
[docs] def projectively_unique(p: npt.ArrayLike, atol: float = 1e-4) -> np.ndarray: """Determines unique points in the projective space given a set of points. Parameters ---------- p: array_like (n, m) The possibly non-unique but equivalent up to scale set :math:`d`-dimensional points stored in :math:`m` columns. atol: float The absolute comparison tolerance with respect to the norm of the cross-product of each point pair. The corresponding norm must be (close to) zero for points that are equivalent up to scale. The tolerance accounts for the corresponding round-off error. Returns ------- numpy.ndarray: The subset of points that are unique in projective space. """ p = np.asarray(p) n = p.shape[-1] # The indices of the strictly upper-triangular part of the adjacency # matrix i, j = np.triu_indices(n, k=1) # Pairwise cross-product between each intersection for filtering out # multiplicative multiples c = np.cross(p.T[i], p.T[j]) # Compute the norm of the cross-products which is zero for a pair of # intersections that are equivalent up to scale d = np.linalg.norm(c, axis=-1) m = np.isclose(d, 0, atol=atol) # mark points as duplicate if cross product with an earlier point has 0 norm duplicate = np.bincount(j, m) > 0 return p[:, ~duplicate]