Source code for pyrigi.framework._general

"""
This module provides some general functionality for frameworks.
"""

from __future__ import annotations

from itertools import combinations

import numpy as np
import sympy as sp
from sympy import Matrix

import pyrigi.graph._utils._input_check as _graph_input_check
from pyrigi._utils._conversion import point_to_vector
from pyrigi._utils._zero_check import is_zero, is_zero_vector
from pyrigi.data_type import Edge, Number, Point, Vertex
from pyrigi.framework.base import FrameworkBase


[docs] def is_quasi_injective( framework: FrameworkBase, numerical: bool = False, tolerance: float = 1e-9 ) -> bool: """ Return whether the realization is quasi-injective. Definitions ----------- :prf:ref:`Quasi-injectivity <def-realization>` Parameters ---------- framework: numerical: Whether the check is symbolic (default) or numerical. tolerance: Used tolerance when checking numerically. Notes ----- For comparing whether two vectors are the same, :func:`.misc.is_zero_vector` is used. See its documentation for the description of the parameters. """ framework._warn_numerical_coord(is_quasi_injective, numerical) for u, v in framework._graph.edges: edge_vector = framework[u] - framework[v] if is_zero_vector(edge_vector, numerical, tolerance): return False return True
[docs] def is_injective( framework: FrameworkBase, numerical: bool = False, tolerance: float = 1e-9 ) -> bool: """ Return whether the realization is injective. Parameters ---------- framework: numerical: Whether the check is symbolic (default) or numerical. tolerance: Used tolerance when checking numerically. Notes ----- For comparing whether two vectors are the same, :func:`.misc.is_zero_vector` is used. See its documentation for the description of the parameters. """ framework._warn_numerical_coord(is_injective, numerical) for u, v in combinations(framework._graph.nodes, 2): edge_vector = framework[u] - framework[v] if is_zero_vector(edge_vector, numerical, tolerance): return False return True
[docs] def is_congruent_realization( framework: FrameworkBase, other_realization: dict[Vertex, Point] | dict[Vertex, Matrix], numerical: bool = False, tolerance: float = 1e-9, ) -> bool: """ Return whether the given realization is congruent to the framework. Definitions ----------- :prf:ref:`Congruent frameworks <def-equivalent-framework>` Parameters ---------- framework: other_realization The realization for checking the congruence. numerical Whether the check is symbolic (default) or numerical. tolerance Used tolerance when checking numerically. """ framework._warn_numerical_coord(is_congruent_realization, numerical) _graph_input_check.is_vertex_order( framework._graph, list(other_realization.keys()), "other_realization" ) for u, v in combinations(framework._graph.nodes, 2): edge_vec = framework[u] - framework[v] dist_squared = (edge_vec.T * edge_vec)[0, 0] other_edge_vec = point_to_vector(other_realization[u]) - point_to_vector( other_realization[v] ) otherdist_squared = (other_edge_vec.T * other_edge_vec)[0, 0] difference = sp.simplify(dist_squared - otherdist_squared) if not is_zero(difference, numerical=numerical, tolerance=tolerance): return False return True
[docs] def is_congruent( framework: FrameworkBase, other_framework: FrameworkBase, numerical: bool = False, tolerance: float = 1e-9, ) -> bool: """ Return whether the given framework is congruent to the framework. Definitions ----------- :prf:ref:`Congruent frameworks <def-equivalent-framework>` Parameters ---------- framework: other_framework The framework for checking the congruence. numerical Whether the check is symbolic (default) or numerical. tolerance Used tolerance when checking numerically. """ framework._input_check_underlying_graphs(other_framework) return is_congruent_realization( framework, other_framework._realization, numerical, tolerance )
[docs] def is_equivalent_realization( framework: FrameworkBase, other_realization: dict[Vertex, Point] | dict[Vertex, Matrix], numerical: bool = False, tolerance: float = 1e-9, ) -> bool: """ Return whether the given realization is equivalent to the framework. Definitions ----------- :prf:ref:`Equivalent frameworks <def-equivalent-framework>` Parameters ---------- framework: other_realization The realization for checking the equivalence. numerical Whether the check is symbolic (default) or numerical. tolerance Used tolerance when checking numerically. """ framework._warn_numerical_coord(is_equivalent_realization, numerical) _graph_input_check.is_vertex_order( framework._graph, list(other_realization.keys()), "other_realization" ) for u, v in framework._graph.edges: edge_vec = framework[u] - framework[v] dist_squared = (edge_vec.T * edge_vec)[0, 0] other_edge_vec = point_to_vector(other_realization[u]) - point_to_vector( other_realization[v] ) otherdist_squared = (other_edge_vec.T * other_edge_vec)[0, 0] difference = sp.simplify(otherdist_squared - dist_squared) if not is_zero(difference, numerical=numerical, tolerance=tolerance): return False return True
[docs] def is_equivalent( framework: FrameworkBase, other_framework: FrameworkBase, numerical: bool = False, tolerance: float = 1e-9, ) -> bool: """ Return whether the given framework is equivalent to the framework. Definitions ----------- :prf:ref:`Equivalent frameworks <def-equivalent-framework>` Parameters ---------- framework: other_framework The framework for checking the equivalence. numerical Whether the check is symbolic (default) or numerical. tolerance Used tolerance when checking numerically. """ framework._input_check_underlying_graphs(other_framework) return is_equivalent_realization( framework, other_framework._realization, numerical, tolerance )
[docs] def edge_lengths( framework: FrameworkBase, numerical: bool = False ) -> dict[Edge, Number]: """ Return the dictionary of the edge lengths. Parameters ------- framework: numerical: If ``True``, numerical positions are used for the computation of the edge lengths. Examples -------- >>> G = Graph([(0,1), (1,2), (2,3), (0,3)]) >>> F = Framework(G, {0:[0,0], 1:[1,0], 2:[1,'1/2 * sqrt(5)'], 3:['1/2','4/3']}) >>> edge_lengths(F, numerical=False) {(0, 1): 1, (0, 3): sqrt(73)/6, (1, 2): sqrt(5)/2, (2, 3): sqrt((-4/3 + sqrt(5)/2)**2 + 1/4)} >>> edge_lengths(F, numerical=True) {(0, 1): 1.0, (0, 3): 1.4240006242195884, (1, 2): 1.118033988749895, (2, 3): 0.5443838790578374} """ # noqa: E501 if numerical: points = framework.realization(as_points=True, numerical=True) return { tuple(e): float( np.linalg.norm(np.array(points[e[0]]) - np.array(points[e[1]])) ) for e in framework._graph.edges } else: points = framework.realization(as_points=True) return { tuple(e): sp.sqrt( sum([(x - y) ** 2 for x, y in zip(points[e[0]], points[e[1]])]) ) for e in framework._graph.edges }