groupoid 0.1.0.dev2__tar.gz → 0.1.0.dev4__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 (78) hide show
  1. groupoid-0.1.0.dev4/.github/dependabot.yml +78 -0
  2. groupoid-0.1.0.dev4/.github/scripts/install_torch_cpu.sh +25 -0
  3. groupoid-0.1.0.dev4/.github/workflows/auto-tag-release.yml +72 -0
  4. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/.github/workflows/ci.yml +18 -18
  5. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/.github/workflows/docs-deploy.yml +8 -7
  6. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/.github/workflows/release.yml +7 -7
  7. groupoid-0.1.0.dev4/.zenodo.json +39 -0
  8. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/CHANGELOG.md +71 -1
  9. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/CITATION.cff +2 -2
  10. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/CONTRIBUTING.md +39 -0
  11. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/LIMITATIONS.md +14 -5
  12. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/PKG-INFO +31 -24
  13. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/PROBLEMS.md +19 -6
  14. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/README.md +30 -23
  15. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/STATUS.md +27 -14
  16. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/codemeta.json +1 -1
  17. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/docs/index.md +12 -10
  18. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/docs/quickstart.md +15 -6
  19. groupoid-0.1.0.dev4/experiments/PREREGISTRATION.md +154 -0
  20. groupoid-0.1.0.dev4/experiments/README.md +64 -0
  21. groupoid-0.1.0.dev4/experiments/RESULTS.md +163 -0
  22. groupoid-0.1.0.dev4/experiments/analyze.py +261 -0
  23. groupoid-0.1.0.dev4/experiments/results.json +12615 -0
  24. groupoid-0.1.0.dev4/experiments/run_benchmark.py +213 -0
  25. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/groupoid/__init__.py +1 -1
  26. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/groupoid/aggregation.py +66 -1
  27. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/groupoid/groupoid.py +5 -0
  28. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/groupoid/optimizer.py +64 -8
  29. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/groupoid.egg-info/PKG-INFO +31 -24
  30. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/groupoid.egg-info/SOURCES.txt +10 -0
  31. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/pyproject.toml +1 -1
  32. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/tests/test_coverage_gaps.py +4 -2
  33. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/tests/test_defensive_paths.py +12 -0
  34. groupoid-0.1.0.dev4/tests/test_optimizer_validation.py +141 -0
  35. groupoid-0.1.0.dev4/tests/test_pipeline_integration.py +130 -0
  36. groupoid-0.1.0.dev2/.github/dependabot.yml +0 -29
  37. groupoid-0.1.0.dev2/.zenodo.json +0 -39
  38. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  39. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  40. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/.github/ISSUE_TEMPLATE/mathematical_correction.md +0 -0
  41. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/.gitignore +0 -0
  42. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/.pre-commit-config.yaml +0 -0
  43. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/LICENSE +0 -0
  44. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/REPRODUCE.md +0 -0
  45. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/SECURITY.md +0 -0
  46. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/benchmarks/__init__.py +0 -0
  47. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/benchmarks/conftest.py +0 -0
  48. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/benchmarks/test_karcher_benchmark.py +0 -0
  49. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/docs/api.md +0 -0
  50. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/docs/overrides/404.html +0 -0
  51. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/docs/theory.md +0 -0
  52. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/examples/README.md +0 -0
  53. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/examples/__init__.py +0 -0
  54. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/examples/synthetic_curve_demo.py +0 -0
  55. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/groupoid/cohomology.py +0 -0
  56. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/groupoid/laplacian.py +0 -0
  57. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/groupoid/manifold.py +0 -0
  58. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/groupoid/persistence.py +0 -0
  59. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/groupoid/sheaf.py +0 -0
  60. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/groupoid/transport.py +0 -0
  61. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/groupoid.egg-info/dependency_links.txt +0 -0
  62. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/groupoid.egg-info/requires.txt +0 -0
  63. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/groupoid.egg-info/top_level.txt +0 -0
  64. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/mkdocs.yml +0 -0
  65. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/scripts/reproduce_sheaf_laplacian.py +0 -0
  66. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/setup.cfg +0 -0
  67. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/tests/__init__.py +0 -0
  68. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/tests/test_aggregation_disconnected.py +0 -0
  69. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/tests/test_cohomology_cocycle.py +0 -0
  70. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/tests/test_imports.py +0 -0
  71. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/tests/test_integration.py +0 -0
  72. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/tests/test_persistence.py +0 -0
  73. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/tests/test_persistence_dimension_labels.py +0 -0
  74. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/tests/test_sheaf_laplacian.py +0 -0
  75. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/tests/test_smoke.py +0 -0
  76. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/tests/test_transport_validation.py +0 -0
  77. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/tests/theory/__init__.py +0 -0
  78. {groupoid-0.1.0.dev2 → groupoid-0.1.0.dev4}/tests/theory/test_mathematical_invariants.py +0 -0
@@ -0,0 +1,78 @@
1
+ # Dependabot configuration.
2
+ # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
3
+ #
4
+ # Opens reviewable update PRs (never auto-merged) so security and version bumps
5
+ # are surfaced proactively, rather than relying on a manual read of the CI
6
+ # dependency-audit output. Complements the CI dependency-vulnerability scan.
7
+ version: 2
8
+ updates:
9
+ # Python dependencies declared in pyproject.toml.
10
+ - package-ecosystem: "pip"
11
+ directory: "/"
12
+ schedule:
13
+ interval: "weekly"
14
+ day: "monday"
15
+ # Pin the run to a predictable local hour. With no time/timezone,
16
+ # Dependabot fires at its default slot in UTC, which lands in the middle
17
+ # of the night for the maintainer. Adjust `timezone` to your actual IANA
18
+ # zone if America/New_York is not yours.
19
+ time: "09:00"
20
+ timezone: "America/New_York"
21
+ open-pull-requests-limit: 5
22
+ # Manage DIRECT dependencies only — the ones declared in pyproject.toml.
23
+ # GROUPOID ships no committed/compiled lockfile, so this is currently a
24
+ # no-op (Dependabot already only edits the pyproject declarations). It is
25
+ # kept for consistency and as a guard: should a compiled lock ever be added,
26
+ # its transitive pins must advance by recompiling the lock, not by standalone
27
+ # Dependabot line-edits. Transitive CVEs are NOT lost: Dependabot *security*
28
+ # updates patch vulnerable dependencies regardless of this allow filter.
29
+ allow:
30
+ - dependency-type: "direct"
31
+ groups:
32
+ # Batch updates by area into a single PR per group to limit noise instead
33
+ # of one PR per package.
34
+ runtime:
35
+ patterns:
36
+ - "torch"
37
+ - "geomstats"
38
+ - "pymanopt"
39
+ - "ripser"
40
+ - "persim"
41
+ - "POT"
42
+ - "networkx"
43
+ - "einops"
44
+ - "numpy"
45
+ - "scipy"
46
+ - "pydantic"
47
+ - "loguru"
48
+ dev:
49
+ patterns:
50
+ - "pytest*"
51
+ - "ruff"
52
+ - "black"
53
+ - "mypy"
54
+ - "hypothesis"
55
+ - "bandit"
56
+ - "pip-audit"
57
+ - "pre-commit"
58
+ docs:
59
+ patterns: ["mkdocs*", "mkdocstrings*"]
60
+ # Fallback batcher. With `allow: direct` and the groups above, this should
61
+ # stay empty; if a future declared dep is not matched above, this keeps it
62
+ # to a single PR instead of one-per-package.
63
+ other:
64
+ patterns: ["*"]
65
+
66
+ # GitHub Actions used in the workflows (keeps action versions current).
67
+ - package-ecosystem: "github-actions"
68
+ directory: "/"
69
+ schedule:
70
+ interval: "weekly"
71
+ day: "monday"
72
+ time: "09:00"
73
+ timezone: "America/New_York"
74
+ open-pull-requests-limit: 5
75
+ groups:
76
+ # Batch all action bumps into a single weekly PR instead of one each.
77
+ actions:
78
+ patterns: ["*"]
@@ -0,0 +1,25 @@
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
@@ -0,0 +1,72 @@
1
+ name: Auto Tag Release
2
+
3
+ # Creates the tag for a release-cut merge to main; it does nothing else.
4
+ # release.yml (build, attestations, PyPI publish, signing, GitHub Release)
5
+ # is triggered solely by that tag push, exactly as it would be from a
6
+ # manual `git tag vX.Y.Z && git push origin vX.Y.Z`. Keeping tag creation
7
+ # and publishing in two separate, single-purpose workflows means the
8
+ # actual publish path has exactly one trigger (a tag push) to reason
9
+ # about, instead of depending on GitHub's anti-recursion behavior for
10
+ # GITHUB_TOKEN-authored pushes not re-triggering the same push event.
11
+ #
12
+ # If a version needs a tag this workflow's format check rejects (see
13
+ # below), tag it manually -- this automation only ever creates a tag
14
+ # when one is missing, so a pre-existing manual tag always takes
15
+ # precedence and this workflow becomes a no-op for that version.
16
+
17
+ on:
18
+ push:
19
+ branches: [main]
20
+ paths:
21
+ - pyproject.toml
22
+
23
+ permissions:
24
+ contents: read
25
+
26
+ jobs:
27
+ tag:
28
+ runs-on: ubuntu-latest
29
+ permissions:
30
+ contents: write # push the tag; nothing else in this workflow needs it
31
+ steps:
32
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
33
+ with:
34
+ fetch-depth: 0 # need full tag history to check for an existing tag
35
+
36
+ - name: Create the release tag if this version doesn't have one yet
37
+ run: |
38
+ set -euo pipefail
39
+
40
+ version=$(grep -m1 '^version = "' pyproject.toml | sed -E 's/version = "(.*)"/\1/')
41
+ if [ -z "${version}" ]; then
42
+ echo "::error::could not extract a version from pyproject.toml"
43
+ exit 1
44
+ fi
45
+
46
+ # PEP 440-ish release/pre-release only: X.Y.Z, optionally with
47
+ # .devN / aN / bN / rcN. No local version segment (+...), no
48
+ # anything else. A version that doesn't match this is presumably
49
+ # intentional and unusual enough to want a human tagging it by
50
+ # hand rather than this automation guessing.
51
+ if ! echo "${version}" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(\.dev[0-9]+|a[0-9]+|b[0-9]+|rc[0-9]+)?$'; then
52
+ echo "::error::version '${version}' is not a recognized release format (expected X.Y.Z, optionally with .devN/aN/bN/rcN, no local metadata); refusing to tag automatically. Tag it manually if this is intentional."
53
+ exit 1
54
+ fi
55
+
56
+ tag="v${version}"
57
+
58
+ if git rev-parse "refs/tags/${tag}" >/dev/null 2>&1; then
59
+ echo "Tag ${tag} already exists; nothing to do."
60
+ exit 0
61
+ fi
62
+
63
+ if ! grep -qF "[${version}]" CHANGELOG.md; then
64
+ echo "::error::CHANGELOG.md has no [${version}] heading; refusing to tag an undocumented release. Add the changelog section as part of the version-bump PR."
65
+ exit 1
66
+ fi
67
+
68
+ echo "Version ${version} has no matching tag; creating ${tag}."
69
+ git config user.name "github-actions[bot]"
70
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
71
+ git tag -a "${tag}" -m "Release ${tag}"
72
+ git push origin "refs/tags/${tag}"
@@ -13,8 +13,8 @@ jobs:
13
13
  lint:
14
14
  runs-on: ubuntu-latest
15
15
  steps:
16
- - uses: actions/checkout@v6
17
- - uses: actions/setup-python@v6
16
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
17
+ - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
18
18
  with:
19
19
  python-version: "3.11"
20
20
  - name: Install dependencies
@@ -25,7 +25,7 @@ jobs:
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
27
  # Consistent with the test / security / docs jobs. CPU torch keeps it lean.
28
- pip install torch --index-url https://download.pytorch.org/whl/cpu
28
+ .github/scripts/install_torch_cpu.sh
29
29
  pip install -e ".[dev]"
30
30
  - name: Run ruff
31
31
  run: ruff check .
@@ -40,14 +40,14 @@ jobs:
40
40
  matrix:
41
41
  python-version: ["3.10", "3.11", "3.12"]
42
42
  steps:
43
- - uses: actions/checkout@v6
44
- - uses: actions/setup-python@v6
43
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
44
+ - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.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
- pip install torch --index-url https://download.pytorch.org/whl/cpu
50
+ .github/scripts/install_torch_cpu.sh
51
51
  pip install -e ".[dev]"
52
52
  - name: Run tests with coverage gate
53
53
  run: >-
@@ -58,8 +58,8 @@ jobs:
58
58
  security:
59
59
  runs-on: ubuntu-latest
60
60
  steps:
61
- - uses: actions/checkout@v6
62
- - uses: actions/setup-python@v6
61
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
62
+ - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
63
63
  with:
64
64
  python-version: "3.11"
65
65
  - name: Install dependencies
@@ -69,7 +69,7 @@ jobs:
69
69
  # packages GROUPOID actually depends on. The previous `safety` step
70
70
  # installed only the tools and never the project, so it audited the
71
71
  # wrong environment. CPU torch keeps the install lean.
72
- pip install torch --index-url https://download.pytorch.org/whl/cpu
72
+ .github/scripts/install_torch_cpu.sh
73
73
  pip install -e .
74
74
  pip install bandit pip-audit
75
75
  # Pin setuptools LAST. torch requires setuptools<82 and pulls in a
@@ -95,8 +95,8 @@ jobs:
95
95
  pre-commit:
96
96
  runs-on: ubuntu-latest
97
97
  steps:
98
- - uses: actions/checkout@v6
99
- - uses: actions/setup-python@v6
98
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
99
+ - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
100
100
  with:
101
101
  python-version: "3.11"
102
102
  - name: Install pre-commit
@@ -107,14 +107,14 @@ jobs:
107
107
  benchmark:
108
108
  runs-on: ubuntu-latest
109
109
  steps:
110
- - uses: actions/checkout@v6
111
- - uses: actions/setup-python@v6
110
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
111
+ - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
112
112
  with:
113
113
  python-version: "3.11"
114
114
  - name: Install dependencies
115
115
  run: |
116
116
  python -m pip install --upgrade pip
117
- pip install torch --index-url https://download.pytorch.org/whl/cpu
117
+ .github/scripts/install_torch_cpu.sh
118
118
  pip install -e ".[dev]"
119
119
  # Benchmarks run for information only. There is no regression gate:
120
120
  # timing baselines recorded on one machine are not comparable to a
@@ -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@v7
131
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
132
132
  with:
133
133
  name: benchmark-results
134
134
  path: benchmark_results.json
@@ -137,14 +137,14 @@ jobs:
137
137
  docs:
138
138
  runs-on: ubuntu-latest
139
139
  steps:
140
- - uses: actions/checkout@v6
141
- - uses: actions/setup-python@v6
140
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
141
+ - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
142
142
  with:
143
143
  python-version: "3.11"
144
144
  - name: Install dependencies
145
145
  run: |
146
146
  python -m pip install --upgrade pip
147
- pip install torch --index-url https://download.pytorch.org/whl/cpu
147
+ .github/scripts/install_torch_cpu.sh
148
148
  pip install -e ".[docs]"
149
149
  - name: Build docs
150
150
  run: mkdocs build --strict
@@ -7,8 +7,6 @@ on:
7
7
 
8
8
  permissions:
9
9
  contents: read
10
- pages: write
11
- id-token: write
12
10
 
13
11
  concurrency:
14
12
  group: pages
@@ -18,27 +16,30 @@ jobs:
18
16
  build:
19
17
  runs-on: ubuntu-latest
20
18
  steps:
21
- - uses: actions/checkout@v6
22
- - uses: actions/setup-python@v6
19
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
20
+ - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
23
21
  with:
24
22
  python-version: "3.11"
25
23
  - name: Install dependencies
26
24
  run: |
27
25
  python -m pip install --upgrade pip
28
- pip install torch --index-url https://download.pytorch.org/whl/cpu
26
+ .github/scripts/install_torch_cpu.sh
29
27
  pip install -e ".[docs]"
30
28
  - name: Build docs
31
29
  run: mkdocs build --strict
32
- - uses: actions/upload-pages-artifact@v5
30
+ - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5
33
31
  with:
34
32
  path: site/
35
33
 
36
34
  deploy:
37
35
  needs: build
38
36
  runs-on: ubuntu-latest
37
+ permissions:
38
+ pages: write
39
+ id-token: write
39
40
  environment:
40
41
  name: github-pages
41
42
  url: ${{ steps.deployment.outputs.page_url }}
42
43
  steps:
43
44
  - id: deployment
44
- uses: actions/deploy-pages@v5
45
+ uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5
@@ -22,7 +22,7 @@ jobs:
22
22
  id-token: write # provenance OIDC
23
23
  attestations: write # actions/attest-build-provenance + attest-sbom
24
24
  steps:
25
- - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
25
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
26
26
  with:
27
27
  ref: ${{ github.event.inputs.ref || github.ref }}
28
28
  fetch-depth: 0
@@ -43,7 +43,7 @@ jobs:
43
43
  with:
44
44
  subject-path: "dist/*"
45
45
  - name: SBOM attestation (CycloneDX)
46
- uses: actions/attest-sbom@5026d3663739160db546203eeaffa6aa1c51a4d6 # v1
46
+ uses: actions/attest-sbom@c604332985a26aa8cf1bdc465b92731239ec6b9e # v4.1.0
47
47
  with:
48
48
  subject-path: "dist/*"
49
49
  sbom-path: "sbom.cdx.json"
@@ -65,7 +65,7 @@ jobs:
65
65
  name: pypi
66
66
  url: https://pypi.org/project/groupoid/
67
67
  steps:
68
- - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
68
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
69
69
  with:
70
70
  name: release-artifacts
71
71
  path: .
@@ -86,19 +86,19 @@ jobs:
86
86
  contents: write # create GitHub Release
87
87
  id-token: write # Sigstore signing OIDC
88
88
  steps:
89
- - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
90
- - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
89
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
90
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
91
91
  with:
92
92
  name: release-artifacts
93
93
  path: .
94
94
  - name: Sign artifacts with Sigstore
95
- uses: sigstore/gh-action-sigstore-python@f514d46b907ebcd5bedc05145c03b69c1edd8b46 # v3.0.0
95
+ uses: sigstore/gh-action-sigstore-python@5b79a39c381910c090341a2c9b0bf022c8b387e1 # v3.4.0
96
96
  with:
97
97
  inputs: |
98
98
  dist/*.tar.gz
99
99
  dist/*.whl
100
100
  - name: Create GitHub Release
101
- uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
101
+ uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
102
102
  with:
103
103
  generate_release_notes: true
104
104
  files: |
@@ -0,0 +1,39 @@
1
+ {
2
+ "upload_type": "software",
3
+ "title": "GROUPOID: Groupoid-based aggregation for federated learning on Riemannian manifolds",
4
+ "version": "0.1.0.dev4",
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), Riemannian optimizers, and Karcher mean aggregation via geomstats.</p><p><strong>Erratum / supersession notice:</strong> this version (v0.1.0.dev4) 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 also carries the v0.1.0.dev2 correction of a first-step magnitude inflation in the RiemannianAdam optimizer's bias initialization, and the v0.1.0.dev3 Morphism str()/format() display fix and documentation-accuracy corrections (v0.1.0.dev3 was cut in source control but never separately tagged or deposited, so this is the first deposit carrying its changes). New in v0.1.0.dev4: the Riemannian SGD and Adam optimizers now parallel-transport their accumulated moments between iterates instead of projecting them (projection could annihilate a geodesic-aligned moment); the parallel-transport and persistent-homology modules are wired into the aggregation pipeline; and a preregistered synthetic benchmark (see the repository's experiments/ directory) tests the central transport-aggregation hypothesis, finding it supported on synthetic frame-misaligned data and still unvalidated on real federated learning tasks. 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
+ "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
+ "cohomology",
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.20787126",
30
+ "relation": "isNewVersionOf",
31
+ "scheme": "doi"
32
+ },
33
+ {
34
+ "identifier": "https://github.com/smaniches/GROUPOID",
35
+ "relation": "isSupplementTo",
36
+ "scheme": "url"
37
+ }
38
+ ]
39
+ }
@@ -10,6 +10,74 @@ 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.dev4] - 2026-07-06
14
+
15
+ `v0.1.0.dev3` was cut in source control (below) but never tagged or
16
+ released: no `v0.1.0.dev3` tag was ever pushed, so PyPI, the GitHub
17
+ Release, and the Zenodo deposit all skip directly from `0.1.0.dev2` to
18
+ this version, which carries both the `0.1.0.dev3` changes and the ones
19
+ below. See the "Releasing" section of `CONTRIBUTING.md` for the
20
+ automation added in this version that prevents this gap from recurring.
21
+
22
+ ### Fixed
23
+ - **Optimizer moment accumulators are now parallel-transported between
24
+ iterates**: Adam's first moment was previously carried across steps by
25
+ tangent projection (which can annihilate a geodesic-aligned moment) and
26
+ the SGD momentum velocity was not moved at all (leaving it non-tangent
27
+ at the new iterate). Both now use the metric's parallel transport (the
28
+ Becigneul-Ganea construction), with projection kept as the fallback for
29
+ metrics without parallel transport. Regression tests discriminate
30
+ transport from projection by exact norm preservation (#49).
31
+
32
+ ### Added
33
+ - `TransportGroupoidAggregator.register_transport_from_points`: computes
34
+ and registers the transport matrix from two client base points via the
35
+ pole or Schild ladder, wiring `groupoid.transport` into the pipeline
36
+ (#49).
37
+ - Opt-in `track_divergence` flag on the aggregator: each round computes
38
+ the persistent-homology summary of the transported parameters with the
39
+ H0-vs-H0 bottleneck distance to the previous round, exposed as
40
+ `FederatedRound.divergence` (default off; existing behavior unchanged),
41
+ wiring `groupoid.persistence` into the pipeline (#49).
42
+ - Descent validation for the Riemannian optimizers: SGD, momentum SGD,
43
+ and Adam descend to a known target on S^2; the optimizer status labels
44
+ in STATUS.md and the docs are upgraded accordingly (general
45
+ convergence-rate analysis still does not exist and is not claimed)
46
+ (#49).
47
+ - A preregistered synthetic benchmark (`experiments/`): preregistration
48
+ pushed before execution, 600 seeded runs, 2x2 transport/mean ablation
49
+ against an oracle estimand, corrupted-cocycle conditions, and a
50
+ committed preregistered analysis (`experiments/RESULTS.md`). Supports
51
+ the transport benefit under frame misalignment (largely by
52
+ construction) and the pooled H^1-error correlation under corruption,
53
+ with the within-level caveat, a failed null check reported as failed,
54
+ and one documented seeding deviation (#49).
55
+ - Release automation: pushing a version bump to `main` now tags and
56
+ releases automatically, removing the manual tag-push step.
57
+
58
+ ## [0.1.0.dev3] - 2026-07-06
59
+
60
+ Cut in source control but never tagged or released; see `0.1.0.dev4` above.
61
+
62
+ ### Fixed
63
+ - **`Morphism` `str()`/`format()` output**: pydantic's `BaseModel` defines
64
+ `__str__` separately from `__repr__`, so `print()`, f-strings, and loguru's
65
+ `{}` formatting dumped the full transport matrix instead of the compact
66
+ `Morphism(A -> B)` form the `__repr__` override implements. `__str__` now
67
+ aliases `__repr__`, with a regression test (#47).
68
+
69
+ ### Changed
70
+ - Docs-site installation pages corrected to state the package is published on
71
+ PyPI as a development pre-release, aligning `docs/index.md` and
72
+ `docs/quickstart.md` with the README and `STATUS.md` corrections from #37
73
+ and #39 (#47).
74
+ - `curvature_adaptive_lr` docstring corrected to describe the implemented
75
+ behavior: the rate is damped in positive curvature and returned unchanged
76
+ otherwise; no enlargement is applied in negative curvature (#47).
77
+ - `LIMITATIONS.md` now records that `torch`, `pymanopt`, `POT`, and `einops`
78
+ are declared runtime dependencies with no current code path (the suite
79
+ passes with all four uninstalled) (#47).
80
+
13
81
  ## [0.1.0.dev2] - 2026-06-21
14
82
 
15
83
  ### Fixed
@@ -86,7 +154,9 @@ Date shown is the tag commit (`v0.1.0.dev0` -> 2a02954). `CITATION.cff` records
86
154
  `date-released: 2026-05-25`, when the core implementation landed; the tag was
87
155
  later placed on the metadata commit.
88
156
 
89
- [Unreleased]: https://github.com/smaniches/GROUPOID/compare/v0.1.0.dev2...HEAD
157
+ [Unreleased]: https://github.com/smaniches/GROUPOID/compare/v0.1.0.dev4...HEAD
158
+ [0.1.0.dev4]: https://github.com/smaniches/GROUPOID/compare/v0.1.0.dev2...v0.1.0.dev4
159
+ [0.1.0.dev3]: https://github.com/smaniches/GROUPOID/compare/v0.1.0.dev2...af42cb4
90
160
  [0.1.0.dev2]: https://github.com/smaniches/GROUPOID/compare/v0.1.0.dev1...v0.1.0.dev2
91
161
  [0.1.0.dev1]: https://github.com/smaniches/GROUPOID/compare/v0.1.0.dev0...v0.1.0.dev1
92
162
  [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.dev2
6
- date-released: 2026-06-21
5
+ version: 0.1.0.dev4
6
+ date-released: 2026-07-06
7
7
  license: Apache-2.0
8
8
  repository-code: "https://github.com/smaniches/GROUPOID"
9
9
  doi: 10.5281/zenodo.20563974
@@ -75,6 +75,45 @@ please open an issue using the "Mathematical Correction" template.
75
75
  6. Ensure CI passes (lint, test, security, docs build).
76
76
  7. Address all review comments before merging.
77
77
 
78
+ ## Releasing
79
+
80
+ 1. Open a release-cut PR that bumps the version across every metadata
81
+ file that carries it: `pyproject.toml`, `groupoid/__init__.py`,
82
+ `codemeta.json`, `CITATION.cff` (version and `date-released`),
83
+ `STATUS.md` (both mentions), and `.zenodo.json` (version, the erratum
84
+ description, and `isNewVersionOf` advanced to the prior version's
85
+ Zenodo DOI). Roll `CHANGELOG.md`'s `Unreleased` section into a new
86
+ version heading with compare links. See PRs #40 or #48 for the
87
+ template.
88
+ 2. Merge the PR to `main`.
89
+
90
+ That's it — merging is the release trigger, split across two workflows
91
+ with one job each so the actual publish path has exactly one trigger to
92
+ reason about:
93
+
94
+ - `.github/workflows/auto-tag-release.yml` runs on every push to `main`
95
+ that touches `pyproject.toml`. It reads the version out of
96
+ `pyproject.toml`, and if no `v<version>` tag exists yet, it creates and
97
+ pushes one (as `github-actions[bot]`) after checking the version has a
98
+ recognized format and that `CHANGELOG.md` documents it. A push with no
99
+ version change, or whose tag already exists, is a no-op.
100
+ - That tag push triggers `.github/workflows/release.yml` exactly as a
101
+ manual `git tag vX.Y.Z && git push origin vX.Y.Z` would: build,
102
+ provenance and SBOM attestation, publish to PyPI via Trusted
103
+ Publishing, Sigstore signing, and a GitHub Release (which in turn
104
+ triggers the Zenodo deposit for the new version DOI). `release.yml`
105
+ itself is unchanged from before this automation existed — it only
106
+ ever runs from a tag push or a manual `workflow_dispatch`.
107
+
108
+ The actual PyPI publish step still runs under the `pypi` GitHub
109
+ Environment; if that environment has required reviewers configured, the
110
+ publish waits for that approval exactly as before. Automating the tag
111
+ does not remove that gate.
112
+
113
+ If you need to re-publish an existing tag to PyPI only (no new GitHub
114
+ Release, no new Zenodo deposit), use the `workflow_dispatch` trigger on
115
+ `release.yml` with the `ref` input set to the existing tag.
116
+
78
117
  ## Issue Templates
79
118
 
80
119
  When reporting bugs or requesting features, please use the appropriate
@@ -59,6 +59,13 @@ system. The following limitations apply.
59
59
  - Requires `scipy < 1.14` for the same reason.
60
60
  - The `ripser` and `persim` packages may have build issues on
61
61
  some platforms (C++ compilation required).
62
+ - `torch`, `pymanopt`, `POT`, and `einops` are declared as required
63
+ runtime dependencies but are not imported by any current code path
64
+ (the full test suite passes with all four uninstalled). Installing
65
+ `groupoid` therefore pulls in packages -- most notably `torch` -- that
66
+ the current code does not use. The `torch` advisory tracked in
67
+ SECURITY.md applies to the installed dependency, not to any code path
68
+ GROUPOID exercises.
62
69
 
63
70
  ## Test coverage
64
71
 
@@ -68,11 +75,13 @@ system. The following limitations apply.
68
75
  defensive guards and a `TYPE_CHECKING` import are excluded via
69
76
  `# pragma: no cover` with justifications.
70
77
  - Coverage is not validation. The transport and persistence modules are
71
- now validated against ground truth (analytic parallel transport;
72
- known-topology point clouds) but remain unintegrated into the pipeline.
73
- The optimizer module has smoke-test coverage only: its steps stay on
74
- the manifold and the curvature-adaptive learning rate behaves sensibly,
75
- but its core descent/convergence behavior is not validated.
78
+ validated against ground truth (analytic parallel transport;
79
+ known-topology point clouds) and are wired into the aggregation
80
+ pipeline (`register_transport_from_points`; the opt-in
81
+ `track_divergence` flag). The optimizer module is validated for
82
+ descent to a known target on S^2 and for parallel-transported moment
83
+ accumulators, but it has no general convergence-rate analysis and is
84
+ not integrated into the pipeline.
76
85
  - No end-to-end test with real neural network training exists.
77
86
  - Property-based tests use 500 examples per property, which provides
78
87
  reasonable but not exhaustive coverage of edge cases.