Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
a2e8ce2
release: bump package metadata to 0.2.3
TheHiddenObserver Aug 4, 2026
3a9665c
release: expose version 0.2.3
TheHiddenObserver Aug 4, 2026
17d4505
docs: prepare 0.2.3 release notes
TheHiddenObserver Aug 4, 2026
daebb33
docs: publish English 0.2.3 changelog
TheHiddenObserver Aug 4, 2026
f8d25c1
docs: publish Chinese 0.2.3 changelog
TheHiddenObserver Aug 4, 2026
2aeb784
ci: add release package validation
TheHiddenObserver Aug 4, 2026
85df838
ci: fix clean-install workspace lookup
TheHiddenObserver Aug 4, 2026
9d6bffc
ci: harden PyPI publish validation
TheHiddenObserver Aug 4, 2026
41a0c03
docs: update the PyPI release procedure
TheHiddenObserver Aug 4, 2026
531fccb
fix(ci): validate existing Cython sources
TheHiddenObserver Aug 4, 2026
7fb359e
docs: align Cython source validation
TheHiddenObserver Aug 4, 2026
1c6fd9d
ci: validate release wheel across platforms
TheHiddenObserver Aug 4, 2026
fec4341
docs: document cross-platform release smoke matrix
TheHiddenObserver Aug 4, 2026
60e745d
docs: add complete v0.2.3 GitHub release notes
TheHiddenObserver Aug 4, 2026
d767cab
ci: require complete versioned GitHub release notes
TheHiddenObserver Aug 4, 2026
be6a1fd
ci: publish GitHub Release from versioned notes
TheHiddenObserver Aug 4, 2026
e43f9f3
docs: make versioned GitHub release notes authoritative
TheHiddenObserver Aug 4, 2026
b1e059c
docs: record authoritative GitHub release publication
TheHiddenObserver Aug 4, 2026
e30f6d8
ci: include versioned release notes in package gate
TheHiddenObserver Aug 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 155 additions & 0 deletions .github/releases/v0.2.3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# statgpu 0.2.3

statgpu 0.2.3 is a substantial survival-analysis release. It completes the first major Cox proportional-hazards implementation phase, expands Cox model selection and penalized estimation, and hardens numerical, inference, and packaging behavior across the supported NumPy, CuPy, and PyTorch backends.

## Highlights

- Complete CoxPH support for **Breslow, Efron, and Exact ties**.
- Delayed entry and `(start, stop]` counting-process data.
- Shared-coefficient stratified Cox models with stratum-specific baselines.
- Subject-aware repeated-row handling and `Surv(start, stop, event)` formula input.
- Extended `CoxPHCV` scoring, fold construction, diagnostics, and final refitting.
- Hardened L1, L2, Elastic Net, SCAD, and MCP penalized Cox estimation.
- Universal `py3-none-any` wheel validated on Linux, Windows, and macOS.

## CoxPH Phase 1 completion

### Tie handling and risk sets

`CoxPH` now supports all three primary tie methods:

- `ties="breslow"`;
- `ties="efron"`;
- `ties="exact"`.

The implementation uses shared NumPy, CuPy, and Torch-CUDA risk-set primitives for the partial-likelihood objective, gradient, information matrix, and baseline estimation. Exact tied-event partitions use backend-native dynamic programming rather than a CPU-only implementation.

### Delayed entry, start-stop rows, and stratification

The public Cox interface now supports:

- delayed entry;
- `(start, stop]` counting-process rows;
- repeated rows belonging to the same subject;
- shared coefficients with stratum-specific baseline hazards;
- formula input through `Surv(start, stop, event)`.

Formula-driven NA removal now keeps entry, cluster, strata, subject, response, and design arrays aligned.

### Inference and baseline prediction

For Breslow and Efron fits, model-based, HC0, HC1, and cluster covariance are supported where the requested data configuration is eligible. Exact ties currently support model-based covariance only; unsupported robust-covariance requests fail explicitly instead of silently changing behavior.

Cox numerical stability has been strengthened through centered risk-set moments and log-domain baseline prediction. Singular information matrices are rejected rather than returning misleading zero standard errors.

Baseline prediction requires `compute_inference=True`. The conventional Breslow baseline estimator is used after coefficient fitting, including when coefficients were fitted with Efron or Exact ties.

## CoxPHCV completion

`CoxPHCV` held-out partial likelihood now supports:

- Breslow, Efron, and Exact ties;
- delayed entry and start-stop data;
- strata;
- subject-grouped folds, so repeated rows from one subject remain in one fold;
- device-native held-out scoring;
- convergence- and failure-aware candidate diagnostics;
- selected-penalty refitting on the complete dataset.

Full-data cache identities, fold validation, cloneability, repeated fitting, pickling, and failed-refit state cleanup were hardened.

Requested two-stage or successive-halving controls currently execute one deterministic exhaustive full-precision candidate pass. This avoids repeated complete-grid fitting without pretending that unsafe screening has occurred.

## Penalized Cox and grouped penalties

Penalized Cox estimation was hardened for:

- L1;
- L2;
- Elastic Net;
- SCAD;
- MCP.

The unidentified Cox intercept was removed, Cox-specific SCAD/MCP warm starts were corrected, and Torch Efron value, gradient, and Hessian paths remain native rather than routing through CuPy.

`PenalizedCoxPHModel` remains an estimation-only API in this release. Passing `compute_inference=True` raises `NotImplementedError` explicitly.

Public Group Lasso and Adaptive Group Lasso behavior now follows the generic loss-gradient and exact group-proximal implementation consistently across supported backends.

## Reliability and API hardening

This release also improves:

- failed-fit and failed-refit state resets;
- singular-information handling;
- censoring- and tie-correct concordance semantics;
- device-label grouping without accidental coercion;
- independence from optional statsmodels for robust Cox covariance;
- CV cache identity and candidate eligibility;
- one-shot `cv_splits` iterator reuse across repeated fit, clone, reconstruction, and pickle.

## Installation and platform support

Base CPU installation:

```bash
pip install statgpu==0.2.3
```

CUDA extras:

```bash
pip install "statgpu[gpu11]==0.2.3"
pip install "statgpu[gpu12]==0.2.3"
```

PyTorch backend:

```bash
pip install "statgpu[torch]==0.2.3"
```

The published wheel is a pure-Python `py3-none-any` artifact. The exact release wheel is clean-installed and exercised on Ubuntu, Windows, and macOS using Python 3.11, including a CPU `LinearRegression.fit/predict` smoke test and public Cox imports. The broader regression suite covers Python 3.9–3.12 on Ubuntu.

The cross-platform wheel validation establishes CPU-wheel portability. It does not add Apple MPS support. CUDA execution still requires a compatible NVIDIA driver/runtime and the matching CuPy or PyTorch package.

Optional Cython CPU accelerators are not embedded in the universal wheel. Their `.pyx`/`.pxd` sources remain in the source distribution for local builds.

## Validation

The final reviewed implementation head for the CoxPH Phase 1 work is:

```text
f05a44ad363b46612e956e137e2f00d040765acb
```

Hosted workflow #960 passed documentation, static, full CPU, and Python 3.9–3.12 regression jobs. The complete CPU suite reported **1881 passed and 662 skipped**.

The exact-head physical-GPU promotion artifact records:

- schema 3;
- 134/134 checks passed;
- zero child and nested return codes;
- empty gate-failure arrays;
- clean source state before and after execution;
- SHA-256 `bd4058450def691dd29e9d78853534016c6da70c33192a97dc312d95cbe5d76d`.

Physical-GPU evidence: https://gist.github.com/TheHiddenObserver/afdcad86a243e68a918d852b92e984a4

No universal GPU speedup claim is made. Performance depends on problem size, backend, hardware, tie method, and synchronization costs.

## Upgrade notes and known limits

- Python 3.9 or newer is required.
- Exact ties do not currently provide robust or cluster covariance.
- `PenalizedCoxPHModel` inference is not implemented in 0.2.3.
- Baseline prediction requires inference-enabled fitting.
- Two-stage and successive-halving CoxPHCV controls currently use the documented exhaustive single-pass safety strategy.
- Apple MPS is not currently a statgpu device backend.

## Full change history

- Main implementation: https://github.com/TheHiddenObserver/statgpu/pull/80
- Release preparation: https://github.com/TheHiddenObserver/statgpu/pull/86
- Full comparison: https://github.com/TheHiddenObserver/statgpu/compare/v0.2.2...v0.2.3
- Repository changelog: https://github.com/TheHiddenObserver/statgpu/blob/master/CHANGELOG.md
144 changes: 129 additions & 15 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,165 @@ name: Publish to PyPI
on:
push:
tags:
- 'v*'
- "v*"

permissions:
contents: read

jobs:
publish:
name: Publish distributions to PyPI
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
python-version: "3.11"

- name: Install build tools
run: |
python -m pip install --upgrade pip
python -m pip install build twine

- name: Verify tag matches package version
- name: Verify tag, package versions, and release notes
run: |
TAG_VERSION=${GITHUB_REF#refs/tags/v}
PKG_VERSION=$(python -c "import re; print(re.search(r\"version\s*=\s*['\\\"]([^'\\\"]+)['\\\"]\", open('pyproject.toml').read()).group(1))")
if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then
echo "ERROR: Tag version ($TAG_VERSION) does not match package version ($PKG_VERSION)"
exit 1
fi
python - <<'PY'
import os
import pathlib
import re
import tomllib

root = pathlib.Path.cwd()
tag = os.environ["GITHUB_REF_NAME"]
if not tag.startswith("v"):
raise SystemExit(f"release tag must start with v: {tag}")
tag_version = tag[1:]

pyproject = tomllib.loads((root / "pyproject.toml").read_text(encoding="utf-8"))
project_version = pyproject["project"]["version"]
init_text = (root / "statgpu/__init__.py").read_text(encoding="utf-8")
match = re.search(r'^__version__\s*=\s*["\']([^"\']+)["\']', init_text, re.M)
if match is None:
raise SystemExit("statgpu/__init__.py does not declare __version__")
package_version = match.group(1)

- name: Build package (pure-Python wheel + sdist)
# STATGPU_NO_EXT=1 -> no compiled extensions, so the wheel is tagged
# py3-none-any (universal). The sdist still ships .pyx/.pxd sources for
# users who want to build the optional C accelerators locally.
if len({tag_version, project_version, package_version}) != 1:
raise SystemExit(
"release version mismatch: "
f"tag={tag_version}, pyproject.toml={project_version}, "
f"statgpu/__init__.py={package_version}"
)

notes_path = root / ".github" / "releases" / f"{tag}.md"
if not notes_path.is_file():
raise SystemExit(f"missing GitHub Release notes: {notes_path.relative_to(root)}")
notes = notes_path.read_text(encoding="utf-8")
if not notes.startswith(f"# statgpu {tag_version}\n"):
raise SystemExit(
f"GitHub Release notes title does not match tag {tag}: "
f"{notes_path.relative_to(root)}"
)
required_sections = [
"## Highlights",
"## Installation and platform support",
"## Validation",
"## Upgrade notes and known limits",
"## Full change history",
]
missing = [section for section in required_sections if section not in notes]
if missing:
raise SystemExit(
"GitHub Release notes are incomplete: " + ", ".join(missing)
)
print(f"validated version {tag_version} and {notes_path.relative_to(root)}")
PY

- name: Build package (pure-Python wheel and sdist)
env:
STATGPU_NO_EXT: "1"
run: python -m build
run: |
rm -rf build dist *.egg-info statgpu.egg-info
python -m build

- name: Check distributions
run: |
python -m twine check dist/*
ls -l dist/
ls -lh dist/

- name: Smoke-install release wheel
run: |
WHEEL="$(realpath dist/*.whl)"
python -m venv "$RUNNER_TEMP/statgpu-publish-smoke"
"$RUNNER_TEMP/statgpu-publish-smoke/bin/python" -m pip install --upgrade pip
"$RUNNER_TEMP/statgpu-publish-smoke/bin/python" -m pip install "$WHEEL"
cd "$RUNNER_TEMP"
"$RUNNER_TEMP/statgpu-publish-smoke/bin/python" - <<'PY'
import os
import statgpu
from statgpu.linear_model import LinearRegression
from statgpu.survival import CoxPH, CoxPHCV

expected = os.environ["GITHUB_REF_NAME"][1:]
assert statgpu.__version__ == expected
assert LinearRegression is not None
assert CoxPH is not None
assert CoxPHCV is not None
print(statgpu.__version__)
PY

- name: Retain validated release artifacts
uses: actions/upload-artifact@v4
with:
name: statgpu-tagged-release-distributions
path: dist/*
if-no-files-found: error
retention-days: 14

- name: Publish to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
run: python -m twine upload --non-interactive dist/*

github-release:
name: Publish GitHub Release
needs: publish
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4

- name: Download validated release artifacts
uses: actions/download-artifact@v4
with:
name: statgpu-tagged-release-distributions
path: dist

- name: Create or update GitHub Release from versioned notes
env:
GH_TOKEN: ${{ github.token }}
run: |
TAG="$GITHUB_REF_NAME"
VERSION="${TAG#v}"
NOTES_FILE=".github/releases/${TAG}.md"
TITLE="statgpu ${VERSION}"

test -f "$NOTES_FILE"
if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
gh release edit "$TAG" \
--repo "$GITHUB_REPOSITORY" \
--title "$TITLE" \
--notes-file "$NOTES_FILE"
gh release upload "$TAG" dist/* \
--repo "$GITHUB_REPOSITORY" \
--clobber
else
gh release create "$TAG" dist/* \
--repo "$GITHUB_REPOSITORY" \
--verify-tag \
--title "$TITLE" \
--notes-file "$NOTES_FILE"
fi
Loading
Loading