phimaker 0.3.0__cp313-none-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
phimaker/__init__.py
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
from wsgiref.simple_server import make_server
|
|
2
|
+
from pyramid.config import Configurator
|
|
3
|
+
from pyramid.view import view_config
|
|
4
|
+
from pyramid.response import FileResponse
|
|
5
|
+
from pyramid.response import Response
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import threading
|
|
8
|
+
import signal
|
|
9
|
+
from contextlib import redirect_stdout
|
|
10
|
+
import io
|
|
11
|
+
import webbrowser
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def build_washboard_object(
|
|
15
|
+
dgms, max_diagram_dim, truncation, dimensions, entrance_times
|
|
16
|
+
):
|
|
17
|
+
def make_pairing_obj(dgm, additional_max_dim=0, dim_shift=0):
|
|
18
|
+
pairings = {"paired": list(dgm.paired), "unpaired": list(dgm.unpaired)}
|
|
19
|
+
pairings = remove_trivial_pairings(pairings)
|
|
20
|
+
pairings = filter_by_dimension(pairings, additional_max_dim, dim_shift)
|
|
21
|
+
pairings = group_by_dimension(pairings, additional_max_dim, dim_shift)
|
|
22
|
+
return pairings
|
|
23
|
+
|
|
24
|
+
# Remove pairings with 0 lifetime
|
|
25
|
+
def remove_trivial_pairings(pairings):
|
|
26
|
+
new_pairings = []
|
|
27
|
+
for pairing in pairings["paired"]:
|
|
28
|
+
if entrance_times[pairing[0]] == entrance_times[pairing[1]]:
|
|
29
|
+
continue
|
|
30
|
+
new_pairings.append(pairing)
|
|
31
|
+
pairings["paired"] = new_pairings
|
|
32
|
+
return pairings
|
|
33
|
+
|
|
34
|
+
# Remove pairings in too high a dimension
|
|
35
|
+
def filter_by_dimension(pairings, additional_max_dim, dim_shift):
|
|
36
|
+
new_paired = []
|
|
37
|
+
new_unpaired = []
|
|
38
|
+
for pairing in pairings["paired"]:
|
|
39
|
+
dim = dimensions[pairing[0]] - dim_shift
|
|
40
|
+
if dim > max_diagram_dim + additional_max_dim:
|
|
41
|
+
continue
|
|
42
|
+
new_paired.append(pairing)
|
|
43
|
+
for unpaired in pairings["unpaired"]:
|
|
44
|
+
dim = dimensions[unpaired] - dim_shift
|
|
45
|
+
# Only add additional_max_dim to pairings
|
|
46
|
+
if dim > max_diagram_dim:
|
|
47
|
+
continue
|
|
48
|
+
new_unpaired.append(unpaired)
|
|
49
|
+
pairings["paired"] = new_paired
|
|
50
|
+
pairings["unpaired"] = new_unpaired
|
|
51
|
+
return pairings
|
|
52
|
+
|
|
53
|
+
def group_by_dimension(pairings, additional_max_dim, dim_shift):
|
|
54
|
+
diagrams_by_dimension = [
|
|
55
|
+
[] for _ in range(max_diagram_dim + additional_max_dim + 1)
|
|
56
|
+
]
|
|
57
|
+
for pairing in pairings["paired"]:
|
|
58
|
+
dim = dimensions[pairing[0]] - dim_shift
|
|
59
|
+
diagrams_by_dimension[dim].append(pairing)
|
|
60
|
+
for unpaired in pairings["unpaired"]:
|
|
61
|
+
dim = dimensions[unpaired] - dim_shift
|
|
62
|
+
diagrams_by_dimension[dim].append([unpaired])
|
|
63
|
+
return diagrams_by_dimension
|
|
64
|
+
|
|
65
|
+
def add_empty_relations(all_pairings, key):
|
|
66
|
+
base_pairings = all_pairings[key]
|
|
67
|
+
for d, diagram_d in enumerate(base_pairings):
|
|
68
|
+
for idx, pairing in enumerate(diagram_d):
|
|
69
|
+
base_pairings[d][idx] = [pairing, {}]
|
|
70
|
+
return base_pairings
|
|
71
|
+
|
|
72
|
+
def fill_relations(all_pairings, key):
|
|
73
|
+
base_pairings = all_pairings[key]
|
|
74
|
+
for d, diagram_d in enumerate(base_pairings):
|
|
75
|
+
for idx, pairing_relations in enumerate(diagram_d):
|
|
76
|
+
pairing = pairing_relations[0]
|
|
77
|
+
relations = compute_relations(all_pairings, key, pairing)
|
|
78
|
+
base_pairings[d][idx] = [pairing, relations]
|
|
79
|
+
return base_pairings
|
|
80
|
+
|
|
81
|
+
def compute_relations(all_pairings, base_key, pairing):
|
|
82
|
+
relations = {}
|
|
83
|
+
for key in all_pairings.keys():
|
|
84
|
+
if key == base_key:
|
|
85
|
+
continue
|
|
86
|
+
these_relations = []
|
|
87
|
+
diagram = all_pairings[key]
|
|
88
|
+
for d, diagram_d in enumerate(diagram):
|
|
89
|
+
for idx, other_pairing in enumerate(diagram_d):
|
|
90
|
+
if not set(pairing).isdisjoint(other_pairing[0]):
|
|
91
|
+
these_relations.append([d, idx])
|
|
92
|
+
relations[key] = these_relations
|
|
93
|
+
return relations
|
|
94
|
+
|
|
95
|
+
def replace_with_f_times(all_pairings):
|
|
96
|
+
for _, diagrams in all_pairings.items():
|
|
97
|
+
for diagram in diagrams:
|
|
98
|
+
for pairing_relations in diagram:
|
|
99
|
+
pairing = pairing_relations[0]
|
|
100
|
+
coords = [entrance_times[idx] for idx in pairing]
|
|
101
|
+
pairing_relations[0] = coords
|
|
102
|
+
return all_pairings
|
|
103
|
+
|
|
104
|
+
obj = {}
|
|
105
|
+
obj["max_dim"] = max_diagram_dim
|
|
106
|
+
obj["pseudo_inf"] = truncation * 1.05
|
|
107
|
+
pairings = {
|
|
108
|
+
"codomain": make_pairing_obj(dgms.f),
|
|
109
|
+
"domain": make_pairing_obj(dgms.g),
|
|
110
|
+
"image": make_pairing_obj(dgms.im),
|
|
111
|
+
"kernel": make_pairing_obj(dgms.ker, dim_shift=1),
|
|
112
|
+
"cokernel": make_pairing_obj(dgms.cok),
|
|
113
|
+
"relative": make_pairing_obj(dgms.rel, additional_max_dim=1),
|
|
114
|
+
}
|
|
115
|
+
for key in pairings.keys():
|
|
116
|
+
pairings[key] = add_empty_relations(pairings, key)
|
|
117
|
+
for key in pairings.keys():
|
|
118
|
+
pairings[key] = fill_relations(pairings, key)
|
|
119
|
+
obj["pairings"] = replace_with_f_times(pairings)
|
|
120
|
+
return obj
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class WashboardServer:
|
|
124
|
+
def __init__(self, washboard_obj=None, host="0.0.0.0", port=6543):
|
|
125
|
+
self.washboard_obj = washboard_obj
|
|
126
|
+
self.host = host
|
|
127
|
+
self.port = port
|
|
128
|
+
self.server = self._setup_server()
|
|
129
|
+
|
|
130
|
+
def _setup_server(self):
|
|
131
|
+
def data_route(request):
|
|
132
|
+
return self.washboard_obj
|
|
133
|
+
|
|
134
|
+
def main_page(request):
|
|
135
|
+
index_file_path = (
|
|
136
|
+
Path(__file__).parent / "washboard/dist/index.html"
|
|
137
|
+
).resolve()
|
|
138
|
+
response = FileResponse(
|
|
139
|
+
index_file_path,
|
|
140
|
+
request=request,
|
|
141
|
+
content_type="text/html",
|
|
142
|
+
)
|
|
143
|
+
return response
|
|
144
|
+
|
|
145
|
+
with Configurator() as config:
|
|
146
|
+
config.add_route("home", "/")
|
|
147
|
+
config.add_view(main_page, route_name="home")
|
|
148
|
+
config.add_route("data", "/data.json")
|
|
149
|
+
config.add_view(data_route, renderer="json", route_name="data")
|
|
150
|
+
config.scan()
|
|
151
|
+
app = config.make_wsgi_app()
|
|
152
|
+
return make_server(self.host, self.port, app)
|
|
153
|
+
|
|
154
|
+
def serve_forever(self):
|
|
155
|
+
self.server.serve_forever()
|
|
156
|
+
|
|
157
|
+
def shutdown(self):
|
|
158
|
+
self.server.shutdown()
|
|
159
|
+
|
|
160
|
+
@staticmethod
|
|
161
|
+
def _get_link(text):
|
|
162
|
+
return f"\u001b]8;;{text}\u001b\\{text}\u001b]8;;\u001b\\"
|
|
163
|
+
|
|
164
|
+
def open(self, open_browser=True):
|
|
165
|
+
server_thread = threading.Thread(
|
|
166
|
+
target=lambda: self.serve_forever(), name="WashboardServer"
|
|
167
|
+
)
|
|
168
|
+
server_thread.start()
|
|
169
|
+
link = f"http://{self.host}:{self.port}"
|
|
170
|
+
link_to_print = self._get_link(link)
|
|
171
|
+
print(f"Hosting on {link_to_print}")
|
|
172
|
+
|
|
173
|
+
def signal_handler(sig, frame):
|
|
174
|
+
print("Closing down server")
|
|
175
|
+
self.shutdown()
|
|
176
|
+
|
|
177
|
+
signal.signal(signal.SIGINT, signal_handler)
|
|
178
|
+
print("Press Ctrl+C to stop")
|
|
179
|
+
|
|
180
|
+
if open_browser:
|
|
181
|
+
webbrowser.open(link)
|
|
182
|
+
|
|
183
|
+
signal.pause()
|
|
184
|
+
|
|
185
|
+
@classmethod
|
|
186
|
+
def build(
|
|
187
|
+
cls,
|
|
188
|
+
dgms,
|
|
189
|
+
max_diagram_dim,
|
|
190
|
+
truncation,
|
|
191
|
+
dimensions,
|
|
192
|
+
entrance_times,
|
|
193
|
+
host="0.0.0.0",
|
|
194
|
+
port=6543,
|
|
195
|
+
):
|
|
196
|
+
obj = build_washboard_object(
|
|
197
|
+
dgms, max_diagram_dim, truncation, dimensions, entrance_times
|
|
198
|
+
)
|
|
199
|
+
return cls(obj, host=host, port=port)
|
|
Binary file
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: phimaker
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Classifier: Programming Language :: Rust
|
|
5
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
6
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
9
|
+
|
|
10
|
+
<div align="center">
|
|
11
|
+
|
|
12
|
+
<h1>phimaker</h1>
|
|
13
|
+
|
|
14
|
+
<b>P</b>ersitent <b>h</b>omology of <b>im</b>ages <b>a</b>nd (co)<b>ker</b>nels.
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
## Overview
|
|
19
|
+
|
|
20
|
+
Phimaker is a Rust library implementing the algorithm introduced in [[1]](#1) for computing persistent homology for kernels, images and cokernels.
|
|
21
|
+
Python bindings are provided via PyO3.
|
|
22
|
+
|
|
23
|
+
Install via
|
|
24
|
+
```
|
|
25
|
+
pip install phimaker
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## References
|
|
29
|
+
|
|
30
|
+
<a id="1">[1]</a>
|
|
31
|
+
Cohen-Steiner, D., Edelsbrunner, H., Harer, J. and Morozov, D., 2009, January.
|
|
32
|
+
Persistent homology for kernels, images, and cokernels.
|
|
33
|
+
In Proceedings of the twentieth annual ACM-SIAM symposium on Discrete algorithms (pp. 1011-1020).
|
|
34
|
+
Society for Industrial and Applied Mathematics.
|
|
35
|
+
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
phimaker-0.3.0.dist-info/METADATA,sha256=xP8oUXpmJvGdhl9YhTKwgmMnvSuILWZSVxnC7-zhWvA,1040
|
|
2
|
+
phimaker-0.3.0.dist-info/WHEEL,sha256=Js3_SR0tO8oIlen-a8NLrjdvVkPOf2AEShAJt-5BUcU,95
|
|
3
|
+
phimaker/_washboard_interface.py,sha256=yeAGaIgQyDQak2IshG5UGKy054FeOMd_x9GmuBq8qoY,7241
|
|
4
|
+
phimaker/__init__.py,sha256=2DIEQ9q2noc_ejC4-IYLgV15JxdFKJsMLztmyLN-J8o,239
|
|
5
|
+
phimaker/phimaker.cp313-win_amd64.pyd,sha256=yaUWuXeBn1muIUau5uSTiqaD3gvjzB9TpGJLOys1-T4,864768
|
|
6
|
+
phimaker-0.3.0.dist-info/RECORD,,
|