groupoid 0.1.0.dev2__py3-none-any.whl → 0.1.0.dev4__py3-none-any.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.
- groupoid/__init__.py +1 -1
- groupoid/aggregation.py +66 -1
- groupoid/groupoid.py +5 -0
- groupoid/optimizer.py +64 -8
- {groupoid-0.1.0.dev2.dist-info → groupoid-0.1.0.dev4.dist-info}/METADATA +31 -24
- groupoid-0.1.0.dev4.dist-info/RECORD +15 -0
- {groupoid-0.1.0.dev2.dist-info → groupoid-0.1.0.dev4.dist-info}/WHEEL +1 -1
- groupoid-0.1.0.dev2.dist-info/RECORD +0 -15
- {groupoid-0.1.0.dev2.dist-info → groupoid-0.1.0.dev4.dist-info}/licenses/LICENSE +0 -0
- {groupoid-0.1.0.dev2.dist-info → groupoid-0.1.0.dev4.dist-info}/top_level.txt +0 -0
groupoid/__init__.py
CHANGED
groupoid/aggregation.py
CHANGED
|
@@ -23,9 +23,11 @@ import numpy as np
|
|
|
23
23
|
import numpy.typing as npt
|
|
24
24
|
from loguru import logger
|
|
25
25
|
|
|
26
|
+
from groupoid import persistence as _persistence
|
|
26
27
|
from groupoid.cohomology import compute_h1
|
|
27
28
|
from groupoid.groupoid import Morphism, compose, inverse
|
|
28
29
|
from groupoid.manifold import karcher_mean
|
|
30
|
+
from groupoid.transport import compute_transport_matrix
|
|
29
31
|
|
|
30
32
|
|
|
31
33
|
class DisconnectedClientGraphError(Exception):
|
|
@@ -42,7 +44,14 @@ class DisconnectedClientGraphError(Exception):
|
|
|
42
44
|
|
|
43
45
|
@dataclass
|
|
44
46
|
class FederatedRound:
|
|
45
|
-
"""Result of a single federated aggregation round.
|
|
47
|
+
"""Result of a single federated aggregation round.
|
|
48
|
+
|
|
49
|
+
``divergence`` is populated only when the aggregator was constructed
|
|
50
|
+
with ``track_divergence=True``: it is the persistent-homology summary
|
|
51
|
+
of the transported client parameters, with the bottleneck distance to
|
|
52
|
+
the previous round's summary (None on the first round with tracking
|
|
53
|
+
off; see :func:`groupoid.persistence.track_divergence`).
|
|
54
|
+
"""
|
|
46
55
|
|
|
47
56
|
global_params: npt.NDArray[np.float64]
|
|
48
57
|
local_updates: dict[str, npt.NDArray[np.float64]]
|
|
@@ -50,6 +59,7 @@ class FederatedRound:
|
|
|
50
59
|
is_consistent: bool
|
|
51
60
|
transport_residuals: dict[str, float]
|
|
52
61
|
round_idx: int = 0
|
|
62
|
+
divergence: _persistence.PersistenceSummary | None = None
|
|
53
63
|
|
|
54
64
|
|
|
55
65
|
@dataclass
|
|
@@ -73,14 +83,23 @@ class TransportGroupoidAggregator:
|
|
|
73
83
|
The node to which all parameters are transported for aggregation.
|
|
74
84
|
consistency_threshold
|
|
75
85
|
Maximum H^1 norm before raising a consistency warning.
|
|
86
|
+
track_divergence
|
|
87
|
+
When True, each :meth:`aggregate` round computes the persistent
|
|
88
|
+
homology of the transported client parameters and the bottleneck
|
|
89
|
+
distance to the previous round (H0 against H0), populating
|
|
90
|
+
``FederatedRound.divergence``.
|
|
76
91
|
"""
|
|
77
92
|
|
|
78
93
|
manifold: object
|
|
79
94
|
graph: nx.DiGraph
|
|
80
95
|
base_node: str
|
|
81
96
|
consistency_threshold: float = 1e-6
|
|
97
|
+
track_divergence: bool = False
|
|
82
98
|
morphisms: dict[tuple[str, str], Morphism] = field(default_factory=dict)
|
|
83
99
|
_round_idx: int = field(default=0, init=False)
|
|
100
|
+
_prev_divergence: _persistence.PersistenceSummary | None = field(
|
|
101
|
+
default=None, init=False, repr=False
|
|
102
|
+
)
|
|
84
103
|
|
|
85
104
|
def register_transport(
|
|
86
105
|
self, source: str, target: str, matrix: npt.NDArray[np.float64]
|
|
@@ -93,6 +112,42 @@ class TransportGroupoidAggregator:
|
|
|
93
112
|
)
|
|
94
113
|
logger.debug("Registered transport {} -> {}", source, target)
|
|
95
114
|
|
|
115
|
+
def register_transport_from_points(
|
|
116
|
+
self,
|
|
117
|
+
source: str,
|
|
118
|
+
target: str,
|
|
119
|
+
source_point: npt.NDArray[np.float64],
|
|
120
|
+
target_point: npt.NDArray[np.float64],
|
|
121
|
+
method: str = "pole",
|
|
122
|
+
n_rungs: int = 2,
|
|
123
|
+
) -> npt.NDArray[np.float64]:
|
|
124
|
+
"""Compute and register a transport map from two manifold points.
|
|
125
|
+
|
|
126
|
+
Builds the parallel-transport matrix along the geodesic from
|
|
127
|
+
``source_point`` to ``target_point`` via
|
|
128
|
+
:func:`groupoid.transport.compute_transport_matrix` (pole or
|
|
129
|
+
Schild's ladder) and registers it for the ``(source, target)``
|
|
130
|
+
edge. This wires the ladder approximations into the aggregation
|
|
131
|
+
pipeline for the case where per-client base points are known but
|
|
132
|
+
explicit transport matrices are not. The ladders are
|
|
133
|
+
approximations with a small residual; see LIMITATIONS.md for the
|
|
134
|
+
measured behavior.
|
|
135
|
+
|
|
136
|
+
Returns
|
|
137
|
+
-------
|
|
138
|
+
np.ndarray
|
|
139
|
+
The registered transport matrix.
|
|
140
|
+
"""
|
|
141
|
+
matrix = compute_transport_matrix(
|
|
142
|
+
self.manifold,
|
|
143
|
+
source_point,
|
|
144
|
+
target_point,
|
|
145
|
+
method=method,
|
|
146
|
+
n_rungs=n_rungs,
|
|
147
|
+
)
|
|
148
|
+
self.register_transport(source, target, matrix)
|
|
149
|
+
return matrix
|
|
150
|
+
|
|
96
151
|
def _get_transport_to_base(self, node: str) -> npt.NDArray[np.float64] | None:
|
|
97
152
|
"""Compute the composite transport map from node to base_node.
|
|
98
153
|
|
|
@@ -194,6 +249,15 @@ class TransportGroupoidAggregator:
|
|
|
194
249
|
nodes = sorted(transported.keys())
|
|
195
250
|
param_stack = np.stack([transported[n] for n in nodes])
|
|
196
251
|
|
|
252
|
+
# Optional topological divergence tracking on the transported
|
|
253
|
+
# parameters (all in the base frame, so rounds are comparable).
|
|
254
|
+
divergence: _persistence.PersistenceSummary | None = None
|
|
255
|
+
if self.track_divergence:
|
|
256
|
+
divergence = _persistence.track_divergence(
|
|
257
|
+
param_stack, previous_summary=self._prev_divergence
|
|
258
|
+
)
|
|
259
|
+
self._prev_divergence = divergence
|
|
260
|
+
|
|
197
261
|
if weights is not None:
|
|
198
262
|
w = np.array([weights.get(n, 1.0) for n in nodes])
|
|
199
263
|
w = w / w.sum()
|
|
@@ -223,6 +287,7 @@ class TransportGroupoidAggregator:
|
|
|
223
287
|
is_consistent=is_consistent,
|
|
224
288
|
transport_residuals=transport_residuals,
|
|
225
289
|
round_idx=self._round_idx,
|
|
290
|
+
divergence=divergence,
|
|
226
291
|
)
|
|
227
292
|
|
|
228
293
|
logger.info(
|
groupoid/groupoid.py
CHANGED
|
@@ -24,6 +24,11 @@ class Morphism(BaseModel):
|
|
|
24
24
|
def __repr__(self) -> str:
|
|
25
25
|
return f"Morphism({self.source} -> {self.target})"
|
|
26
26
|
|
|
27
|
+
# pydantic's BaseModel defines __str__ separately (a field dump including
|
|
28
|
+
# the full transport matrix), which would bypass the compact __repr__ in
|
|
29
|
+
# print(), f-strings, and loguru's {} formatting. Alias it explicitly.
|
|
30
|
+
__str__ = __repr__
|
|
31
|
+
|
|
27
32
|
|
|
28
33
|
class CompositionError(Exception):
|
|
29
34
|
"""Raised when morphism composition is not defined."""
|
groupoid/optimizer.py
CHANGED
|
@@ -9,6 +9,15 @@ This module provides:
|
|
|
9
9
|
- Riemannian SGD with retraction
|
|
10
10
|
- Riemannian Adam with exponential map updates
|
|
11
11
|
- Adaptive learning rate based on sectional curvature
|
|
12
|
+
|
|
13
|
+
Accumulated moments (the SGD momentum velocity and Adam's first moment)
|
|
14
|
+
live in the tangent space of the iterate that produced them. After each
|
|
15
|
+
step they are parallel-transported into the new iterate's tangent space
|
|
16
|
+
when the metric provides parallel transport (the construction of
|
|
17
|
+
Becigneul & Ganea, "Riemannian Adaptive Optimization Methods", ICLR 2019),
|
|
18
|
+
and projected onto it otherwise. Transport is an isometry, so it moves the
|
|
19
|
+
moment without shrinking it; projection is a coarser fallback that can
|
|
20
|
+
lose the component normal to the new tangent space.
|
|
12
21
|
"""
|
|
13
22
|
|
|
14
23
|
from __future__ import annotations
|
|
@@ -21,13 +30,42 @@ import numpy.typing as npt
|
|
|
21
30
|
from loguru import logger
|
|
22
31
|
|
|
23
32
|
|
|
33
|
+
def _transport_moment(
|
|
34
|
+
manifold: Any, # geomstats manifold; no upstream type stubs
|
|
35
|
+
moment: npt.NDArray[np.float64],
|
|
36
|
+
base_point: npt.NDArray[np.float64],
|
|
37
|
+
new_point: npt.NDArray[np.float64],
|
|
38
|
+
) -> npt.NDArray[np.float64]:
|
|
39
|
+
"""Move an accumulated moment from base_point's tangent space to new_point's.
|
|
40
|
+
|
|
41
|
+
Uses the metric's parallel transport when available (isometric: the
|
|
42
|
+
moment's norm is preserved exactly). Falls back to projection onto the
|
|
43
|
+
new tangent space for metrics that do not implement parallel transport,
|
|
44
|
+
mirroring the compatibility shims elsewhere in this package.
|
|
45
|
+
"""
|
|
46
|
+
metric = manifold.metric
|
|
47
|
+
if hasattr(metric, "parallel_transport"):
|
|
48
|
+
try:
|
|
49
|
+
transported: npt.NDArray[np.float64] = metric.parallel_transport(
|
|
50
|
+
moment, base_point, end_point=new_point
|
|
51
|
+
)
|
|
52
|
+
return transported
|
|
53
|
+
except NotImplementedError:
|
|
54
|
+
pass
|
|
55
|
+
projected: npt.NDArray[np.float64] = manifold.to_tangent(moment, new_point)
|
|
56
|
+
return projected
|
|
57
|
+
|
|
58
|
+
|
|
24
59
|
@dataclass
|
|
25
60
|
class RiemannianSGD:
|
|
26
61
|
"""Riemannian stochastic gradient descent.
|
|
27
62
|
|
|
28
63
|
Updates parameters by computing the Riemannian gradient (projection
|
|
29
64
|
of Euclidean gradient onto tangent space) and retracting back to
|
|
30
|
-
the manifold via the exponential map.
|
|
65
|
+
the manifold via the exponential map. With momentum, the velocity is
|
|
66
|
+
parallel-transported into each new iterate's tangent space (see the
|
|
67
|
+
module docstring), so it accumulates geometry-consistently across
|
|
68
|
+
steps.
|
|
31
69
|
|
|
32
70
|
Parameters
|
|
33
71
|
----------
|
|
@@ -64,7 +102,10 @@ class RiemannianSGD:
|
|
|
64
102
|
# Project gradient onto tangent space
|
|
65
103
|
riemannian_grad = self.manifold.to_tangent(euclidean_grad, point)
|
|
66
104
|
|
|
67
|
-
# Apply momentum
|
|
105
|
+
# Apply momentum. _velocity was parallel-transported into `point`'s
|
|
106
|
+
# tangent space at the end of the previous step; the to_tangent wrap
|
|
107
|
+
# keeps the combination numerically tangent (and tolerates callers
|
|
108
|
+
# stepping from a point other than the previous iterate).
|
|
68
109
|
vel: npt.NDArray[np.float64]
|
|
69
110
|
if self.momentum > 0:
|
|
70
111
|
if self._velocity is None:
|
|
@@ -73,7 +114,6 @@ class RiemannianSGD:
|
|
|
73
114
|
vel = self.manifold.to_tangent(
|
|
74
115
|
self.momentum * self._velocity + riemannian_grad, point
|
|
75
116
|
)
|
|
76
|
-
self._velocity = vel
|
|
77
117
|
update = -self.lr * vel
|
|
78
118
|
else:
|
|
79
119
|
update = -self.lr * riemannian_grad
|
|
@@ -81,6 +121,10 @@ class RiemannianSGD:
|
|
|
81
121
|
# Retract to manifold via exponential map
|
|
82
122
|
new_point: npt.NDArray[np.float64] = self.manifold.metric.exp(update, point)
|
|
83
123
|
|
|
124
|
+
# Carry the velocity to the new iterate's tangent space.
|
|
125
|
+
if self.momentum > 0:
|
|
126
|
+
self._velocity = _transport_moment(self.manifold, vel, point, new_point)
|
|
127
|
+
|
|
84
128
|
return new_point
|
|
85
129
|
|
|
86
130
|
|
|
@@ -90,7 +134,11 @@ class RiemannianAdam:
|
|
|
90
134
|
|
|
91
135
|
Adapts the Adam optimizer to Riemannian manifolds by maintaining
|
|
92
136
|
exponential moving averages of the Riemannian gradient and its
|
|
93
|
-
squared norm, with updates via the exponential map.
|
|
137
|
+
squared norm, with updates via the exponential map. The first moment
|
|
138
|
+
is parallel-transported into each new iterate's tangent space (see
|
|
139
|
+
the module docstring); the second moment is a scalar gradient-norm
|
|
140
|
+
average and needs no transport because parallel transport is an
|
|
141
|
+
isometry (norms are invariant).
|
|
94
142
|
|
|
95
143
|
Parameters
|
|
96
144
|
----------
|
|
@@ -143,6 +191,10 @@ class RiemannianAdam:
|
|
|
143
191
|
# m_hat = m_1 / (1 - beta1) recovers `grad` on the first step. Seeding
|
|
144
192
|
# m_1 = grad directly would leave the 1/(1-beta1) factor uncancelled and
|
|
145
193
|
# inflate the first update by ~10x (beta1=0.9).
|
|
194
|
+
# _m was parallel-transported into `point`'s tangent space at the end
|
|
195
|
+
# of the previous step; the to_tangent wrap keeps the combination
|
|
196
|
+
# numerically tangent (and tolerates callers stepping from a point
|
|
197
|
+
# other than the previous iterate).
|
|
146
198
|
first_moment: npt.NDArray[np.float64]
|
|
147
199
|
if self._m is None:
|
|
148
200
|
first_moment = (1 - self.beta1) * grad
|
|
@@ -150,7 +202,6 @@ class RiemannianAdam:
|
|
|
150
202
|
first_moment = self.manifold.to_tangent(
|
|
151
203
|
self.beta1 * self._m + (1 - self.beta1) * grad, point
|
|
152
204
|
)
|
|
153
|
-
self._m = first_moment
|
|
154
205
|
|
|
155
206
|
# Update biased second moment (scalar, norm-based)
|
|
156
207
|
self._v = self.beta2 * self._v + (1 - self.beta2) * grad_norm_sq
|
|
@@ -164,6 +215,10 @@ class RiemannianAdam:
|
|
|
164
215
|
update = self.manifold.to_tangent(update, point)
|
|
165
216
|
|
|
166
217
|
new_point: npt.NDArray[np.float64] = self.manifold.metric.exp(update, point)
|
|
218
|
+
|
|
219
|
+
# Carry the first moment to the new iterate's tangent space.
|
|
220
|
+
self._m = _transport_moment(self.manifold, first_moment, point, new_point)
|
|
221
|
+
|
|
167
222
|
return new_point
|
|
168
223
|
|
|
169
224
|
|
|
@@ -175,9 +230,10 @@ def curvature_adaptive_lr(
|
|
|
175
230
|
) -> float:
|
|
176
231
|
"""Adapt learning rate based on local sectional curvature.
|
|
177
232
|
|
|
178
|
-
In regions of high positive curvature, geodesics converge and
|
|
179
|
-
|
|
180
|
-
|
|
233
|
+
In regions of high positive curvature, geodesics converge and we
|
|
234
|
+
should take smaller steps, so the base rate is damped by
|
|
235
|
+
``1 / (1 + kappa)``. In flat or negatively curved regions the base
|
|
236
|
+
rate is returned unchanged; no enlargement is applied.
|
|
181
237
|
|
|
182
238
|
Parameters
|
|
183
239
|
----------
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: groupoid
|
|
3
|
-
Version: 0.1.0.
|
|
3
|
+
Version: 0.1.0.dev4
|
|
4
4
|
Summary: Research prototype: groupoid-based aggregation for federated learning on Riemannian manifolds
|
|
5
5
|
Author-email: Santiago Maniches <santiago.maniches@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -109,21 +109,20 @@ integration tests:
|
|
|
109
109
|
- **Federated aggregation pipeline**: transport-aware aggregation with
|
|
110
110
|
H^1 consistency checking, multi-round convergence tested
|
|
111
111
|
(`groupoid.aggregation`)
|
|
112
|
-
|
|
113
|
-
## Implemented, validated against ground truth, not yet integrated
|
|
114
|
-
|
|
115
|
-
These modules have unit tests that validate behavior against a
|
|
116
|
-
known-correct reference, but are not yet wired into the main aggregation
|
|
117
|
-
pipeline:
|
|
118
|
-
|
|
119
112
|
- **Parallel transport**: Schild's ladder and pole ladder
|
|
120
|
-
(`groupoid.transport`)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
113
|
+
(`groupoid.transport`), wired into the pipeline via
|
|
114
|
+
`TransportGroupoidAggregator.register_transport_from_points` (computes
|
|
115
|
+
and registers the transport matrix from two client base points). The
|
|
116
|
+
pole ladder is validated against geomstats' analytic parallel transport
|
|
117
|
+
on S^2 -- it matches in direction (cosine > 0.999) and magnitude.
|
|
118
|
+
Schild's ladder is a coarser first-order approximation and is asserted
|
|
119
|
+
as such. See [LIMITATIONS.md](LIMITATIONS.md) for the convergence
|
|
120
|
+
caveat.
|
|
125
121
|
- **Persistent homology**: Vietoris-Rips filtration for divergence
|
|
126
|
-
tracking (`groupoid.persistence`)
|
|
122
|
+
tracking (`groupoid.persistence`), wired into the pipeline via the
|
|
123
|
+
aggregator's opt-in `track_divergence` flag (per-round H0-vs-H0
|
|
124
|
+
bottleneck distance on the transported parameters, exposed as
|
|
125
|
+
`FederatedRound.divergence`). Unit-tested against point clouds of
|
|
127
126
|
known topology: a circle's dominant 1-cycle (via maximum persistence),
|
|
128
127
|
two-cluster component counting (`betti_0 == 2` at a finite filtration),
|
|
129
128
|
and a translation-invariant bottleneck distance. The persistence diagram
|
|
@@ -133,17 +132,20 @@ pipeline:
|
|
|
133
132
|
the H0 diagram. The Betti numbers are degenerate under the default
|
|
134
133
|
`thresh=inf` filtration; see [LIMITATIONS.md](LIMITATIONS.md).
|
|
135
134
|
|
|
136
|
-
## Implemented
|
|
135
|
+
## Implemented and validated, not yet integrated
|
|
137
136
|
|
|
138
|
-
This module
|
|
139
|
-
|
|
140
|
-
and it is not yet wired into the main aggregation pipeline:
|
|
137
|
+
This module is validated against known-correct references but not yet
|
|
138
|
+
wired into the main aggregation pipeline:
|
|
141
139
|
|
|
142
140
|
- **Riemannian optimizers**: SGD and Adam with exponential map
|
|
143
|
-
retraction;
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
(
|
|
141
|
+
retraction; the momentum velocity and Adam first moment are
|
|
142
|
+
parallel-transported between iterates (with a projection fallback for
|
|
143
|
+
metrics without parallel transport). Validated: descent to a known
|
|
144
|
+
target on S^2 (geodesic-distance objective) for SGD, momentum SGD, and
|
|
145
|
+
Adam; transported moments preserve norm exactly where projection would
|
|
146
|
+
annihilate them; the curvature-adaptive learning rate is covered for
|
|
147
|
+
both its damping and fallback branches. No general convergence-rate
|
|
148
|
+
guarantees are established (`groupoid.optimizer`)
|
|
147
149
|
|
|
148
150
|
## Status
|
|
149
151
|
|
|
@@ -178,8 +180,13 @@ idea -- transport-groupoid aggregation with cohomological consistency checking
|
|
|
178
180
|
In short: use Flower/FedML/TFF for the FL system, use geomstats/pymanopt for
|
|
179
181
|
manifold math; GROUPOID is a research prototype testing whether combining a
|
|
180
182
|
transport groupoid with sheaf-cohomological consistency yields a better
|
|
181
|
-
aggregation operator than Euclidean FedAvg.
|
|
182
|
-
|
|
183
|
+
aggregation operator than Euclidean FedAvg. A preregistered synthetic
|
|
184
|
+
benchmark ([experiments/](experiments/)) supports the transport benefit
|
|
185
|
+
under frame misalignment -- an effect largely built into the synthetic
|
|
186
|
+
setup -- and shows the pooled H^1 norm tracks corruption-induced error
|
|
187
|
+
across corruption levels, though it does not rank runs within a level.
|
|
188
|
+
The hypothesis remains **unvalidated on real federated learning tasks**
|
|
189
|
+
(see [STATUS.md](STATUS.md)).
|
|
183
190
|
|
|
184
191
|
## Installation
|
|
185
192
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
groupoid/__init__.py,sha256=k4p0zjEcIY-NV9ZTo33Qu2u95M3TBAXMxo-qg25YK2k,839
|
|
2
|
+
groupoid/aggregation.py,sha256=_HCkjvL6iYUYy_F5TAE-o2_WHTRDhCkTBelee92EfA0,11481
|
|
3
|
+
groupoid/cohomology.py,sha256=CHZDSGjPPuDNXQG8f3xnx3sDlxpEmlYeWqRiCpfRNpU,3567
|
|
4
|
+
groupoid/groupoid.py,sha256=VsZThPAamxXnNWZp3YYJtSv_AiHnw9PYajCAI2Kmkyc,2246
|
|
5
|
+
groupoid/laplacian.py,sha256=jqZEU6yvBocUqKg30XUojIUlDoGZVfyMEBKNRWhdG6w,6189
|
|
6
|
+
groupoid/manifold.py,sha256=2jP68SwLhbpl4EItvLBRnV6_k6l2thTFzchudIzjGJw,2100
|
|
7
|
+
groupoid/optimizer.py,sha256=RmleLbxpIiUIVihYFHKsN49YAbuslV6Ip5xG2s_-QrI,9575
|
|
8
|
+
groupoid/persistence.py,sha256=BuiBr00qRAXMPjdlZKuBRFhBUwFge7v2a-rn5bTF3LU,7229
|
|
9
|
+
groupoid/sheaf.py,sha256=hlhvbP_rfQPy7t-EbvD8jTkjsRYv3eyk49Th2ehRNL0,2653
|
|
10
|
+
groupoid/transport.py,sha256=5ItpmiIEqjEzEcFY8H1EAWiJpWHg-9h2d-UVkLAnR9A,5816
|
|
11
|
+
groupoid-0.1.0.dev4.dist-info/licenses/LICENSE,sha256=qd0jMtXL8CN6XNdjjC4r2RX5u2FS6Y0TG1FtWTH4TTM,10766
|
|
12
|
+
groupoid-0.1.0.dev4.dist-info/METADATA,sha256=TeyF3BIGLTWnNOTn9uIXDGQCSR0MoWnx2cCKW2AU_x4,11914
|
|
13
|
+
groupoid-0.1.0.dev4.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
14
|
+
groupoid-0.1.0.dev4.dist-info/top_level.txt,sha256=avPz-dSDwo-4zKd7EoxNoLEEwEOYTAtByUD6BTFV9bY,9
|
|
15
|
+
groupoid-0.1.0.dev4.dist-info/RECORD,,
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
groupoid/__init__.py,sha256=Ygp2CQP485aQAdyO20G5BchVyM8TZPPgeLiWEYrDL4A,839
|
|
2
|
-
groupoid/aggregation.py,sha256=5SbaiZkfA4HJGVR09n9oyzlEAGFyTN1eUZDVAcHoBYU,8768
|
|
3
|
-
groupoid/cohomology.py,sha256=CHZDSGjPPuDNXQG8f3xnx3sDlxpEmlYeWqRiCpfRNpU,3567
|
|
4
|
-
groupoid/groupoid.py,sha256=L2WmTG8-Sh_r6TsgmIJ077zRG-nz1OyYEqsorAJf1w0,1992
|
|
5
|
-
groupoid/laplacian.py,sha256=jqZEU6yvBocUqKg30XUojIUlDoGZVfyMEBKNRWhdG6w,6189
|
|
6
|
-
groupoid/manifold.py,sha256=2jP68SwLhbpl4EItvLBRnV6_k6l2thTFzchudIzjGJw,2100
|
|
7
|
-
groupoid/optimizer.py,sha256=DPFVT93aPkygFkVrRVE0_H2I9dCkbX_qRo2-YTpZB6E,6650
|
|
8
|
-
groupoid/persistence.py,sha256=BuiBr00qRAXMPjdlZKuBRFhBUwFge7v2a-rn5bTF3LU,7229
|
|
9
|
-
groupoid/sheaf.py,sha256=hlhvbP_rfQPy7t-EbvD8jTkjsRYv3eyk49Th2ehRNL0,2653
|
|
10
|
-
groupoid/transport.py,sha256=5ItpmiIEqjEzEcFY8H1EAWiJpWHg-9h2d-UVkLAnR9A,5816
|
|
11
|
-
groupoid-0.1.0.dev2.dist-info/licenses/LICENSE,sha256=qd0jMtXL8CN6XNdjjC4r2RX5u2FS6Y0TG1FtWTH4TTM,10766
|
|
12
|
-
groupoid-0.1.0.dev2.dist-info/METADATA,sha256=EmZ_ljhNpC0ffCfVBxU9r7IOi7RV3I0zdiIPdgmdUk0,11217
|
|
13
|
-
groupoid-0.1.0.dev2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
14
|
-
groupoid-0.1.0.dev2.dist-info/top_level.txt,sha256=avPz-dSDwo-4zKd7EoxNoLEEwEOYTAtByUD6BTFV9bY,9
|
|
15
|
-
groupoid-0.1.0.dev2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|