groupoid 0.1.0.dev1__tar.gz → 0.1.0.dev2__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.
Files changed (66) hide show
  1. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/.github/workflows/ci.yml +13 -13
  2. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/.github/workflows/docs-deploy.yml +4 -4
  3. groupoid-0.1.0.dev2/.github/workflows/release.yml +107 -0
  4. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/.zenodo.json +3 -3
  5. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/CHANGELOG.md +16 -1
  6. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/CITATION.cff +2 -2
  7. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/PKG-INFO +10 -3
  8. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/README.md +8 -1
  9. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/STATUS.md +3 -2
  10. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/codemeta.json +1 -1
  11. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/groupoid/__init__.py +1 -1
  12. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/groupoid/optimizer.py +6 -2
  13. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/groupoid.egg-info/PKG-INFO +10 -3
  14. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/groupoid.egg-info/SOURCES.txt +1 -0
  15. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/groupoid.egg-info/requires.txt +1 -1
  16. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/pyproject.toml +2 -2
  17. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/tests/test_smoke.py +21 -0
  18. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  19. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  20. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/.github/ISSUE_TEMPLATE/mathematical_correction.md +0 -0
  21. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/.github/dependabot.yml +0 -0
  22. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/.gitignore +0 -0
  23. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/.pre-commit-config.yaml +0 -0
  24. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/CONTRIBUTING.md +0 -0
  25. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/LICENSE +0 -0
  26. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/LIMITATIONS.md +0 -0
  27. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/PROBLEMS.md +0 -0
  28. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/REPRODUCE.md +0 -0
  29. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/SECURITY.md +0 -0
  30. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/benchmarks/__init__.py +0 -0
  31. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/benchmarks/conftest.py +0 -0
  32. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/benchmarks/test_karcher_benchmark.py +0 -0
  33. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/docs/api.md +0 -0
  34. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/docs/index.md +0 -0
  35. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/docs/overrides/404.html +0 -0
  36. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/docs/quickstart.md +0 -0
  37. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/docs/theory.md +0 -0
  38. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/examples/README.md +0 -0
  39. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/examples/__init__.py +0 -0
  40. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/examples/synthetic_curve_demo.py +0 -0
  41. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/groupoid/aggregation.py +0 -0
  42. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/groupoid/cohomology.py +0 -0
  43. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/groupoid/groupoid.py +0 -0
  44. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/groupoid/laplacian.py +0 -0
  45. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/groupoid/manifold.py +0 -0
  46. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/groupoid/persistence.py +0 -0
  47. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/groupoid/sheaf.py +0 -0
  48. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/groupoid/transport.py +0 -0
  49. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/groupoid.egg-info/dependency_links.txt +0 -0
  50. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/groupoid.egg-info/top_level.txt +0 -0
  51. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/mkdocs.yml +0 -0
  52. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/scripts/reproduce_sheaf_laplacian.py +0 -0
  53. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/setup.cfg +0 -0
  54. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/tests/__init__.py +0 -0
  55. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/tests/test_aggregation_disconnected.py +0 -0
  56. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/tests/test_cohomology_cocycle.py +0 -0
  57. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/tests/test_coverage_gaps.py +0 -0
  58. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/tests/test_defensive_paths.py +0 -0
  59. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/tests/test_imports.py +0 -0
  60. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/tests/test_integration.py +0 -0
  61. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/tests/test_persistence.py +0 -0
  62. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/tests/test_persistence_dimension_labels.py +0 -0
  63. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/tests/test_sheaf_laplacian.py +0 -0
  64. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/tests/test_transport_validation.py +0 -0
  65. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/tests/theory/__init__.py +0 -0
  66. {groupoid-0.1.0.dev1 → groupoid-0.1.0.dev2}/tests/theory/test_mathematical_invariants.py +0 -0
@@ -13,8 +13,8 @@ jobs:
13
13
  lint:
14
14
  runs-on: ubuntu-latest
15
15
  steps:
16
- - uses: actions/checkout@v4
17
- - uses: actions/setup-python@v5
16
+ - uses: actions/checkout@v6
17
+ - uses: actions/setup-python@v6
18
18
  with:
19
19
  python-version: "3.11"
20
20
  - name: Install dependencies
@@ -40,8 +40,8 @@ jobs:
40
40
  matrix:
41
41
  python-version: ["3.10", "3.11", "3.12"]
42
42
  steps:
43
- - uses: actions/checkout@v4
44
- - uses: actions/setup-python@v5
43
+ - uses: actions/checkout@v6
44
+ - uses: actions/setup-python@v6
45
45
  with:
46
46
  python-version: ${{ matrix.python-version }}
47
47
  - name: Install dependencies
@@ -58,8 +58,8 @@ jobs:
58
58
  security:
59
59
  runs-on: ubuntu-latest
60
60
  steps:
61
- - uses: actions/checkout@v4
62
- - uses: actions/setup-python@v5
61
+ - uses: actions/checkout@v6
62
+ - uses: actions/setup-python@v6
63
63
  with:
64
64
  python-version: "3.11"
65
65
  - name: Install dependencies
@@ -95,8 +95,8 @@ jobs:
95
95
  pre-commit:
96
96
  runs-on: ubuntu-latest
97
97
  steps:
98
- - uses: actions/checkout@v4
99
- - uses: actions/setup-python@v5
98
+ - uses: actions/checkout@v6
99
+ - uses: actions/setup-python@v6
100
100
  with:
101
101
  python-version: "3.11"
102
102
  - name: Install pre-commit
@@ -107,8 +107,8 @@ jobs:
107
107
  benchmark:
108
108
  runs-on: ubuntu-latest
109
109
  steps:
110
- - uses: actions/checkout@v4
111
- - uses: actions/setup-python@v5
110
+ - uses: actions/checkout@v6
111
+ - uses: actions/setup-python@v6
112
112
  with:
113
113
  python-version: "3.11"
114
114
  - name: Install dependencies
@@ -128,7 +128,7 @@ jobs:
128
128
  pytest benchmarks/ -v --benchmark-json=benchmark_results.json || true
129
129
  - name: Upload benchmark results
130
130
  if: always()
131
- uses: actions/upload-artifact@v4
131
+ uses: actions/upload-artifact@v7
132
132
  with:
133
133
  name: benchmark-results
134
134
  path: benchmark_results.json
@@ -137,8 +137,8 @@ jobs:
137
137
  docs:
138
138
  runs-on: ubuntu-latest
139
139
  steps:
140
- - uses: actions/checkout@v4
141
- - uses: actions/setup-python@v5
140
+ - uses: actions/checkout@v6
141
+ - uses: actions/setup-python@v6
142
142
  with:
143
143
  python-version: "3.11"
144
144
  - name: Install dependencies
@@ -18,8 +18,8 @@ jobs:
18
18
  build:
19
19
  runs-on: ubuntu-latest
20
20
  steps:
21
- - uses: actions/checkout@v4
22
- - uses: actions/setup-python@v5
21
+ - uses: actions/checkout@v6
22
+ - uses: actions/setup-python@v6
23
23
  with:
24
24
  python-version: "3.11"
25
25
  - name: Install dependencies
@@ -29,7 +29,7 @@ jobs:
29
29
  pip install -e ".[docs]"
30
30
  - name: Build docs
31
31
  run: mkdocs build --strict
32
- - uses: actions/upload-pages-artifact@v3
32
+ - uses: actions/upload-pages-artifact@v5
33
33
  with:
34
34
  path: site/
35
35
 
@@ -41,4 +41,4 @@ jobs:
41
41
  url: ${{ steps.deployment.outputs.page_url }}
42
42
  steps:
43
43
  - id: deployment
44
- uses: actions/deploy-pages@v4
44
+ uses: actions/deploy-pages@v5
@@ -0,0 +1,107 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v[0-9]*"
7
+ workflow_dispatch:
8
+ inputs:
9
+ ref:
10
+ description: "Existing tag/ref to build and publish to PyPI (e.g. v0.1.0.dev1). PyPI only — does NOT create a GitHub Release or Zenodo deposit. Leave blank for a normal tag-triggered release."
11
+ required: false
12
+ default: ""
13
+
14
+ permissions:
15
+ contents: read
16
+
17
+ jobs:
18
+ build:
19
+ runs-on: ubuntu-latest
20
+ permissions:
21
+ contents: read
22
+ id-token: write # provenance OIDC
23
+ attestations: write # actions/attest-build-provenance + attest-sbom
24
+ steps:
25
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
26
+ with:
27
+ ref: ${{ github.event.inputs.ref || github.ref }}
28
+ fetch-depth: 0
29
+ - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
30
+ with:
31
+ python-version: "3.12"
32
+ cache: pip
33
+ - name: Install build tooling
34
+ run: |
35
+ python -m pip install --upgrade pip build
36
+ pip install cyclonedx-bom
37
+ - name: Build sdist + wheel
38
+ run: python -m build
39
+ - name: Generate CycloneDX SBOM
40
+ run: cyclonedx-py requirements -o sbom.cdx.json <(pip freeze)
41
+ - name: Provenance attestation (dist)
42
+ uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
43
+ with:
44
+ subject-path: "dist/*"
45
+ - name: SBOM attestation (CycloneDX)
46
+ uses: actions/attest-sbom@5026d3663739160db546203eeaffa6aa1c51a4d6 # v1
47
+ with:
48
+ subject-path: "dist/*"
49
+ sbom-path: "sbom.cdx.json"
50
+ - name: Upload artifacts
51
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
52
+ with:
53
+ name: release-artifacts
54
+ path: |
55
+ dist/*
56
+ sbom.cdx.json
57
+
58
+ publish-pypi:
59
+ needs: build
60
+ runs-on: ubuntu-latest
61
+ permissions:
62
+ contents: read
63
+ id-token: write # PyPI Trusted Publishing OIDC
64
+ environment:
65
+ name: pypi
66
+ url: https://pypi.org/project/groupoid/
67
+ steps:
68
+ - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
69
+ with:
70
+ name: release-artifacts
71
+ path: .
72
+ - name: Publish to PyPI (Trusted Publishing, OIDC)
73
+ uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
74
+ with:
75
+ packages-dir: dist
76
+ print-hash: true
77
+
78
+ sign-and-release:
79
+ needs: build
80
+ # Only on a real tag push — a manual workflow_dispatch (used to publish an
81
+ # already-released tag to PyPI) must NOT create a duplicate GitHub Release
82
+ # or trigger a second Zenodo deposit.
83
+ if: github.event_name == 'push'
84
+ runs-on: ubuntu-latest
85
+ permissions:
86
+ contents: write # create GitHub Release
87
+ id-token: write # Sigstore signing OIDC
88
+ steps:
89
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
90
+ - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
91
+ with:
92
+ name: release-artifacts
93
+ path: .
94
+ - name: Sign artifacts with Sigstore
95
+ uses: sigstore/gh-action-sigstore-python@f514d46b907ebcd5bedc05145c03b69c1edd8b46 # v3.0.0
96
+ with:
97
+ inputs: |
98
+ dist/*.tar.gz
99
+ dist/*.whl
100
+ - name: Create GitHub Release
101
+ uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
102
+ with:
103
+ generate_release_notes: true
104
+ files: |
105
+ dist/*
106
+ sbom.cdx.json
107
+ *.sigstore.json
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "upload_type": "software",
3
3
  "title": "GROUPOID: Groupoid-based aggregation for federated learning on Riemannian manifolds",
4
- "version": "0.1.0.dev1",
5
- "description": "<p>Pre-alpha research prototype exploring groupoid-based aggregation for federated learning on Riemannian manifolds. Implements transport groupoid morphisms, first cohomology (H^1) for consistency detection, the cellular sheaf Laplacian for spectral analysis, parallel transport (Schild's and pole ladders), and Karcher mean aggregation via geomstats.</p><p><strong>Erratum / supersession notice:</strong> this version (v0.1.0.dev1) corrects mathematical bugs present in the earlier v0.1.0.dev0 snapshot (version DOI 10.5281/zenodo.20563975): the sheaf (connection) Laplacian is now a verified positive-semidefinite operator with a transport-consistent kernel; persistence diagrams retain the homology-dimension label so H0 and H1 are no longer conflated; and H^1 cohomology raises on incomplete cocycles instead of forming partial holonomy. The v0.1.0.dev0 version DOI must not be cited for results. Cite the concept DOI (10.5281/zenodo.20563974), which always resolves to the latest corrected version, or this version or later.</p>",
4
+ "version": "0.1.0.dev2",
5
+ "description": "<p>Pre-alpha research prototype exploring groupoid-based aggregation for federated learning on Riemannian manifolds. Implements transport groupoid morphisms, first cohomology (H^1) for consistency detection, the cellular sheaf Laplacian for spectral analysis, parallel transport (Schild's and pole ladders), and Karcher mean aggregation via geomstats.</p><p><strong>Erratum / supersession notice:</strong> this version (v0.1.0.dev2) carries the mathematical corrections introduced in v0.1.0.dev1 over the earlier v0.1.0.dev0 snapshot (version DOI 10.5281/zenodo.20563975): the sheaf (connection) Laplacian is a verified positive-semidefinite operator with a transport-consistent kernel; persistence diagrams retain the homology-dimension label so H0 and H1 are no longer conflated; and H^1 cohomology raises on incomplete cocycles instead of forming partial holonomy. It additionally corrects a first-step magnitude inflation in the RiemannianAdam optimizer's bias initialization (a component not used by the aggregation results). The v0.1.0.dev0 version DOI must not be cited for results. Cite the concept DOI (10.5281/zenodo.20563974), which always resolves to the latest corrected version, or this version or later.</p>",
6
6
  "creators": [
7
7
  {
8
8
  "name": "Maniches, Santiago",
@@ -26,7 +26,7 @@
26
26
  "scheme": "doi"
27
27
  },
28
28
  {
29
- "identifier": "10.5281/zenodo.20563975",
29
+ "identifier": "10.5281/zenodo.20719394",
30
30
  "relation": "isNewVersionOf",
31
31
  "scheme": "doi"
32
32
  },
@@ -10,6 +10,20 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
10
10
 
11
11
  Nothing yet.
12
12
 
13
+ ## [0.1.0.dev2] - 2026-06-21
14
+
15
+ ### Fixed
16
+ - **RiemannianAdam first-moment bias initialization**: the first moment was
17
+ seeded as `grad` while the zero-init bias correction `m / (1 - beta1**t)` was
18
+ still applied, leaving `1/(1 - beta1)` uncancelled and inflating the first
19
+ optimization step ~10x (beta1 = 0.9). Now seeded as `(1 - beta1) * grad` so the
20
+ bias correction recovers `grad`, matching standard Adam. The optimizer is not
21
+ integrated into the aggregation pipeline, so no published result changes (#39).
22
+
23
+ ### Changed
24
+ - `STATUS.md` corrected to state the package is published on PyPI as a development
25
+ pre-release (`groupoid 0.1.0.dev2`) rather than "not published on PyPI" (#39).
26
+
13
27
  ## [0.1.0.dev1] - 2026-06-16
14
28
 
15
29
  First tag cut on mathematically corrected code. The earlier `v0.1.0.dev0` tag and
@@ -72,6 +86,7 @@ Date shown is the tag commit (`v0.1.0.dev0` -> 2a02954). `CITATION.cff` records
72
86
  `date-released: 2026-05-25`, when the core implementation landed; the tag was
73
87
  later placed on the metadata commit.
74
88
 
75
- [Unreleased]: https://github.com/smaniches/GROUPOID/compare/v0.1.0.dev1...HEAD
89
+ [Unreleased]: https://github.com/smaniches/GROUPOID/compare/v0.1.0.dev2...HEAD
90
+ [0.1.0.dev2]: https://github.com/smaniches/GROUPOID/compare/v0.1.0.dev1...v0.1.0.dev2
76
91
  [0.1.0.dev1]: https://github.com/smaniches/GROUPOID/compare/v0.1.0.dev0...v0.1.0.dev1
77
92
  [0.1.0.dev0]: https://github.com/smaniches/GROUPOID/releases/tag/v0.1.0.dev0
@@ -2,8 +2,8 @@ cff-version: 1.2.0
2
2
  message: "If you use this software, please cite it as below."
3
3
  type: software
4
4
  title: "GROUPOID: Groupoid-based aggregation for federated learning on Riemannian manifolds"
5
- version: 0.1.0.dev1
6
- date-released: 2026-06-16
5
+ version: 0.1.0.dev2
6
+ date-released: 2026-06-21
7
7
  license: Apache-2.0
8
8
  repository-code: "https://github.com/smaniches/GROUPOID"
9
9
  doi: 10.5281/zenodo.20563974
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: groupoid
3
- Version: 0.1.0.dev1
3
+ Version: 0.1.0.dev2
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
@@ -29,7 +29,7 @@ Requires-Dist: POT>=0.9
29
29
  Requires-Dist: networkx>=3.0
30
30
  Requires-Dist: einops>=0.7
31
31
  Requires-Dist: numpy<2.0,>=1.24
32
- Requires-Dist: scipy<1.14,>=1.10
32
+ Requires-Dist: scipy<1.16,>=1.10
33
33
  Requires-Dist: torch>=2.0
34
34
  Requires-Dist: pydantic>=2.0
35
35
  Requires-Dist: loguru>=0.7
@@ -66,6 +66,7 @@ Dynamic: license-file
66
66
  # GROUPOID
67
67
 
68
68
  [![CI](https://github.com/smaniches/GROUPOID/actions/workflows/ci.yml/badge.svg)](https://github.com/smaniches/GROUPOID/actions/workflows/ci.yml)
69
+ [![PyPI](https://img.shields.io/pypi/v/groupoid.svg)](https://pypi.org/project/groupoid/)
69
70
  [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
70
71
  [![Python 3.10-3.12](https://img.shields.io/badge/python-3.10--3.12-blue.svg)](https://www.python.org/downloads/)
71
72
  [![Status](https://img.shields.io/badge/status-pre--alpha-orange.svg)](#status)
@@ -187,7 +188,13 @@ Requires **Python 3.10, 3.11, or 3.12**. Python 3.13+ is not supported: the
187
188
  [LIMITATIONS.md](LIMITATIONS.md)) have no wheels there, so `pip` will refuse
188
189
  with a `Requires-Python` message rather than attempt a source build.
189
190
 
190
- From source (not published on PyPI):
191
+ From PyPI:
192
+
193
+ ```bash
194
+ pip install groupoid
195
+ ```
196
+
197
+ From source (for development):
191
198
 
192
199
  ```bash
193
200
  git clone https://github.com/smaniches/GROUPOID.git
@@ -1,6 +1,7 @@
1
1
  # GROUPOID
2
2
 
3
3
  [![CI](https://github.com/smaniches/GROUPOID/actions/workflows/ci.yml/badge.svg)](https://github.com/smaniches/GROUPOID/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/groupoid.svg)](https://pypi.org/project/groupoid/)
4
5
  [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
5
6
  [![Python 3.10-3.12](https://img.shields.io/badge/python-3.10--3.12-blue.svg)](https://www.python.org/downloads/)
6
7
  [![Status](https://img.shields.io/badge/status-pre--alpha-orange.svg)](#status)
@@ -122,7 +123,13 @@ Requires **Python 3.10, 3.11, or 3.12**. Python 3.13+ is not supported: the
122
123
  [LIMITATIONS.md](LIMITATIONS.md)) have no wheels there, so `pip` will refuse
123
124
  with a `Requires-Python` message rather than attempt a source build.
124
125
 
125
- From source (not published on PyPI):
126
+ From PyPI:
127
+
128
+ ```bash
129
+ pip install groupoid
130
+ ```
131
+
132
+ From source (for development):
126
133
 
127
134
  ```bash
128
135
  git clone https://github.com/smaniches/GROUPOID.git
@@ -32,7 +32,8 @@
32
32
  - No federated training loop with real neural networks exists yet.
33
33
  - No differential privacy mechanism is implemented.
34
34
  - No formal convergence analysis or proofs exist.
35
- - The package is not published on PyPI.
35
+ - The package is published on PyPI only as an early development pre-release
36
+ (`groupoid 0.1.0.dev2`); no stable release exists yet.
36
37
 
37
38
  ## Validation status
38
39
 
@@ -70,5 +71,5 @@ not capture.
70
71
 
71
72
  ## Versioning
72
73
 
73
- This project uses `0.1.0.dev1` to indicate pre-release development.
74
+ This project uses `0.1.0.dev2` to indicate pre-release development.
74
75
  The API is unstable and will change without notice.
@@ -6,7 +6,7 @@
6
6
  "codeRepository": "https://github.com/smaniches/GROUPOID",
7
7
  "programmingLanguage": "Python",
8
8
  "license": "Apache-2.0",
9
- "version": "0.1.0.dev1",
9
+ "version": "0.1.0.dev2",
10
10
  "author": [
11
11
  {
12
12
  "@type": "Person",
@@ -1,6 +1,6 @@
1
1
  """GROUPOID: Groupoid-based federated learning with Riemannian geometry."""
2
2
 
3
- __version__ = "0.1.0.dev1"
3
+ __version__ = "0.1.0.dev2"
4
4
 
5
5
  from groupoid.aggregation import (
6
6
  DisconnectedClientGraphError,
@@ -138,10 +138,14 @@ class RiemannianAdam:
138
138
  grad = self.manifold.to_tangent(euclidean_grad, point)
139
139
  grad_norm_sq = float(np.sum(grad**2))
140
140
 
141
- # Update biased first moment (tangent vector)
141
+ # Update biased first moment (tangent vector). Seed from zero like the
142
+ # second moment below: m_1 = (1 - beta1) * grad, so the bias correction
143
+ # m_hat = m_1 / (1 - beta1) recovers `grad` on the first step. Seeding
144
+ # m_1 = grad directly would leave the 1/(1-beta1) factor uncancelled and
145
+ # inflate the first update by ~10x (beta1=0.9).
142
146
  first_moment: npt.NDArray[np.float64]
143
147
  if self._m is None:
144
- first_moment = grad.copy()
148
+ first_moment = (1 - self.beta1) * grad
145
149
  else:
146
150
  first_moment = self.manifold.to_tangent(
147
151
  self.beta1 * self._m + (1 - self.beta1) * grad, point
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: groupoid
3
- Version: 0.1.0.dev1
3
+ Version: 0.1.0.dev2
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
@@ -29,7 +29,7 @@ Requires-Dist: POT>=0.9
29
29
  Requires-Dist: networkx>=3.0
30
30
  Requires-Dist: einops>=0.7
31
31
  Requires-Dist: numpy<2.0,>=1.24
32
- Requires-Dist: scipy<1.14,>=1.10
32
+ Requires-Dist: scipy<1.16,>=1.10
33
33
  Requires-Dist: torch>=2.0
34
34
  Requires-Dist: pydantic>=2.0
35
35
  Requires-Dist: loguru>=0.7
@@ -66,6 +66,7 @@ Dynamic: license-file
66
66
  # GROUPOID
67
67
 
68
68
  [![CI](https://github.com/smaniches/GROUPOID/actions/workflows/ci.yml/badge.svg)](https://github.com/smaniches/GROUPOID/actions/workflows/ci.yml)
69
+ [![PyPI](https://img.shields.io/pypi/v/groupoid.svg)](https://pypi.org/project/groupoid/)
69
70
  [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
70
71
  [![Python 3.10-3.12](https://img.shields.io/badge/python-3.10--3.12-blue.svg)](https://www.python.org/downloads/)
71
72
  [![Status](https://img.shields.io/badge/status-pre--alpha-orange.svg)](#status)
@@ -187,7 +188,13 @@ Requires **Python 3.10, 3.11, or 3.12**. Python 3.13+ is not supported: the
187
188
  [LIMITATIONS.md](LIMITATIONS.md)) have no wheels there, so `pip` will refuse
188
189
  with a `Requires-Python` message rather than attempt a source build.
189
190
 
190
- From source (not published on PyPI):
191
+ From PyPI:
192
+
193
+ ```bash
194
+ pip install groupoid
195
+ ```
196
+
197
+ From source (for development):
191
198
 
192
199
  ```bash
193
200
  git clone https://github.com/smaniches/GROUPOID.git
@@ -20,6 +20,7 @@ pyproject.toml
20
20
  .github/ISSUE_TEMPLATE/mathematical_correction.md
21
21
  .github/workflows/ci.yml
22
22
  .github/workflows/docs-deploy.yml
23
+ .github/workflows/release.yml
23
24
  benchmarks/__init__.py
24
25
  benchmarks/conftest.py
25
26
  benchmarks/test_karcher_benchmark.py
@@ -6,7 +6,7 @@ POT>=0.9
6
6
  networkx>=3.0
7
7
  einops>=0.7
8
8
  numpy<2.0,>=1.24
9
- scipy<1.14,>=1.10
9
+ scipy<1.16,>=1.10
10
10
  torch>=2.0
11
11
  pydantic>=2.0
12
12
  loguru>=0.7
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "groupoid"
7
- version = "0.1.0.dev1"
7
+ version = "0.1.0.dev2"
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"
@@ -44,7 +44,7 @@ dependencies = [
44
44
  "networkx>=3.0",
45
45
  "einops>=0.7",
46
46
  "numpy>=1.24,<2.0",
47
- "scipy>=1.10,<1.14",
47
+ "scipy>=1.10,<1.16",
48
48
  "torch>=2.0",
49
49
  "pydantic>=2.0",
50
50
  "loguru>=0.7",
@@ -115,6 +115,27 @@ class TestOptimizerSmoke:
115
115
  assert new_point.shape == (3,)
116
116
  assert manifold.belongs(new_point, atol=1e-4)
117
117
 
118
+ def test_riemannian_adam_first_step_magnitude(self):
119
+ """The first step moves ~lr in geodesic distance, not lr/(1 - beta1).
120
+
121
+ Standard Adam's bias correction yields m_hat_1 = grad on step 1, so the
122
+ update is -lr * grad / (||grad|| + eps) and the geodesic displacement is
123
+ ~lr. Seeding the first moment as grad without the (1 - beta1) factor
124
+ leaves 1/(1 - beta1) uncancelled and inflates the first step to ~10*lr
125
+ (beta1 = 0.9). This test fails on that bug.
126
+ """
127
+ from geomstats.geometry.hypersphere import Hypersphere
128
+
129
+ from groupoid.optimizer import RiemannianAdam
130
+
131
+ manifold = Hypersphere(dim=2)
132
+ point = np.array([0.0, 0.0, 1.0])
133
+ grad = np.array([0.1, 0.2, 0.3])
134
+ lr = 0.01
135
+ new_point = RiemannianAdam(manifold=manifold, lr=lr).step(point, grad)
136
+ dist = float(manifold.metric.dist(point, new_point))
137
+ assert abs(dist - lr) < 0.2 * lr
138
+
118
139
 
119
140
  class TestAggregationSmoke:
120
141
  """Deterministic aggregation test."""
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes