Source code for toponetx.classes.reportviews

"""Module with views.

Such as:
HyperEdgeView, CellView, SimplexView, NodeView.
"""

from abc import ABC, abstractmethod
from collections.abc import Collection, Hashable, Iterable, Iterator, Sequence
from itertools import chain
from typing import Any, Generic, Literal, TypeVar

from toponetx.classes.cell import Cell
from toponetx.classes.complex import Atom
from toponetx.classes.hyperedge import HyperEdge
from toponetx.classes.path import Path
from toponetx.classes.simplex import Simplex

__all__ = [
    "AtomView",
    "HyperEdgeView",
    "ColoredHyperEdgeView",
    "CellView",
    "SimplexView",
    "NodeView",
    "PathView",
]

T_Atom = TypeVar("T_Atom", bound=Atom)


[docs] class AtomView(ABC, Generic[T_Atom]): """Abstract class representing a read-only view on a collection of atoms.""" @abstractmethod def __contains__(self, atom: Any) -> bool: """Check if a given element is in the view. Parameters ---------- atom : Any The element to check. Returns ------- bool Whether the element is in the view. """ @abstractmethod def __getitem__(self, atom: Any) -> dict: """Get the attributes of a given element. Parameters ---------- atom : Any The element of interest. Returns ------- dict The attributes associated with the element. Raises ------ KeyError If the element is not in the view. """ @abstractmethod def __iter__(self) -> Iterator[T_Atom]: """Iterate over all elements in the view. Returns ------- Iterator Iterator to iterate over all elements in the view. """ @abstractmethod def __len__(self) -> int: """Return the number of elements in the view. Returns ------- int The number of elements in the view. """
[docs] class CellView(AtomView[Cell]): """A CellView class for cells of a CellComplex."""
[docs] def __init__(self) -> None: # Initialize a dictionary to hold cells, with keys being the tuple # that defines the cell, and values being dictionaries of cell objects # with different attributes self._cells: dict[tuple[Hashable, ...], dict[int, Cell]] = {}
def __getitem__(self, cell: Any) -> dict[Hashable, Any]: """Return the attributes of a given cell. Parameters ---------- cell : Any The cell of interest. Returns ------- dict[Hashable, Any] The attributes associated with the cell. Raises ------ KeyError If the cell is not in the cell dictionary. """ if isinstance(cell, Cell): if cell.elements not in self._cells: raise KeyError( f"cell {cell!r} is not in the cell dictionary", ) # If there is only one cell with these elements, return its attributes if len(self._cells[cell.elements]) == 1: k = next(iter(self._cells[cell.elements].keys())) return self._cells[cell.elements][k]._attributes # If there are multiple cells with these elements, return the attributes of all cells return [ self._cells[cell.elements][c]._attributes for c in self._cells[cell.elements] ] # If a tuple or list is passed in, assume it represents a cell if isinstance(cell, Iterable): cell = tuple(cell) if cell in self._cells: if len(self._cells[cell]) == 1: k = next(iter(self._cells[cell].keys())) return self._cells[cell][k]._attributes return [self._cells[cell][c]._attributes for c in self._cells[cell]] raise KeyError(f"cell {cell} is not in the cell dictionary") raise TypeError("Input must be a tuple, list or a cell.")
[docs] def raw(self, cell: tuple | list | Cell) -> Cell | list[Cell]: """Index the raw cell objects analogous to the overall index of CellView. Parameters ---------- cell : tuple, list, or cell The cell of interest. Returns ------- Cell or list of Cells The raw Cell objects. If more than one cell with the same boundary exists, returns a list; otherwise a single cell. Raises ------ KeyError If the cell is not in the cell dictionary. """ if isinstance(cell, Cell): if cell.elements not in self._cells: raise KeyError(f"cell {cell!r} is not in the cell dictionary") # If there is only one cell with these elements, return its attributes if len(self._cells[cell.elements]) == 1: k = next(iter(self._cells[cell.elements].keys())) return self._cells[cell.elements][k] # If there are multiple cells with these elements, return the attributes of all cells return [self._cells[cell.elements][c] for c in self._cells[cell.elements]] # If a tuple or list is passed in, assume it represents a cell if isinstance(cell, tuple | list): cell = tuple(cell) if cell in self._cells: if len(self._cells[cell]) == 1: k = next(iter(self._cells[cell].keys())) return self._cells[cell][k] return [self._cells[cell][c] for c in self._cells[cell]] raise KeyError(f"cell {cell} is not in the cell dictionary") raise TypeError("Input must be a tuple, list or a cell.")
def __len__(self) -> int: """Return the number of cells in the cell view. Returns ------- int The number of cells in the cell view. """ return sum(len(self._cells[cell]) for cell in self._cells) def __iter__(self) -> Iterator[Cell]: """Iterate over all cells in the cell view. Returns ------- Iterator Iterator to iterate over all cells in the cell view. """ return iter( [ self._cells[cell][key] for cell in self._cells for key in self._cells[cell] ] ) def __contains__(self, atom: Any) -> bool: """Check if a given element is in the cell view. Parameters ---------- atom : Any The element to check. Returns ------- bool Whether the element is in the cell view. """ if not isinstance(atom, Cell | tuple | list): return False atom = Cell(atom) return any(atom.is_homotopic_to(x) for x in self._cells) def __repr__(self) -> str: """Return a string representation of the cell view. Returns ------- str The __repr__ representation of the cell view. """ return f"CellView({[self._cells[cell][key] for cell in self._cells for key in self._cells[cell]] })" def __str__(self) -> str: """Return a string representation of the cell view. Returns ------- str The __str__ representation of the cell view. """ return f"CellView({[self._cells[cell][key] for cell in self._cells for key in self._cells[cell]]})"
[docs] class ColoredHyperEdgeView(AtomView): """A class for viewing the cells/hyperedges of a colored hypergraph. Provides methods for accessing, and retrieving information about the cells/hyperedges of a complex. Examples -------- >>> hev = tnx.ColoredHyperEdgeView() """
[docs] def __init__(self) -> None: self.hyperedge_dict = {}
def __getitem__(self, atom: Any) -> dict[Hashable, Any]: """Return the user-defined attributes associated with the given hyperedge. Parameters ---------- atom : Any The hyperedge for which to return the associated user-defined attributes. Returns ------- dict[Hashable, Any] The user-defined attributes associated with the given atom. Raises ------ KeyError If the hyperedge does not exist. """ if isinstance(atom, Hashable) and not isinstance(atom, Collection): atom = (atom,) if len(atom) == 0: raise KeyError(f"Hyperedge {atom} is not in the complex.") if len(atom) == 2: if isinstance(atom, HyperEdge): hyperedge_elements = atom.elements key = 0 elif isinstance(atom[0], Iterable) and isinstance(atom[1], int): hyperedge_elements_ = atom[0] if not isinstance(hyperedge_elements_, HyperEdge): hyperedge_elements, key = atom else: _, key = atom hyperedge_elements = hyperedge_elements_.elements else: hyperedge_elements = atom key = 0 else: hyperedge_elements = atom key = 0 if not isinstance(hyperedge_elements, Iterable) or len(hyperedge_elements) == 0: raise KeyError(f"Hyperedge {atom} is not in the complex.") for i in self.allranks: if frozenset(hyperedge_elements) in self.hyperedge_dict[i]: return self.hyperedge_dict[i][frozenset(hyperedge_elements)][key] raise KeyError(f"Hyperedge {atom} is not in the complex.") @property def shape(self) -> tuple[int, ...]: """Compute shape. Returns ------- tuple[int, ...] The shape of the ColoredHyperEdge. """ shape = [] for i in self.allranks: sm = sum(len(self.hyperedge_dict[i][k]) for k in self.hyperedge_dict[i]) shape.append(sm) return tuple(shape) def __len__(self) -> int: """Compute the number of nodes. Returns ------- int The number of nodes in the ColoredHyperEdge. """ return sum(self.shape[1:]) def __iter__(self) -> Iterator: """Iterate over the hyperedges. Returns ------- Iterator The iterator to iterate over the hyperedges. """ lst = [] for r in self.hyperedge_dict: if r == 0: continue lst.extend( (he, k) for he in self.hyperedge_dict[r] for k in self.hyperedge_dict[r][he] ) return iter(lst) def __contains__(self, atom: Any) -> bool: """Check if hyperedge is in the hyperedges. Parameters ---------- atom : Any The hyperedge to check. Returns ------- bool Return `True` if the hyperedge is contained within the hyperedges. Notes ----- Assumption of input here hyperedge = ( elements of hyperedge, key of hyperedge) """ if isinstance(atom, Hashable) and not isinstance(atom, Collection): atom = (atom,) if len(atom) == 0: return False if len(atom) == 2: if isinstance(atom, HyperEdge): hyperedge_elements = atom.elements key = 0 elif isinstance(atom[0], Iterable) and isinstance(atom[1], int): hyperedge_elements_ = atom[0] if not isinstance(hyperedge_elements_, HyperEdge): hyperedge_elements, key = atom else: _, key = atom hyperedge_elements = hyperedge_elements_.elements else: hyperedge_elements = atom key = 0 else: hyperedge_elements = atom key = 0 if not isinstance(hyperedge_elements, Iterable) or len(hyperedge_elements) == 0: return False for i in self.allranks: if frozenset(hyperedge_elements) in self.hyperedge_dict[i]: return key in self.hyperedge_dict[i][frozenset(hyperedge_elements)] return False def __repr__(self) -> str: """Return string representation of hyperedges. Returns ------- str The __repr__ string representation of the hyperedges. """ return f"ColoredHyperEdgeView({[(tuple(x[0]),x[1]) for x in self]})" def __str__(self) -> str: """Return string representation of hyperedges. Returns ------- str The __str__ string representation of the hyperedges. """ return f"ColoredHyperEdgeView({[(tuple(x[0]),x[1]) for x in self]})"
[docs] def skeleton(self, rank: int, store_hyperedge_key: bool = True): """Skeleton of the complex. Parameters ---------- rank : int Rank of the skeleton. store_hyperedge_key : bool, default=True Whether to return the hyperedge key or not. Returns ------- list of frozensets The skeleton of rank `rank`. """ if rank not in self.hyperedge_dict: return [] if store_hyperedge_key: return sorted( [ (he, k) for he in self.hyperedge_dict[rank] for k in self.hyperedge_dict[rank][he] ] ) return sorted( [ he for he in self.hyperedge_dict[rank] for k in self.hyperedge_dict[rank][he] ] )
[docs] def get_rank(self, edge): """Get the rank of a given hyperedge. Parameters ---------- edge : Iterable, Hashable or ColoredHyperEdge The edge for which to get the rank. Returns ------- int The rank of the given colored hyperedge. """ if isinstance(edge, HyperEdge): if len(edge) == 0: return 0 for i in list(self.allranks): if frozenset(edge.elements) in self.hyperedge_dict[i]: return i raise KeyError(f"hyperedge {edge.elements} is not in the complex") if isinstance(edge, str): if frozenset({edge}) in self.hyperedge_dict[0]: return 0 raise KeyError(f"hyperedge {frozenset({edge})} is not in the complex") if isinstance(edge, Iterable): if len(edge) == 0: return 0 for i in list(self.allranks): if frozenset(edge) in self.hyperedge_dict[i]: return i raise KeyError(f"hyperedge {edge} is not in the complex") if isinstance(edge, Hashable) and not isinstance(edge, Iterable): if frozenset({edge}) in self.hyperedge_dict[0]: return 0 raise KeyError(f"hyperedge {frozenset({edge})} is not in the complex") return None
@property def allranks(self) -> list[int]: """All ranks. Returns ------- list[int] The sorted list of all ranks. """ return sorted(self.hyperedge_dict.keys())
[docs] class HyperEdgeView(AtomView): """A class for viewing the cells/hyperedges of a combinatorial complex. Provides methods for accessing, and retrieving information about the cells/hyperedges of a complex. Examples -------- >>> hev = tnx.HyperEdgeView() """
[docs] def __init__(self) -> None: self.hyperedge_dict = {}
@staticmethod def _to_frozen_set(hyperedge): """Convert a hyperedge into a frozen set. Parameters ---------- hyperedge : HyperEdge | Iterable | Hashable The hyperedge that is to be converted to a frozen set. Returns ------- frozenset Returns a frozenset of the elements contained in the hyperedge. """ if isinstance(hyperedge, HyperEdge): hyperedge_ = hyperedge.elements elif isinstance(hyperedge, Iterable): hyperedge_ = frozenset(hyperedge) elif isinstance(hyperedge, Hashable) and not isinstance(hyperedge, Iterable): hyperedge_ = frozenset([hyperedge]) return hyperedge_ def __getitem__(self, hyperedge: Any) -> dict: """Get item. Parameters ---------- hyperedge : Hashable or HyperEdge DESCRIPTION. Returns ------- dict or list or dicts Return dict of attributes associated with that hyperedges. """ hyperedge_ = HyperEdgeView._to_frozen_set(hyperedge) rank = self.get_rank(hyperedge_) return self.hyperedge_dict[rank][hyperedge_] @property def shape(self) -> tuple[int, ...]: """Compute shape. Returns ------- tuple[int, ...] A tuple representing the shape of the hyperedge. """ return tuple(len(self.hyperedge_dict[i]) for i in self.allranks) def __len__(self) -> int: """Compute the number of nodes. Returns ------- int The number of nodes present in the HyperEdgeView. """ return sum(self.shape) def __iter__(self) -> Iterator[HyperEdge]: """Iterate over the hyperedges. Returns ------- Iterator Iterator object over the hyperedges. """ return chain.from_iterable(self.hyperedge_dict.values()) def __contains__(self, atom: Collection) -> bool: """Check if e is in the hyperedges. Parameters ---------- atom : Collection The hyperedge that needs to be checked for containership in the HyperEdgeView. Returns ------- bool Returns `True` if the hyperedge e is contained within the HyperEdgeView, else return `False`. """ if len(self.hyperedge_dict) == 0: return False all_ranks = self.allranks if isinstance(atom, HyperEdge): if len(atom) == 0: return False for i in all_ranks: if frozenset(atom.elements) in self.hyperedge_dict[i]: return True return False if isinstance(atom, Iterable): if len(atom) == 0: return False return any(frozenset(atom) in self.hyperedge_dict[i] for i in all_ranks) if isinstance(atom, Hashable): return frozenset({atom}) in self.hyperedge_dict[0] return None def __repr__(self) -> str: """Return string representation of hyperedges. Returns ------- str The __repr__ string representation of HyperEdgeView. """ return f"HyperEdgeView({[tuple(x) for x in self]})" def __str__(self) -> str: """Return string representation of hyperedges. Returns ------- str The __str__ string representation of HyperEdgeView. """ return f"HyperEdgeView({[tuple(x) for x in self]})"
[docs] def skeleton( self, rank: int, level: Literal[ "equal", "upper", "up", "lower", "down", "uppereq", "upeq", "lowereq", "downeq", ] = "equal", ): """Skeleton of the complex. Parameters ---------- rank : int Rank of the skeleton. level : str, default="equal" Level of the skeleton. Returns ------- list of frozensets The skeleton of rank `rank`. """ if level == "equal": if rank in self.allranks: return sorted(self.hyperedge_dict[rank].keys()) return [] if level in {"upper", "up"}: elements = [] for rank_i in self.allranks: if rank_i > rank: elements = elements + list(self.hyperedge_dict[rank_i].keys()) return sorted(elements) if level in {"lower", "down"}: elements = [] for rank_i in self.allranks: if rank_i < rank: elements = elements + list(self.hyperedge_dict[rank_i].keys()) return sorted(elements) if level in {"uppereq", "upeq"}: elements = [] for rank_i in self.allranks: if rank_i >= rank: elements = elements + list(self.hyperedge_dict[rank_i].keys()) return sorted(elements) if level in {"lowereq", "downeq"}: elements = [] for rank_i in self.allranks: if rank_i <= rank: elements = elements + list(self.hyperedge_dict[rank_i].keys()) return sorted(elements) raise ValueError( "level must be 'equal', 'uppereq', 'lowereq', 'upeq', 'downeq', 'uppereq', 'lower', 'up', or 'down'" )
[docs] def get_rank(self, edge): """Get the rank of a hyperedge. Parameters ---------- edge : Iterable, Hashable or HyperEdge The edge for which to get the rank. Returns ------- int The rank of the given hyperedge. """ if isinstance(edge, HyperEdge): if len(edge) == 0: return 0 for i in list(self.allranks): if frozenset(edge.elements) in self.hyperedge_dict[i]: return i raise KeyError(f"hyperedge {edge.elements} is not in the complex") if isinstance(edge, str): if frozenset({edge}) in self.hyperedge_dict[0]: return 0 raise KeyError(f"hyperedge {frozenset({edge})} is not in the complex") if isinstance(edge, Iterable): if len(edge) == 0: return 0 for i in list(self.allranks): if frozenset(edge) in self.hyperedge_dict[i]: return i raise KeyError(f"hyperedge {edge} is not in the complex") if isinstance(edge, Hashable) and not isinstance(edge, Iterable): if frozenset({edge}) in self.hyperedge_dict[0]: return 0 raise KeyError(f"hyperedge {frozenset({edge})} is not in the complex") return None
@property def allranks(self): """All ranks. Returns ------- list[hashable] The sorted list of all ranks. """ return sorted(self.hyperedge_dict.keys()) def _get_lower_rank(self, rank): """Get a lower rank compared to given rank. Parameters ---------- rank : int The rank to be used to get a lower rank. Returns ------- int A rank below the current rank available in the HyperEdgeView. """ if len(self.allranks) == 0: return -1 ranks = sorted(self.allranks) if rank <= min(ranks) or rank >= max(ranks): return -1 return ranks[ranks.index(rank) - 1] def _get_higher_rank(self, rank): """Get a higher rank compared to given rank. Parameters ---------- rank : int The rank to be used to get a higher rank. Returns ------- int A rank above the current rank available in the HyperEdgeView. """ if len(self.allranks) == 0: return -1 ranks = sorted(self.allranks) if rank <= min(ranks) or rank >= max(ranks): return -1 return ranks[ranks.index(rank) + 1]
[docs] class SimplexView(AtomView[Simplex]): """Simplex View class. The SimplexView class is used to provide a view/read only information into a subset of the nodes in a simplex. These classes are used in conjunction with the SimplicialComplex class for view/read only purposes for simplices in simplicial complexes. Attributes ---------- max_dim : int Maximum dimension of the simplices in the SimplexView instance. faces_dict : list of dict A list containing dictionaries of faces for each dimension. """
[docs] def __init__(self) -> None: self.max_dim = -1 self.faces_dict = []
def __getitem__(self, simplex: Any) -> dict: """Get the dictionary of attributes associated with the given simplex. Parameters ---------- simplex : tuple, list or Simplex A tuple or list of nodes representing a simplex. Returns ------- dict A dictionary of attributes associated with the given simplex. Raises ------ KeyError If the simplex is not in the simplex view. """ if isinstance(simplex, Simplex): simplex = simplex.elements if isinstance(simplex, Hashable) and not isinstance(simplex, Iterable): simplex = frozenset({simplex}) simplex = frozenset(simplex) if ( len(self.faces_dict) >= len(simplex) and simplex in self.faces_dict[len(simplex) - 1] ): return self.faces_dict[len(simplex) - 1][simplex] raise KeyError(f"input {simplex} is not in the simplex dictionary") @property def shape(self) -> tuple[int, ...]: """Return the number of simplices in each dimension. Returns ------- tuple of ints A tuple of integers representing the number of simplices in each dimension. """ return tuple(len(self.faces_dict[i]) for i in range(len(self.faces_dict))) def __len__(self) -> int: """Return the number of simplices in the SimplexView instance. Returns ------- int Returns the number of simplices in the SimplexView instance. """ return sum(self.shape) def __iter__(self) -> Iterator: """Return an iterator over all simplices in the simplex view. Returns ------- Iterator Returns an iterator over all simplices in the simplex view. """ return chain.from_iterable(self.faces_dict) def __contains__(self, atom: Any) -> bool: """Check if a simplex is in the simplex view. Parameters ---------- atom : Any The simplex to be checked for membership in the simplex view. Returns ------- bool True if the simplex is in the simplex view, False otherwise. Examples -------- Check if a node is in the simplex view: >>> view = tnx.SimplexView() >>> view.faces_dict.append({frozenset({1}): {"weight": 1}}) >>> view.max_dim = 0 >>> 1 in view True >>> 2 in view False Check if a simplex is in the simplex view: >>> view.faces_dict.append({frozenset({1, 2}): {"weight": 1}}) >>> view.max_dim = 1 >>> {1, 2} in view True >>> {1, 3} in view False >>> {1, 2, 3} in view False """ if isinstance(atom, Iterable): atom = frozenset(atom) if not 0 < len(atom) <= self.max_dim + 1: return False return atom in self.faces_dict[len(atom) - 1] if isinstance(atom, Hashable): return frozenset({atom}) in self.faces_dict[0] return False def __repr__(self) -> str: """Return string representation that can be used to recreate it. Returns ------- str Returns the __repr__ representation of the object. """ all_simplices: list[tuple[int, ...]] = [] for i in range(len(self.faces_dict)): all_simplices += [tuple(j) for j in self.faces_dict[i]] return f"SimplexView({all_simplices})" def __str__(self) -> str: """Return detailed string representation of the simplex view. Returns ------- str Returns the __str__ representation of the object. """ all_simplices: list[tuple[int, ...]] = [] for i in range(len(self.faces_dict)): all_simplices += [tuple(j) for j in self.faces_dict[i]] return f"SimplexView({all_simplices})"
[docs] class NodeView: """Node view class. Parameters ---------- objectdict : dict A dictionary of nodes with their attributes. cell_type : type The type of the cell. colored_nodes : bool, optional Whether or not the nodes are colored. """
[docs] def __init__(self, objectdict, cell_type, colored_nodes: bool = False) -> None: if len(objectdict) != 0: self.nodes = objectdict[0] else: self.nodes = {} if cell_type is None: raise ValueError("cell_type cannot be None") self.cell_type = cell_type self.colored_nodes = colored_nodes
def __repr__(self) -> str: """Return string representation of nodes. Returns ------- str Returns the __repr__ representation of the object. """ all_nodes = [tuple(j) for j in self.nodes] return f"NodeView({all_nodes})" def __iter__(self) -> Iterator: """Return an iterator over all nodes in the node view. Returns ------- Iterator Returns an iterator over all nodes in the node view. """ return iter(self.nodes) def __getitem__(self, cell): """Get item. Parameters ---------- cell : tuple list or AbstractCell or Simplex A cell. Returns ------- dict or list Dict of attributes associated with that cells. """ if isinstance(cell, Iterable): cell = frozenset(cell) if cell in self.nodes: if self.colored_nodes: return self.nodes[cell][0] return self.nodes[cell] elif isinstance(cell, Hashable): if cell in self: if self.colored_nodes: return self.nodes[frozenset({cell})][0] return self.nodes[frozenset({cell})] raise KeyError(f"input {cell} is not in the node set of the complex") def __len__(self) -> int: """Compute the number of nodes. Returns ------- int Returns the number of nodes. """ return len(self.nodes) def __contains__(self, e) -> bool: """Check if e is in the nodes. Parameters ---------- e : Hashable | Iterable The node to check for. Returns ------- bool Return `True` if e is contained in NodeView, else Return `False`. """ if isinstance(e, Hashable) and not isinstance(e, self.cell_type): return frozenset({e}) in self.nodes if isinstance(e, self.cell_type): return e.elements in self.nodes if isinstance(e, Iterable): if len(e) == 1: return frozenset(e) in self.nodes return None return False
[docs] class PathView(SimplexView): """Path view class.""" def __getitem__(self, path: Any) -> dict: """Get the dictionary of attributes associated with the given path. Parameters ---------- path : Any A tuple or list of nodes representing a path. It can also be a Path object. It can also be a single node represented by int or str. Returns ------- dict or list or dict A dictionary of attributes associated with the given path. Raises ------ KeyError If the path is not in this view. """ if isinstance(path, Path): path = path.elements if isinstance(path, Hashable) and not isinstance(path, Iterable): path = (path,) path = tuple(path) if path in self.faces_dict[len(path) - 1]: return self.faces_dict[len(path) - 1][path] raise KeyError(f"input {path} is not in the path dictionary") def __contains__(self, atom: Any) -> bool: """Check if a path is in the path view. Parameters ---------- atom : Any The path to be checked for membership in the path view. Returns ------- bool True if the path is in the path view, False otherwise. """ if isinstance(atom, Sequence): atom = tuple(atom) if not 0 < len(atom) <= self.max_dim + 1: return False return atom in self.faces_dict[len(atom) - 1] if isinstance(atom, Path): atom = atom.elements if not 0 < len(atom) <= self.max_dim + 1: return False return atom in self.faces_dict[len(atom) - 1] if isinstance(atom, Hashable): return (atom,) in self.faces_dict[0] return False def __repr__(self) -> str: """Return string representation that can be used to recreate it. Returns ------- str Returns the __repr__ representation of the object. """ all_paths: list[tuple[int | str, ...]] = [] for i in range(len(self.faces_dict)): all_paths += [tuple(j) for j in self.faces_dict[i]] return f"PathView({all_paths})" def __str__(self) -> str: """Return detailed string representation of the path view. Returns ------- str Returns the __str__ representation of the object. """ all_paths: list[tuple[int | str, ...]] = [] for i in range(len(self.faces_dict)): all_paths += [tuple(j) for j in self.faces_dict[i]] return f"PathView({all_paths})"