• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

peekxc / splex / 9994776669

18 Jul 2024 03:58PM UTC coverage: 84.701% (-0.09%) from 84.787%
9994776669

push

github

peekxc
Changes for linux and windows systems related to printing

8 of 8 new or added lines in 3 files covered. (100.0%)

3 existing lines in 2 files now uncovered.

836 of 987 relevant lines covered (84.7%)

5.08 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.67
/src/splex/RankComplex.py
1

2
import numbers
6✔
3
import numpy as np 
6✔
4

5
from .meta import *
6✔
6
from combin import comb_to_rank, rank_to_comb
6✔
7
from .generics import *
6✔
8
from .predicates import *
6✔
9
from .Simplex import *
6✔
10
# from ..Complex import *
11
from more_itertools import unique_everseen
6✔
12
from collections import Counter
6✔
13
from .complex_abcs import Complex
6✔
14

15
class RankComplex(Complex, Sequence, ComplexLike):
6✔
16
  """Simplicial complex represented via the combinatorial number system.
17
  
18
  A rank complex is a simplicial complex that uses a correspondence between the natural numbers and simplices, the _combinatorial number system_,
19
  to store simplices as plain integers in contiguous memory. The integers are computed by _ranking_ each simplex, i.e. bijecting each p-simplex to an 
20
  integer in the range [0, comb(n,p+1)).
21

22
  Computationally, the simplices and their dimensions are stored via ranks as 64-bit/8-bit unsigned integers, respectively, in a structured numpy array.
23
  When needed, their vertex representations are computed on the fly by inverting the correspondence ('unranking'). This process can be prone to overflow due to the 
24
  growth rate of the binomial coefficient---however, for low-dimensional complexes it is fairly safe. In particular, if the vertex labels 
25
  always start from 0, then any _d_-dimensional complex of with _n_ unique vertex labels will be representable without overflow if: 
26

27
  - _d_ <= 0 and _n_ <= 2**64 - 1
28
  - _d_ <= 1 and _n_ <= ~ 6B 
29
  - _d_ <= 2 and _n_ <= ~ 4.5M 
30
  - _d_ <= 3 and _n_ <= ~ 125K
31
  - _d_ <= 4 and _n_ <= ~ 15K
32
  ...
33
  
34
  The smallest _n_ that causes overflow for complete complexes is 68, and thus this data structure should be avoided when very 
35
  high-dimensional complexes are needed. 
36

37
  Attributes:
38
    simplices: structured ndarray of dtype [('rank', uint64), ('dim', uint8)] containing the simplex ranks and dimensions, respectively. 
39
  """
40
  
41
  @staticmethod 
6✔
42
  def _str_rank(item: SimplexConvertible) -> tuple:
6✔
43
    s = Simplex(item)
6✔
44
    return comb_to_rank(s), dim(s)
6✔
45

46
  def __init__(self, simplices: Iterable[SimplexConvertible] = None) -> None:
6✔
47
    """"""
48
    # assert isinstance(simplices, Iterable) and is_repeatable(simplices), "Iterable must be repeatable. A generator is not sufficient!"
49
    # simplices = faces(simplices) if isinstance(simplices, ComplexLike) else simplices 
50
    self.s_dtype = np.dtype([('rank', np.uint64), ('dim', np.uint8)])
6✔
51
    if simplices is not None:
6✔
52
      simplices = list(map(Simplex, simplices))
6✔
53
      sset = unique_everseen(faces(simplices))
6✔
54
      self.simplices = np.unique(np.array([RankComplex._str_rank(s) for s in sset], dtype=self.s_dtype))
6✔
55
    else:
56
      self.simplices = np.empty(dtype=self.s_dtype, shape=(0,0))
6✔
57

58
  def __len__(self) -> int: 
6✔
59
    return len(self.simplices)
×
60
  
61
  def __contains__(self, x: SimplexConvertible) -> bool:
6✔
62
    return comb_to_rank(x) in self.simplices['rank']
×
63
    
64
  def dim(self) -> int: 
6✔
65
    """The maximal dimension of any simplex in the complex."""
66
    return np.max(self.simplices['dim'])
6✔
67

68
  def faces(self, p: int = None, **kwargs) -> Iterable['SimplexLike']:
6✔
69
    """Enumerates the faces of the complex.
70
    
71
    Parameters:
72
      p: optional integer indicating which dimension of faces to enumerate. Default to None (enumerates all faces).
73
    
74
    Returns:
75
      generator which yields on evaluation yields the simplex
76
    """
77
    if p is not None: ## Returns a simplexWrapper
6✔
78
      assert isinstance(p, numbers.Integral)
6✔
79
      p_ranks = self.simplices['rank'][self.simplices['dim'] == p]
6✔
80
      return rank_to_comb(p_ranks, k=p+1, order='colex') if len(p_ranks) > 0 else np.empty(shape=(0,), dtype=np.int64)
6✔
81
    else:
82
      return map(Simplex, rank_to_comb(self.simplices['rank'], k=self.simplices['dim']+1, order='colex'))
6✔
83

84
  def card(self, p: int = None) -> Union[tuple, int]:
6✔
85
    if p is None: 
6✔
86
      # return tuple(Counter(self.simplices['dim']).values())
87
      return tuple(np.bincount(self.simplices['dim']))
6✔
88
    else: 
89
      return np.sum(self.simplices['dim'] == p)
6✔
90

91
  def __iter__(self) -> Iterable[SimplexLike]:
6✔
92
    """Enumerates the faces of the complex."""
93
    yield from rank_to_comb(self.simplices['rank'], k=self.simplices['dim']+1, order='colex')
6✔
94

95
  def __getitem__(self, index: Union[int, slice]) -> Union[SimplexConvertible, Iterable]:
6✔
96
    """Retrieves a simplex at some index position. 
97
    
98
    Note this constructs the simplex on demand from its rank information. 
99
    """
100
    if isinstance(index, Integral):
×
101
      s = rank_to_comb(self.simplices['rank'][index], k=self.simplices['dim'][index]+1, order='colex')
×
102
      return Simplex(s)
×
103
    elif isinstance(index, slice):
×
104
      return map(Simplex, rank_to_comb(self.simplices['rank'][index], k=self.simplices['dim'][index]+1, order='colex'))
×
105
    else:
106
      raise ValueError(f"Invalid index type '{type(index)}' given.")
×
107

108
  def add(self, item: SimplexConvertible) -> None: ## TODO: consider array module with numpy array fcasting 
6✔
109
    """Adds a simplex and its faces to the complex, if they do not already exist.
110
    
111
    If _item_ is already in the complex, the underlying complex is not modified. 
112
    """
113
    if item not in self:
6✔
114
      face_ranks = np.array([RankComplex._str_rank(f) for f in faces(item)], dtype=self.s_dtype)
6✔
115
      self.simplices = np.unique(np.append(self.simplices, face_ranks))
6✔
116
    # new_faces = []
117
    # for s in simplices:
118
    #   face_ranks = [RankComplex._str_rank(f) for f in faces(s)]
119
    #   new_faces.extend(face_ranks)
120
    # new_faces = np.array(new_faces, dtype=self.s_dtype)
121
    # self.simplices = np.unique(np.append(self.simplices, new_faces))
122

123
  def cofaces(self, item: SimplexConvertible):
6✔
124
    s = Simplex(item)
6✔
125
    yield from filter(lambda t: Simplex(t) >= s, iter(self))
6✔
126

127
  ## TODO: need to add cofaces to enable remove 
128
  ## TODO: distinguish simplex convertible from Iterable of simplices--need to revisit the predicates
129
  def remove(self, item: SimplexConvertible) -> None:
6✔
130
    """Removes simplices from the complex. They must exist.
131

132
    If any of the supplied _simplices_ are not in the complex, raise a KeyError.
133
    """
134
    s_item = np.array([RankComplex._str_rank(item)], dtype=self.s_dtype)
6✔
135
    if s_item not in self.simplices:
6✔
136
      raise KeyError(f"{str(item)} not in complex.")
×
137
    s_cofaces = np.array([RankComplex._str_rank(item) for f in self.cofaces(item)], dtype=self.s_dtype)
6✔
138
    self.simplices = np.setdiff1d(self.simplices, s_cofaces)
6✔
139
    # faces_to_remove = np.array([(rank_colex(s), dim(s)) for s in simplices], dtype=self.s_dtype)
140
    # in_complex = np.array([s in self.simplices for s in faces_to_remove])
141
    # if any(~in_complex):
142
    #   bad = list(simplices)[np.flatnonzero(~in_complex)[0]]
143
    #   raise KeyError(f"{bad} not in complex.")
144
    # self.simplices = np.setdiff1d(self.simplices, faces_to_remove)
145

146
  def discard(self, item: SimplexConvertible) -> None:
6✔
147
    """Removes simplices from the complex, if they exist.
148
    
149
    If none of the supplied _simplices_ are in the complex, the simplices are not modified.  
150
    """
151
    s_cofaces = np.array([RankComplex._str_rank(f) for f in self.cofaces(item)], dtype=self.s_dtype)
6✔
152
    if len(s_cofaces) > 0:
6✔
153
      self.simplices = np.setdiff1d(self.simplices, s_cofaces)
6✔
154

155
  def __contains__(self, item: SimplexConvertible) -> bool:
6✔
156
    # s = Simplex(item)
157
    if len(item) == 0: 
6✔
158
      return True # always contains the empty face
×
159
    s = np.array([RankComplex._str_rank(item)], self.s_dtype)
6✔
160
    return self.simplices.__contains__(s)
6✔
161
    # return self.data.__contains__()
162

163
  def __len__(self) -> int: 
6✔
164
    return len(self.simplices)
6✔
165
  
166
  def __repr__(self) -> str:
6✔
UNCOV
167
    if len(self) == 0:
×
168
      return "< Empty rank complex >"
×
UNCOV
169
    return f"{type(self).__name__} with {card(self)} {tuple(range(0,dim(self)+1))}-simplices"
×
170

171
  # def __array__(self, dtype=None):
172
  #   return self.simplices
173
  
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc