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

peekxc / splex / 6566946791

18 Oct 2023 09:47PM UTC coverage: 82.528% (-2.2%) from 84.734%
6566946791

push

github

peekxc
Working on the abc for filtration types. I think I have a good idea of what a good wrapper type should implement. Started the Filtration abc, which going to be similar to a Sequence type mixed with a Set type, but have some additional constraints

58 of 58 new or added lines in 5 files covered. (100.0%)

666 of 807 relevant lines covered (82.53%)

2.48 hits per line

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

70.59
/src/splex/filtrations/SetFiltration.py
1
import numpy as np  
3✔
2
from ..meta import *
3✔
3
from ..generics import *
3✔
4
from ..Simplex import *
3✔
5
from ..complexes import * 
3✔
6
from .filter_abcs import Filtration
3✔
7
from sortedcontainers import SortedSet
3✔
8
from more_itertools import spy
3✔
9
import bisect
3✔
10

11
# Requires: __getitem__, __delitem__, __setitem__ , __iter__, and __len__ a
12
# Inferred: pop, clear, update, and setdefault
13
# https://treyhunner.com/2019/04/why-you-shouldnt-inherit-from-list-and-dict-in-python/
14
class SetFiltration(Filtration, Sequence):
3✔
15
  """Filtered complex of simplices uses _SortedSet_.
16

17
  This class represents a filtration of simplices by associating keys of a given index set with _SortedSet_'s of _Simplex_ instances.
18
  """
19

20
  @classmethod
3✔
21
  def _key_dim_lex(cls, s: ValueSimplex) -> bool:
3✔
22
    return (s.value, len(s), tuple(s), s)
3✔
23
  
24
  @classmethod
3✔
25
  def _key_dim_colex(cls, s: ValueSimplex) -> bool:
3✔
26
    return (s.value, len(s), tuple(s), s)
×
27

28
  @classmethod
3✔
29
  def _sorted_set(self, iterable: Iterable[ValueSimplex] = None, order: str = "lex") -> SortedSet:
3✔
30
    """Returns a newly allocated Sorted Set w/ lexicographical poset ordering."""
31
    if order == "lex":
3✔
32
      key = SetFiltration._key_dim_lex
3✔
33
    elif order == "colex":
×
34
      key = SetFiltration._key_dim_lex
×
35
    else: 
36
      raise ValueError(f"Invalid order '{str(order)}' given.")
×
37
    if iterable is not None: 
3✔
38
      head, iterable = spy(iterable)
3✔
39
      assert isinstance(head[0], ValueSimplex)
3✔
40
      return SortedSet(iterable, key)
3✔
41
    else:
42
      return SortedSet(None, key)
3✔
43
  
44
  def __init__(self, simplices: Union[ComplexLike, Iterable] = None, f: Optional[Callable] = None, order: str = 'lex') -> None:
3✔
45
    """Constructs a filtration by storing simplices in a _SortedSet_ container.
46

47
    Accepts any of the following pairs: 
48
      - Iterable of (key, simplex) pairs
49
      - Iterable[SimplexConvertible], f = None
50
      - Iterable[SimplexConvertible], f = Callable
51
    """
52
    self.data = SetFiltration._sorted_set()
3✔
53
    self.n_simplices = tuple()
3✔
54
    if is_complex_like(simplices):
3✔
55
      if isinstance(f, Callable):
3✔
56
        self.update((ValueSimplex(s, f(s)) for s in simplices))
3✔
57
      else:
58
        raise ValueError("Must supply filter function 'f' for ComplexLike inputs.")
×
59
    elif isinstance(simplices, Iterable):
3✔
60
      if isinstance(f, Callable):
3✔
61
        self.update((ValueSimplex(s,value=f(s)) for s in simplices))
×
62
      else:
63
        self.update((ValueSimplex(s,value=k) for k,s in simplices)) ## accept pairs, like a normal dict
3✔
64
    elif simplices is None:
3✔
65
      pass # Allow default constructible for empty filtrations
3✔
66
    else: 
67
      raise ValueError("Invalid input")
×
68

69
  ## --- Collection/Set requirements --- 
70
  def __iter__(self) -> Iterator[ValueSimplex]:
3✔
71
    """ Yields pairs (index, simplex) from the filtration. """
72
    yield from ((s.value, Simplex(s)) for s in self.data)
3✔
73

74
  def __len__(self) -> int:
3✔
75
    return len(self.data)
3✔
76

77
  def __contains__(self, k: SimplexConvertible) -> bool: # simplex-wise only 
3✔
78
    return Simplex(k) in self.data
3✔
79

80
  ## --- Sequence requirements ---
81
  def __getitem__(self, key: Any) -> Simplex: 
3✔
82
    return self.data.__getitem__(key)
3✔
83

84
  def index(self, item: SimplexConvertible) -> int:  
3✔
85
    s = Simplex(item)
×
86
    for i,x in iter(s):
×
87
      if x == s:
×
88
        return i
×
89
    return -1
×
90
    # return True # self.data.
91
    # bisect.bisect_left(self.data, Simplex(item), key=lambda vs: Simplex(vs))
92

93
  def count(self, item: SimplexConvertible) -> int: 
3✔
94
    s = Simplex(item)
×
95
    s_count = 0
×
96
    for i,x in iter(s):
×
97
      s_count += (x == s)
×
98
    return s_count
×
99

100
  ## --- MutableSequence requirements --- 
101
  # def __setitem__(self, key: Any, value: Union[Collection[Integral], SortedSet]):
102
  #   self.data.__setitem__(key, self._sorted_set(v))
103

104
  # def __delitem__(self, key: Any):
105
  #   self.data.pop(key)
106

107
  # def insert(self, index: Any, simplex: SimplexConvertible):
108
  #   self.data.add(ValueSimplex(simplex, index))
109

110
  ## --- MutableSet requirements ---
111
  def add(self, simplex: SimplexConvertible) -> None:
3✔
112
    assert isinstance(simplex, SimplexConvertible) # or isinstance(simplex, tuple)
3✔
113
    simplex = ValueSimplex(simplex, 0.0) if not hasattr(simplex, "value") else simplex
3✔
114
    ns = list(self.n_simplices) + [0]*(dim(simplex)-self.dim())
3✔
115
    for f in faces(simplex):
3✔
116
      if f not in self.data:
3✔
117
        self.data.add(ValueSimplex(f, simplex.value))
3✔
118
        ns[dim(f)] += 1
3✔
119
    self.n_simplices = tuple(ns)
3✔
120

121
  def update(self, simplices: Iterable[ValueSimplex]) -> None: 
3✔
122
    for s in simplices: 
3✔
123
      self.add(s)
3✔
124

125
  # def discard(self, simplex: ValueSimplex):
126
  #   assert isinstance(simplex, ValueSimplex) or isinstance(simplex, tuple)
127
  #   simplex = ValueSimplex(simplex[1], simplex[0]) if isinstance(simplex, tuple) else simplex
128
  #   s_cofaces = list(self.cofaces(simplex))
129
  #   ns = list(self.n_simplices) 
130
  #   for c in s_cofaces:
131
  #     self.data.discard(c)
132
  #     ns[dim(c)] -= 1
133
  #   self.n_simplices = tuple(rstrip(ns, lambda x: x <= 0))
134

135
  ## --- splex generics support --- 
136
  def dim(self) -> int:
3✔
137
    return len(self.n_simplices)-1
3✔
138

139
  def faces(self, p: int = None, **kwargs) -> Iterator[ValueSimplex]:
3✔
140
    assert isinstance(p, Integral) or p is None, f"Invalid p:{p} given"
3✔
141
    #return self.values() if p is None else filter(lambda s: len(s) == p+1, self.values())
142
    return iter(self.data) if p is None else filter(lambda s: len(s) == p+1, iter(self.data))
3✔
143

144
  ## --- Filtration specific enhancements --- 
145
  def indices(self) -> Iterator[Any]:
3✔
146
    return (i for i,s in iter(self))
3✔
147

148
  def reindex(self, index_set: Union[Iterable, Callable]) -> None:
3✔
149
    """Given a totally ordered key set of the same length of the filtation, or a callable, reindexes the simplices in the filtration"""
150
    if isinstance(index_set, Iterable):
×
151
      assert len(index_set) == len(self), "Index set length not match the number of simplices in the filtration!"
×
152
      assert all((i <= j for i,j in pairwise(index_set))), "Index set is not totally ordered!"
×
153
      new = SetFiltration(zip(iter(index_set), faces(self)))
×
154
      self.data = new.data
×
155
      assert self.n_simplices == new.n_simplices, "Invalid reindexing; simplex counts changed"
×
156
    elif isinstance(index_set, Callable):
×
157
      new = SetFiltration(simplices=faces(self), f=index_set)
×
158
      self.data = new.data
×
159
      assert self.n_simplices == new.n_simplices, "Invalid reindexing; simplex counts changed"
×
160
    else:
161
      raise ValueError("invalid index set supplied")
×
162

163
  ## Additional functions
164
  def cofaces(self, item: Collection[int]) -> Iterable:
3✔
165
    s = Simplex(item)
×
166
    yield from filter(lambda t: t >= s, iter(self))
×
167

168
  ## --- Miscelleneous --- 
169
  def copy(self) -> 'SetFiltration':
3✔
170
    new = SetFiltration()
3✔
171
    from copy import deepcopy
3✔
172
    new.data = SetFiltration._sorted_set(self.data) #deepcopy(self.data)
3✔
173
    new.n_simplices = deepcopy(self.n_simplices)
3✔
174
    return new 
3✔
175
  
176
  # def _add_ns(self, ) -> None:
177

178
  # ## delegate new behavior to new methods: __iadd__, __isub__
179
  # def update(self, other: Iterable[Tuple[Any, Collection[Integral]]]):
180
  #   for k,v in other:
181
  #     self.data.__setitem__(k, self._sorted_set(v))
182

183
  ## Returns the value of the item with the specified key.
184
  ## If key doesn't exist, set's F[key] = default and returns default
185
  # def setdefault(self, key, default=None):
186
  #   if key in self.data:
187
  #     return self[key] # value type 
188
  #   else:
189
  #     self.__setitem__(key, default)
190
  #     return self[key]   # value type  
191

192
  # https://peps.python.org/pep-0584/
193
  # def __or__(self, other: Union[Iterable[Tuple[int, int]], Mapping]):
194
  #   new = self.copy()
195
  #   new.update(SortedDict(other))
196
  #   return new
197

198
  # def __ror__(self, other: Union[Iterable[Tuple[int, int]], Mapping]):
199
  #   new = SortedDict(other)
200
  #   new.update(self.data)
201
  #   return new
202

203
  ## In-place union '|=' operator 
204
  # TODO: map Collection[Integral] -> SimplexLike? 
205
  # def __ior__(self, other: Union[Iterable[Tuple[int, int]], Mapping]):
206
  #   self.data.update(other)
207
  #   return self
208
  
209
  # ## In-place append '+=' operator ; true dict union/merge, retaining values
210
  # def __iadd__(self, other: Iterable[Tuple[Any, SimplexLike]]):
211
  #   for k,v in other:
212
  #     if len(Simplex(v)) >= 1:
213
  #       # print(f"key={str(k)}, val={str(v)}")
214
  #       s_set = self.setdefault(k, self._sorted_set())
215
  #       f = Simplex(v)
216
  #       if not(f in s_set):
217
  #         s_set.add(f)
218
  #         if len(f) > len(self.shape):
219
  #           self.shape = tuple(list(tuple(self.shape)) + [1])
220
  #         else:
221
  #           t = self.shape
222
  #           self.shape = tuple(t[i]+1 if i == (len(f)-1) else t[i] for i in range(len(t)))   
223
  #   return self
224

225
  # ## Copy-add '+' operator 
226
  # def __add__(self, other: Iterable[Tuple[int, int]]):
227
  #   new = self.copy()
228
  #   new += other 
229
  #   return new
230

231
  ## Keys yields the index set. Set expand = True to get linearized order. 
232
  ## TODO: Make view objects
233
  # def keys(self):
234
  #   it_keys = chain()
235
  #   for k,v in self.data.items():
236
  #     it_keys = chain(it_keys, repeat(k, len(v)))
237
  #   return it_keys
238

239
  # def values(self):
240
  #   it_vals = chain()
241
  #   for v in self.data.values():
242
  #     it_vals = chain(it_vals, iter(v))
243
  #   return it_vals
244

245
  # def items(self):
246
  #   it_keys, it_vals = chain(), chain()
247
  #   for k,v in self.data.items():
248
  #     it_keys = chain(it_keys, repeat(k, len(v)))
249
  #     it_vals = chain(it_vals, iter(v))
250
  #   return zip(it_keys, it_vals)
251

252

253

254
# class MySet(Set):
255
#   def __init__(self, L):
256
#     self.data = set(L)
257
#   def __contains__(self, val):
258
#     return val in self.data
259
#   def __iter__(self):
260
#     return iter(self.data)
261
#   def __len__(self):
262
#     return len(self.data)
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

© 2025 Coveralls, Inc