1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
|
"""Bindings for the Persistent Homology Algorithm Toolbox
PHAT is a tool for algebraic topology. It can be used via phat.py to compute
persistent (co)homology from boundary matrices, using various reduction
algorithms and column data representations.
Here is a simple example of usage.
We will build an ordered boundary matrix of this simplicial complex consisting of a single triangle::
3
|\\
| \\
| \\
| \\ 4
5| \\
| \\
| 6 \\
| \\
|________\\
0 2 1
Now the code::
import phat
# define a boundary matrix with the chosen internal representation
boundary_matrix = phat.boundary_matrix(representation = phat.representations.vector_vector)
# set the respective columns -- (dimension, boundary) pairs
boundary_matrix.columns = [ (0, []),
(0, []),
(1, [0,1]),
(0, []),
(1, [1,3]),
(1, [0,3]),
(2, [2,4,5])]
# or equivalently,
# boundary_matrix = phat.boundary_matrix(representation = ...,
# columns = ...)
# would combine the creation of the matrix and
# the assignment of the columns
# print some information of the boundary matrix:
print()
print("The boundary matrix has %d columns:" % len(boundary_matrix.columns))
for col in boundary_matrix.columns:
s = "Column %d represents a cell of dimension %d." % (col.index, col.dimension)
if (col.boundary):
s = s + " Its boundary consists of the cells " + " ".join([str(c) for c in col.boundary])
print(s)
print("Overall, the boundary matrix has %d entries." % len(boundary_matrix))
pairs = boundary_matrix.compute_persistence_pairs()
pairs.sort()
print()
print("There are %d persistence pairs: " % len(pairs))
for pair in pairs:
print("Birth: %d, Death: %d" % pair)
Please see https://bitbucket.org/phat-code/phat/python for more information.
"""
import _phat
import enum
from _phat import persistence_pairs
#The public API for the module
__all__ = ['boundary_matrix',
'persistence_pairs',
'representations',
'reductions']
class representations(enum.Enum):
"""Available representations for internal storage of columns in
a `boundary_matrix`
"""
bit_tree_pivot_column = 1
sparse_pivot_column = 2
full_pivot_column = 3
vector_vector = 4
vector_heap = 5
vector_set = 6
vector_list = 7
class reductions(enum.Enum):
"""Available reduction algorithms"""
twist_reduction = 1
chunk_reduction = 2
standard_reduction = 3
row_reduction = 4
spectral_sequence_reduction = 5
class column:
"""A view on one column of data in a boundary matrix"""
def __init__(self, matrix, index):
"""INTERNAL. Columns are created automatically by boundary matrices.
There is no need to construct them directly"""
self._matrix = matrix
self._index = index
@property
def index(self):
"""The 0-based index of this column in its boundary matrix"""
return self._index
@property
def dimension(self):
"""The dimension of the column (0 = point, 1 = line, 2 = triangle, etc.)"""
return self._matrix._matrix.get_dim(self._index)
@dimension.setter
def dimension(self, value):
return self._matrix._matrix.set_dim(self._index, value)
@property
def boundary(self):
"""The boundary values in this column, i.e. the other columns that this column is bounded by"""
return self._matrix._matrix.get_col(self._index)
@boundary.setter
def boundary(self, values):
return self._matrix._matrix.set_col(self._index, values)
class boundary_matrix:
"""Boundary matrices that store the shape information of a cell complex.
"""
def __init__(self, representation = representations.bit_tree_pivot_column, source = None, columns = None):
"""
The boundary matrix will use the specified implementation for storing its
column data. If the `source` parameter is specified, it will be assumed to
be another boundary matrix, whose data should be copied into the new
matrix.
Parameters
----------
representation : phat.representation, optional
The type of column storage to use in the requested boundary matrix.
source : phat.boundary_matrix, optional
If provided, creates the requested matrix as a copy of the data and dimensions
in `source`.
columns : column list, or list of (dimension, boundary) tuples, optional
If provided, loads these columns into the new boundary matrix. Note that
columns will be loaded in the order given, not according to their ``index`` properties.
Returns
-------
matrix : boundary_matrix
"""
self._representation = representation
if source:
self._matrix = _convert(source, representation)
else:
self._matrix = self.__matrix_for_representation(representation)()
if columns:
self.columns = columns
@property
def columns(self):
"""A collection of column objects"""
return [column(self, i) for i in range(self._matrix.get_num_cols())]
@columns.setter
def columns(self, columns):
for col in columns:
if not (isinstance(col, column) or isinstance(col, tuple)):
raise TypeError("All columns must be column objects, or (dimension, values) tuples")
if len(columns) != len(self.dimensions):
self._matrix.set_dims([0] * len(columns))
for i, col in enumerate(columns):
if isinstance(col, column):
self._matrix.set_dim(i, col.dimension)
self._matrix.set_col(i, col.boundary)
else:
dimension, values = col
self._matrix.set_dim(i, dimension)
self._matrix.set_col(i, values)
@property
def dimensions(self):
"""A collection of dimensions, equivalent to [c.dimension for c in self.columns]"""
return [self._matrix.get_dim(i) for i in range(self._matrix.get_num_cols())]
@dimensions.setter
def dimensions(self, dimensions):
return self._matrix.set_dims(dimensions)
def __matrix_for_representation(self, representation):
short_name = _short_name(representation.name)
return getattr(_phat, "boundary_matrix_" + short_name)
def __eq__(self, other):
return self._matrix == other._matrix
def __len__(self):
return self._matrix.get_num_entries()
#Pickle support
def __getstate__(self):
(dimensions, columns) = self._matrix.get_vector_vector()
return (self._representation, dimensions, columns)
#Pickle support
def __setstate__(self, state):
presentation, dimensions, columns = state
self._representation = representation
self._matrix = self.__matrix_for_representation(representation)
self._matrix.set_vector_vector(dimensions, columns)
def load(self, file_name, mode = 'b'):
"""Load this boundary matrix from a file
Parameters
----------
file_name : string
The file name to load
mode : string, optional (defaults to 'b')
The mode ('b' for binary, 't' for text) to use for working with the file
Returns
-------
success : bool
"""
if mode == 'b':
return self._matrix.load_binary(file_name)
elif mode == 't':
return self._matrix.load_ascii(file_name)
else:
raise ValueError("Only 'b' - binary and 't' - text modes are supported")
def save(self, file_name, mode = 'b'):
"""Save this boundary matrix to a file
Parameters
----------
file_name : string
The file name to load
mode : string, optional (defaults to 'b')
The mode ('b' for binary, 't' for text) to use for working with the file
Returns
-------
success : bool
"""
if mode == 'b':
return self._matrix.save_binary(file_name)
elif mode == 't':
return self._matrix.save_ascii(file_name)
else:
raise ValueError("Only 'b' - binary and 't' - text modes are supported")
def compute_persistence_pairs(self,
reduction = reductions.twist_reduction):
"""Computes persistence pairs (birth, death) for the given boundary matrix."""
representation_short_name = _short_name(self._representation.name)
algo_name = reduction.name
algo_short_name = _short_name(algo_name)
#Look up an implementation that matches the requested characteristics
#in the _phat module
function = getattr(_phat, "compute_persistence_pairs_" + representation_short_name + "_" + algo_short_name)
return function(self._matrix)
def compute_persistence_pairs_dualized(self,
reduction = reductions.twist_reduction):
"""Computes persistence pairs (birth, death) from the dualized form of the given boundary matrix."""
representation_short_name = _short_name(self._representation.name)
algo_name = reduction.name
algo_short_name = _short_name(algo_name)
#Look up an implementation that matches the requested characteristics
#in the _phat module
function = getattr(_phat, "compute_persistence_pairs_dualized_" + representation_short_name + "_" + algo_short_name)
return function(self._matrix)
def convert(self, representation):
"""Copy this matrix to another with a different representation"""
return boundary_matrix(representation, self)
def _short_name(name):
"""An internal API that takes leading characters from words
For instance, 'bit_tree_pivot_column' becomes 'btpc'
"""
return "".join([n[0] for n in name.split("_")])
def _convert(source, to_representation):
"""Internal - function to convert from one `boundary_matrix` implementation to another"""
class_name = source._representation.name
source_rep_short_name = _short_name(class_name)
to_rep_short_name = _short_name(to_representation.name)
function = getattr(_phat, "convert_%s_to_%s" % (source_rep_short_name, to_rep_short_name))
return function(source._matrix)
|