import numpy as np
import ddg
import ddg.math.projective as pmath
from ddg import nonexact
from ddg.halfedge._copy import copy
from ddg.halfedge._get import single_edges
from ddg.halfedge._modify import remove_edge, remove_vertex
[docs]def dehomogenize(
hds,
co_attr_homogeneous="co",
co_attr_affine="co",
in_place=True,
atol=None,
rtol=None,
):
"""Dehomogenize a hds, removing infinity crossings.
Let x_i = 0 be the hyperplane at infinity. The function works by deleting
vertices that are in this plane and edges (and therefore faces) that cross
this plane. **This depends on the representative vectors!**
Parameters
----------
hds : Surface
co_attr_homogeneous : str (default='co')
Name of attribute that stores homogeneous coordinates.
co_attr_affine : str (default='co')
Name of attribute to store affine coordinates after dehomogenizing.
in_place : bool (default=True)
Whether to modify the `hds` or create a copy. The copy will only have
the attributes `co_attr_homogeneous` and `co_attr_affine`.
Returns
-------
hds : Surface
The dehomogenized hds. Either the original, modified object or a copy
with vertex attributes with names given in `co_attr_homogeneous` and
`co_attr_affine`.
"""
if not in_place:
# co_attr_affine is created/set later
hds = copy(hds, verts_attr_list=[co_attr_homogeneous])
# Delete vertices at infinity
for v in hds.verts:
p = getattr(v, co_attr_homogeneous)
if nonexact.isclose(p[-1], 0, atol=atol, rtol=rtol):
remove_vertex(v)
# Delete edges that pass through the plane at infinity
# Convert this generator to list first to get all elements. Modifying the
# hds while using the generator can lead to "AttributeError: _succ"
for e in list(single_edges(hds)):
p1 = getattr(e.head, co_attr_homogeneous)
p2 = getattr(e.tail, co_attr_homogeneous)
if np.sign(p1[-1]) != np.sign(p2[-1]):
remove_edge(e)
# Set new dehomogenized coordinate attribute
# Node attributes are descriptors stored in class attributes
if not hasattr(hds.verts, co_attr_affine):
hds.verts.add_attribute(co_attr_affine)
for v in hds.verts:
p = getattr(v, co_attr_homogeneous)
p = pmath.dehomogenize(p, atol=atol, rtol=rtol)
setattr(v, co_attr_affine, p)
return hds
[docs]def laplace_invariant(edge, co_attr="co", line_family_attr=None, atol=1e-4):
r"""Function to compute the Laplace invariant of an interior edge.
Namely the cross ratio q(e.tail, L^{(i)}_{j}, e.head, L^{(i)}_{-j}),
where L^{(i)}_{\pm j} are the intersection points of the previous and next
lines of the same line family with the line given by the given edge.
A line family is interpreted as either one of the two directions (e.g.
horizontal and vertical) introduced by the preimage lattice of \mathbb{Z}^2.
If a boundary edge (or an edge where e.opp is a boundary edge) is given the
return value is None.
Parameters
----------
edge: ddg.halfedge.Surface.edges
Edge to compute the laplace invariant from.
co_attr: str (default='co')
Coordinate attribute at the vertices used for computation.
line_family_attr: NodeAttribute
Attribute of edges, that distinguishes the horizontal and vertical
family of lines.
If given, and True on an edge, than the reciprocal of the computed
value is set to this edge.
atol: float (default=1e-4)
Tolerance to verify computations for coefficients of the planes induces
by the quads of either two edges.
Returns
-------
float, None
float for interior edges and None for boundary edges
"""
if ddg.halfedge._get.is_boundary_edge(edge) or ddg.halfedge._get.is_boundary_edge(
edge.opp
):
return None
Q1 = [e.tail for e in ddg.halfedge._get.edge_loop(edge)]
Q2 = [
e.tail
for e in ddg.halfedge._get.edge_loop(
ddg.halfedge._get.opposite_edge_in_face(edge.opp).opp
)
]
Q1 = np.array([ddg.math.projective.homogenize(getattr(v, co_attr)) for v in Q1])
Q2 = np.array([ddg.math.projective.homogenize(getattr(v, co_attr)) for v in Q2])
C1 = ddg.math.linalg.linear_dependence(Q1, atol=atol)
C2 = ddg.math.linalg.linear_dependence(Q2, atol=atol)
assert nonexact.allclose(np.dot(C1, Q1), 0, atol=atol)
assert nonexact.allclose(np.dot(C2, Q2), 0, atol=atol)
value = (C1[1] * C2[3]) / (C1[0] * C2[2])
if line_family_attr:
if line_family_attr[edge]:
value = 1 / value
return value
[docs]def laplace_invariant_cross(v, laplace_inv_attr):
r"""Function to compute the product of the laplace invariants of the four
edges incident to a single vertex.
Enumerating successive edges in a vertex star (L_0 * L_2) / (L_1 * L_3) is
the value that is returned. For discrete Koenigs nets this value is 1.
Parameters
----------
v: ddg.halfedge.Surface.verts
Vertex to compute the laplace invariant from.
laplace_inv_attr: NodeAttribute of edges
Attribute to read the laplace invariants of the edges from.
Can be set e.g. using the laplace_invariant function.
Returns
-------
float, None
None if the given vertex is a boundary vertex else the computed value"""
if ddg.halfedge._get.is_boundary_vertex(v):
return None
out_edges = [e for e in ddg.halfedge._get.out_edges(v)]
assert len(out_edges) == 4
laplace_inv = [laplace_inv_attr[e] for e in out_edges]
laplace_inv_cross = (laplace_inv[0] * laplace_inv[2]) / (
laplace_inv[1] * laplace_inv[3]
)
return laplace_inv_cross
[docs]def laplace_invariant_quad(face, laplace_inv_attr):
r"""Function to return the product of the laplace invariants of the four
edges incident to a face. Enumerating successive edges in a face
(L_0 * L_2) / (L_1 * L_3) is the value that is returned.
For a Q-net that is build from the intersection points of the diagonals of
faces of a discrete Koenigs net (the Doliwa dual) this value is 1.
Parameters
----------
face: ddg.halfedge.Surface.faces
Face to compute the laplace invariant from.
laplace_inv_attr: NodeAttribute of edges
Attribute to read the laplace invariants of the edges from.
Can be set e.g. using the laplace_invariant function.
Returns
-------
float, None
None if the given face is a boundary face else the computed value"""
if not np.alltrue(
[
not ddg.halfedge._get.is_boundary_vertex(v)
for v in ddg.halfedge._get.face_vertices(face)
]
):
return None
face_edges = [e for e in ddg.halfedge._get.edge_loop(face.edge)]
assert len(face_edges) == 4
laplace_inv = [laplace_inv_attr[e] for e in face_edges]
laplace_inv_quad = (laplace_inv[0] * laplace_inv[2]) / (
laplace_inv[1] * laplace_inv[3]
)
return laplace_inv_quad