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

peekxc / splex / 9978687287

17 Jul 2024 05:22PM UTC coverage: 84.772%. Remained the same
9978687287

push

github

peekxc
Simplex changes to adjust for numpy arrays

1 of 2 new or added lines in 1 file covered. (50.0%)

835 of 985 relevant lines covered (84.77%)

2.54 hits per line

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

91.0
/src/splex/Simplex.py
1
from __future__ import annotations # for mypy to recognize self return types
3✔
2
from numbers import Number, Integral
3✔
3
from typing import *
3✔
4
from .generics import * 
3✔
5

6
from itertools import * 
3✔
7
from more_itertools import collapse, unique_justseen, seekable, spy
3✔
8
from dataclassy import dataclass
3✔
9
import numpy as np 
3✔
10

11
@dataclass(frozen=True, slots=True, init=False, repr=False, eq=False)
3✔
12
class SimplexBase: # forget about hashable to make compatible as a data class 
3✔
13
  """Base class for comparable simplex-like classes with integer vertex labels."""
14
  vertices: Union[tuple[int], Tuple[()]] = ()
3✔
15
  def __init__(self, v: SimplexConvertible):
3✔
16
    object.__setattr__(self, 'vertices', tuple(unique_justseen(sorted(collapse(v)))))
3✔
17
    
18
  def __eq__(self, other: object) -> bool: 
3✔
19
    if not isinstance(other, SimplexConvertible):
3✔
20
      return False
×
21
    if len(self) != len(other): return False
3✔
22
    return(all(v == w for (v, w) in zip(iter(self.vertices), iter(other))))
3✔
23
  
24
  def __len__(self) -> int:
3✔
25
    return len(self.vertices)
3✔
26
  
27
  def __lt__(self, other: SimplexConvertible[IT]) -> bool:
3✔
28
    if len(self) >= len(other): 
3✔
29
      return(False)
3✔
30
    else:
31
      return(all([v in other for v in self.vertices]))
3✔
32
  
33
  def __le__(self, other: Collection[IT]) -> bool: 
3✔
34
    if len(self) > len(other): 
3✔
35
      return(False)
3✔
36
    elif len(self) == len(other):
3✔
37
      return self.__eq__(other)
3✔
38
    else:
39
      return self < other
3✔
40
  
41
  def __ge__(self, other: Collection[IT]) -> bool:
3✔
42
    if len(self) < len(other): 
3✔
43
      return(False)
3✔
44
    elif len(self) == len(other):
3✔
45
      return self.__eq__(other)
3✔
46
    else:
47
      return self > other
3✔
48
  
49
  def __gt__(self, other: Collection[IT]) -> bool:
3✔
50
    """Simplex-wise comparison."""
51
    if len(self) <= len(other): 
3✔
52
      return(False)
×
53
    else:
54
      return(all([v in self.vertices for v in other]))
3✔
55
  
56
  def __contains__(self, __x: int) -> bool:
3✔
57
    """Vertex-wise membership test."""
58
    if not isinstance(__x, Number): 
3✔
59
      return False
×
60
    return self.vertices.__contains__(__x)
3✔
61
  
62
  def __iter__(self) -> Iterator[int]:
3✔
63
    """Vertex-wise iteration."""
64
    return iter(self.vertices)
3✔
65
  
66
  def __getitem__(self, index: int) -> int:
3✔
67
    """Vertex-wise indexing."""
68
    return self.vertices[index] # auto handles IndexError exception 
3✔
69
  
70
  def __sub__(self, other: SimplexConvertible) -> Simplex:
3✔
71
    """Vertex-wise set difference."""
72
    return Simplex(set(self.vertices) - set(Simplex(other).vertices))
3✔
73
  
74
  def __add__(self, other: SimplexConvertible) -> Simplex:
3✔
75
    """Vertex-wise set union."""
76
    return Simplex(set(self.vertices) | set(Simplex(other).vertices))
×
77
  
78
  def __hash__(self) -> int:
3✔
79
    """ Vertex-wise hashing to support equality tests and hashability """
80
    return hash(self.vertices)
3✔
81

82
  def __repr__(self) -> str:
3✔
83
    """ Default str representation prints the vertex labels delimited by commas """
84
    return str(tuple(self.vertices)).replace(',','') if self.dim() == 0 else str(tuple(self.vertices)).replace(' ','')
3✔
85

86
  def faces(self, p: Optional[int] = None, data: bool = False, **kwargs) -> Iterator[Simplex]: 
3✔
87
    dim: int = len(self.vertices)
3✔
88
    if p is None:
3✔
89
      g = map(Simplex, chain(*[combinations(self.vertices, d) for d in range(1, dim+1)]))
3✔
90
    else: 
91
      g = map(Simplex, combinations(self.vertices, p+1))
3✔
92
      # g = filter(lambda s: len(s) == p+1, self.faces()) # type: ignore
93
    return zip_data(g, data)
3✔
94
    # g = g if data == False else handle_data(g, data)
95
  
96
  def boundary(self) -> Iterator[Simplex]: 
3✔
97
    if len(self.vertices) == 0: 
3✔
98
      return self.vertices
×
99
    yield from map(Simplex, combinations(self.vertices, len(self.vertices)-1))
3✔
100

101
  def dim(self) -> int: 
3✔
102
    return len(self.vertices)-1
3✔
103

104
  def __array__(self, dtype = None) -> np.ndarray: 
3✔
105
    """Support native array conversion."""
106
    dtype = np.uint32 if dtype is None else dtype
3✔
107
    return np.asarray(self.vertices, dtype = dtype)
3✔
108

109
  def __int__(self) -> int:
3✔
110
    if len(self.vertices) != 1: raise ValueError(f"Invalid conversion of simplex {str(self)} to integer")
×
NEW
111
    return int(self.vertices[0])
×
112

113
@dataclass(slots=True, frozen=True, init=False, repr=False, eq=False)
3✔
114
class Simplex(SimplexBase): # , Generic[IT]
3✔
115
  """Simplex dataclass.
116

117
  A simplex is a value type object supporting set-like behavior. Simplex instances are hashable, comparable, immutable, and homogenous. 
118
  """
119
  def __init__(self, v: SimplexConvertible):
3✔
120
    # t = tuple(unique_justseen(sorted(collapse(v))))
121
    super(Simplex, self).__init__(v)
3✔
122
    # object.__setattr__(self, 'vertices', t)
123

124
@dataclass(slots=False, frozen=False, init=False, repr=False, eq=False)
3✔
125
class PropertySimplex(SimplexBase):
3✔
126
  """Dataclass for representing a simplex associated with arbitrary properties. 
127

128
  A simplex is a value type object supporting set-like behavior. Simplex instances are hashable, comparable, immutable, and homogenous. 
129

130
  Unlike the _Simplex_ class, this class is neither frozen nor slotted, thus it supports arbitrary field assignments.  
131
  """
132
  def __init__(self, v: SimplexConvertible, **kwargs) -> None:
3✔
133
    # super(SimplexBase, self).__init__()
134
    super(PropertySimplex, self).__init__(v)
3✔
135
    self.__dict__.update(kwargs)
3✔
136
    # object.__setattr__(self, )
137
  def __setattr__(self, key: str, value: Any) -> None:
3✔
138
    self.__dict__[key] = value
3✔
139

140

141

142
## TODO: Is this entire class not worth it?
143
## 1. inequality tests become muddled. could inherit all the same behavior but then augment to use value to break ties
144
## 2. ... but then the value is not a filter value, should be first comparison made 
145
## 3. boundary and face enumeration become an issue---do they inherit the value? Or are they just simplex's? 
146
## the former must be done at the filtration class level, whereas the latter can be done simply by downcasts in the process 
147
## 4. ... the distinction between the typical Simplex is that although a regular simplex in a complex might have faces not taking the same 
148
## memory as the ones enumerated by a given simplex, we call them equivalent via the hashable equality test. In essence, a Simplex really is a value-type, 
149
## in the sense that it is an r-type, whereas a Filtered Simplex acts more like an l-value. In the context of C#, (https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/value-types), 
150
## Simplex's are structure types that support value semantics. In contrast, one could have several ValueSimplexes of the same underlying simplex, but different values. 
151
## Moreover, **two filtrations could have identical ValueSimplexes with different faces/boundaries**
152
## -----
153
## Suppose we just inherit all the struct-like behavior of a regular simplex. 
154
## 1. Equality testing is still vertex-wise. Non-vertex Values are ignored.
155
## 2. Inequality ordering is unchanged and valid. It is up to the class that uses Value Simplices to ensure they are ordered correctly. 
156
## 3. boundary and face enumeration would just downcast to simplices / discard values. 
157
## 4. faces(S, p=1, data=True) would yield something like 
158
## (o) for s in faces(S, p=1, data=False) => default behavior
159
## (a) for s,d in faces(S, p=1, data=True) => d is empty dict view {} for Simplex 
160
## (b) for s,d in faces(S, p=1, data=True) => d is { 'value' : ... } for ValueSimplex 
161
## (c) for s,d in faces(S, p=1, data=True) => d is dict view for PropertySimplex
162
## If (b) is changed such that d is a Number type, code would be simpler, but changes the interface. 
163
## ... https://networkx.org/documentation/stable/developer/nxeps/nxep-0002.html
164
## S.faces(data=True) should return a FaceDataView such that S.faces(data=True)[s] return the attribute dictionary of s 
165
## Note: Should use more_itertools Sequence views to support indexing, slicing, and length queries.
166
## ahh but then, S.faces() should in actuality return a VIEW 
167
## more_itertools supports constructing SequenceViews which support indexing, slicing, and length queries, though they likely require a Sequence input, 
168
## which demands __get_item__, which SimplexTree doesn't necessary have. 
169
## SequenceView's are iterable though, so the contract of faces is still in place. Suggests faces(..., p = Number, ...) could be potentially very multi-pronged:
170
## - Supports __iter__ + __array__ + __array_interface__ + Sequence + SequenceView + w/ Rank Complex 
171
## - Supports __iter__ + __array__ + Sequence + SequenceView for SetComplex 
172
## - Supports __iter__ + __array__ for SimplexTree
173

174
@dataclass(slots=True, frozen=True, init=False, repr=False, eq=False)
3✔
175
class ValueSimplex(SimplexBase, Generic[IT]):
3✔
176
  """Dataclass for representing a simplex associated with a single numerical value. 
177

178
  A simplex is a value type object supporting set-like behavior. Simplex instances are hashable, comparable, immutable, and homogenous.
179

180
  Unlike the _Simplex_ class, this class is has an additional value slot to change the poset
181
  """
182
  value: Number
3✔
183
  def __init__(self, v: SimplexConvertible, value: Number) -> None:
3✔
184
    if not isinstance(value, Number): # todo re-work the order or just consider dtype
3✔
185
      value = np.take(np.squeeze(value),0) if hasattr(value, 'dtype') else value
3✔
186
      value = float(value) if (hasattr(value, '__float__') and not isinstance(value, Number)) else value
3✔
187
      value = int(value) if (hasattr(value, '__int__') and not isinstance(value, Number)) else value
3✔
188
    assert isinstance(value, Number), "Value must be a number"
3✔
189
    t = tuple(unique_justseen(sorted(collapse(v))))
3✔
190
    object.__setattr__(self, 'value', value)
3✔
191
    object.__setattr__(self, 'vertices', t)
3✔
192

193
  def __repr__(self) -> str:
3✔
194
    idx_str = f"{self.value}" if isinstance(self.value, Integral) else f"{self.value:.2f}"
×
195
    return idx_str+":"+str(self.vertices).replace(',','') if self.dim() == 0 else idx_str+":"+str(self.vertices).replace(' ','')
×
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