structboost 0.2.0__tar.gz → 0.4.0__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 (43) hide show
  1. {structboost-0.2.0 → structboost-0.4.0}/CHANGELOG.md +116 -0
  2. {structboost-0.2.0 → structboost-0.4.0}/CITATION.cff +2 -2
  3. {structboost-0.2.0 → structboost-0.4.0}/PKG-INFO +3 -3
  4. {structboost-0.2.0 → structboost-0.4.0}/README.md +1 -1
  5. {structboost-0.2.0 → structboost-0.4.0}/pyproject.toml +1 -1
  6. {structboost-0.2.0 → structboost-0.4.0}/src/structboost/_decoder.py +0 -6
  7. {structboost-0.2.0 → structboost-0.4.0}/src/structboost/_model.py +174 -464
  8. {structboost-0.2.0 → structboost-0.4.0}/src/structboost/_persistence.py +12 -6
  9. {structboost-0.2.0 → structboost-0.4.0}/src/structboost/_types.py +29 -46
  10. {structboost-0.2.0 → structboost-0.4.0}/src/structboost/_utils.py +10 -12
  11. {structboost-0.2.0 → structboost-0.4.0}/tests/test_bae.py +66 -105
  12. {structboost-0.2.0 → structboost-0.4.0}/tests/test_bae_diagnostics.py +1 -9
  13. {structboost-0.2.0 → structboost-0.4.0}/tests/test_bae_layer.py +1 -1
  14. {structboost-0.2.0 → structboost-0.4.0}/tests/test_bae_persistence.py +0 -13
  15. {structboost-0.2.0 → structboost-0.4.0}/tests/test_bae_transfer.py +4 -12
  16. {structboost-0.2.0 → structboost-0.4.0}/tests/test_covcache_precompute.py +1 -1
  17. {structboost-0.2.0 → structboost-0.4.0}/tests/test_rng_isolation.py +1 -1
  18. {structboost-0.2.0 → structboost-0.4.0}/tests/test_stability.py +114 -148
  19. {structboost-0.2.0 → structboost-0.4.0}/.gitignore +0 -0
  20. {structboost-0.2.0 → structboost-0.4.0}/CODE_OF_CONDUCT.md +0 -0
  21. {structboost-0.2.0 → structboost-0.4.0}/CONTRIBUTING.md +0 -0
  22. {structboost-0.2.0 → structboost-0.4.0}/LICENSE +0 -0
  23. {structboost-0.2.0 → structboost-0.4.0}/conftest.py +0 -0
  24. {structboost-0.2.0 → structboost-0.4.0}/src/structboost/__init__.py +0 -0
  25. {structboost-0.2.0 → structboost-0.4.0}/src/structboost/_annotation.py +0 -0
  26. {structboost-0.2.0 → structboost-0.4.0}/src/structboost/_boosting.py +0 -0
  27. {structboost-0.2.0 → structboost-0.4.0}/src/structboost/_encoder.py +0 -0
  28. {structboost-0.2.0 → structboost-0.4.0}/src/structboost/_explorer.py +0 -0
  29. {structboost-0.2.0 → structboost-0.4.0}/src/structboost/_io.py +0 -0
  30. {structboost-0.2.0 → structboost-0.4.0}/src/structboost/_plotting.py +0 -0
  31. {structboost-0.2.0 → structboost-0.4.0}/src/structboost/_simulation.py +0 -0
  32. {structboost-0.2.0 → structboost-0.4.0}/src/structboost/_stability.py +0 -0
  33. {structboost-0.2.0 → structboost-0.4.0}/src/structboost/py.typed +0 -0
  34. {structboost-0.2.0 → structboost-0.4.0}/tests/test_allboost.py +0 -0
  35. {structboost-0.2.0 → structboost-0.4.0}/tests/test_annotation.py +0 -0
  36. {structboost-0.2.0 → structboost-0.4.0}/tests/test_bae_init.py +0 -0
  37. {structboost-0.2.0 → structboost-0.4.0}/tests/test_batch_integration.py +0 -0
  38. {structboost-0.2.0 → structboost-0.4.0}/tests/test_explorer.py +0 -0
  39. {structboost-0.2.0 → structboost-0.4.0}/tests/test_linear_ceiling.py +0 -0
  40. {structboost-0.2.0 → structboost-0.4.0}/tests/test_obs_encoding.py +0 -0
  41. {structboost-0.2.0 → structboost-0.4.0}/tests/test_public_api.py +0 -0
  42. {structboost-0.2.0 → structboost-0.4.0}/tests/test_resolve_mandatory.py +0 -0
  43. {structboost-0.2.0 → structboost-0.4.0}/tests/test_simulation.py +0 -0
@@ -3,6 +3,122 @@
3
3
  Releases follow [semantic versioning](https://semver.org). While the project is
4
4
  pre-1.0, a minor bump may break API.
5
5
 
6
+ ### [0.4.0] - 2026-08-12
7
+
8
+ Three defaults change. No configuration fields are added or removed, and anyone
9
+ who sets these explicitly is unaffected.
10
+
11
+ **`enable_early_stopping` now defaults to `False`.** The criterion is a
12
+ convergence check being used as a quality check. There is no validation split, so
13
+ the training loss cannot see a model that is starting to memorize, and patience
14
+ fires long before the model is done: measured against ground truth it stopped at
15
+ iteration 90 on a dataset peaking at 259, and at 196 on a simulated scenario
16
+ peaking at 560, returning marker-recovery F1 0.502 against 0.787. Across three
17
+ real datasets (mouse cortex, human pancreas, human immune) the best iteration
18
+ ranged from 154 to 1975 — always past where patience fires.
19
+
20
+ **No stopping rule replaced it, deliberately.** Latent stability, encoder-support
21
+ overlap and held-out reconstruction were each measured as candidates and each
22
+ rejected. The representation settles long before gene selection does — on one
23
+ dataset consecutive latent codes were rank-identical while the gene set still
24
+ turned over 65% cumulatively — and no observable signal tracks the quality peak.
25
+ A latent-stability rule was built and tuned; it fired at iteration ~109 on all
26
+ three real datasets regardless of where quality peaked, and on one of them it was
27
+ worse than not stopping at all. It is not shipped, not even off by default: an
28
+ option that should never be enabled is pure carrying cost, which is the same
29
+ argument that removed four fields in 0.3.0. `max_iterations=1000` is a defensible
30
+ middle of the measured range, not an optimum.
31
+
32
+ **`BAE.stability_selection(threshold=...)` now defaults to `0.5`, from `0.7`.**
33
+ 0.7 is too aggressive whenever the latent representation is still moving: across
34
+ three real datasets it removed 21-52% of recovered marker genes relative to the
35
+ fitted encoder, and 0-7% even when the representation had settled. At 0.5 the
36
+ worst loss over the same six runs was 6%. The standalone
37
+ `structboost.stability_selection` keeps 0.7, because its Meinshausen-Buhlmann
38
+ bound is undefined at or below 0.5.
39
+
40
+ **The `dim_match_quality` warning now fires below 0.85, from 0.5.** It is the gate
41
+ on whether iteration frequencies mean anything: they describe gene-set drift only
42
+ if the counted iterations describe one representation. Runs sitting at 0.70-0.79 —
43
+ comfortably above the old warning — already lost a quarter to a half of their
44
+ recovered markers at the default threshold, while runs at 0.93 and above lost
45
+ none. The guide now documents it as a lookup: at least 0.93, either threshold is
46
+ safe; 0.70-0.79, use 0.3-0.5 and prefer the flat union.
47
+
48
+ Also fixed: the gene-selection guide still showed
49
+ `fit(adata, stability_selection="iteration")`, which stopped being valid in 0.3.0
50
+ when that argument became a bool.
51
+
52
+ ### [0.3.0] - 2026-08-11
53
+
54
+ **Breaking.** Five settings are gone and `BAE.stability_selection` has one mode
55
+ instead of two. Every removed option was off by default, so a fit that took the
56
+ defaults is unaffected: the encoder matrix is bitwise identical across a plain
57
+ fit, a batch-integrated fit, and both disentanglement methods. What changes is the
58
+ surface you have to reason about.
59
+
60
+ `boosting_nu` stays at `0.1`. Raising it to `0.3` was measured and deferred: at
61
+ the default `stepno=50` it matches `0.1`'s marker recovery three to four times
62
+ faster and wins outright on low-signal data, but then degrades if training
63
+ continues, and the training-MSE stopping rule cannot see it happening. The guide
64
+ records the numbers; the two changes belong together and will land together.
65
+
66
+ **Checkpoints written by 0.2.0 no longer load.** `restore_payload` splats the
67
+ stored config into `BAEConfig`, so a dropped field is an unexpected keyword
68
+ argument rather than a missing one. The format is bumped to 5 and the loader
69
+ refuses 4 by name. No migration is written: the method is under active
70
+ development and a checkpoint is cheap to regenerate, whereas a compatibility
71
+ shim for options that no longer exist is not.
72
+
73
+ Removed from `BAEConfig`:
74
+
75
+ - `decoder_dropout_rate` and `decoder_use_batch_norm`. Batch norm was already
76
+ documented as harmful (marker-recovery F1 0.59-0.73) because the boosting
77
+ target is computed with the decoder in eval mode and the update applied in
78
+ train mode. Dropout has exactly the same inconsistency and it was never written
79
+ down: the target comes from the full network, the update from a thinned one.
80
+ With both gone the decoder is a deterministic per-cell function, which is the
81
+ property `_compute_boosting_targets` has always relied on.
82
+ - `disentanglement_standardize`. Both `disentanglement` methods stay.
83
+ - `standardize_targets`. It was a genuine trade-off — higher selection precision,
84
+ roughly half the recall — but it is superseded by
85
+ `stability_selection(threshold=...)` -> `stable_encoder()` -> `apply_encoder()`,
86
+ which is the same trade with a dial that reports what it is doing. Two knobs for
87
+ one trade-off is worse than one.
88
+
89
+ Removed from `fit`:
90
+
91
+ - `balance_obs`, and with it the `sample_weights` parameter that threaded through
92
+ nine methods and both stability paths. Its own documentation conceded the
93
+ limit: the weights never reached the `allboost` fit, so gene selection stayed
94
+ unbalanced no matter what they were set to. The measured effect was modest (per
95
+ group reconstruction-MSE spread 0.231 -> 0.150) for a mechanism that promised
96
+ more than it delivered.
97
+
98
+ `BAE.stability_selection` is now iteration mode only; `mode`, `subsample_frac`,
99
+ `n_subsamples` and `n_iterations` are gone, and `fit(stability_selection=...)`
100
+ takes a bool. Iteration mode was already the default and measures the lower
101
+ false-discovery rate (0.26 against 0.31). The subsample path stays available as
102
+ the standalone `structboost.stability_selection`, which is where it belongs: it
103
+ resamples cells in the Meinshausen-Buhlmann scheme and works on an `allboost`
104
+ problem, so supervised users with no training loop to iterate over still have it.
105
+ The Meinshausen-Buhlmann bound it reports was measured to be violated by roughly
106
+ an order of magnitude when the targets come from a model fitted on the same
107
+ cells, and that warning moved with it.
108
+
109
+ `uns["bae"]["variance_explained"]` is now written by every fit. It used to appear
110
+ only when a covariate argument was passed, which left the quality workflow the
111
+ guide documents — compare it against `linear_ceiling` — raising `KeyError` on a
112
+ plain `fit(adata)`. The metric has nothing to do with covariates; only the
113
+ per-group breakdown does, and that stays behind the covariate guard.
114
+
115
+ Two fixes found along the way: the `allboost` example in the guide passed
116
+ `mode="standard"`, an argument `allboost` has never accepted, so it raised
117
+ `TypeError` as written. And `disentangle_boosting_targets` projects through the
118
+ origin, with no intercept — exact residualization only on centered targets. That
119
+ was masked by `disentanglement_standardize`, which centered them; with the flag
120
+ gone the assumption is documented instead.
121
+
6
122
  ### [0.2.0] - 2026-08-05
7
123
 
8
124
  The boosting loop got faster without changing what it computes. Measured
@@ -2,8 +2,8 @@ cff-version: 1.2.0
2
2
  message: "If you use structboost in your research, please cite it."
3
3
  title: "structboost"
4
4
  type: software
5
- version: "0.2.0"
6
- date-released: "2026-08-05"
5
+ version: "0.4.0"
6
+ date-released: "2026-08-11"
7
7
  license: MIT
8
8
  authors:
9
9
  - family-names: Brunn
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: structboost
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: Boosting Autoencoders (BAE) and componentwise L2 boosting utilities for scverse-style workflows.
5
5
  Project-URL: Homepage, https://github.com/NiklasBrunn/structboost
6
6
  Project-URL: Documentation, https://niklasbrunn.github.io/structboost
@@ -153,7 +153,7 @@ is not identifiable: many different sparse gene sets reconstruct the data about
153
153
  equally well, and a fit returns one of them.
154
154
 
155
155
  ```python
156
- res = model.stability_selection(adata, mode="iteration")
156
+ res = model.stability_selection(adata)
157
157
  genes = [adata.var_names[res.stable_support[:, j]] for j in range(res.frequency.shape[1])]
158
158
  ```
159
159
 
@@ -78,7 +78,7 @@ is not identifiable: many different sparse gene sets reconstruct the data about
78
78
  equally well, and a fit returns one of them.
79
79
 
80
80
  ```python
81
- res = model.stability_selection(adata, mode="iteration")
81
+ res = model.stability_selection(adata)
82
82
  genes = [adata.var_names[res.stable_support[:, j]] for j in range(res.frequency.shape[1])]
83
83
  ```
84
84
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "structboost"
7
- version = "0.2.0"
7
+ version = "0.4.0"
8
8
  description = "Boosting Autoencoders (BAE) and componentwise L2 boosting utilities for scverse-style workflows."
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -55,11 +55,7 @@ class BAEDecoder(nn.Module):
55
55
 
56
56
  for i in range(len(dims) - 1):
57
57
  layers.append(nn.Linear(dims[i], dims[i + 1]))
58
- if config.decoder_use_batch_norm:
59
- layers.append(nn.BatchNorm1d(dims[i + 1]))
60
58
  layers.append(self.activation)
61
- if config.decoder_dropout_rate > 0:
62
- layers.append(nn.Dropout(config.decoder_dropout_rate))
63
59
 
64
60
  self.hidden = nn.Sequential(*layers)
65
61
  # Final projection to output space
@@ -83,8 +79,6 @@ class BAEDecoder(nn.Module):
83
79
  for module in self.modules():
84
80
  if isinstance(module, nn.Linear):
85
81
  module.reset_parameters()
86
- elif isinstance(module, nn.BatchNorm1d):
87
- module.reset_parameters()
88
82
 
89
83
  def forward(self, z: torch.Tensor, covariates: torch.Tensor | None = None) -> torch.Tensor:
90
84
  """Decode latent representation to reconstruction.