Source code for pyrigi.framework._rigidity.infinitesimal

"""
This module provides algorithms related to infinitesimal rigidity of frameworks.
"""

from __future__ import annotations

import warnings
from copy import deepcopy

import numpy as np
import sympy as sp
from sympy import Matrix, binomial, flatten

import pyrigi.graph._utils._input_check as _graph_input_check
from pyrigi._utils._conversion import sympy_expr_to_float
from pyrigi._utils._zero_check import is_zero_vector
from pyrigi._utils.linear_algebra import _normalize_flex, _reduced_null_space
from pyrigi.data_type import (
    Edge,
    InfFlex,
    Number,
    Sequence,
    Vertex,
)
from pyrigi.framework.base import FrameworkBase
from pyrigi.graph import _general as graph_general


[docs] def rigidity_matrix( framework: FrameworkBase, vertex_order: Sequence[Vertex] = None, edge_order: Sequence[Edge] = None, ) -> Matrix: r""" Construct the rigidity matrix of the framework. Definitions ----------- * :prf:ref:`Rigidity matrix <def-rigidity-matrix>` Parameters ---------- framework: vertex_order: A list of vertices, providing the ordering for the columns of the rigidity matrix. If none is provided, the list from :meth:`.Graph.vertex_list` is taken. edge_order: A list of edges, providing the ordering for the rows of the rigidity matrix. If none is provided, the list from :meth:`.Graph.edge_list` is taken. Examples -------- >>> F = Framework.Complete([(0,0),(2,0),(1,3)]) >>> rigidity_matrix(F) Matrix([ [-2, 0, 2, 0, 0, 0], [-1, -3, 0, 0, 1, 3], [ 0, 0, 1, -3, -1, 3]]) """ vertex_order = _graph_input_check.is_vertex_order(framework._graph, vertex_order) edge_order = _graph_input_check.is_edge_order(framework._graph, edge_order) # ``delta`` is responsible for distinguishing the edges (i,j) and (j,i) def delta(e, w): # the parameter e represents an edge # the parameter w represents a vertex if w == e[0]: return 1 if w == e[1]: return -1 return 0 return Matrix( [ flatten( [ delta(e, w) * (framework[e[0]] - framework[e[1]]) for w in vertex_order ] ) for e in edge_order ] )
[docs] def rigidity_matrix_rank( framework: FrameworkBase, numerical: bool = False, tolerance: float = 1e-9 ) -> int: """ Return the rank of the rigidity matrix. Definitions ----------- :prf:ref:`Rigidity matrix <def-rigidity-matrix>` Parameters ---------- framework: numerical: If ``True``, the rank of the rigidity matrix with entries as floats is computed. *Warning:* For ``numerical=True`` the numerical rank computation may produce different results than the computation over exact coordinates. tolerance: Numerical tolerance used for computing the rigidity matrix rank. Examples -------- >>> K4 = Framework.Complete([[0,0], [1,0], [1,1], [0,1]]) >>> rigidity_matrix_rank(K4) # the complete graph is a circuit 5 >>> K4.delete_edge([0,1]) >>> rigidity_matrix_rank(K4) # deleting a bar gives full rank 5 >>> K4.delete_edge([2,3]) >>> rigidity_matrix_rank(K4) #so now deleting an edge lowers the rank 4 """ framework._warn_numerical_coord(rigidity_matrix_rank, numerical) if numerical: F = FrameworkBase( framework._graph, framework.realization(as_points=True, numerical=True) ) return np.linalg.matrix_rank( np.array(rigidity_matrix(F)).astype(np.float64), tol=tolerance ) return rigidity_matrix(framework).rank()
[docs] def trivial_inf_flexes( framework: FrameworkBase, vertex_order: Sequence[Vertex] = None ) -> list[Matrix]: r""" Return a basis of the vector subspace of trivial infinitesimal flexes. Definitions ----------- :prf:ref:`Trivial infinitesimal flexes <def-trivial-inf-flex>` Parameters ---------- framework: vertex_order: A list of vertices, providing the ordering for the entries of the infinitesimal flexes. Examples -------- >>> F = Framework.Complete([(0,0), (2,0), (0,2)]) >>> trivial_inf_flexes(F) [Matrix([ [1], [0], [1], [0], [1], [0]]), Matrix([ [0], [1], [0], [1], [0], [1]]), Matrix([ [ 0], [ 0], [ 0], [ 2], [-2], [ 0]])] """ vertex_order = _graph_input_check.is_vertex_order(framework._graph, vertex_order) dim = framework.dim translations = [ Matrix.vstack(*[A for _ in vertex_order]) for A in Matrix.eye(dim).columnspace() ] basis_skew_symmetric = [] for i in range(1, dim): for j in range(i): A = Matrix.zeros(dim) A[i, j] = 1 A[j, i] = -1 basis_skew_symmetric += [A] inf_rot = [ Matrix.vstack(*[A * framework[v] for v in vertex_order]) for A in basis_skew_symmetric ] matrix_inf_flexes = Matrix.hstack(*(translations + inf_rot)) return matrix_inf_flexes.transpose().echelon_form().transpose().columnspace()
[docs] def nontrivial_inf_flexes(framework: FrameworkBase, **kwargs) -> list[Matrix]: """ Return non-trivial infinitesimal flexes. See :func:`~.inf_flexes` for possible keywords. Definitions ----------- :prf:ref:`Infinitesimal flex <def-inf-rigid-framework>` Examples -------- >>> import pyrigi.graphDB as graphs >>> F = Framework.Circular(graphs.CompleteBipartite(3, 3)) >>> nontrivial_inf_flexes(F) [Matrix([ [ 3/2], [-sqrt(3)/2], [ 1], [ 0], [ 0], [ 0], [ 3/2], [-sqrt(3)/2], [ 1], [ 0], [ 0], [ 0]])] """ return inf_flexes(framework, include_trivial=False, **kwargs)
[docs] def inf_flexes( framework: FrameworkBase, include_trivial: bool = False, vertex_order: Sequence[Vertex] = None, numerical: bool = False, tolerance: float = 1e-9, fixed_vertices: Sequence[Vertex] = [], ) -> list[Matrix] | list[list[float]]: r""" Return a basis of the space of infinitesimal flexes. Return a lift of a basis of the quotient of the vector space of infinitesimal flexes modulo trivial infinitesimal flexes, if ``include_trivial=False``. Return a basis of the vector space of infinitesimal flexes if ``include_trivial=True``. Definitions ----------- :prf:ref:`Infinitesimal flex <def-inf-flex>` Parameters ---------- framework: include_trivial: Boolean that decides, whether the trivial flexes should be included. vertex_order: A list of vertices, providing the ordering for the entries of the infinitesimal flexes. If none is provided, the list from :meth:`.Graph.vertex_list` is taken. numerical: Determines whether the output is symbolic (default) or numerical. tolerance: Used tolerance when computing the infinitesimal flex numerically. fixed_vertices: Fixed vertices are assigned a flex of length 0. The default value is an empty list. They can be provided as a sequence of vertices. Examples -------- >>> F = Framework.Complete([[0,0], [1,0], [1,1], [0,1]]) >>> F.delete_edges([(0,2), (1,3)]) >>> inf_flexes(F, include_trivial=False) [Matrix([ [1], [0], [1], [0], [0], [0], [0], [0]])] >>> F = Framework( ... Graph([[0, 1], [0, 3], [0, 4], [1, 3], [1, 4], [2, 3], [2, 4]]), ... {0: [0, 0], 1: [0, 1], 2: [0, 2], 3: [1, 2], 4: [-1, 2]}, ... ) >>> inf_flexes(F) [Matrix([ [0], [0], [0], [0], [0], [1], [0], [0], [0], [0]])] """ framework._warn_numerical_coord(inf_flexes, numerical) vertex_order = _graph_input_check.is_vertex_order(framework._graph, vertex_order) if not isinstance(fixed_vertices, Sequence) or ( fixed_vertices is not None and not all(v in framework._graph.nodes for v in fixed_vertices) ): raise ValueError( "All `fixed_vertices` need to be contained in the underlying graph." ) if ( len(fixed_vertices) > 2 or len(fixed_vertices) == 2 and not framework._graph.has_edge(*fixed_vertices) ): warnings.warn( "The `fixed_vertices` are not an edge of the graph, so the resulting " + "nontrivial infinitesimal flexes may be affected." ) # Define the columns of the rigidity matrix that are not fixed by `fixed_vertices` free_columns = [ framework.dim * i + j for (i, v) in enumerate(vertex_order) for j in range(framework.dim) if v not in fixed_vertices ] if include_trivial: if not numerical: rig_matrix = rigidity_matrix(framework, vertex_order=vertex_order) else: F = FrameworkBase( framework._graph, framework.realization(as_points=True, numerical=True) ) rig_matrix = np.array(rigidity_matrix(F, vertex_order=vertex_order)).astype( np.float64 ) all_inf_flexes = _reduced_null_space( rig_matrix, free_columns, numerical=numerical, tolerance=tolerance ) return [all_inf_flexes[:, i] for i in range(all_inf_flexes.shape[1])] if not numerical: rig_matrix = rigidity_matrix(framework, vertex_order=vertex_order) all_inf_flexes = _reduced_null_space(rig_matrix, free_columns, numerical=False) all_inf_flexes = [all_inf_flexes[:, i] for i in range(all_inf_flexes.shape[1])] triv_inf_flexes = trivial_inf_flexes(framework, vertex_order=vertex_order) s = len(triv_inf_flexes) extend_basis_matrix = Matrix.hstack(*triv_inf_flexes) for inf_flex in all_inf_flexes: tmp_matrix = Matrix.hstack(extend_basis_matrix, inf_flex) if not tmp_matrix.rank() == extend_basis_matrix.rank(): extend_basis_matrix = Matrix.hstack(extend_basis_matrix, inf_flex) basis = extend_basis_matrix.columnspace() return basis[s:] else: F = FrameworkBase( framework._graph, framework.realization(as_points=True, numerical=True) ) rig_matrix = np.array(rigidity_matrix(F, vertex_order=vertex_order)).astype( np.float64 ) all_inf_flexes = _reduced_null_space( rig_matrix, free_columns, numerical=True, tolerance=tolerance ) all_inf_flexes = [all_inf_flexes[:, i] for i in range(all_inf_flexes.shape[1])] triv_inf_flexes = trivial_inf_flexes(framework, vertex_order=vertex_order) s = len(triv_inf_flexes) extend_basis_matrix = np.column_stack( [np.array(v, dtype=float).ravel() for v in triv_inf_flexes] ) for inf_flex in all_inf_flexes: inf_flex = np.reshape(inf_flex, (-1, 1)) tmp_matrix = np.hstack((extend_basis_matrix, inf_flex)) if not np.linalg.matrix_rank( tmp_matrix, tol=tolerance ) == np.linalg.matrix_rank(extend_basis_matrix, tol=tolerance): extend_basis_matrix = np.hstack((extend_basis_matrix, inf_flex)) basis = extend_basis_matrix[ :, s : np.linalg.matrix_rank(extend_basis_matrix, tol=tolerance) ] return [ _normalize_flex(list(basis[:, i]), numerical=True, tolerance=tolerance) for i in range(basis.shape[1]) ]
[docs] def is_inf_rigid( framework: FrameworkBase, numerical: bool = False, tolerance: float = 1e-9 ) -> bool: """ Return whether the framework is infinitesimally rigid. Definitions ----------- :prf:ref:`Infinitesimal rigidity <def-inf-rigid-framework>` Parameters ---------- framework: numerical: If ``True``, the rigidity matrix rank computation for determining rigidity is numerical. *Warning:* For ``numerical=True`` the numerical rank computation may produce different results than the computation over symbolic coordinates. tolerance: Numerical tolerance used for computing the rigidity matrix rank. Examples -------- >>> from pyrigi import frameworkDB >>> F1 = frameworkDB.CompleteBipartite(4,4) >>> is_inf_rigid(F1) True >>> F2 = frameworkDB.Cycle(4,dim=2) >>> is_inf_rigid(F2) False """ framework._warn_numerical_coord(is_inf_rigid, numerical) if framework._graph.number_of_nodes() <= framework.dim + 1: return rigidity_matrix_rank( framework, numerical=numerical, tolerance=tolerance ) == binomial(framework._graph.number_of_nodes(), 2) else: return rigidity_matrix_rank( framework, numerical=numerical, tolerance=tolerance ) == framework.dim * framework._graph.number_of_nodes() - binomial( framework.dim + 1, 2 )
[docs] def is_inf_flexible(framework: FrameworkBase, **kwargs) -> bool: """ Return whether the framework is infinitesimally flexible. For implementation details and possible parameters, see :func:`~.is_inf_rigid`. Definitions ----------- :prf:ref:`Infinitesimal rigidity <def-inf-rigid-framework>` """ return not is_inf_rigid(framework, **kwargs)
[docs] def is_min_inf_rigid(framework: FrameworkBase, use_copy: bool = True, **kwargs) -> bool: """ Return whether the framework is minimally infinitesimally rigid. For implementation details and possible parameters, see :func:`~.is_inf_rigid`. Definitions ----- :prf:ref:`Minimal infinitesimal rigidity <def-min-rigid-framework>` Parameters ---------- framework: use_copy: If ``False``, the framework's edges are deleted and added back during runtime. Otherwise, a new modified framework is created, while the original framework remains unchanged (default). Examples -------- >>> F = Framework.Complete([[0,0], [1,0], [1,1], [0,1]]) >>> is_min_inf_rigid(F) False >>> F.delete_edge((0,2)) >>> is_min_inf_rigid(F) True """ if not is_inf_rigid(framework, **kwargs): return False F = framework if use_copy: F = deepcopy(framework) for edge in graph_general.edge_list(F._graph): F.delete_edge(edge) if is_inf_rigid(F, **kwargs): F.add_edge(edge) return False F.add_edge(edge) return True
def _transform_inf_flex_to_pointwise( framework: FrameworkBase, inf_flex: Matrix | Sequence, vertex_order: Sequence[Vertex] = None, ) -> dict[Vertex, list[Number]]: r""" Transform the natural data type of a flex (``Matrix``) to a dictionary that maps a vertex to a ``Sequence`` of coordinates (i.e. a vector). Parameters ---------- framework: inf_flex: An infinitesimal flex in the form of a ``Matrix``. vertex_order: If ``None``, the :meth:`.Graph.vertex_list` is taken as the vertex order. Examples ---- >>> F = Framework.from_points([(0,0), (1,0), (0,1)]) >>> F.add_edges([(0,1),(0,2)]) >>> flex = nontrivial_inf_flexes(F)[0] >>> from pyrigi.framework._rigidity.infinitesimal import _transform_inf_flex_to_pointwise >>> _transform_inf_flex_to_pointwise(F, flex) {0: [1, 0], 1: [1, 0], 2: [0, 0]} Notes ---- For example, this function can be used for generating an infinitesimal flex for plotting purposes. """ # noqa: E501 vertex_order = _graph_input_check.is_vertex_order(framework._graph, vertex_order) if ( isinstance(inf_flex, Matrix) and ( inf_flex.shape[1] != 1 or inf_flex.shape[0] != framework.dim * len(vertex_order) ) ) or ( isinstance(inf_flex, Sequence) and len(inf_flex) != framework.dim * len(vertex_order) ): raise ValueError("The provided `inf_flex` does not have the correct format.") return { vertex_order[i]: [inf_flex[i * framework.dim + j] for j in range(framework.dim)] for i in range(len(vertex_order)) }
[docs] def is_vector_inf_flex( framework: FrameworkBase, inf_flex: Sequence[Number], vertex_order: Sequence[Vertex] = None, numerical: bool = False, tolerance: float = 1e-9, ) -> bool: r""" Return whether a vector is an infinitesimal flex of the framework. Definitions ----------- * :prf:ref:`Infinitesimal flex <def-inf-flex>` * :prf:ref:`Rigidity Matrix <def-rigidity-matrix>` Parameters ---------- framework: inf_flex: An infinitesimal flex of the framework specified by a vector. vertex_order: A list of vertices specifying the order in which ``inf_flex`` is given. If none is provided, the list from :meth:`~.Graph.vertex_list` is taken. numerical: A Boolean determining whether the evaluation of the product of the ``inf_flex`` and the rigidity matrix is symbolic or numerical. tolerance: Absolute tolerance that is the threshold for acceptable numerical flexes. This parameter is used to determine the number of digits, to which accuracy the symbolic expressions are evaluated. Examples -------- >>> from pyrigi import frameworkDB as fws >>> F = fws.Square() >>> q = [0,0,0,0,-2,0,-2,0] >>> is_vector_inf_flex(F, q) True >>> q[0] = 1 >>> is_vector_inf_flex(F, q) False >>> F = Framework.Complete([[0,0], [1,1]]) >>> is_vector_inf_flex(F, ["sqrt(2)","-sqrt(2)",0,0], vertex_order=[1,0]) True """ framework._warn_numerical_coord(is_vector_inf_flex, numerical) vertex_order = _graph_input_check.is_vertex_order(framework._graph, vertex_order) return is_zero_vector( rigidity_matrix(framework, vertex_order=vertex_order) * Matrix(inf_flex), numerical=numerical, tolerance=tolerance, )
[docs] def is_dict_inf_flex( framework: FrameworkBase, vert_to_flex: dict[Vertex, Sequence[Number]], **kwargs ) -> bool: """ Return whether a dictionary specifies an infinitesimal flex of the framework. Definitions ----------- :prf:ref:`Infinitesimal flex <def-inf-flex>` Parameters ---------- framework: vert_to_flex: Dictionary that maps the vertex labels to vectors of the same dimension as the framework is. Examples -------- >>> F = Framework.Complete([[0,0], [1,1]]) >>> is_dict_inf_flex(F, {0:[0,0], 1:[-1,1]}) True >>> is_dict_inf_flex(F, {0:[0,0], 1:["sqrt(2)","-sqrt(2)"]}) True Notes ----- See :func:`.is_vector_inf_flex`. """ _graph_input_check.is_vertex_order( framework._graph, list(vert_to_flex.keys()), "vert_to_flex" ) dict_to_list = [] for v in graph_general.vertex_list(framework._graph): dict_to_list += list(vert_to_flex[v]) return is_vector_inf_flex( framework, dict_to_list, vertex_order=graph_general.vertex_list(framework._graph), **kwargs, )
[docs] def is_vector_nontrivial_inf_flex( framework: FrameworkBase, inf_flex: Sequence[Number], vertex_order: Sequence[Vertex] = None, numerical: bool = False, tolerance: float = 1e-9, ) -> bool: r""" Return whether an infinitesimal flex is nontrivial. Definitions ----------- :prf:ref:`Nontrivial infinitesimal flex <def-trivial-inf-flex>` Parameters ---------- framework: inf_flex: An infinitesimal flex of the framework. vertex_order: A list of vertices specifying the order in which ``inf_flex`` is given. If none is provided, the list from :meth:`.Graph.vertex_list` is taken. numerical: A Boolean determining whether the evaluation of the product of the `inf_flex` and the rigidity matrix is symbolic or numerical. tolerance: Absolute tolerance that is the threshold for acceptable numerical flexes. This parameter is used to determine the number of digits, to which accuracy the symbolic expressions are evaluated. Examples -------- >>> from pyrigi import frameworkDB as fws >>> F = fws.Square() >>> q = [0,0,0,0,-2,0,-2,0] >>> is_vector_nontrivial_inf_flex(F, q) True >>> q = [1,-1,1,1,-1,1,-1,-1] >>> is_vector_inf_flex(F, q) True >>> is_vector_nontrivial_inf_flex(F, q) False Notes ----- This is done by solving a linear system composed of a matrix $A$ whose columns are given by a basis of the trivial flexes and the vector $b$ given by the input flex. $b$ is trivial if and only if there is a linear combination of the columns in $A$ producing $b$. In other words, when there is a solution to $Ax=b$, then $b$ is a trivial infinitesimal motion. Otherwise, $b$ is nontrivial. In the ``numerical=True`` case we compute a least squares solution $x$ of the overdetermined linear system and compare the values in $Ax$ to the values in $b$. """ framework._warn_numerical_coord(is_vector_nontrivial_inf_flex, numerical) vertex_order = _graph_input_check.is_vertex_order(framework._graph, vertex_order) if not is_vector_inf_flex( framework, inf_flex, vertex_order=vertex_order, numerical=numerical, tolerance=tolerance, ): return False if not numerical: Q_trivial = Matrix.hstack( *(trivial_inf_flexes(framework, vertex_order=vertex_order)) ) system = Q_trivial, Matrix(inf_flex) return sp.linsolve(system) == sp.EmptySet else: Q_trivial = np.array( [ sympy_expr_to_float(flex, tolerance=tolerance) for flex in trivial_inf_flexes(framework, vertex_order=vertex_order) ] ).transpose() b = np.array(sympy_expr_to_float(inf_flex, tolerance=tolerance)).transpose() x = np.linalg.lstsq(Q_trivial, b, rcond=None)[0] return not is_zero_vector( np.dot(Q_trivial, x) - b, numerical=True, tolerance=tolerance )
[docs] def is_dict_nontrivial_inf_flex( framework: FrameworkBase, vert_to_flex: dict[Vertex, Sequence[Number]], **kwargs ) -> bool: r""" Return whether a dictionary specifies an infinitesimal flex which is nontrivial. See :func:`.is_vector_nontrivial_inf_flex` for details, particularly concerning the possible parameters. Definitions ----------- :prf:ref:`Nontrivial infinitesimal flex <def-trivial-inf-flex>` Parameters ---------- framework: vert_to_flex: An infinitesimal flex of the framework in the form of a dictionary. Examples -------- >>> from pyrigi import frameworkDB as fws >>> F = fws.Square() >>> q = {0:[0,0], 1: [0,0], 2:[-2,0], 3:[-2,0]} >>> is_dict_nontrivial_inf_flex(F, q) True >>> q = {0:[1,-1], 1: [1,1], 2:[-1,1], 3:[-1,-1]} >>> is_dict_nontrivial_inf_flex(F, q) False """ _graph_input_check.is_vertex_order( framework._graph, list(vert_to_flex.keys()), "vert_to_flex" ) dict_to_list = [] for v in graph_general.vertex_list(framework._graph): dict_to_list += list(vert_to_flex[v]) return is_vector_nontrivial_inf_flex( framework, dict_to_list, vertex_order=graph_general.vertex_list(framework._graph), **kwargs, )
[docs] def is_nontrivial_flex( framework: FrameworkBase, inf_flex: InfFlex, **kwargs, ) -> bool: """ Alias for :func:`.is_vector_nontrivial_inf_flex` and :func:`.is_dict_nontrivial_inf_flex`. It is distinguished between instances of ``list`` and instances of ``dict`` to call one of the alias methods. Definitions ----------- :prf:ref:`Nontrivial infinitesimal flex <def-trivial-inf-flex>` Parameters ---------- framework: inf_flex """ if isinstance(inf_flex, list | tuple | Matrix): return is_vector_nontrivial_inf_flex(framework, inf_flex, **kwargs) elif isinstance(inf_flex, dict): return is_dict_nontrivial_inf_flex(framework, inf_flex, **kwargs) else: raise TypeError( "The `inf_flex` must be specified either by a vector or a dictionary!" )
[docs] def is_vector_trivial_inf_flex( framework: FrameworkBase, inf_flex: Sequence[Number], **kwargs ) -> bool: r""" Return whether an infinitesimal flex is trivial. See also :func:`.is_vector_nontrivial_inf_flex` for details, particularly concerning the possible parameters. Definitions ----------- :prf:ref:`Trivial infinitesimal flex <def-trivial-inf-flex>` Parameters ---------- framework: inf_flex: An infinitesimal flex of the framework. Examples -------- >>> from pyrigi import frameworkDB as fws >>> F = fws.Square() >>> q = [0,0,0,0,-2,0,-2,0] >>> is_vector_trivial_inf_flex(F, q) False >>> q = [1,-1,1,1,-1,1,-1,-1] >>> is_vector_trivial_inf_flex(F, q) True """ if not is_vector_inf_flex(framework, inf_flex, **kwargs): return False return not is_vector_nontrivial_inf_flex(framework, inf_flex, **kwargs)
[docs] def is_dict_trivial_inf_flex( framework: FrameworkBase, inf_flex: dict[Vertex, Sequence[Number]], **kwargs ) -> bool: r""" Return whether an infinitesimal flex specified by a dictionary is trivial. See :func:`.is_vector_trivial_inf_flex` for details, particularly concerning the possible parameters. Definitions ----------- :prf:ref:`Trivial infinitesimal flex <def-trivial-inf-flex>` Parameters ---------- framework: inf_flex: An infinitesimal flex of the framework in the form of a dictionary. Examples -------- >>> from pyrigi import frameworkDB as fws >>> F = fws.Square() >>> q = {0:[0,0], 1: [0,0], 2:[-2,0], 3:[-2,0]} >>> is_dict_trivial_inf_flex(F, q) False >>> q = {0:[1,-1], 1: [1,1], 2:[-1,1], 3:[-1,-1]} >>> is_dict_trivial_inf_flex(F, q) True """ _graph_input_check.is_vertex_order( framework._graph, list(inf_flex.keys()), "vert_to_flex" ) dict_to_list = [] for v in graph_general.vertex_list(framework._graph): dict_to_list += list(inf_flex[v]) return is_vector_trivial_inf_flex( framework, dict_to_list, vertex_order=graph_general.vertex_list(framework._graph), **kwargs, )
[docs] def is_trivial_flex( framework: FrameworkBase, inf_flex: InfFlex, **kwargs, ) -> bool: """ Alias for :func:`.is_vector_trivial_inf_flex` and :func:`.is_dict_trivial_inf_flex`. It is distinguished between instances of ``list`` and instances of ``dict`` to call one of the alias methods. Definitions ----------- :prf:ref:`Trivial infinitesimal flex <def-trivial-inf-flex>` Parameters ---------- framework: inf_flex """ if isinstance(inf_flex, list | tuple | Matrix): return is_vector_trivial_inf_flex(framework, inf_flex, **kwargs) elif isinstance(inf_flex, dict): return is_dict_trivial_inf_flex(framework, inf_flex, **kwargs) else: raise TypeError( "The `inf_flex` must be specified either by a vector or a dictionary!" )