groupoid 0.1.0.dev6__tar.gz → 0.1.0.dev7__tar.gz
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-0.1.0.dev6 → groupoid-0.1.0.dev7}/.github/dependabot.yml +0 -1
- groupoid-0.1.0.dev7/.github/workflows/artifact-transfer.yml +46 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/.github/workflows/ci.yml +17 -37
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/.github/workflows/docs-deploy.yml +1 -2
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/.github/workflows/release.yml +18 -12
- groupoid-0.1.0.dev7/.zenodo.json +34 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/CHANGELOG.md +35 -1
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/CITATION.cff +2 -1
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/CONTRIBUTING.md +8 -6
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/LIMITATIONS.md +17 -6
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/PKG-INFO +7 -4
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/README.md +6 -2
- groupoid-0.1.0.dev7/SECURITY.md +31 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/STATUS.md +6 -4
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/codemeta.json +1 -1
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/groupoid/__init__.py +1 -1
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/groupoid/persistence.py +25 -12
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/groupoid.egg-info/PKG-INFO +7 -4
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/groupoid.egg-info/SOURCES.txt +1 -1
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/groupoid.egg-info/requires.txt +0 -1
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/pyproject.toml +1 -2
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_persistence.py +22 -10
- groupoid-0.1.0.dev6/.github/scripts/install_torch_cpu.sh +0 -25
- groupoid-0.1.0.dev6/.zenodo.json +0 -39
- groupoid-0.1.0.dev6/SECURITY.md +0 -43
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/.github/ISSUE_TEMPLATE/mathematical_correction.md +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/.github/workflows/auto-tag-release.yml +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/.gitignore +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/.pre-commit-config.yaml +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/CORRECTION_NOTICE.md +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/LICENSE +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/PROBLEMS.md +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/REPRODUCE.md +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/benchmarks/__init__.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/benchmarks/conftest.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/benchmarks/test_karcher_benchmark.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/docs/api.md +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/docs/index.md +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/docs/overrides/404.html +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/docs/quickstart.md +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/docs/theory.md +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/examples/README.md +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/examples/__init__.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/examples/synthetic_curve_demo.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/experiments/PREREGISTRATION.md +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/experiments/README.md +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/experiments/RESULTS.md +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/experiments/analyze.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/experiments/results.json +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/experiments/run_benchmark.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/groupoid/aggregation.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/groupoid/cohomology.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/groupoid/groupoid.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/groupoid/laplacian.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/groupoid/manifold.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/groupoid/optimizer.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/groupoid/sheaf.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/groupoid/transport.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/groupoid.egg-info/dependency_links.txt +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/groupoid.egg-info/top_level.txt +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/mkdocs.yml +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/scripts/bind_release_sbom.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/scripts/reproduce_sheaf_laplacian.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/setup.cfg +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/__init__.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_aggregation_disconnected.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_audit_regressions.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_cohomology_cocycle.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_coverage_gaps.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_defensive_paths.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_imports.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_integration.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_optimizer_validation.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_persistence_dimension_labels.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_pipeline_integration.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_release_sbom_binding.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_release_workflow_integrity.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_scientific_corrections.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_sheaf_laplacian.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_smoke.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/test_transport_validation.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/theory/__init__.py +0 -0
- {groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/tests/theory/test_mathematical_invariants.py +0 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: Artifact transfer integrity
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
paths: [".github/workflows/**"]
|
|
7
|
+
pull_request:
|
|
8
|
+
branches: [main]
|
|
9
|
+
paths: [".github/workflows/**"]
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
round-trip:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
timeout-minutes: 5
|
|
18
|
+
steps:
|
|
19
|
+
- name: Create fixture and independent expected checksum
|
|
20
|
+
shell: bash
|
|
21
|
+
run: |
|
|
22
|
+
set -euo pipefail
|
|
23
|
+
mkdir -p payload/nested
|
|
24
|
+
printf '%s\n' "$GITHUB_SHA" > payload/nested/fixture.txt
|
|
25
|
+
(cd payload && sha256sum nested/fixture.txt) > expected.sha256
|
|
26
|
+
- name: Upload fixture
|
|
27
|
+
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
28
|
+
with:
|
|
29
|
+
name: artifact-transfer-proof
|
|
30
|
+
path: payload/
|
|
31
|
+
if-no-files-found: error
|
|
32
|
+
retention-days: 1
|
|
33
|
+
- name: Remove original fixture before downloading
|
|
34
|
+
run: rm -r payload
|
|
35
|
+
- name: Download fixture
|
|
36
|
+
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
37
|
+
with:
|
|
38
|
+
name: artifact-transfer-proof
|
|
39
|
+
path: restored/
|
|
40
|
+
- name: Verify restored bytes and directory layout
|
|
41
|
+
shell: bash
|
|
42
|
+
run: |
|
|
43
|
+
set -euo pipefail
|
|
44
|
+
cd restored
|
|
45
|
+
test "$(find . -type f | wc -l)" -eq 1
|
|
46
|
+
sha256sum --check ../expected.sha256
|
|
@@ -14,7 +14,7 @@ jobs:
|
|
|
14
14
|
runs-on: ubuntu-latest
|
|
15
15
|
steps:
|
|
16
16
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
17
|
-
- uses: actions/setup-python@
|
|
17
|
+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
18
18
|
with:
|
|
19
19
|
python-version: "3.11"
|
|
20
20
|
- name: Install dependencies
|
|
@@ -24,8 +24,8 @@ jobs:
|
|
|
24
24
|
# numpy / pydantic stubs (NDArray dtype args, the BaseModel base class).
|
|
25
25
|
# Linting only against `ruff black mypy` left numpy/pydantic uninstalled,
|
|
26
26
|
# so --strict raised disallow_subclassing_any on the BaseModel subclass.
|
|
27
|
-
#
|
|
28
|
-
.
|
|
27
|
+
# All jobs now install the declared runtime dependencies, without
|
|
28
|
+
# the unused optional PyTorch backend.
|
|
29
29
|
pip install -e ".[dev]"
|
|
30
30
|
- name: Run ruff
|
|
31
31
|
run: ruff check .
|
|
@@ -41,13 +41,12 @@ jobs:
|
|
|
41
41
|
python-version: ["3.10", "3.11", "3.12"]
|
|
42
42
|
steps:
|
|
43
43
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
44
|
-
- uses: actions/setup-python@
|
|
44
|
+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
45
45
|
with:
|
|
46
46
|
python-version: ${{ matrix.python-version }}
|
|
47
47
|
- name: Install dependencies
|
|
48
48
|
run: |
|
|
49
49
|
python -m pip install --upgrade pip
|
|
50
|
-
.github/scripts/install_torch_cpu.sh
|
|
51
50
|
pip install -e ".[dev]"
|
|
52
51
|
- name: Run tests with coverage gate
|
|
53
52
|
run: >-
|
|
@@ -59,7 +58,7 @@ jobs:
|
|
|
59
58
|
runs-on: ubuntu-latest
|
|
60
59
|
steps:
|
|
61
60
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
62
|
-
- uses: actions/setup-python@
|
|
61
|
+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
63
62
|
with:
|
|
64
63
|
python-version: "3.11"
|
|
65
64
|
- name: Install dependencies
|
|
@@ -68,42 +67,25 @@ jobs:
|
|
|
68
67
|
# Install the project's real runtime closure so pip-audit scans the
|
|
69
68
|
# packages GROUPOID actually depends on. The previous `safety` step
|
|
70
69
|
# installed only the tools and never the project, so it audited the
|
|
71
|
-
# wrong environment.
|
|
72
|
-
.github/scripts/install_torch_cpu.sh
|
|
70
|
+
# wrong environment. No unused PyTorch install is injected.
|
|
73
71
|
pip install -e .
|
|
74
72
|
pip install bandit pip-audit
|
|
75
|
-
#
|
|
76
|
-
#
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
# dependency-consistent with torch (verified: pip check clean).
|
|
80
|
-
python -m pip install --upgrade "setuptools>=78.1.1,<82"
|
|
73
|
+
# Setuptools is build tooling, but remains in the audited CI environment.
|
|
74
|
+
# No torch<82 constraint remains; require the patched release.
|
|
75
|
+
python -m pip install --upgrade "setuptools>=84.1.0"
|
|
76
|
+
python -m pip check
|
|
81
77
|
- name: Run bandit
|
|
82
78
|
run: bandit -r groupoid/ -c pyproject.toml
|
|
83
79
|
- name: Audit dependencies for known vulnerabilities (pip-audit)
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
|
|
87
|
-
# exceptions, both disclosed in SECURITY.md:
|
|
88
|
-
# - CVE-2025-3000 (torch): no fixed release exists upstream, and OSV
|
|
89
|
-
# expresses the affected range (listed versions up to v2.6.0) as a
|
|
90
|
-
# Git/commit boundary pip-audit cannot version-resolve, so it
|
|
91
|
-
# conservatively flags the installed torch. The torch.load / JIT
|
|
92
|
-
# deserialization path is not exercised by GROUPOID. Revisit the ignore
|
|
93
|
-
# once upstream publishes a fixed release.
|
|
94
|
-
# - PYSEC-2026-3447 (CVE-2026-59890, setuptools <83): the fix (83.0.0) is
|
|
95
|
-
# unreachable while torch requires setuptools<82 (see the install step
|
|
96
|
-
# above). The flaw is a macOS-only (APFS/HFS+) Unicode-normalization
|
|
97
|
-
# bypass of MANIFEST.in exclude rules when building sdists; all CI and
|
|
98
|
-
# release builds run on ubuntu-latest. Drop the ignore once torch
|
|
99
|
-
# permits setuptools>=83.
|
|
100
|
-
run: pip-audit --ignore-vuln CVE-2025-3000 --ignore-vuln PYSEC-2026-3447
|
|
80
|
+
# Hard gate on the installed runtime and audit-tool closure.
|
|
81
|
+
# No CVE/PYSEC ignore flags or waived vulnerable packages.
|
|
82
|
+
run: pip-audit
|
|
101
83
|
|
|
102
84
|
pre-commit:
|
|
103
85
|
runs-on: ubuntu-latest
|
|
104
86
|
steps:
|
|
105
87
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
106
|
-
- uses: actions/setup-python@
|
|
88
|
+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
107
89
|
with:
|
|
108
90
|
python-version: "3.11"
|
|
109
91
|
- name: Install pre-commit
|
|
@@ -115,13 +97,12 @@ jobs:
|
|
|
115
97
|
runs-on: ubuntu-latest
|
|
116
98
|
steps:
|
|
117
99
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
118
|
-
- uses: actions/setup-python@
|
|
100
|
+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
119
101
|
with:
|
|
120
102
|
python-version: "3.11"
|
|
121
103
|
- name: Install dependencies
|
|
122
104
|
run: |
|
|
123
105
|
python -m pip install --upgrade pip
|
|
124
|
-
.github/scripts/install_torch_cpu.sh
|
|
125
106
|
pip install -e ".[dev]"
|
|
126
107
|
# Benchmarks run for information only. There is no regression gate:
|
|
127
108
|
# timing baselines recorded on one machine are not comparable to a
|
|
@@ -135,7 +116,7 @@ jobs:
|
|
|
135
116
|
pytest benchmarks/ -v --benchmark-json=benchmark_results.json || true
|
|
136
117
|
- name: Upload benchmark results
|
|
137
118
|
if: always()
|
|
138
|
-
uses: actions/upload-artifact@
|
|
119
|
+
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
139
120
|
with:
|
|
140
121
|
name: benchmark-results
|
|
141
122
|
path: benchmark_results.json
|
|
@@ -145,13 +126,12 @@ jobs:
|
|
|
145
126
|
runs-on: ubuntu-latest
|
|
146
127
|
steps:
|
|
147
128
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
148
|
-
- uses: actions/setup-python@
|
|
129
|
+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
149
130
|
with:
|
|
150
131
|
python-version: "3.11"
|
|
151
132
|
- name: Install dependencies
|
|
152
133
|
run: |
|
|
153
134
|
python -m pip install --upgrade pip
|
|
154
|
-
.github/scripts/install_torch_cpu.sh
|
|
155
135
|
pip install -e ".[docs]"
|
|
156
136
|
- name: Build docs
|
|
157
137
|
run: mkdocs build --strict
|
|
@@ -17,13 +17,12 @@ jobs:
|
|
|
17
17
|
runs-on: ubuntu-latest
|
|
18
18
|
steps:
|
|
19
19
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
20
|
-
- uses: actions/setup-python@
|
|
20
|
+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
21
21
|
with:
|
|
22
22
|
python-version: "3.11"
|
|
23
23
|
- name: Install dependencies
|
|
24
24
|
run: |
|
|
25
25
|
python -m pip install --upgrade pip
|
|
26
|
-
.github/scripts/install_torch_cpu.sh
|
|
27
26
|
pip install -e ".[docs]"
|
|
28
27
|
- name: Build docs
|
|
29
28
|
run: mkdocs build --strict
|
|
@@ -145,7 +145,7 @@ jobs:
|
|
|
145
145
|
exit 1
|
|
146
146
|
fi
|
|
147
147
|
echo "release target ${RELEASE_TARGET} matches pyproject version ${version}"
|
|
148
|
-
- uses: actions/setup-python@
|
|
148
|
+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
149
149
|
with:
|
|
150
150
|
python-version: "3.12"
|
|
151
151
|
cache: pip
|
|
@@ -283,7 +283,7 @@ jobs:
|
|
|
283
283
|
with:
|
|
284
284
|
subject-path: "dist/*"
|
|
285
285
|
- name: Upload dist artifacts
|
|
286
|
-
uses: actions/upload-artifact@
|
|
286
|
+
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
287
287
|
with:
|
|
288
288
|
name: release-dist
|
|
289
289
|
path: dist/
|
|
@@ -300,11 +300,11 @@ jobs:
|
|
|
300
300
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
301
301
|
with:
|
|
302
302
|
ref: ${{ github.event.inputs.ref || github.ref }}
|
|
303
|
-
- uses: actions/setup-python@
|
|
303
|
+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
304
304
|
with:
|
|
305
305
|
python-version: "3.12"
|
|
306
306
|
cache: pip
|
|
307
|
-
- uses: actions/download-artifact@
|
|
307
|
+
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
308
308
|
with:
|
|
309
309
|
name: release-dist
|
|
310
310
|
path: dist
|
|
@@ -347,7 +347,7 @@ jobs:
|
|
|
347
347
|
fi
|
|
348
348
|
python scripts/bind_release_sbom.py --verify-only --sbom sbom.cdx.json --wheel "${wheels[0]}"
|
|
349
349
|
- name: Upload SBOM artifact
|
|
350
|
-
uses: actions/upload-artifact@
|
|
350
|
+
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
351
351
|
with:
|
|
352
352
|
name: release-sbom
|
|
353
353
|
path: sbom.cdx.json
|
|
@@ -365,11 +365,11 @@ jobs:
|
|
|
365
365
|
id-token: write # attestation OIDC
|
|
366
366
|
attestations: write # actions/attest
|
|
367
367
|
steps:
|
|
368
|
-
- uses: actions/download-artifact@
|
|
368
|
+
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
369
369
|
with:
|
|
370
370
|
name: release-dist
|
|
371
371
|
path: dist
|
|
372
|
-
- uses: actions/download-artifact@
|
|
372
|
+
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
373
373
|
with:
|
|
374
374
|
name: release-sbom
|
|
375
375
|
path: .
|
|
@@ -407,7 +407,7 @@ jobs:
|
|
|
407
407
|
name: pypi
|
|
408
408
|
url: https://pypi.org/project/groupoid/
|
|
409
409
|
steps:
|
|
410
|
-
- uses: actions/download-artifact@
|
|
410
|
+
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
411
411
|
with:
|
|
412
412
|
name: release-dist
|
|
413
413
|
path: dist
|
|
@@ -479,7 +479,7 @@ jobs:
|
|
|
479
479
|
# step below still runs, so COMPLETE still proves PyPI is correct.
|
|
480
480
|
- name: Publish to PyPI (Trusted Publishing, OIDC)
|
|
481
481
|
if: needs.build.outputs.adopted != 'true'
|
|
482
|
-
uses: pypa/gh-action-pypi-publish@
|
|
482
|
+
uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # release/v1
|
|
483
483
|
with:
|
|
484
484
|
packages-dir: dist
|
|
485
485
|
print-hash: true
|
|
@@ -501,8 +501,14 @@ jobs:
|
|
|
501
501
|
import hashlib, json, pathlib, sys, time, urllib.error, urllib.request
|
|
502
502
|
|
|
503
503
|
dist = pathlib.Path("dist")
|
|
504
|
+
# PEP 740 .publish.attestation sidecars may be written to dist/ by
|
|
505
|
+
# the PyPI publishing action. They are attestations attached to the
|
|
506
|
+
# distributions, not separate package files in PyPI JSON urls.
|
|
507
|
+
# Compare every actual wheel/sdist byte without treating sidecars
|
|
508
|
+
# as missing distributions.
|
|
504
509
|
local = {p.name: hashlib.sha256(p.read_bytes()).hexdigest()
|
|
505
|
-
for p in sorted(dist.iterdir())
|
|
510
|
+
for p in sorted(dist.iterdir())
|
|
511
|
+
if p.is_file() and p.name.endswith((".whl", ".tar.gz"))}
|
|
506
512
|
wheel = next(n for n in local if n.endswith(".whl"))
|
|
507
513
|
project, version = wheel.split("-")[0], wheel.split("-")[1]
|
|
508
514
|
url = f"https://pypi.org/pypi/{project}/{version}/json"
|
|
@@ -565,11 +571,11 @@ jobs:
|
|
|
565
571
|
id-token: write # Sigstore signing OIDC
|
|
566
572
|
steps:
|
|
567
573
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
568
|
-
- uses: actions/download-artifact@
|
|
574
|
+
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
569
575
|
with:
|
|
570
576
|
name: release-dist
|
|
571
577
|
path: dist
|
|
572
|
-
- uses: actions/download-artifact@
|
|
578
|
+
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
573
579
|
with:
|
|
574
580
|
name: release-sbom
|
|
575
581
|
path: .
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"upload_type": "software",
|
|
3
|
+
"title": "GROUPOID: Groupoid-based aggregation for federated learning on Riemannian manifolds",
|
|
4
|
+
"version": "0.1.0.dev7",
|
|
5
|
+
"description": "<p>Pre-alpha research prototype exploring groupoid-based aggregation for federated learning on Riemannian manifolds. The supported point-valued aggregation path uses explicit invertible point actions, a cycle-basis holonomy defect diagnostic, the cellular sheaf Laplacian, and Karcher mean aggregation via geomstats. Tangent-vector parallel transport is provided separately through Schild and pole ladder utilities.</p><p><strong>Corrective release v0.1.0.dev7:</strong> the finite H0 bottleneck distance to an empty diagram is now half the longest unmatched interval, rather than an incorrect zero (both comparison directions tested). The unused mandatory PyTorch dependency was removed, reducing installation and audit complexity, and CI dependency scanning now runs without vulnerability exceptions. Pre-alpha research maturity, known limits on the H0-only diagnostic, and historical experiment results are unchanged. Existing environments may retain PyTorch after an upgrade.</p><p><strong>Library correction in v0.1.0.dev6:</strong> in v0.1.0.dev5 and earlier, the pole ladder used a step schedule that did not reach the endpoint for more than one rung, and the function named schild_ladder was a pole-ladder variant rather than Schild's construction; tangent-vector transport results from those versions with n_rungs greater than 1, including the default two-rung tangent transport operator, are incorrect. v0.1.0.dev6 corrects both ladders and adds input validation for unknown transport methods, n_rungs below 1, negative, non-finite or zero-sum aggregation weights, and non-finite holonomy maps. The preregistered benchmark uses explicit transport maps, not the ladders; its stored results are unchanged and were reproduced bit-for-bit under the dev6 candidate. The project remains a pre-alpha research prototype.</p><p><strong>Scientific correction in v0.1.0.dev5:</strong> earlier releases described the cycle-basis holonomy statistic as a first-cohomology (H^1) norm. The implementation actually computes the maximum Frobenius deviation of holonomy from identity over the particular NetworkX cycle basis. Exact zero has a flatness interpretation under explicit connectedness, completeness, invertibility, reciprocal opposite-edge registration when both directions are supplied, and complete evaluation of every cycle emitted by the NetworkX cycle-basis routine (an argument specific to the NetworkX Paton implementation exercised here, not to arbitrary cycle bases), but the nonzero magnitude is basis-dependent and is not invariant under arbitrary invertible changes of frame. The stored preregistered benchmark values are unchanged; the historical H2 rho and permutation p-value are reinterpreted as statistics for this fixed cycle-basis defect, not for a canonical H^1 norm. This version also withdraws register_transport_from_points from the supported point-valued aggregation path: its ambient square operator is assembled from transported tangent vectors and does not generally define the invertible manifold point action that aggregation requires. Tangent-vector ladder validation remains supported. The ambient tangent-matrix helper is restricted to vector-shaped point representations. Direct registered matrices are supported only under the explicit point-action contract documented in the repository, including reciprocal opposite arrows when both orientations are stored. See CORRECTION_NOTICE.md for the full correction record.</p><p>The earlier v0.1.0.dev0 version DOI 10.5281/zenodo.20563975 remains superseded and must not be cited for results. Exact historical versions should be cited by their version-specific DOI; project-level DOI metadata is maintained through the Zenodo version family.</p>",
|
|
6
|
+
"creators": [
|
|
7
|
+
{
|
|
8
|
+
"name": "Maniches, Santiago",
|
|
9
|
+
"affiliation": "TOPOLOGICA LLC",
|
|
10
|
+
"orcid": "0009-0005-6480-1987"
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"license": "Apache-2.0",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"federated learning",
|
|
16
|
+
"Riemannian geometry",
|
|
17
|
+
"groupoid",
|
|
18
|
+
"sheaf theory",
|
|
19
|
+
"holonomy",
|
|
20
|
+
"topological data analysis"
|
|
21
|
+
],
|
|
22
|
+
"related_identifiers": [
|
|
23
|
+
{
|
|
24
|
+
"identifier": "10.5281/zenodo.20563974",
|
|
25
|
+
"relation": "isVersionOf",
|
|
26
|
+
"scheme": "doi"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"identifier": "https://github.com/smaniches/GROUPOID",
|
|
30
|
+
"relation": "isSupplementTo",
|
|
31
|
+
"scheme": "url"
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
@@ -8,6 +8,39 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.1.0.dev7] - 2026-10-29
|
|
12
|
+
|
|
13
|
+
Pre-alpha corrective release distributing the October 29 fixes already merged
|
|
14
|
+
on `main` (#77, #78 and #81). No new experiments, measured federated-learning
|
|
15
|
+
performance gains, or general convergence claims are introduced. Archived
|
|
16
|
+
preregistration and historical benchmark outputs remain unchanged.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- **Finite H0 bottleneck versus an empty diagram (#77).** The previous
|
|
21
|
+
`track_divergence` fallback returned `0.0` when either finite H0 diagram
|
|
22
|
+
was empty. The mathematically correct cost is half the largest unmatched
|
|
23
|
+
persistence interval. For a two-point cloud separated by 4 versus a
|
|
24
|
+
singleton, the result is now **2.0** in either direction, not zero;
|
|
25
|
+
empty-versus-empty remains zero. Regression tests cover both directions.
|
|
26
|
+
The metric remains an H0-only geometric diagnostic, **not** a certificate
|
|
27
|
+
of federated model convergence or predictive stability.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- **Removed an unused mandatory dependency (#81).** `torch` was declared as
|
|
32
|
+
required but is not imported by the implemented library. New installations
|
|
33
|
+
no longer require PyTorch or its dependency graph. The security CI installs
|
|
34
|
+
the actual supported runtime closure, requires patched `setuptools`, checks
|
|
35
|
+
`pip check`, and runs `pip-audit` without advisory ignores. The full
|
|
36
|
+
Python 3.10–3.12 tests, docs, and code-quality checks passed on `main`.
|
|
37
|
+
**Compatibility note:** PyTorch is no longer installed automatically;
|
|
38
|
+
callers requiring it for their own workflows must install it separately.
|
|
39
|
+
Upgrading an existing environment does not uninstall previously installed
|
|
40
|
+
PyTorch or other packages; independently audit those environments.
|
|
41
|
+
- Updated SHA-pinned `actions/setup-python` and PyPI publishing action (#78).
|
|
42
|
+
No algorithmic behavior was changed by this CI maintenance.
|
|
43
|
+
|
|
11
44
|
## [0.1.0.dev6]
|
|
12
45
|
|
|
13
46
|
Pre-alpha development release distributing the transport and validation fixes
|
|
@@ -268,7 +301,8 @@ Date shown is the tag commit (`v0.1.0.dev0` -> 2a02954). `CITATION.cff` records
|
|
|
268
301
|
`date-released: 2026-05-25`, when the core implementation landed; the tag was
|
|
269
302
|
later placed on the metadata commit.
|
|
270
303
|
|
|
271
|
-
[Unreleased]: https://github.com/smaniches/GROUPOID/compare/v0.1.0.
|
|
304
|
+
[Unreleased]: https://github.com/smaniches/GROUPOID/compare/v0.1.0.dev7...HEAD
|
|
305
|
+
[0.1.0.dev7]: https://github.com/smaniches/GROUPOID/compare/v0.1.0.dev6...v0.1.0.dev7
|
|
272
306
|
[0.1.0.dev6]: https://github.com/smaniches/GROUPOID/compare/v0.1.0.dev5...v0.1.0.dev6
|
|
273
307
|
[0.1.0.dev5]: https://github.com/smaniches/GROUPOID/compare/v0.1.0.dev4...v0.1.0.dev5
|
|
274
308
|
[0.1.0.dev4]: https://github.com/smaniches/GROUPOID/compare/v0.1.0.dev2...v0.1.0.dev4
|
|
@@ -2,7 +2,8 @@ cff-version: 1.2.0
|
|
|
2
2
|
message: "If you use this software, please cite the Concept DOI below so the citation resolves to the latest version."
|
|
3
3
|
type: software
|
|
4
4
|
title: "GROUPOID: Groupoid-based aggregation for federated learning on Riemannian manifolds"
|
|
5
|
-
version: 0.1.0.
|
|
5
|
+
version: 0.1.0.dev7
|
|
6
|
+
date-released: 2026-10-29
|
|
6
7
|
license: Apache-2.0
|
|
7
8
|
repository-code: "https://github.com/smaniches/GROUPOID"
|
|
8
9
|
doi: 10.5281/zenodo.20563974
|
|
@@ -80,18 +80,20 @@ please open an issue using the "Mathematical Correction" template.
|
|
|
80
80
|
1. Open a release-cut PR that bumps the version across every metadata
|
|
81
81
|
file that carries it: `pyproject.toml`, `groupoid/__init__.py`,
|
|
82
82
|
`codemeta.json`, `CITATION.cff` (version and `date-released`),
|
|
83
|
-
`STATUS.md` (
|
|
84
|
-
description, and `
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
`STATUS.md` (version), and `.zenodo.json` (version, the correction
|
|
84
|
+
description, and its concept DOI `isVersionOf` relationship). Only include
|
|
85
|
+
`isNewVersionOf` when the immediately preceding record DOI has been
|
|
86
|
+
independently verified; never inherit a stale predecessor. Roll
|
|
87
|
+
`CHANGELOG.md`'s `Unreleased` section into a new version heading with
|
|
88
|
+
compare links. See PRs #40 or #48 for the
|
|
87
89
|
template.
|
|
88
90
|
2. Merge the PR to `main`.
|
|
89
91
|
|
|
90
92
|
That's it — merging is the release trigger, split across two workflows
|
|
91
93
|
with one job each:
|
|
92
94
|
|
|
93
|
-
- `.github/workflows/auto-tag-release.yml` runs
|
|
94
|
-
|
|
95
|
+
- `.github/workflows/auto-tag-release.yml` runs after successful CI for a
|
|
96
|
+
push to the **current** `main` commit. It reads the version out of
|
|
95
97
|
`pyproject.toml`, and if no `v<version>` tag exists yet, it creates and
|
|
96
98
|
pushes one (as `github-actions[bot]`) after checking the version has a
|
|
97
99
|
recognized format and that `CHANGELOG.md` documents it. A push with no
|
|
@@ -89,6 +89,15 @@ system. The following limitations apply.
|
|
|
89
89
|
The informative loop signal lives in `max_persistence` / the finite
|
|
90
90
|
bars; meaningful component counts require passing a finite
|
|
91
91
|
`max_edge_length` between the intra- and inter-cluster scales.
|
|
92
|
+
- The opt-in `track_divergence` metric measures only changes in the
|
|
93
|
+
**finite H0** persistence diagram. It is invariant under translations,
|
|
94
|
+
rotations, and other pairwise-distance-preserving changes, and ignores
|
|
95
|
+
changes confined to H1 or higher homology. Thus a zero bottleneck
|
|
96
|
+
does **not** demonstrate client-model convergence or predictive
|
|
97
|
+
stability. If one diagram has no finite H0 bars, its distance to a
|
|
98
|
+
nonempty diagram is half the longest unmatched interval's lifetime,
|
|
99
|
+
not zero. Validation against model optimization would require
|
|
100
|
+
independent parameter-space or task-loss evidence.
|
|
92
101
|
- No formal convergence rate analysis exists for the groupoid
|
|
93
102
|
aggregation method.
|
|
94
103
|
|
|
@@ -98,12 +107,14 @@ system. The following limitations apply.
|
|
|
98
107
|
- Requires `scipy < 1.16` for the same reason.
|
|
99
108
|
- The `ripser` and `persim` packages may have build issues on
|
|
100
109
|
some platforms (C++ compilation required).
|
|
101
|
-
- `torch
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
110
|
+
- The former required `torch` dependency was removed because the current
|
|
111
|
+
implementation and entire documented test suite do not import it. It had
|
|
112
|
+
constrained `filelock` and `setuptools` to vulnerable versions; no current
|
|
113
|
+
GROUPOID code requires PyTorch. Future PyTorch functionality requires
|
|
114
|
+
a new separately tested dependency contract.
|
|
115
|
+
- `pymanopt`, `POT`, and `einops` are still declared as required runtime
|
|
116
|
+
dependencies but not imported in the current implementation. Their removal
|
|
117
|
+
is a separate compatibility change, not part of this security correction.
|
|
107
118
|
|
|
108
119
|
## Test coverage
|
|
109
120
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: groupoid
|
|
3
|
-
Version: 0.1.0.
|
|
3
|
+
Version: 0.1.0.dev7
|
|
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
|
|
@@ -30,7 +30,6 @@ Requires-Dist: networkx>=3.0
|
|
|
30
30
|
Requires-Dist: einops>=0.7
|
|
31
31
|
Requires-Dist: numpy<2.0,>=1.24
|
|
32
32
|
Requires-Dist: scipy<1.16,>=1.10
|
|
33
|
-
Requires-Dist: torch>=2.0
|
|
34
33
|
Requires-Dist: pydantic>=2.0
|
|
35
34
|
Requires-Dist: loguru>=0.7
|
|
36
35
|
Provides-Extra: privacy
|
|
@@ -139,8 +138,12 @@ integration tests:
|
|
|
139
138
|
retains a homology-dimension label, and `track_divergence` compares H0
|
|
140
139
|
against H0 only (it does not pool features across dimensions); this is
|
|
141
140
|
verified against an independent minimum-spanning-tree reconstruction of
|
|
142
|
-
the H0 diagram.
|
|
143
|
-
|
|
141
|
+
the H0 diagram. A zero value is a geometric diagnostic, **not** a
|
|
142
|
+
certificate of client-model convergence: translations and H1-only
|
|
143
|
+
changes can leave it unchanged. A finite H0 diagram compared with
|
|
144
|
+
an empty one incurs the nonzero bottleneck-to-diagonal cost.
|
|
145
|
+
The Betti numbers are degenerate under the default `thresh=inf`
|
|
146
|
+
filtration; see [LIMITATIONS.md](LIMITATIONS.md).
|
|
144
147
|
|
|
145
148
|
## Implemented and validated, not yet integrated
|
|
146
149
|
|
|
@@ -74,8 +74,12 @@ integration tests:
|
|
|
74
74
|
retains a homology-dimension label, and `track_divergence` compares H0
|
|
75
75
|
against H0 only (it does not pool features across dimensions); this is
|
|
76
76
|
verified against an independent minimum-spanning-tree reconstruction of
|
|
77
|
-
the H0 diagram.
|
|
78
|
-
|
|
77
|
+
the H0 diagram. A zero value is a geometric diagnostic, **not** a
|
|
78
|
+
certificate of client-model convergence: translations and H1-only
|
|
79
|
+
changes can leave it unchanged. A finite H0 diagram compared with
|
|
80
|
+
an empty one incurs the nonzero bottleneck-to-diagonal cost.
|
|
81
|
+
The Betti numbers are degenerate under the default `thresh=inf`
|
|
82
|
+
filtration; see [LIMITATIONS.md](LIMITATIONS.md).
|
|
79
83
|
|
|
80
84
|
## Implemented and validated, not yet integrated
|
|
81
85
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Scope
|
|
4
|
+
|
|
5
|
+
GROUPOID is a research prototype. It is not designed for deployment in
|
|
6
|
+
security-sensitive environments.
|
|
7
|
+
|
|
8
|
+
## Reporting a vulnerability
|
|
9
|
+
|
|
10
|
+
If you discover a security issue, please report it by emailing
|
|
11
|
+
santiago.maniches@gmail.com. Do not open a public issue.
|
|
12
|
+
|
|
13
|
+
## Supported versions
|
|
14
|
+
|
|
15
|
+
Only the latest commit on `main` is supported. There are no stable
|
|
16
|
+
releases.
|
|
17
|
+
|
|
18
|
+
## Known limitations
|
|
19
|
+
|
|
20
|
+
- No differential privacy is implemented despite optional dependencies
|
|
21
|
+
being listed. Do not rely on this software for privacy guarantees.
|
|
22
|
+
- Input validation is minimal. The library assumes trusted inputs from
|
|
23
|
+
the caller.
|
|
24
|
+
- The older dependency closure required `torch` despite no implemented code
|
|
25
|
+
path importing it. This pulled in vulnerable `filelock` and constrained
|
|
26
|
+
`setuptools` below patched releases. The current source dependency closure
|
|
27
|
+
removes that unused requirement and the corresponding CI installer. CI
|
|
28
|
+
verifies the actual installed environment using `pip check` and a hard
|
|
29
|
+
`pip-audit` gate without CVE/PYSEC ignore flags. This does not retroactively
|
|
30
|
+
change any published package version, and it does not establish that other
|
|
31
|
+
environments or future dependency updates are vulnerability-free.
|
|
@@ -25,9 +25,11 @@
|
|
|
25
25
|
against H0 only; this is verified against an independent
|
|
26
26
|
minimum-spanning-tree reconstruction of the H0 diagram (the finite H0
|
|
27
27
|
death times equal the MST edge weights), and an H1-only change is shown
|
|
28
|
-
not to leak into the H0 divergence.
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
not to leak into the H0 divergence. Degenerate one-empty-diagram
|
|
29
|
+
comparisons use the nonzero bottleneck-to-diagonal distance.
|
|
30
|
+
It is wired into the aggregator via the opt-in `track_divergence`
|
|
31
|
+
flag but cannot establish model convergence. See the Betti-degeneracy
|
|
32
|
+
caveat in LIMITATIONS.md.
|
|
31
33
|
- The optimizer module is validated against known-correct references:
|
|
32
34
|
Riemannian SGD (with and without momentum) and Adam descend to a known
|
|
33
35
|
target on S^2 (geodesic-distance objective), and the momentum/first-
|
|
@@ -88,5 +90,5 @@ not capture.
|
|
|
88
90
|
|
|
89
91
|
## Versioning
|
|
90
92
|
|
|
91
|
-
This project uses `0.1.0.
|
|
93
|
+
This project uses `0.1.0.dev7` to indicate pre-release development.
|
|
92
94
|
The API is unstable and will change without notice.
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
"""Persistent homology for federated model divergence detection.
|
|
2
2
|
|
|
3
3
|
Applies topological data analysis to track how the distribution of
|
|
4
|
-
client model parameters evolves across federated rounds.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
client model parameters evolves across federated rounds. A stable
|
|
5
|
+
persistence diagram is not a convergence certificate:
|
|
6
|
+
pairwise-distance-preserving translations/rotations and some genuine
|
|
7
|
+
model-configuration changes can have unchanged diagrams. The tool
|
|
8
|
+
reports a dimension-specific geometric diagnostic, not convergence or
|
|
9
|
+
model-performance evidence.
|
|
10
|
+
|
|
11
|
+
Uses Vietoris-Rips filtration on point clouds of model parameters
|
|
12
|
+
and compares finite H0 diagrams across rounds with bottleneck distance.
|
|
13
|
+
It does not calculate Wasserstein distance or test higher-dimensional
|
|
14
|
+
changes in the inter-round comparison.
|
|
12
15
|
"""
|
|
13
16
|
|
|
14
17
|
from __future__ import annotations
|
|
@@ -160,9 +163,9 @@ def track_divergence(
|
|
|
160
163
|
) -> PersistenceSummary:
|
|
161
164
|
"""Track federation divergence across rounds.
|
|
162
165
|
|
|
163
|
-
Computes persistence of
|
|
164
|
-
|
|
165
|
-
|
|
166
|
+
Computes persistence of a point cloud and, with a prior summary,
|
|
167
|
+
compares its finite H0 diagrams by bottleneck distance. A zero
|
|
168
|
+
value does not imply convergence of the underlying client models.
|
|
166
169
|
|
|
167
170
|
Parameters
|
|
168
171
|
----------
|
|
@@ -198,6 +201,16 @@ def track_divergence(
|
|
|
198
201
|
summary.bottleneck_to_previous = float(dist)
|
|
199
202
|
logger.info("Bottleneck distance to previous round: {:.4f}", dist)
|
|
200
203
|
else:
|
|
201
|
-
|
|
204
|
+
# The bottleneck distance to the empty diagram is NOT zero:
|
|
205
|
+
# every unmatched finite interval must match the diagonal,
|
|
206
|
+
# costing half its lifetime in L-infinity. This applies in
|
|
207
|
+
# either direction. Only two empty diagrams have distance 0.
|
|
208
|
+
unmatched = current_h0 if len(current_h0) > 0 else prev_h0
|
|
209
|
+
if len(unmatched) > 0:
|
|
210
|
+
summary.bottleneck_to_previous = float(
|
|
211
|
+
np.max(unmatched[:, 1] - unmatched[:, 0]) / 2.0
|
|
212
|
+
)
|
|
213
|
+
else:
|
|
214
|
+
summary.bottleneck_to_previous = 0.0
|
|
202
215
|
|
|
203
216
|
return summary
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: groupoid
|
|
3
|
-
Version: 0.1.0.
|
|
3
|
+
Version: 0.1.0.dev7
|
|
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
|
|
@@ -30,7 +30,6 @@ Requires-Dist: networkx>=3.0
|
|
|
30
30
|
Requires-Dist: einops>=0.7
|
|
31
31
|
Requires-Dist: numpy<2.0,>=1.24
|
|
32
32
|
Requires-Dist: scipy<1.16,>=1.10
|
|
33
|
-
Requires-Dist: torch>=2.0
|
|
34
33
|
Requires-Dist: pydantic>=2.0
|
|
35
34
|
Requires-Dist: loguru>=0.7
|
|
36
35
|
Provides-Extra: privacy
|
|
@@ -139,8 +138,12 @@ integration tests:
|
|
|
139
138
|
retains a homology-dimension label, and `track_divergence` compares H0
|
|
140
139
|
against H0 only (it does not pool features across dimensions); this is
|
|
141
140
|
verified against an independent minimum-spanning-tree reconstruction of
|
|
142
|
-
the H0 diagram.
|
|
143
|
-
|
|
141
|
+
the H0 diagram. A zero value is a geometric diagnostic, **not** a
|
|
142
|
+
certificate of client-model convergence: translations and H1-only
|
|
143
|
+
changes can leave it unchanged. A finite H0 diagram compared with
|
|
144
|
+
an empty one incurs the nonzero bottleneck-to-diagonal cost.
|
|
145
|
+
The Betti numbers are degenerate under the default `thresh=inf`
|
|
146
|
+
filtration; see [LIMITATIONS.md](LIMITATIONS.md).
|
|
144
147
|
|
|
145
148
|
## Implemented and validated, not yet integrated
|
|
146
149
|
|
|
@@ -19,7 +19,7 @@ pyproject.toml
|
|
|
19
19
|
.github/ISSUE_TEMPLATE/bug_report.md
|
|
20
20
|
.github/ISSUE_TEMPLATE/feature_request.md
|
|
21
21
|
.github/ISSUE_TEMPLATE/mathematical_correction.md
|
|
22
|
-
.github/
|
|
22
|
+
.github/workflows/artifact-transfer.yml
|
|
23
23
|
.github/workflows/auto-tag-release.yml
|
|
24
24
|
.github/workflows/ci.yml
|
|
25
25
|
.github/workflows/docs-deploy.yml
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "groupoid"
|
|
7
|
-
version = "0.1.0.
|
|
7
|
+
version = "0.1.0.dev7"
|
|
8
8
|
description = "Research prototype: groupoid-based aggregation for federated learning on Riemannian manifolds"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -45,7 +45,6 @@ dependencies = [
|
|
|
45
45
|
"einops>=0.7",
|
|
46
46
|
"numpy>=1.24,<2.0",
|
|
47
47
|
"scipy>=1.10,<1.16",
|
|
48
|
-
"torch>=2.0",
|
|
49
48
|
"pydantic>=2.0",
|
|
50
49
|
"loguru>=0.7",
|
|
51
50
|
]
|
|
@@ -11,7 +11,8 @@ against point clouds with known structure, not just line execution:
|
|
|
11
11
|
scales -- this validates component counting.
|
|
12
12
|
- ``track_divergence`` is exercised across all three of its branches: no
|
|
13
13
|
previous summary, a previous summary with non-empty H0 diagrams (bottleneck
|
|
14
|
-
computed), and
|
|
14
|
+
computed), and mathematically correct empty-to-nonempty and
|
|
15
|
+
empty-to-empty bottleneck distances.
|
|
15
16
|
|
|
16
17
|
Caveat validated and documented: with the default ``thresh=inf`` filtration,
|
|
17
18
|
``betti_0``/``betti_1`` count only bars that die at infinity, so on a single
|
|
@@ -126,12 +127,23 @@ class TestTrackDivergence:
|
|
|
126
127
|
assert shifted.bottleneck_to_previous == pytest.approx(0.0, abs=1e-6)
|
|
127
128
|
|
|
128
129
|
@pytest.mark.filterwarnings("ignore:The input point cloud has more columns:UserWarning")
|
|
129
|
-
def
|
|
130
|
-
"""
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
130
|
+
def test_single_empty_diagram_has_nonzero_bottleneck(self):
|
|
131
|
+
"""A finite H0 bar [0, 4] must match the diagonal at cost 2."""
|
|
132
|
+
pair = np.array([[0.0, 0.0], [4.0, 0.0]])
|
|
133
|
+
singleton = np.array([[0.0, 0.0]])
|
|
134
|
+
pair_summary = track_divergence(pair, max_dim=0)
|
|
135
|
+
single_summary = track_divergence(singleton, max_dim=0)
|
|
136
|
+
|
|
137
|
+
# Both orders use the same symmetric bottleneck metric.
|
|
138
|
+
going_to_empty = track_divergence(singleton, previous_summary=pair_summary, max_dim=0)
|
|
139
|
+
coming_from_empty = track_divergence(pair, previous_summary=single_summary, max_dim=0)
|
|
140
|
+
assert going_to_empty.bottleneck_to_previous == pytest.approx(2.0, abs=1e-6)
|
|
141
|
+
assert coming_from_empty.bottleneck_to_previous == pytest.approx(2.0, abs=1e-6)
|
|
142
|
+
|
|
143
|
+
@pytest.mark.filterwarnings("ignore:The input point cloud has more columns:UserWarning")
|
|
144
|
+
def test_two_empty_finite_h0_diagrams_have_zero_bottleneck(self):
|
|
145
|
+
"""Two singleton point clouds have no finite H0 bars at all."""
|
|
146
|
+
singleton = np.array([[0.0, 0.0]])
|
|
147
|
+
prior = track_divergence(singleton, max_dim=0)
|
|
148
|
+
current = track_divergence(singleton + 100.0, previous_summary=prior, max_dim=0)
|
|
149
|
+
assert current.bottleneck_to_previous == 0.0
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Installs CPU-only torch from the PyTorch index with retries.
|
|
3
|
-
#
|
|
4
|
-
# That index (download.pytorch.org/whl/cpu, Cloudflare R2-backed as
|
|
5
|
-
# download-r2.pytorch.org) intermittently drops the TLS handshake
|
|
6
|
-
# (SSLV3_ALERT_HANDSHAKE_FAILURE) from GitHub-hosted runners. pip's own
|
|
7
|
-
# built-in retries reuse the same connection pool within one process and
|
|
8
|
-
# do not help here; a fresh `pip install` invocation gets a fresh TLS
|
|
9
|
-
# connection and has in practice succeeded where the retries within a
|
|
10
|
-
# single invocation did not. Five attempts with linear backoff (10s, 20s,
|
|
11
|
-
# 30s, 40s) before giving up.
|
|
12
|
-
set -euo pipefail
|
|
13
|
-
|
|
14
|
-
max_attempts=5
|
|
15
|
-
attempt=1
|
|
16
|
-
until pip install torch --index-url https://download.pytorch.org/whl/cpu; do
|
|
17
|
-
if [ "${attempt}" -ge "${max_attempts}" ]; then
|
|
18
|
-
echo "::error::torch install failed after ${max_attempts} attempts" >&2
|
|
19
|
-
exit 1
|
|
20
|
-
fi
|
|
21
|
-
sleep_seconds=$((attempt * 10))
|
|
22
|
-
echo "torch install attempt ${attempt}/${max_attempts} failed; retrying in ${sleep_seconds}s" >&2
|
|
23
|
-
sleep "${sleep_seconds}"
|
|
24
|
-
attempt=$((attempt + 1))
|
|
25
|
-
done
|
groupoid-0.1.0.dev6/.zenodo.json
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"upload_type": "software",
|
|
3
|
-
"title": "GROUPOID: Groupoid-based aggregation for federated learning on Riemannian manifolds",
|
|
4
|
-
"version": "0.1.0.dev6",
|
|
5
|
-
"description": "<p>Pre-alpha research prototype exploring groupoid-based aggregation for federated learning on Riemannian manifolds. The supported point-valued aggregation path uses explicit invertible point actions, a cycle-basis holonomy defect diagnostic, the cellular sheaf Laplacian, and Karcher mean aggregation via geomstats. Tangent-vector parallel transport is provided separately through Schild and pole ladder utilities.</p><p><strong>Library correction in v0.1.0.dev6:</strong> in v0.1.0.dev5 and earlier, the pole ladder used a step schedule that did not reach the endpoint for more than one rung, and the function named schild_ladder was a pole-ladder variant rather than Schild's construction; tangent-vector transport results from those versions with n_rungs greater than 1, including the default two-rung tangent transport operator, are incorrect. v0.1.0.dev6 corrects both ladders and adds input validation for unknown transport methods, n_rungs below 1, negative, non-finite or zero-sum aggregation weights, and non-finite holonomy maps. The preregistered benchmark uses explicit transport maps, not the ladders; its stored results are unchanged and were reproduced bit-for-bit under the dev6 candidate. The project remains a pre-alpha research prototype.</p><p><strong>Scientific correction in v0.1.0.dev5:</strong> earlier releases described the cycle-basis holonomy statistic as a first-cohomology (H^1) norm. The implementation actually computes the maximum Frobenius deviation of holonomy from identity over the particular NetworkX cycle basis. Exact zero has a flatness interpretation under explicit connectedness, completeness, invertibility, reciprocal opposite-edge registration when both directions are supplied, and complete evaluation of every cycle emitted by the NetworkX cycle-basis routine (an argument specific to the NetworkX Paton implementation exercised here, not to arbitrary cycle bases), but the nonzero magnitude is basis-dependent and is not invariant under arbitrary invertible changes of frame. The stored preregistered benchmark values are unchanged; the historical H2 rho and permutation p-value are reinterpreted as statistics for this fixed cycle-basis defect, not for a canonical H^1 norm. This version also withdraws register_transport_from_points from the supported point-valued aggregation path: its ambient square operator is assembled from transported tangent vectors and does not generally define the invertible manifold point action that aggregation requires. Tangent-vector ladder validation remains supported. The ambient tangent-matrix helper is restricted to vector-shaped point representations. Direct registered matrices are supported only under the explicit point-action contract documented in the repository, including reciprocal opposite arrows when both orientations are stored. See CORRECTION_NOTICE.md for the full correction record.</p><p>The earlier v0.1.0.dev0 version DOI 10.5281/zenodo.20563975 remains superseded and must not be cited for results. Exact historical versions should be cited by their version-specific DOI; project-level DOI metadata is maintained through the Zenodo version family.</p>",
|
|
6
|
-
"creators": [
|
|
7
|
-
{
|
|
8
|
-
"name": "Maniches, Santiago",
|
|
9
|
-
"affiliation": "TOPOLOGICA LLC",
|
|
10
|
-
"orcid": "0009-0005-6480-1987"
|
|
11
|
-
}
|
|
12
|
-
],
|
|
13
|
-
"license": "Apache-2.0",
|
|
14
|
-
"keywords": [
|
|
15
|
-
"federated learning",
|
|
16
|
-
"Riemannian geometry",
|
|
17
|
-
"groupoid",
|
|
18
|
-
"sheaf theory",
|
|
19
|
-
"holonomy",
|
|
20
|
-
"topological data analysis"
|
|
21
|
-
],
|
|
22
|
-
"related_identifiers": [
|
|
23
|
-
{
|
|
24
|
-
"identifier": "10.5281/zenodo.20563974",
|
|
25
|
-
"relation": "isVersionOf",
|
|
26
|
-
"scheme": "doi"
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
"identifier": "10.5281/zenodo.21963047",
|
|
30
|
-
"relation": "isNewVersionOf",
|
|
31
|
-
"scheme": "doi"
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
"identifier": "https://github.com/smaniches/GROUPOID",
|
|
35
|
-
"relation": "isSupplementTo",
|
|
36
|
-
"scheme": "url"
|
|
37
|
-
}
|
|
38
|
-
]
|
|
39
|
-
}
|
groupoid-0.1.0.dev6/SECURITY.md
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# Security Policy
|
|
2
|
-
|
|
3
|
-
## Scope
|
|
4
|
-
|
|
5
|
-
GROUPOID is a research prototype. It is not designed for deployment in
|
|
6
|
-
security-sensitive environments.
|
|
7
|
-
|
|
8
|
-
## Reporting a vulnerability
|
|
9
|
-
|
|
10
|
-
If you discover a security issue, please report it by emailing
|
|
11
|
-
santiago.maniches@gmail.com. Do not open a public issue.
|
|
12
|
-
|
|
13
|
-
## Supported versions
|
|
14
|
-
|
|
15
|
-
Only the latest commit on `main` is supported. There are no stable
|
|
16
|
-
releases.
|
|
17
|
-
|
|
18
|
-
## Known limitations
|
|
19
|
-
|
|
20
|
-
- No differential privacy is implemented despite optional dependencies
|
|
21
|
-
being listed. Do not rely on this software for privacy guarantees.
|
|
22
|
-
- Input validation is minimal. The library assumes trusted inputs from
|
|
23
|
-
the caller.
|
|
24
|
-
- The `torch` runtime dependency is flagged by `pip-audit` for advisory
|
|
25
|
-
CVE-2025-3000. OSV expresses its affected range as a Git/commit range whose
|
|
26
|
-
listed versions run up to v2.6.0, with no published `fixed` release. Because
|
|
27
|
-
that upper bound is a source commit rather than a resolvable version,
|
|
28
|
-
`pip-audit` conservatively flags the installed `torch` (currently 2.12.0, past
|
|
29
|
-
the listed v2.6.0 affected range) instead of clearing it. CI tracks the
|
|
30
|
-
advisory via a documented `--ignore-vuln`; there is no fixed release to
|
|
31
|
-
upgrade to, and the ignore is revisited when upstream publishes one. The
|
|
32
|
-
advisory concerns crafted-checkpoint / JIT deserialization (`torch.load` and
|
|
33
|
-
related paths), which GROUPOID's own code does not exercise.
|
|
34
|
-
- The CI-pinned `setuptools` (held to `>=78.1.1,<82` because `torch` requires
|
|
35
|
-
`setuptools<82`) is flagged by `pip-audit` for advisory PYSEC-2026-3447
|
|
36
|
-
(CVE-2026-59890): prior to setuptools 83.0.0, `MANIFEST.in` exclude,
|
|
37
|
-
`global-exclude`, `recursive-exclude`, and `prune` directives could be
|
|
38
|
-
bypassed on macOS APFS/HFS+ by NFD-normalized file names, packing excluded
|
|
39
|
-
files into source distributions. The fixed release (83.0.0) is outside the
|
|
40
|
-
window torch permits, and the affected code path requires building an sdist
|
|
41
|
-
on a macOS filesystem — all GROUPOID CI and release builds run on
|
|
42
|
-
`ubuntu-latest`. CI tracks the advisory via a documented `--ignore-vuln`;
|
|
43
|
-
the ignore is revisited once torch allows `setuptools>=83`.
|
|
File without changes
|
|
File without changes
|
{groupoid-0.1.0.dev6 → groupoid-0.1.0.dev7}/.github/ISSUE_TEMPLATE/mathematical_correction.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|