Source code for pyrigi.framework.framework

"""
Module for the functionality concerning frameworks.
"""

from __future__ import annotations

from random import randrange
from typing import Any

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

import pyrigi._utils._input_check as _input_check
from pyrigi._utils._doc import copy_doc, doc_category, generate_category_tables
from pyrigi.data_type import (
    DirectedEdge,
    Edge,
    InfFlex,
    Number,
    Point,
    Sequence,
    Stress,
    Vertex,
)
from pyrigi.framework.base import FrameworkBase
from pyrigi.graph import Graph
from pyrigi.graph import _general as graph_general
from pyrigi.graphDB import Complete as CompleteGraph
from pyrigi.plot_style import PlotStyle

from . import _general as general
from ._export import export
from ._plot import plot
from ._rigidity import infinitesimal as infinitesimal_rigidity
from ._rigidity import matroidal as matroidal_rigidity
from ._rigidity import redundant as redundant_rigidity
from ._rigidity import second_order as second_order_rigidity
from ._rigidity import stress as stress_rigidity
from ._transformations import transformations

__doctest_requires__ = {
    tuple(["Framework." + func_name for func_name in func_names]): pkgs
    for func_names, pkgs in export.__doctest_requires__.items()
}


[docs] class Framework(FrameworkBase): r""" This class provides the functionality for frameworks. Definitions ----------- * :prf:ref:`Framework <def-framework>` * :prf:ref:`Realization <def-realization>` Parameters ---------- graph: A graph without loops. realization: A dictionary mapping the vertices of the graph to points in $\RR^d$. The dimension $d$ is retrieved from the points in realization. If ``graph`` is empty, and hence also the ``realization``, the dimension is set to 0 (:meth:`.Empty` can be used to construct an empty framework with different dimension). Examples -------- >>> F = Framework(Graph([[0,1]]), {0:[1,2], 1:[0,5]}) >>> print(F) Framework in 2-dimensional space consisting of: Graph with vertices [0, 1] and edges [[0, 1]] Realization {0:(1, 2), 1:(0, 5)} Notice that the realization of a vertex can be accessed using ``[ ]``: >>> F[0] Matrix([ [1], [2]]) METHODS Notes ----- Internally, the realization is represented as ``dict[Vertex,Matrix]``. However, :meth:`~Framework.realization` can also return ``dict[Vertex,Point]``. """
[docs] @doc_category("Plotting") @copy_doc(plot.plot2D) def plot2D( self, plot_style: PlotStyle = None, projection_matrix: Matrix = None, random_seed: int = None, coordinates: Sequence[int] = None, inf_flex: int | InfFlex = None, stress: int | Stress = None, edge_colors_custom: Sequence[Sequence[Edge]] | dict[str, Sequence[Edge]] = None, stress_label_positions: dict[DirectedEdge, float] = None, arc_angles_dict: Sequence[float] | dict[DirectedEdge, float] = None, filename: str = None, dpi=300, **kwargs, ) -> None: return plot.plot2D( self, plot_style=plot_style, projection_matrix=projection_matrix, random_seed=random_seed, coordinates=coordinates, inf_flex=inf_flex, stress=stress, edge_colors_custom=edge_colors_custom, stress_label_positions=stress_label_positions, arc_angles_dict=arc_angles_dict, filename=filename, dpi=dpi, **kwargs, )
[docs] @doc_category("Plotting") @copy_doc(plot.animate3D_rotation) def animate3D_rotation( self, plot_style: PlotStyle = None, edge_colors_custom: Sequence[Sequence[Edge]] | dict[str, Sequence[Edge]] = None, total_frames: int = 100, delay: int = 75, rotation_axis: str | Sequence[Number] = None, **kwargs, ) -> Any: return plot.animate3D_rotation( self, plot_style=plot_style, edge_colors_custom=edge_colors_custom, total_frames=total_frames, delay=delay, rotation_axis=rotation_axis, **kwargs, )
[docs] @doc_category("Plotting") @copy_doc(plot.plot3D) def plot3D( self, plot_style: PlotStyle = None, projection_matrix: Matrix = None, random_seed: int = None, coordinates: Sequence[int] = None, inf_flex: int | InfFlex = None, stress: int | Stress = None, edge_colors_custom: Sequence[Sequence[Edge]] | dict[str, Sequence[Edge]] = None, stress_label_positions: dict[DirectedEdge, float] = None, filename: str = None, dpi=300, **kwargs, ) -> None: return plot.plot3D( self, plot_style=plot_style, projection_matrix=projection_matrix, random_seed=random_seed, coordinates=coordinates, inf_flex=inf_flex, stress=stress, edge_colors_custom=edge_colors_custom, stress_label_positions=stress_label_positions, filename=filename, dpi=dpi, **kwargs, )
[docs] @doc_category("Plotting") @copy_doc(plot.plot) def plot( self, plot_style: PlotStyle = None, **kwargs, ) -> None: return plot.plot( self, plot_style=plot_style, **kwargs, )
[docs] @doc_category("Other") @copy_doc(export.to_tikz) def to_tikz( self, vertex_style: str | dict[str, Sequence[Vertex]] = "fvertex", edge_style: str | dict[str, Sequence[Edge]] = "edge", label_style: str = "labelsty", figure_opts: str = "", vertex_in_labels: bool = False, vertex_out_labels: bool = False, default_styles: bool = True, ) -> str: return export.to_tikz( self, vertex_style=vertex_style, edge_style=edge_style, label_style=label_style, figure_opts=figure_opts, vertex_in_labels=vertex_in_labels, vertex_out_labels=vertex_out_labels, default_styles=default_styles, )
[docs] @classmethod @doc_category("Class methods") def from_points(cls, points: Sequence[Point]) -> Framework: """ Generate a framework from a list of points. The list of vertices of the underlying graph is taken to be ``[0,...,len(points)-1]``. The underlying graph has no edges. Parameters ---------- points: The realization of the framework that this method outputs is provided as a list of points. Examples -------- >>> F = Framework.from_points([(1,2), (2,3)]) >>> print(F) Framework in 2-dimensional space consisting of: Graph with vertices [0, 1] and edges [] Realization {0:(1, 2), 1:(2, 3)} """ vertices = range(len(points)) realization = {v: points[v] for v in vertices} return Framework(Graph.from_vertices(vertices), realization)
[docs] @classmethod @doc_category("Class methods") def Random( cls, graph: Graph, dim: int = 2, rand_range: int | Sequence[int] = None, numerical: bool = False, ) -> Framework: """ Return a framework with random realization. Depending on the parameter ``numerical``, the realization either consists of random integers (``numerical=False``) or random floats (``numerical=True``). Parameters ---------- dim: The dimension of the constructed framework. graph: Graph for which the random realization should be constructed. rand_range: Sets the range of random numbers from which the realization is sampled. The format is either an interval ``(a,b)`` or a single integer ``a``, which produces the range ``(-a,a)``. If ``rand_range=None``, then the range is set to ``(-a,a)`` for ``a = 10^4 * n * dim`` in the case that ``numerical=False``, where ``n`` is the number of vertices. For ``numerical=True``, we set the default interval to ``(-1,1)``. numerical: A boolean indicating whether numerical coordinates should be used. Examples -------- >>> F = Framework.Random(Graph([(0,1), (1,2), (0,2)])) >>> print(F) # doctest: +SKIP Framework in 2-dimensional space consisting of: Graph with vertices [0, 1, 2] and edges [[0, 1], [0, 2], [1, 2]] Realization {0:(122, 57), 1:(27, 144), 2:(50, 98)} """ _input_check.dimension(dim) if rand_range is None: if numerical: a, b = -1, 1 else: b = 10**4 * graph.number_of_nodes() * dim a = -b elif isinstance(rand_range, list | tuple): if not len(rand_range) == 2: raise ValueError("If `rand_range` is a list, it must be of length 2.") a, b = rand_range elif isinstance(rand_range, int): if rand_range <= 0: raise ValueError("If `rand_range` is an int, it must be positive") b = rand_range a = -b else: raise TypeError("`rand_range` must be either a list or a single int.") if numerical: realization = { v: [a + np.random.rand() * (b - a) for _ in range(dim)] for v in graph.nodes } else: realization = { v: [randrange(a, b) for _ in range(dim)] for v in graph.nodes } return Framework(graph, realization)
[docs] @classmethod @doc_category("Class methods") def Circular(cls, graph: Graph) -> Framework: """ Return the framework with a regular unit circle realization in the plane. Parameters ---------- graph: Underlying graph on which the framework is constructed. Examples ---- >>> import pyrigi.graphDB as graphs >>> F = Framework.Circular(graphs.CompleteBipartite(4, 2)) >>> print(F) Framework in 2-dimensional space consisting of: Graph with vertices [0, 1, 2, 3, 4, 5] and edges ... Realization {0:(1, 0), 1:(1/2, sqrt(3)/2), ... """ n = graph.number_of_nodes() return Framework( graph, { v: [sp.cos(2 * i * sp.pi / n), sp.sin(2 * i * sp.pi / n)] for i, v in enumerate(graph_general.vertex_list(graph)) }, )
[docs] @classmethod @doc_category("Class methods") def Collinear(cls, graph: Graph, dim: int = 1) -> Framework: """ Return the framework with a realization on the x-axis. Parameters ---------- dim: The dimension of the space in which the framework is constructed. graph: Underlying graph on which the framework is constructed. Examples -------- >>> import pyrigi.graphDB as graphs >>> print(Framework.Collinear(graphs.Complete(3), dim=2)) Framework in 2-dimensional space consisting of: Graph with vertices [0, 1, 2] and edges [[0, 1], [0, 2], [1, 2]] Realization {0:(0, 0), 1:(1, 0), 2:(2, 0)} """ _input_check.dimension(dim) return Framework( graph, { v: [i] + [0 for _ in range(dim - 1)] for i, v in enumerate(graph_general.vertex_list(graph)) }, )
[docs] @classmethod @doc_category("Class methods") def Simplicial(cls, graph: Graph, dim: int = None) -> Framework: """ Return the framework with a realization on the ``dim``-simplex. Parameters ---------- graph: Underlying graph on which the framework is constructed. dim: The dimension ``dim`` has to be at least the number of vertices of the ``graph`` minus one. If ``dim`` is not specified, then the least possible one is used. Examples ---- >>> F = Framework.Simplicial(Graph([(0,1), (1,2), (2,3), (0,3)]), 4) >>> F.realization(as_points=True) {0: [0, 0, 0, 0], 1: [1, 0, 0, 0], 2: [0, 1, 0, 0], 3: [0, 0, 1, 0]} >>> F = Framework.Simplicial(Graph([(0,1), (1,2), (2,3), (0,3)])) >>> F.realization(as_points=True) {0: [0, 0, 0], 1: [1, 0, 0], 2: [0, 1, 0], 3: [0, 0, 1]} """ if dim is None: dim = graph.number_of_nodes() - 1 _input_check.integrality_and_range( dim, "dimension d", max([1, graph.number_of_nodes() - 1]) ) return Framework( graph, { v: [1 if j == i - 1 else 0 for j in range(dim)] for i, v in enumerate(graph_general.vertex_list(graph)) }, )
[docs] @classmethod @doc_category("Class methods") def Complete(cls, points: Sequence[Point]) -> Framework: """ Generate a framework on the complete graph from a given list of points. The vertices of the underlying graph are taken to be the list ``[0,...,len(points)-1]``. Parameters ---------- points: The realization of the framework that this method outputs is provided as a list of points. Examples -------- >>> F = Framework.Complete([(1,),(2,),(3,),(4,)]); print(F) Framework in 1-dimensional space consisting of: Graph with vertices [0, 1, 2, 3] and edges [[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3]] Realization {0:(1,), 1:(2,), 2:(3,), 3:(4,)} """ # noqa: E501 if not points: raise ValueError("The list of points cannot be empty!") Kn = CompleteGraph(len(points)) return Framework(Kn, {v: pos for v, pos in zip(Kn.nodes, points)})
[docs] @doc_category("Framework properties") @copy_doc(general.is_quasi_injective) def is_quasi_injective( self, numerical: bool = False, tolerance: float = 1e-9 ) -> bool: return general.is_quasi_injective( self, numerical=numerical, tolerance=tolerance )
[docs] @doc_category("Framework properties") @copy_doc(general.is_injective) def is_injective(self, numerical: bool = False, tolerance: float = 1e-9) -> bool: return general.is_injective(self, numerical=numerical, tolerance=tolerance)
[docs] @doc_category("Infinitesimal rigidity") @copy_doc(infinitesimal_rigidity.rigidity_matrix) def rigidity_matrix( self, vertex_order: Sequence[Vertex] = None, edge_order: Sequence[Edge] = None, ) -> Matrix: return infinitesimal_rigidity.rigidity_matrix( self, vertex_order=vertex_order, edge_order=edge_order )
[docs] @doc_category("Infinitesimal rigidity") @copy_doc(stress_rigidity.is_dict_stress) def is_dict_stress(self, dict_stress: dict[Edge, Number], **kwargs) -> bool: return stress_rigidity.is_dict_stress(self, dict_stress=dict_stress, **kwargs)
[docs] @doc_category("Infinitesimal rigidity") @copy_doc(stress_rigidity.is_vector_stress) def is_vector_stress( self, stress: Sequence[Number], edge_order: Sequence[Edge] = None, numerical: bool = False, tolerance=1e-9, ) -> bool: return stress_rigidity.is_vector_stress( self, stress=stress, edge_order=edge_order, numerical=numerical, tolerance=tolerance, )
[docs] @doc_category("Infinitesimal rigidity") @copy_doc(stress_rigidity.is_stress) def is_stress(self, stress: Stress, **kwargs) -> bool: return stress_rigidity.is_stress(self, stress=stress, **kwargs)
[docs] @doc_category("Infinitesimal rigidity") @copy_doc(stress_rigidity.stress_matrix) def stress_matrix( self, stress: Stress, edge_order: Sequence[Edge] = None, vertex_order: Sequence[Vertex] = None, ) -> Matrix: return stress_rigidity.stress_matrix( self, stress=stress, edge_order=edge_order, vertex_order=vertex_order )
[docs] @doc_category("Infinitesimal rigidity") @copy_doc(infinitesimal_rigidity.trivial_inf_flexes) def trivial_inf_flexes(self, vertex_order: Sequence[Vertex] = None) -> list[Matrix]: return infinitesimal_rigidity.trivial_inf_flexes( self, vertex_order=vertex_order )
[docs] @doc_category("Infinitesimal rigidity") @copy_doc(infinitesimal_rigidity.nontrivial_inf_flexes) def nontrivial_inf_flexes(self, **kwargs) -> list[Matrix]: return infinitesimal_rigidity.nontrivial_inf_flexes(self, **kwargs)
[docs] @doc_category("Infinitesimal rigidity") @copy_doc(infinitesimal_rigidity.inf_flexes) def inf_flexes( self, include_trivial: bool = False, vertex_order: Sequence[Vertex] = None, numerical: bool = False, tolerance: float = 1e-9, ) -> list[Matrix] | list[list[float]]: return infinitesimal_rigidity.inf_flexes( self, include_trivial=include_trivial, vertex_order=vertex_order, numerical=numerical, tolerance=tolerance, )
[docs] @doc_category("Infinitesimal rigidity") @copy_doc(stress_rigidity.stresses) def stresses( self, edge_order: Sequence[Edge] = None, numerical: bool = False, tolerance: float = 1e-9, ) -> list[Matrix] | list[list[float]]: return stress_rigidity.stresses( self, edge_order=edge_order, numerical=numerical, tolerance=tolerance )
[docs] @doc_category("Infinitesimal rigidity") @copy_doc(infinitesimal_rigidity.rigidity_matrix_rank) def rigidity_matrix_rank( self, numerical: bool = False, tolerance: float = 1e-9 ) -> int: return infinitesimal_rigidity.rigidity_matrix_rank( self, numerical=numerical, tolerance=tolerance )
[docs] @doc_category("Infinitesimal rigidity") @copy_doc(infinitesimal_rigidity.is_inf_rigid) def is_inf_rigid(self, numerical: bool = False, tolerance: float = 1e-9) -> bool: return infinitesimal_rigidity.is_inf_rigid( self, numerical=numerical, tolerance=tolerance )
[docs] @doc_category("Infinitesimal rigidity") @copy_doc(infinitesimal_rigidity.is_inf_flexible) def is_inf_flexible(self, **kwargs) -> bool: return infinitesimal_rigidity.is_inf_flexible(self, **kwargs)
[docs] @doc_category("Infinitesimal rigidity") @copy_doc(infinitesimal_rigidity.is_min_inf_rigid) def is_min_inf_rigid(self, use_copy: bool = True, **kwargs) -> bool: return infinitesimal_rigidity.is_min_inf_rigid( self, use_copy=use_copy, **kwargs )
[docs] @doc_category("Infinitesimal rigidity") @copy_doc(matroidal_rigidity.is_independent) def is_independent(self, **kwargs) -> bool: return matroidal_rigidity.is_independent(self, **kwargs)
[docs] @doc_category("Infinitesimal rigidity") @copy_doc(matroidal_rigidity.is_dependent) def is_dependent(self, **kwargs) -> bool: return matroidal_rigidity.is_dependent(self, **kwargs)
[docs] @doc_category("Infinitesimal rigidity") @copy_doc(matroidal_rigidity.is_isostatic) def is_isostatic(self, **kwargs) -> bool: return matroidal_rigidity.is_isostatic(self, **kwargs)
[docs] @doc_category("Other") @copy_doc(second_order_rigidity.is_prestress_stable) def is_prestress_stable( self, numerical: bool = False, tolerance: float = 1e-9, inf_flexes: Sequence[InfFlex] = None, stresses: Sequence[Stress] = None, ) -> bool: return second_order_rigidity.is_prestress_stable( self, numerical=numerical, tolerance=tolerance, inf_flexes=inf_flexes, stresses=stresses, )
[docs] @doc_category("Other") @copy_doc(second_order_rigidity.is_second_order_rigid) def is_second_order_rigid( self, numerical: bool = False, tolerance: float = 1e-9, inf_flexes: Sequence[InfFlex] = None, stresses: Sequence[Stress] = None, ) -> bool: return second_order_rigidity.is_second_order_rigid( self, numerical=numerical, tolerance=tolerance, inf_flexes=inf_flexes, stresses=stresses, )
[docs] @doc_category("Infinitesimal rigidity") @copy_doc(redundant_rigidity.is_redundantly_inf_rigid) def is_redundantly_inf_rigid(self, use_copy: bool = True, **kwargs) -> bool: return redundant_rigidity.is_redundantly_inf_rigid( self, use_copy=use_copy, **kwargs )
[docs] @doc_category("Framework properties") @copy_doc(general.is_congruent_realization) def is_congruent_realization( self, other_realization: dict[Vertex, Point] | dict[Vertex, Matrix], numerical: bool = False, tolerance: float = 1e-9, ) -> bool: return general.is_congruent_realization( self, other_realization=other_realization, numerical=numerical, tolerance=tolerance, )
[docs] @doc_category("Framework properties") @copy_doc(general.is_congruent) def is_congruent( self, other_framework: Framework, numerical: bool = False, tolerance: float = 1e-9, ) -> bool: return general.is_congruent( self, other_framework=other_framework, numerical=numerical, tolerance=tolerance, )
[docs] @doc_category("Framework properties") @copy_doc(general.is_equivalent_realization) def is_equivalent_realization( self, other_realization: dict[Vertex, Point] | dict[Vertex, Matrix], numerical: bool = False, tolerance: float = 1e-9, ) -> bool: return general.is_equivalent_realization( self, other_realization=other_realization, numerical=numerical, tolerance=tolerance, )
[docs] @doc_category("Framework properties") @copy_doc(general.is_equivalent) def is_equivalent( self, other_framework: Framework, numerical: bool = False, tolerance: float = 1e-9, ) -> bool: return general.is_equivalent( self, other_framework=other_framework, numerical=numerical, tolerance=tolerance, )
[docs] @doc_category("Framework manipulation") @copy_doc(transformations.translate) def translate( self, vector: Point | Matrix, inplace: bool = True ) -> None | Framework: return transformations.translate(self, vector, inplace=inplace)
[docs] @doc_category("Framework manipulation") @copy_doc(transformations.rescale) def rescale(self, factor: Number, inplace: bool = True) -> None | Framework: return transformations.rescale(self, factor, inplace=inplace)
[docs] @doc_category("Framework manipulation") @copy_doc(transformations.rotate2D) def rotate2D( self, angle: float, rotation_center: Point = [0, 0], inplace: bool = True ) -> None | Framework: return transformations.rotate2D( self, angle, rotation_center=rotation_center, inplace=inplace )
[docs] @doc_category("Framework manipulation") @copy_doc(transformations.rotate3D) def rotate3D( self, angle: Number, axis_direction: Sequence[Number] = [0, 0, 1], axis_shift: Point = [0, 0, 0], inplace: bool = True, ) -> None | Framework: return transformations.rotate3D( self, angle, axis_direction=axis_direction, axis_shift=axis_shift, inplace=inplace, )
[docs] @doc_category("Framework manipulation") @copy_doc(transformations.rotate) def rotate(self, **kwargs) -> None | Framework: return transformations.rotate(self, **kwargs)
[docs] @doc_category("Framework manipulation") @copy_doc(transformations.projected_realization) def projected_realization( self, proj_dim: int = None, projection_matrix: Matrix = None, random_seed: int = None, coordinates: Sequence[int] = None, ) -> tuple[dict[Vertex, Point], Matrix]: return transformations.projected_realization( self, proj_dim=proj_dim, projection_matrix=projection_matrix, random_seed=random_seed, coordinates=coordinates, )
[docs] @doc_category("Other") @copy_doc(general.edge_lengths) def edge_lengths(self, numerical: bool = False) -> dict[Edge, Number]: return general.edge_lengths(self, numerical=numerical)
[docs] @doc_category("Other") @copy_doc(export.generate_stl_bars) def generate_stl_bars( self, scale: float = 1.0, width_of_bars: float = 8.0, height_of_bars: float = 3.0, holes_diameter: float = 4.3, filename_prefix: str = "bar_", output_dir: str = "stl_output", ) -> None: return export.generate_stl_bars( self, scale=scale, width_of_bars=width_of_bars, height_of_bars=height_of_bars, holes_diameter=holes_diameter, filename_prefix=filename_prefix, output_dir=output_dir, )
[docs] @doc_category("Infinitesimal rigidity") @copy_doc(infinitesimal_rigidity.is_vector_inf_flex) def is_vector_inf_flex( self, inf_flex: Sequence[Number], vertex_order: Sequence[Vertex] = None, numerical: bool = False, tolerance: float = 1e-9, ) -> bool: return infinitesimal_rigidity.is_vector_inf_flex( self, inf_flex=inf_flex, vertex_order=vertex_order, numerical=numerical, tolerance=tolerance, )
[docs] @doc_category("Infinitesimal rigidity") @copy_doc(infinitesimal_rigidity.is_dict_inf_flex) def is_dict_inf_flex( self, vert_to_flex: dict[Vertex, Sequence[Number]], **kwargs ) -> bool: return infinitesimal_rigidity.is_dict_inf_flex( self, vert_to_flex=vert_to_flex, **kwargs )
[docs] @doc_category("Infinitesimal rigidity") @copy_doc(infinitesimal_rigidity.is_vector_nontrivial_inf_flex) def is_vector_nontrivial_inf_flex( self, inf_flex: Sequence[Number], vertex_order: Sequence[Vertex] = None, numerical: bool = False, tolerance: float = 1e-9, ) -> bool: return infinitesimal_rigidity.is_vector_nontrivial_inf_flex( self, inf_flex=inf_flex, vertex_order=vertex_order, numerical=numerical, tolerance=tolerance, )
[docs] @doc_category("Infinitesimal rigidity") @copy_doc(infinitesimal_rigidity.is_dict_nontrivial_inf_flex) def is_dict_nontrivial_inf_flex( self, vert_to_flex: dict[Vertex, Sequence[Number]], **kwargs ) -> bool: return infinitesimal_rigidity.is_dict_nontrivial_inf_flex( self, vert_to_flex=vert_to_flex, **kwargs )
[docs] @doc_category("Infinitesimal rigidity") @copy_doc(infinitesimal_rigidity.is_nontrivial_flex) def is_nontrivial_flex( self, inf_flex: InfFlex, **kwargs, ) -> bool: return infinitesimal_rigidity.is_nontrivial_flex( self, inf_flex=inf_flex, **kwargs )
[docs] @doc_category("Infinitesimal rigidity") @copy_doc(infinitesimal_rigidity.is_vector_trivial_inf_flex) def is_vector_trivial_inf_flex(self, inf_flex: Sequence[Number], **kwargs) -> bool: return infinitesimal_rigidity.is_vector_trivial_inf_flex( self, inf_flex=inf_flex, **kwargs )
[docs] @doc_category("Infinitesimal rigidity") @copy_doc(infinitesimal_rigidity.is_dict_trivial_inf_flex) def is_dict_trivial_inf_flex( self, inf_flex: dict[Vertex, Sequence[Number]], **kwargs ) -> bool: return infinitesimal_rigidity.is_dict_trivial_inf_flex( self, inf_flex=inf_flex, **kwargs )
[docs] @doc_category("Infinitesimal rigidity") @copy_doc(infinitesimal_rigidity.is_trivial_flex) def is_trivial_flex( self, inf_flex: InfFlex, **kwargs, ) -> bool: return infinitesimal_rigidity.is_trivial_flex(self, inf_flex=inf_flex, **kwargs)
Framework.__doc__ = Framework.__doc__.replace( "METHODS", generate_category_tables( Framework, 1, [ "Attribute getters", "Framework properties", "Class methods", "Framework manipulation", "Infinitesimal rigidity", "Plotting", "Other", "Waiting for implementation", ], include_all=False, ), )