sonic-spatial 1.0.0rc1__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 (54) hide show
  1. sonic_spatial-1.0.0rc1/.github/workflows/release.yml +80 -0
  2. sonic_spatial-1.0.0rc1/.gitignore +80 -0
  3. sonic_spatial-1.0.0rc1/.readthedocs.yaml +20 -0
  4. sonic_spatial-1.0.0rc1/CHANGELOG.md +292 -0
  5. sonic_spatial-1.0.0rc1/LICENSE +29 -0
  6. sonic_spatial-1.0.0rc1/MANIFEST.in +9 -0
  7. sonic_spatial-1.0.0rc1/PKG-INFO +321 -0
  8. sonic_spatial-1.0.0rc1/README.md +264 -0
  9. sonic_spatial-1.0.0rc1/compat/quadsv/README.md +14 -0
  10. sonic_spatial-1.0.0rc1/compat/quadsv/pyproject.toml +24 -0
  11. sonic_spatial-1.0.0rc1/compat/quadsv/src/quadsv/__init__.py +50 -0
  12. sonic_spatial-1.0.0rc1/docs/Makefile +25 -0
  13. sonic_spatial-1.0.0rc1/docs/changelog.rst +5 -0
  14. sonic_spatial-1.0.0rc1/docs/conf.py +158 -0
  15. sonic_spatial-1.0.0rc1/docs/guides/faq.rst +113 -0
  16. sonic_spatial-1.0.0rc1/docs/guides/installation.rst +69 -0
  17. sonic_spatial-1.0.0rc1/docs/guides/kernels.rst +401 -0
  18. sonic_spatial-1.0.0rc1/docs/guides/multisample.rst +324 -0
  19. sonic_spatial-1.0.0rc1/docs/guides/quickstart.rst +352 -0
  20. sonic_spatial-1.0.0rc1/docs/guides/scaling.rst +319 -0
  21. sonic_spatial-1.0.0rc1/docs/guides/theory.rst +211 -0
  22. sonic_spatial-1.0.0rc1/docs/index.rst +119 -0
  23. sonic_spatial-1.0.0rc1/pyproject.toml +182 -0
  24. sonic_spatial-1.0.0rc1/setup.cfg +4 -0
  25. sonic_spatial-1.0.0rc1/setup.py +8 -0
  26. sonic_spatial-1.0.0rc1/src/sonic/__init__.py +73 -0
  27. sonic_spatial-1.0.0rc1/src/sonic/_rasterize.py +95 -0
  28. sonic_spatial-1.0.0rc1/src/sonic/_version.py +24 -0
  29. sonic_spatial-1.0.0rc1/src/sonic/api.py +182 -0
  30. sonic_spatial-1.0.0rc1/src/sonic/comparators/__init__.py +33 -0
  31. sonic_spatial-1.0.0rc1/src/sonic/comparators/base.py +1323 -0
  32. sonic_spatial-1.0.0rc1/src/sonic/comparators/features.py +1250 -0
  33. sonic_spatial-1.0.0rc1/src/sonic/comparators/grid.py +588 -0
  34. sonic_spatial-1.0.0rc1/src/sonic/comparators/irregular.py +600 -0
  35. sonic_spatial-1.0.0rc1/src/sonic/comparators/multisample.py +1669 -0
  36. sonic_spatial-1.0.0rc1/src/sonic/comparators/normalization.py +375 -0
  37. sonic_spatial-1.0.0rc1/src/sonic/detectors/__init__.py +18 -0
  38. sonic_spatial-1.0.0rc1/src/sonic/detectors/base.py +116 -0
  39. sonic_spatial-1.0.0rc1/src/sonic/detectors/grid.py +680 -0
  40. sonic_spatial-1.0.0rc1/src/sonic/detectors/irregular.py +1415 -0
  41. sonic_spatial-1.0.0rc1/src/sonic/kernels/__init__.py +35 -0
  42. sonic_spatial-1.0.0rc1/src/sonic/kernels/base.py +902 -0
  43. sonic_spatial-1.0.0rc1/src/sonic/kernels/fft.py +1067 -0
  44. sonic_spatial-1.0.0rc1/src/sonic/kernels/matrix.py +450 -0
  45. sonic_spatial-1.0.0rc1/src/sonic/kernels/nufft.py +1482 -0
  46. sonic_spatial-1.0.0rc1/src/sonic/statistics.py +1369 -0
  47. sonic_spatial-1.0.0rc1/src/sonic/utils.py +491 -0
  48. sonic_spatial-1.0.0rc1/src/sonic_spatial.egg-info/PKG-INFO +321 -0
  49. sonic_spatial-1.0.0rc1/src/sonic_spatial.egg-info/SOURCES.txt +52 -0
  50. sonic_spatial-1.0.0rc1/src/sonic_spatial.egg-info/dependency_links.txt +1 -0
  51. sonic_spatial-1.0.0rc1/src/sonic_spatial.egg-info/requires.txt +29 -0
  52. sonic_spatial-1.0.0rc1/src/sonic_spatial.egg-info/scm_file_list.json +66 -0
  53. sonic_spatial-1.0.0rc1/src/sonic_spatial.egg-info/scm_version.json +8 -0
  54. sonic_spatial-1.0.0rc1/src/sonic_spatial.egg-info/top_level.txt +1 -0
@@ -0,0 +1,80 @@
1
+ name: Release
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ jobs:
11
+ build:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ with:
16
+ fetch-depth: 0
17
+ - uses: actions/setup-python@v5
18
+ with:
19
+ python-version: "3.12"
20
+ - run: python -m pip install build twine
21
+ - run: python -m build
22
+ - run: python -m build compat/quadsv --outdir compat-dist
23
+ - run: python -m twine check dist/* compat-dist/*
24
+ - name: Verify release version and wheel ownership
25
+ env:
26
+ RELEASE_TAG: ${{ github.event.release.tag_name }}
27
+ run: |
28
+ case "$RELEASE_TAG" in
29
+ v*) version="${RELEASE_TAG#v}" ;;
30
+ *) echo "Release tag must start with v"; exit 1 ;;
31
+ esac
32
+ sonic_wheel="dist/sonic_spatial-${version}-py3-none-any.whl"
33
+ quadsv_wheel="compat-dist/quadsv-${version}-py3-none-any.whl"
34
+ test -f "$sonic_wheel"
35
+ test -f "$quadsv_wheel"
36
+ unzip -Z1 "$sonic_wheel" | grep -qx 'sonic/__init__.py'
37
+ ! unzip -Z1 "$sonic_wheel" | grep -q '^quadsv/'
38
+ unzip -Z1 "$quadsv_wheel" | grep -qx 'quadsv/__init__.py'
39
+ unzip -p "$quadsv_wheel" 'quadsv-*.dist-info/METADATA' |
40
+ grep -Eq '^Requires-Dist: sonic-spatial(<2,>=1\.0\.0rc1|>=1\.0\.0rc1,<2)$'
41
+ - uses: actions/upload-artifact@v4
42
+ with:
43
+ name: sonic-dist
44
+ path: dist/
45
+ - uses: actions/upload-artifact@v4
46
+ with:
47
+ name: quadsv-dist
48
+ path: compat-dist/
49
+
50
+ publish-sonic:
51
+ needs: build
52
+ runs-on: ubuntu-latest
53
+ environment: pypi
54
+ permissions:
55
+ id-token: write
56
+ steps:
57
+ - uses: actions/download-artifact@v4
58
+ with:
59
+ name: sonic-dist
60
+ path: dist/
61
+ - name: Publish SONIC
62
+ uses: pypa/gh-action-pypi-publish@release/v1
63
+ with:
64
+ packages-dir: dist
65
+
66
+ publish-quadsv:
67
+ needs: [build, publish-sonic]
68
+ runs-on: ubuntu-latest
69
+ environment: pypi
70
+ permissions:
71
+ id-token: write
72
+ steps:
73
+ - uses: actions/download-artifact@v4
74
+ with:
75
+ name: quadsv-dist
76
+ path: compat-dist/
77
+ - name: Publish QuadSV compatibility package
78
+ uses: pypa/gh-action-pypi-publish@release/v1
79
+ with:
80
+ packages-dir: compat-dist
@@ -0,0 +1,80 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ pip-wheel-metadata/
20
+ share/python-wheels/
21
+ *.egg-info/
22
+ .installed.cfg
23
+ *.egg
24
+ MANIFEST
25
+
26
+ # Virtual environments
27
+ venv/
28
+ ENV/
29
+ env/
30
+ .venv
31
+
32
+ # Jupyter Notebook
33
+ .ipynb_checkpoints
34
+
35
+ # PyCharm
36
+ .idea/
37
+
38
+ # VSCode
39
+ .vscode/
40
+
41
+ # Testing
42
+ .pytest_cache/
43
+ .coverage
44
+ htmlcov/
45
+ .tox/
46
+ .nox/
47
+ .hypothesis/
48
+
49
+ # Data + analysis outputs (project-root-anchored so nested packages aren't ignored)
50
+ /data/
51
+ /results/
52
+ *.h5ad
53
+ *.csv
54
+ figures/
55
+ *.png
56
+ *.jpg
57
+ *.pdf
58
+
59
+ # OS
60
+ .DS_Store
61
+ Thumbs.db
62
+
63
+ # Misc
64
+ *.log
65
+ .cache/
66
+
67
+ # copilot
68
+ .github/copilot-instructions.md
69
+
70
+ # Sphinx documentation build
71
+ docs/_build/
72
+ docs/_static/
73
+ docs/_templates/
74
+
75
+ # setuptools-scm generated version file (see [tool.setuptools_scm] in pyproject.toml)
76
+ src/sonic/_version.py
77
+
78
+ # claude code
79
+ CLAUDE.md
80
+ .claude/
@@ -0,0 +1,20 @@
1
+ # Read the Docs configuration file.
2
+ # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details.
3
+
4
+ version: 2
5
+
6
+ build:
7
+ os: ubuntu-24.04
8
+ tools:
9
+ python: "3.12"
10
+
11
+ sphinx:
12
+ configuration: docs/conf.py
13
+ fail_on_warning: true
14
+
15
+ python:
16
+ install:
17
+ - method: pip
18
+ path: .
19
+ extra_requirements:
20
+ - docs
@@ -0,0 +1,292 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [1.0.0rc1] - 2026-08-29
11
+
12
+ ### Changed
13
+ - **QuadSV is now SONIC** (Spatial Organization through Nonrandom-pattern
14
+ Inference and Comparison). The canonical distribution is `sonic-spatial`
15
+ and the import package is `sonic`; the `quadsv` distribution and import
16
+ namespace remain as a compatibility bridge for existing users.
17
+
18
+ ### Added
19
+ - **GLM design API for cross-sample pattern comparison.** New public
20
+ `compare_glm(spectra, design, contrast, …)` generalises the
21
+ two-group test to arbitrary OLS designs (binary, continuous,
22
+ multi-factor) with an analytic Wald test. The two-group case is
23
+ recovered exactly. `Comparator.test_diff_freq(...)` gains a
24
+ `contrast=` argument (column name, dict, or contrast vector).
25
+ - **Analytic null for `log_l2`** (`null="analytic"`) on
26
+ `compare_two_groups`, `compare_two_groups_masked`, and
27
+ `compare_glm`. Per-gene statistic is integrated via Liu's
28
+ approximation against a pooled-across-genes **full** within-group
29
+ Σ (a single 30×30 eigendecomposition before each Liu integration);
30
+ bypasses the small-n permutation BH-floor while keeping mean
31
+ within-group null FPR at ~0.012 across the three benchmark panels.
32
+ Emits a `UserWarning` at residual df < 3. The masked variant uses
33
+ a mask-aware pooled estimator with per-gene noncentrality scaling
34
+ so genes with different observed cohorts get correctly-scaled
35
+ eigenvalues.
36
+ - **Analytic Welch t tests for `compare_two_groups_scalar`**.
37
+ Computes per-gene two-sided p-values from the
38
+ Welch-Satterthwaite t-distribution; the scalar DE companion uses
39
+ this fixed analytic null rather than exposing a `null=` selector.
40
+ - **`normalize_shape: bool = False` keyword** on every spectrum-input
41
+ comparison test (`compare_two_groups`, `compare_two_groups_masked`,
42
+ `compare_glm`). When True, divides each per-(sample, gene)
43
+ spectrum by its sum along the frequency axis before the statistic
44
+ is computed, so the test fires only on shape-only redistribution
45
+ of power across radial frequencies. Statistic-agnostic; default
46
+ False preserves prior behaviour.
47
+ - **Comparator null-covariance and effective-rank diagnostics**:
48
+ `Comparator.effective_rank(weights=None)` for per-sample heterogeneity
49
+ in the gene spectrum cross-frequency covariance;
50
+ `Comparator.estimate_null_covariance(design, contrast=...)`for the
51
+ pooled log-spectrum covariance, weighted covariance, scaled Liu
52
+ eigenvalues, effective rank, residual df, and masked-path eligibility
53
+ metadata used by `test_diff_freq(..., statistic="log_l2", null="analytic")`.
54
+ - **Top-level convenience exports**:
55
+ `sonic.Detector(data, …)` and `sonic.Comparator(data_list, …)`
56
+ factories that dispatch on `AnnData` vs `SpatialData`;
57
+ `sonic.compute_null_params`, `sonic.auto_chunk_size`,
58
+ `sonic.liu_sf` promoted to top level (canonical
59
+ `sonic.statistics` paths still work).
60
+ - **Public-API freeze test** (`tests/test_public_api.py`) snapshots
61
+ `__all__`, docstring presence, canonical-path identity, and
62
+ asserts removed legacy paths raise `ModuleNotFoundError`.
63
+ - **Convenience input modes for `Comparator.normalize_covariates`.**
64
+ In addition to the existing per-sample
65
+ `Sequence[np.ndarray]` of pre-rasterized
66
+ `(n_covariates, ny, nx)` images, the method now accepts a shared
67
+ `Sequence[str]` of column names; the subclass interprets it
68
+ natively:
69
+
70
+ * `ComparatorIrregular` looks each key up in `adata.obs.columns`
71
+ first, then `adata.var_names` (preferring obs on collision);
72
+ the resolved per-spot vector is NUFFTed directly onto the
73
+ sample's k-grid — so the same call accepts deconvolved
74
+ cell-type proportion columns *and* per-gene expression columns
75
+ (e.g., a housekeeping or marker gene) interchangeably.
76
+ * `ComparatorGrid` forwards the keys as `value_key=` to
77
+ `spatialdata.rasterize_bins`, so any combination of `.obs`
78
+ columns and `var_names` in the comparator's table works.
79
+
80
+ Mode is detected from the first element's type. Both paths reduce
81
+ to the same `(n_covariates, K)` per-sample features fed into the
82
+ log-space residualization, so the math is identical — only the
83
+ input boilerplate is different.
84
+
85
+ ### Changed
86
+ - **Breaking: package layout migrated to `src/sonic/`** with the
87
+ four conceptual layers as physical subpackages —
88
+ `sonic.kernels.{fft,nufft}`,
89
+ `sonic.detectors.{base,irregular,grid}`,
90
+ `sonic.comparators.{__init__,multisample}`. `import sonic` and
91
+ `from sonic import …` keep working; editable installs must be
92
+ reissued (`pip install -e ".[dev]"`). Lint / format commands now
93
+ target `src/ tests/`.
94
+ - **Breaking: unified `normalize_*` surface API in
95
+ `sonic.comparators.multisample`** (no aliases):
96
+ * `normalize_by_background` → `normalize_background`
97
+ * `residualize_against_covariates` → `normalize_covariates`
98
+ * `shape_normalize` → `normalize_shape`
99
+ Consistent first-arg `spectra`, keyword-only after, `eps=1e-12`
100
+ default on every helper, and NumPy-style docstrings with LaTeX
101
+ math. `normalize_covariates`'s first positional arg is renamed
102
+ `gene_spectra` → `spectra`, and its implementation now operates in
103
+ **log-space**: it residualises `log(spectra + ε)` against
104
+ `[1, log(C^T + ε)]` and exponentiates, so the output stays strictly
105
+ positive and composes cleanly with downstream `log_l2` tests.
106
+ Log-space `normalize_covariates` also **commutes exactly** with
107
+ `normalize_background` (left- vs right-multiplication of the
108
+ log-spectrum matrix by orthogonal-projection matrices on disjoint
109
+ axes), so the two can be applied in either order. The remaining
110
+ chainable comparator instance method follows the rename:
111
+ * `.residualize()` → `.normalize_covariates()`
112
+ - **Breaking: `Comparator.fit()` renamed to `Comparator.compute_spectra()`**.
113
+ The method computes per-sample radial-binned power spectra rather than
114
+ fitting model parameters; the new name describes the operation
115
+ directly and matches the codebase's verb-first method convention. All
116
+ three keyword arguments (`n_jobs`, `landmark_genes`, `progress`) and
117
+ the chainable `return self` behaviour are unchanged.
118
+ - **Breaking: `design` moved from Comparator constructor to test
119
+ time.** The cross-sample contrast is no longer a construction
120
+ argument — it is supplied directly to `.test_diff_freq(design, ...)`
121
+ / `.test_diff_expr(design, ...)` (positional first arg). A single fitted comparator can now
122
+ serve any number of unrelated contrasts on the same `spectra_`
123
+ without recomputing per-sample spectra. `min_samples_per_group`
124
+ follows `design` to `test_diff_freq` (kwarg) since it's a property
125
+ of the design's group sizes, not of the spectra. `design` accepts
126
+ the same three forms as before:
127
+ * 1-D array / Series of binary labels → two-sample dispatch
128
+ (`compare_two_groups` / `compare_two_groups_masked`);
129
+ * 2-D `np.ndarray` of shape `(n_samples, p)` → GLM design matrix,
130
+ used verbatim by `compare_glm`;
131
+ * `pandas.DataFrame` → GLM design, patsy-encoded by
132
+ `compare_glm`.
133
+ - **Breaking: default `null` switched from `"permutation"` to
134
+ `"analytic"` across the spectral comparison surface** —
135
+ `Comparator.test_diff_freq`,
136
+ `sonic.comparators.multisample.compare_two_groups`, and
137
+ `sonic.comparators.multisample.compare_two_groups_masked`. The
138
+ analytic Wald test (Liu mixture-χ² null) bypasses the small-n permutation
139
+ BH-floor and is the only path that works on every dispatch target
140
+ (binary permutation/analytic + GLM analytic), so it makes a single sensible
141
+ package-wide default. Callers who want the permutation null must
142
+ now pass `null="permutation"` explicitly. As a related
143
+ ergonomic fix, `compare_two_groups{,_masked}(statistic="welch_t_cauchy",
144
+ null="analytic")` no longer raises — `welch_t_cauchy` carries its own
145
+ analytic null (documented as ignoring the `null` kwarg) so the
146
+ package default `null="analytic"` is treated as a no-op for that
147
+ statistic.
148
+ - **Breaking: statistical-test naming cleanup** in
149
+ `sonic.comparators.multisample` and the corresponding
150
+ `Comparator.test_diff_*` methods:
151
+ * **`compare_designs` → `compare_glm`.** The plural form was
152
+ awkward (one design per call); `compare_glm` names the test
153
+ family at the call site and parallels the binary
154
+ `compare_two_groups` cleanly.
155
+ * **Statistic `"cauchy_welch"` → `"welch_t_cauchy"`.** The new
156
+ token reads in pipeline order (per-bin Welch t first, gene-level
157
+ Cauchy combination second) and disambiguates from naming the
158
+ gene-level aggregator alone.
159
+ * **Scalar DE `null=` selector retired** on
160
+ `compare_two_groups_scalar` / `Comparator.test_diff_expr`.
161
+ Scalar DE now always uses the analytic Welch-Satterthwaite
162
+ t-distribution null.
163
+ * **`null="liu"` alias retired.** The `liu` token referred to
164
+ the numerical algorithm used to integrate the analytic χ² mixture
165
+ tail (see `sonic.statistics.liu_sf`), not a separate
166
+ statistical concept. Single canonical token: `analytic`.
167
+ - **Breaking: Comparator attribute surface narrowed** (sklearn-style
168
+ moderate-privacy convention). The public surface is now `samples`,
169
+ `gene_names`, `feature_mode`, `freq_edges`, plus the
170
+ trailing-underscore fitted attributes (`spectra_`, `dc_`,
171
+ `presence_`, `rotation_angles_`). `design`/`groups_` are no longer
172
+ carried as instance state — the comparator is design-agnostic.
173
+ Internal config knobs that were inadvertently public are now
174
+ single-underscore-prefixed: `_n_radial_bins`, `_fft_solver`,
175
+ `_workers`, `_presence_threshold`, `_spacings`, `_grid_shapes`,
176
+ `_spectrum_fft_solver`, `_fft_chunk_size`, `_spacing_override`,
177
+ `_bins`, `_table_name`, `_col_key`, `_row_key`, `_value_key`.
178
+ - **Breaking: Comparator test methods renamed and aligned with the
179
+ standalone `compare_*` API** in `sonic.comparators.multisample`:
180
+ * `.test_pattern()` → `.test_diff_freq()` — gains a new
181
+ `normalize_shape: bool = False` keyword, forwarded to its
182
+ dispatch target (`compare_two_groups`,
183
+ `compare_two_groups_masked`, or `compare_glm`) so users get
184
+ the shape-only DF path without mutating `cmp.spectra_`.
185
+ * `.test_expression()` → `.test_diff_expr()` — uses analytic
186
+ t-distribution tests for scalar DE and supports both binary
187
+ two-group and GLM contrast designs.
188
+
189
+ ### Removed
190
+ - **Breaking: `groups=` / `design=` constructor kwargs on
191
+ `ComparatorIrregular` and `ComparatorGrid` are gone.** Supply the
192
+ 1-D labels or design matrix to the test method instead
193
+ (`cmp.test_diff_freq(design, ...)`, `cmp.test_diff_expr(design,
194
+ ...)`). The comparator no longer carries design state; one fitted
195
+ comparator can serve any number of contrasts on the same spectra.
196
+ - **Breaking: `Comparator.shape_normalize()` chainable method
197
+ retired.** Use the equivalent
198
+ `cmp.test_diff_freq(..., normalize_shape=True)` keyword path for the
199
+ one-shot non-destructive test, or call
200
+ `sonic.comparators.multisample.normalize_shape(cmp.spectra_)`
201
+ directly to obtain the standalone transform. The previous in-place
202
+ method silently mutated `cmp.spectra_` and surprised subsequent
203
+ `.test_diff_freq()`/`.test_diff_expr()` calls on the same comparator.
204
+ - **Breaking: the `test = test_pattern` alias retired.** Use the
205
+ explicit `cmp.test_diff_freq(...)` (or `cmp.test_diff_expr(...)` for
206
+ the DE companion); the unqualified `cmp.test()` was ambiguous once
207
+ the API exposed two complementary tests.
208
+ - **Breaking: `center` argument retired** across the comparator API.
209
+ `ComparatorIrregular`, `ComparatorGrid`, and
210
+ `compute_sample_spectrum` no longer accept `center`. Per-gene
211
+ mean centring (the previous default) is now the only spectrum
212
+ normalisation path. The `_ZSCORE_CLIP` constant, the
213
+ `zscore_clip` parameter, and the per-bin clamp in the NUFFT loop
214
+ are deleted (~50 LOC).
215
+ - **Breaking: `benchmark_statistics` function and the matching
216
+ `Comparator.benchmark()` method retired.** Invoke
217
+ `compare_two_groups` directly with each `statistic=` value to A/B
218
+ compare on the same fitted spectra (~95 LOC).
219
+ - **Breaking: `statistic="hotelling_lw"` and `statistic="mmd_rbf"`
220
+ paths retired** from every comparison function. Both were
221
+ impractically slow and consistently dominated on sensitivity by
222
+ `log_l2 + null='analytic'` or `welch_t_cauchy`. `_AVAILABLE_STATISTICS`
223
+ now reads `("log_l2", "welch_t_cauchy")`.
224
+ - **Breaking: six legacy-path shim modules removed** —
225
+ `sonic.fft`, `sonic.nufft`, `sonic.detector`,
226
+ `sonic.detector_grid`, `sonic._detector_base`,
227
+ `sonic.multisample`. Use the canonical subpackage paths.
228
+ - **Breaking: backend ABCs `Kernel` and `MatrixKernelBase` no
229
+ longer re-exported from top-level `sonic`**. They live at
230
+ `sonic.kernels` and are intended for backend authors.
231
+
232
+ ### Fixed
233
+ - CI workflow install step referenced non-existent extras
234
+ (`[dev,test,spatial]` and `[docs,spatial]`); narrowed to the
235
+ actual `[dev]` / `[docs]` extras in `pyproject.toml`.
236
+
237
+ ## Release Process
238
+
239
+ The immediate release is the real prerelease `1.0.0rc1`. The thorough
240
+ documentation rewrite remains deferred to the final `1.0.0`; until the RC is
241
+ tagged, keep its changes under `Unreleased`.
242
+
243
+ - [x] Review the current installation, migration, and release notes for
244
+ accuracy; the full documentation rewrite is not an RC blocker.
245
+ - [x] Run the full test suite: `pytest tests/ --cov=sonic`.
246
+ - [x] Run lint checks: `ruff check src tests compat/quadsv/src`.
247
+ - [x] Build the documentation without warnings:
248
+ `sphinx-build -W -b html docs/ docs/_build/`.
249
+ - [x] Add the pending `sonic-spatial` publisher with owner `JiayuSuPKU`,
250
+ repository `sonic`, workflow `release.yml`, and environment `pypi`.
251
+ - [x] Add the additional `quadsv` publisher with the same identity. Keep the
252
+ existing QuadSV credential until this release succeeds.
253
+ - [x] Update the compatibility requirement to
254
+ `sonic-spatial>=1.0.0rc1,<2` and make
255
+ the compatibility wheel, rather than the SONIC wheel, own the deprecated
256
+ `quadsv` import namespace.
257
+ - [x] Confirm the new Read the Docs project builds successfully and legacy
258
+ documentation links remain available or redirect.
259
+ - [x] Add a dated `1.0.0rc1` section below `Unreleased`, leaving a new empty
260
+ `Unreleased` section for future changes.
261
+ - [ ] Commit the release preparation and create an annotated tag:
262
+ `git tag -a v1.0.0rc1 -m "SONIC 1.0.0rc1"`.
263
+ - [x] Build both distributions: `python -m build` and
264
+ `python -m build compat/quadsv --outdir compat-dist`.
265
+ - [x] Verify their metadata:
266
+ `python -m twine check dist/* compat-dist/*`.
267
+ - [ ] Push `v1.0.0rc1`, then publish its GitHub prerelease to trigger trusted
268
+ publishing of SONIC followed by the QuadSV compatibility package.
269
+ - [ ] Verify clean installations of `sonic-spatial==1.0.0rc1` and
270
+ `quadsv==1.0.0rc1` from PyPI, then remove obsolete QuadSV credentials.
271
+
272
+ ## [0.1.0] - 2026-02-02
273
+
274
+ ### Added
275
+ - Initial public release
276
+ - Q-test framework for univariate spatial pattern detection
277
+ - R-test framework for bivariate spatial co-expression
278
+ - Core kernel methods: Gaussian, Matérn, CAR, Graph Laplacian, Moran's I
279
+ - Implicit mode for scalable large-N computation (N > 5000)
280
+ - FFT acceleration for regular grid data (Visium HD)
281
+ - `DetectorIrregular` for AnnData integration (genome-wide SVG detection)
282
+ - `DetectorGrid` for large-scale Visium HD analysis
283
+ - Null approximation methods: CLT, Welch/Satterthwaite, Liu
284
+ - Comprehensive test suite (unit + integration tests)
285
+ - Tutorial test cases demonstrating all major workflows
286
+ - Complete documentation with quickstart and theory sections
287
+ - Support for Python 3.10, 3.11, 3.12
288
+
289
+ ## [0.1.1]
290
+
291
+ ### Fixed
292
+ - Fix type hinting issues in `quadsv.kernels` module
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Jiayu Su
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,9 @@
1
+ # Manifest for package distribution
2
+ include README.md
3
+ include LICENSE
4
+ include pyproject.toml
5
+ recursive-include src/sonic *.py
6
+ recursive-exclude tests *
7
+ recursive-exclude data *
8
+ recursive-exclude figures *
9
+ exclude *.ipynb