bsvae 0.3.0__tar.gz → 0.3.2__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 (42) hide show
  1. bsvae-0.3.2/PKG-INFO +224 -0
  2. bsvae-0.3.2/README.md +192 -0
  3. {bsvae-0.3.0 → bsvae-0.3.2}/pyproject.toml +2 -1
  4. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/cli/networks.py +59 -0
  5. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/cli/simulate.py +98 -3
  6. bsvae-0.3.2/src/bsvae/cli/sweep_k.py +511 -0
  7. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/cli/train.py +91 -5
  8. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/models/gmm_prior.py +17 -4
  9. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/models/gmvae.py +20 -4
  10. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/models/losses.py +234 -34
  11. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/networks/extract_networks.py +13 -7
  12. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/networks/module_extraction.py +4 -8
  13. bsvae-0.3.2/src/bsvae/simulation/__init__.py +24 -0
  14. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/simulation/generate.py +2 -2
  15. bsvae-0.3.2/src/bsvae/simulation/scenario.py +611 -0
  16. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/utils/modelIO.py +5 -4
  17. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/utils/training.py +129 -12
  18. bsvae-0.3.0/PKG-INFO +0 -162
  19. bsvae-0.3.0/README.md +0 -130
  20. bsvae-0.3.0/src/bsvae/simulation/__init__.py +0 -5
  21. {bsvae-0.3.0 → bsvae-0.3.2}/LICENSE +0 -0
  22. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/__init__.py +0 -0
  23. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/cli/__init__.py +0 -0
  24. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/hyperparam.ini +0 -0
  25. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/latent/__init__.py +0 -0
  26. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/latent/latent_analysis.py +0 -0
  27. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/latent/latent_export.py +0 -0
  28. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/main.py +0 -0
  29. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/models/__init__.py +0 -0
  30. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/models/decoder.py +0 -0
  31. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/models/encoder.py +0 -0
  32. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/models/vae.py +0 -0
  33. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/networks/__init__.py +0 -0
  34. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/networks/cli.py +0 -0
  35. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/networks/utils.py +0 -0
  36. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/simulation/metrics.py +0 -0
  37. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/utils/__init__.py +0 -0
  38. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/utils/datasets.py +0 -0
  39. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/utils/evaluate.py +0 -0
  40. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/utils/helpers.py +0 -0
  41. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/utils/hierarchy.py +0 -0
  42. {bsvae-0.3.0 → bsvae-0.3.2}/src/bsvae/utils/initialization.py +0 -0
bsvae-0.3.2/PKG-INFO ADDED
@@ -0,0 +1,224 @@
1
+ Metadata-Version: 2.4
2
+ Name: bsvae
3
+ Version: 0.3.2
4
+ Summary: Gaussian Mixture VAE for multi-modal biological module discovery in omics data.
5
+ License: GPL-3.0-only
6
+ License-File: LICENSE
7
+ Keywords: variational-autoencoder,gaussian-mixture-model,pytorch,bioinformatics,gene-expression,module-discovery,omics
8
+ Author: Kynon J Benjamin
9
+ Author-email: kj.benjamin90@gmail.com
10
+ Requires-Python: >=3.11,<3.14
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
18
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
19
+ Requires-Dist: faiss-cpu (>=1.7)
20
+ Requires-Dist: h5py (>=3.0)
21
+ Requires-Dist: numpy (<2.3)
22
+ Requires-Dist: pandas (>=2.3.2,<3.0.0)
23
+ Requires-Dist: scikit-learn (>=1.7.2,<2.0.0)
24
+ Requires-Dist: scipy (>=1.16.2,<2.0.0)
25
+ Requires-Dist: torch (>=2.8.0,<3.0.0)
26
+ Requires-Dist: tqdm (>=4.67.1,<5.0.0)
27
+ Project-URL: Bug Tracker, https://github.com/heart-gen/BSVAE/issues
28
+ Project-URL: homepage, https://bsvae.readthedocs.io/en/latest/
29
+ Project-URL: repository, https://github.com/heart-gen/BSVAE
30
+ Description-Content-Type: text/markdown
31
+
32
+ # BSVAE: Gaussian Mixture VAE for Module Discovery
33
+
34
+ [![PyTorch](https://img.shields.io/badge/PyTorch-%3E%3D2.8-orange)](https://pytorch.org/)
35
+ [![Python](https://img.shields.io/badge/Python-%3E%3D3.11-blue)](https://python.org/)
36
+ [![License](https://img.shields.io/badge/license-GPLv3-blue.svg)](LICENSE)
37
+ [![Documentation](https://readthedocs.org/projects/bsvae/badge/?version=latest)](https://bsvae.readthedocs.io/)
38
+
39
+ BSVAE is a PyTorch package for feature-level module discovery in omics data. The main model, `GMMModuleVAE`, learns latent structure across features such as genes, transcripts, or proteins, then supports downstream network extraction, module assignment, latent analysis, and simulation-based benchmarking.
40
+
41
+ ## What The Package Provides
42
+
43
+ - `bsvae-train` for training a two-phase GMM-VAE
44
+ - `bsvae-sweep-k` for selecting the number of modules (`K`) with held-out validation and optional stability replicates
45
+ - `bsvae-networks` for post-training network extraction, module extraction, latent export, and latent analysis
46
+ - `bsvae-simulate` for synthetic data generation, scenario-grid simulation, and module-recovery benchmarking
47
+
48
+ ## Installation
49
+
50
+ Install from PyPI:
51
+
52
+ ```bash
53
+ pip install bsvae
54
+ ```
55
+
56
+ Install from source:
57
+
58
+ ```bash
59
+ git clone https://github.com/heart-gen/BSVAE.git
60
+ cd BSVAE
61
+ pip install -e .
62
+ ```
63
+
64
+ Optional dependency:
65
+
66
+ - `anndata` is only needed for `.h5ad` input or `.h5ad` latent export helpers
67
+
68
+ ## Input Data Contract
69
+
70
+ Training and most downstream commands expect an expression matrix in `features x samples` orientation:
71
+
72
+ - rows are features such as genes, transcripts, or proteins
73
+ - columns are sample IDs
74
+ - the first column is the feature index when using CSV/TSV
75
+
76
+ Supported loaders:
77
+
78
+ - `.csv` / `.csv.gz`
79
+ - `.tsv` / `.tsv.gz`
80
+ - `.h5` / `.hdf5`
81
+ - `.h5ad` with optional `anndata`
82
+
83
+ ## CLI Entry Points
84
+
85
+ - `bsvae-train`
86
+ - `bsvae-sweep-k`
87
+ - `bsvae-networks`
88
+ - `bsvae-simulate`
89
+
90
+ ## Quickstart
91
+
92
+ Train a small model:
93
+
94
+ ```bash
95
+ bsvae-train pilot_run \
96
+ --dataset data/expression.csv \
97
+ --epochs 50 \
98
+ --n-modules 12 \
99
+ --latent-dim 16
100
+ ```
101
+
102
+ Recommended production flow: select `K` first, then use the retrained final model.
103
+
104
+ ```bash
105
+ bsvae-sweep-k sweep1 \
106
+ --dataset data/expression.csv \
107
+ --k-grid 8,12,16,24,32 \
108
+ --sweep-epochs 60 \
109
+ --stability-reps 5 \
110
+ --val-frac 0.1
111
+ ```
112
+
113
+ This writes sweep outputs under `results/sweep1/sweep_k/` and retrains the selected model under `results/sweep1/final_k<K>/`.
114
+
115
+ Extract module assignments from the final model:
116
+
117
+ ```bash
118
+ bsvae-networks extract-modules \
119
+ --model-path results/sweep1/final_k16 \
120
+ --dataset data/expression.csv \
121
+ --output-dir results/sweep1/final_k16/modules \
122
+ --expr data/expression.csv \
123
+ --soft-eigengenes
124
+ ```
125
+
126
+ Export latents:
127
+
128
+ ```bash
129
+ bsvae-networks export-latents \
130
+ --model-path results/sweep1/final_k16 \
131
+ --dataset data/expression.csv \
132
+ --output results/sweep1/final_k16/latents
133
+ ```
134
+
135
+ This command writes `results/sweep1/final_k16/latents.npz` containing `mu`, `logvar`, `gamma`, and `feature_ids`.
136
+
137
+ Extract feature-feature networks:
138
+
139
+ ```bash
140
+ bsvae-networks extract-networks \
141
+ --model-path results/sweep1/final_k16 \
142
+ --dataset data/expression.csv \
143
+ --output-dir results/sweep1/final_k16/networks \
144
+ --methods mu_cosine gamma_knn \
145
+ --top-k 50
146
+ ```
147
+
148
+ Run latent-space analysis:
149
+
150
+ ```bash
151
+ bsvae-networks latent-analysis \
152
+ --model-path results/sweep1/final_k16 \
153
+ --dataset data/expression.csv \
154
+ --output-dir results/sweep1/final_k16/latent_analysis \
155
+ --kmeans-k 16 \
156
+ --umap
157
+ ```
158
+
159
+ ## Training Outputs
160
+
161
+ `bsvae-train` writes to `results/<experiment>/` by default:
162
+
163
+ - `model.pt`
164
+ - `specs.json`
165
+ - `train_losses.csv`
166
+ - `model-<epoch>.pt` checkpoints when `--checkpoint-every` is greater than zero
167
+
168
+ `bsvae-sweep-k` writes:
169
+
170
+ - `results/<name>/sweep_k/sweep_results.csv`
171
+ - `results/<name>/sweep_k/sweep_summary.json`
172
+ - `results/<name>/sweep_k/k<K>/rep_<rep>/...` per-sweep run outputs
173
+ - `results/<name>/final_k<K>/...` for the final retrained model when `--train-final` is enabled
174
+
175
+ ## Simulation and Benchmarking
176
+
177
+ Generate one synthetic dataset:
178
+
179
+ ```bash
180
+ bsvae-simulate generate \
181
+ --output data/sim_expr.csv \
182
+ --save-ground-truth data/sim_truth.csv
183
+ ```
184
+
185
+ Generate a scenario grid:
186
+
187
+ ```bash
188
+ bsvae-simulate init-config --output sim.yaml
189
+
190
+ bsvae-simulate generate-grid \
191
+ --config sim.yaml \
192
+ --outdir results/sim_pub_v1 \
193
+ --reps 30 \
194
+ --base-seed 13
195
+ ```
196
+
197
+ Validate the generated grid:
198
+
199
+ ```bash
200
+ bsvae-simulate validate-grid --grid-dir results/sim_pub_v1
201
+ ```
202
+
203
+ Each scenario replicate writes method-ready files under `results/sim_pub_v1/scenarios/<scenario_id>/rep_<rep>/`, including:
204
+
205
+ - `expr/features_x_samples.tsv.gz`
206
+ - `expr/samples_x_features.tsv.gz`
207
+ - `truth/modules_hard.csv`
208
+ - `method_inputs.json`
209
+
210
+ ## Documentation
211
+
212
+ See the docs for the full workflow and command reference:
213
+
214
+ - `docs/index.md`
215
+ - `docs/quickstart.md`
216
+ - `docs/tutorial.md`
217
+ - `docs/cli.md`
218
+ - `docs/networks.md`
219
+ - `docs/hyperparameters.md`
220
+
221
+ ## License
222
+
223
+ This project is licensed under the [GNU General Public License v3.0](LICENSE).
224
+
bsvae-0.3.2/README.md ADDED
@@ -0,0 +1,192 @@
1
+ # BSVAE: Gaussian Mixture VAE for Module Discovery
2
+
3
+ [![PyTorch](https://img.shields.io/badge/PyTorch-%3E%3D2.8-orange)](https://pytorch.org/)
4
+ [![Python](https://img.shields.io/badge/Python-%3E%3D3.11-blue)](https://python.org/)
5
+ [![License](https://img.shields.io/badge/license-GPLv3-blue.svg)](LICENSE)
6
+ [![Documentation](https://readthedocs.org/projects/bsvae/badge/?version=latest)](https://bsvae.readthedocs.io/)
7
+
8
+ BSVAE is a PyTorch package for feature-level module discovery in omics data. The main model, `GMMModuleVAE`, learns latent structure across features such as genes, transcripts, or proteins, then supports downstream network extraction, module assignment, latent analysis, and simulation-based benchmarking.
9
+
10
+ ## What The Package Provides
11
+
12
+ - `bsvae-train` for training a two-phase GMM-VAE
13
+ - `bsvae-sweep-k` for selecting the number of modules (`K`) with held-out validation and optional stability replicates
14
+ - `bsvae-networks` for post-training network extraction, module extraction, latent export, and latent analysis
15
+ - `bsvae-simulate` for synthetic data generation, scenario-grid simulation, and module-recovery benchmarking
16
+
17
+ ## Installation
18
+
19
+ Install from PyPI:
20
+
21
+ ```bash
22
+ pip install bsvae
23
+ ```
24
+
25
+ Install from source:
26
+
27
+ ```bash
28
+ git clone https://github.com/heart-gen/BSVAE.git
29
+ cd BSVAE
30
+ pip install -e .
31
+ ```
32
+
33
+ Optional dependency:
34
+
35
+ - `anndata` is only needed for `.h5ad` input or `.h5ad` latent export helpers
36
+
37
+ ## Input Data Contract
38
+
39
+ Training and most downstream commands expect an expression matrix in `features x samples` orientation:
40
+
41
+ - rows are features such as genes, transcripts, or proteins
42
+ - columns are sample IDs
43
+ - the first column is the feature index when using CSV/TSV
44
+
45
+ Supported loaders:
46
+
47
+ - `.csv` / `.csv.gz`
48
+ - `.tsv` / `.tsv.gz`
49
+ - `.h5` / `.hdf5`
50
+ - `.h5ad` with optional `anndata`
51
+
52
+ ## CLI Entry Points
53
+
54
+ - `bsvae-train`
55
+ - `bsvae-sweep-k`
56
+ - `bsvae-networks`
57
+ - `bsvae-simulate`
58
+
59
+ ## Quickstart
60
+
61
+ Train a small model:
62
+
63
+ ```bash
64
+ bsvae-train pilot_run \
65
+ --dataset data/expression.csv \
66
+ --epochs 50 \
67
+ --n-modules 12 \
68
+ --latent-dim 16
69
+ ```
70
+
71
+ Recommended production flow: select `K` first, then use the retrained final model.
72
+
73
+ ```bash
74
+ bsvae-sweep-k sweep1 \
75
+ --dataset data/expression.csv \
76
+ --k-grid 8,12,16,24,32 \
77
+ --sweep-epochs 60 \
78
+ --stability-reps 5 \
79
+ --val-frac 0.1
80
+ ```
81
+
82
+ This writes sweep outputs under `results/sweep1/sweep_k/` and retrains the selected model under `results/sweep1/final_k<K>/`.
83
+
84
+ Extract module assignments from the final model:
85
+
86
+ ```bash
87
+ bsvae-networks extract-modules \
88
+ --model-path results/sweep1/final_k16 \
89
+ --dataset data/expression.csv \
90
+ --output-dir results/sweep1/final_k16/modules \
91
+ --expr data/expression.csv \
92
+ --soft-eigengenes
93
+ ```
94
+
95
+ Export latents:
96
+
97
+ ```bash
98
+ bsvae-networks export-latents \
99
+ --model-path results/sweep1/final_k16 \
100
+ --dataset data/expression.csv \
101
+ --output results/sweep1/final_k16/latents
102
+ ```
103
+
104
+ This command writes `results/sweep1/final_k16/latents.npz` containing `mu`, `logvar`, `gamma`, and `feature_ids`.
105
+
106
+ Extract feature-feature networks:
107
+
108
+ ```bash
109
+ bsvae-networks extract-networks \
110
+ --model-path results/sweep1/final_k16 \
111
+ --dataset data/expression.csv \
112
+ --output-dir results/sweep1/final_k16/networks \
113
+ --methods mu_cosine gamma_knn \
114
+ --top-k 50
115
+ ```
116
+
117
+ Run latent-space analysis:
118
+
119
+ ```bash
120
+ bsvae-networks latent-analysis \
121
+ --model-path results/sweep1/final_k16 \
122
+ --dataset data/expression.csv \
123
+ --output-dir results/sweep1/final_k16/latent_analysis \
124
+ --kmeans-k 16 \
125
+ --umap
126
+ ```
127
+
128
+ ## Training Outputs
129
+
130
+ `bsvae-train` writes to `results/<experiment>/` by default:
131
+
132
+ - `model.pt`
133
+ - `specs.json`
134
+ - `train_losses.csv`
135
+ - `model-<epoch>.pt` checkpoints when `--checkpoint-every` is greater than zero
136
+
137
+ `bsvae-sweep-k` writes:
138
+
139
+ - `results/<name>/sweep_k/sweep_results.csv`
140
+ - `results/<name>/sweep_k/sweep_summary.json`
141
+ - `results/<name>/sweep_k/k<K>/rep_<rep>/...` per-sweep run outputs
142
+ - `results/<name>/final_k<K>/...` for the final retrained model when `--train-final` is enabled
143
+
144
+ ## Simulation and Benchmarking
145
+
146
+ Generate one synthetic dataset:
147
+
148
+ ```bash
149
+ bsvae-simulate generate \
150
+ --output data/sim_expr.csv \
151
+ --save-ground-truth data/sim_truth.csv
152
+ ```
153
+
154
+ Generate a scenario grid:
155
+
156
+ ```bash
157
+ bsvae-simulate init-config --output sim.yaml
158
+
159
+ bsvae-simulate generate-grid \
160
+ --config sim.yaml \
161
+ --outdir results/sim_pub_v1 \
162
+ --reps 30 \
163
+ --base-seed 13
164
+ ```
165
+
166
+ Validate the generated grid:
167
+
168
+ ```bash
169
+ bsvae-simulate validate-grid --grid-dir results/sim_pub_v1
170
+ ```
171
+
172
+ Each scenario replicate writes method-ready files under `results/sim_pub_v1/scenarios/<scenario_id>/rep_<rep>/`, including:
173
+
174
+ - `expr/features_x_samples.tsv.gz`
175
+ - `expr/samples_x_features.tsv.gz`
176
+ - `truth/modules_hard.csv`
177
+ - `method_inputs.json`
178
+
179
+ ## Documentation
180
+
181
+ See the docs for the full workflow and command reference:
182
+
183
+ - `docs/index.md`
184
+ - `docs/quickstart.md`
185
+ - `docs/tutorial.md`
186
+ - `docs/cli.md`
187
+ - `docs/networks.md`
188
+ - `docs/hyperparameters.md`
189
+
190
+ ## License
191
+
192
+ This project is licensed under the [GNU General Public License v3.0](LICENSE).
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "bsvae"
3
- version = "0.3.0" # dynamically resolved by poetry-dynamic-versioning
3
+ version = "0.3.2" # dynamically resolved by poetry-dynamic-versioning
4
4
  description = "Gaussian Mixture VAE for multi-modal biological module discovery in omics data."
5
5
  readme = "README.md"
6
6
  authors = ["Kynon J Benjamin <kj.benjamin90@gmail.com>"]
@@ -50,6 +50,7 @@ repository = "https://github.com/heart-gen/BSVAE"
50
50
  bsvae-train = "bsvae.cli.train:cli"
51
51
  bsvae-networks = "bsvae.cli.networks:cli"
52
52
  bsvae-simulate = "bsvae.cli.simulate:cli"
53
+ bsvae-sweep-k = "bsvae.cli.sweep_k:cli"
53
54
 
54
55
  [tool.poetry.group.dev.dependencies]
55
56
  pytest = ">=8.4.2,<9.0.0"
@@ -64,6 +64,12 @@ def build_parser() -> argparse.ArgumentParser:
64
64
  mp.add_argument("--use-leiden", action="store_true",
65
65
  help="Also run Leiden clustering as fallback comparison.")
66
66
  mp.add_argument("--leiden-resolution", type=float, default=1.0)
67
+ mp.add_argument("--tx2gene", type=str, default=None,
68
+ help="TSV (transcript_id, gene_id) for isoform-to-gene aggregation.")
69
+ mp.add_argument("--aggregate-to-gene", action="store_true", default=False,
70
+ help="Aggregate isoform-level γ to gene level by averaging across "
71
+ "isoforms per gene. Saves gamma_gene.npz and "
72
+ "hard_assignments_gene.npz. Requires --tx2gene.")
67
73
 
68
74
  # ------------------------------------------------------------------
69
75
  # export-latents
@@ -120,6 +126,35 @@ def handle_extract_networks(args, logger: logging.Logger) -> None:
120
126
  logger.info("Network extraction complete → %s", args.output_dir)
121
127
 
122
128
 
129
+ def _aggregate_gamma_to_gene(
130
+ gamma: "np.ndarray",
131
+ feature_ids: List[str],
132
+ tx2gene_path: str,
133
+ ) -> "tuple[np.ndarray, list[str]]":
134
+ """Average isoform-level γ (F_tx, K) to gene level, renormalise rows to sum 1."""
135
+ import numpy as np
136
+ from collections import defaultdict
137
+ from bsvae.utils.hierarchy import load_tx2gene
138
+
139
+ tx2gene = load_tx2gene(path=tx2gene_path, feature_ids=feature_ids)
140
+ # Map every feature to its gene_id; fall back to itself for unmapped features
141
+ fid_to_gene = {fid: tx2gene.get(fid, fid) for fid in feature_ids}
142
+
143
+ gene_to_idxs: dict = defaultdict(list)
144
+ for i, fid in enumerate(feature_ids):
145
+ gene_to_idxs[fid_to_gene[fid]].append(i)
146
+
147
+ gene_ids = list(gene_to_idxs.keys())
148
+ gamma_gene = np.zeros((len(gene_ids), gamma.shape[1]), dtype=np.float32)
149
+ for j, gid in enumerate(gene_ids):
150
+ gamma_gene[j] = gamma[gene_to_idxs[gid]].mean(axis=0)
151
+
152
+ row_sums = gamma_gene.sum(axis=1, keepdims=True)
153
+ row_sums = np.where(row_sums > 0, row_sums, 1.0)
154
+ gamma_gene /= row_sums
155
+ return gamma_gene, gene_ids
156
+
157
+
123
158
  def handle_extract_modules(args, logger: logging.Logger) -> None:
124
159
  import numpy as np
125
160
  from bsvae.utils.modelIO import load_model
@@ -144,6 +179,30 @@ def handle_extract_modules(args, logger: logging.Logger) -> None:
144
179
  model.n_modules, len(feature_ids),
145
180
  )
146
181
 
182
+ if args.aggregate_to_gene:
183
+ if not args.tx2gene:
184
+ logger.warning("--aggregate-to-gene requires --tx2gene; skipping.")
185
+ else:
186
+ gamma_gene, gene_ids = _aggregate_gamma_to_gene(
187
+ result["gamma"], result["feature_ids"], args.tx2gene
188
+ )
189
+ hard_gene = gamma_gene.argmax(axis=1).astype(np.int32)
190
+ out = Path(args.output_dir)
191
+ np.savez_compressed(
192
+ out / "gamma_gene.npz",
193
+ gamma=gamma_gene,
194
+ feature_ids=np.array(gene_ids, dtype=object),
195
+ )
196
+ np.savez_compressed(
197
+ out / "hard_assignments_gene.npz",
198
+ hard_assignments=hard_gene,
199
+ feature_ids=np.array(gene_ids, dtype=object),
200
+ )
201
+ logger.info(
202
+ "Gene-level aggregation: %d transcripts → %d genes",
203
+ len(result["feature_ids"]), len(gene_ids),
204
+ )
205
+
147
206
  if args.soft_eigengenes and args.expr:
148
207
  import pandas as pd
149
208
  expr = pd.read_csv(args.expr, index_col=0, sep="\t" if args.expr.endswith(".tsv") else ",")
@@ -3,8 +3,12 @@ bsvae-simulate — Generate synthetic omics data and benchmark module methods.
3
3
 
4
4
  Subcommands
5
5
  -----------
6
- generate Simulate omics data with known block-diagonal module structure.
7
- benchmark ARI/NMI/enrichment vs WGCNA/GNVAE on simulated data.
6
+ generate Simulate omics data with known block-diagonal module structure.
7
+ benchmark ARI/NMI/enrichment vs WGCNA/GNVAE on simulated data.
8
+ init-config Write starter simulation grid config.
9
+ generate-grid Generate all scenarios/replicates defined in config.
10
+ generate-scenario Generate one scenario/replicate.
11
+ validate-grid Validate generated grid output structure.
8
12
  """
9
13
 
10
14
  from __future__ import annotations
@@ -43,7 +47,7 @@ def build_parser() -> argparse.ArgumentParser:
43
47
  gp.add_argument("--between-corr", type=float, default=0.0,
44
48
  help="Between-module correlation (default: 0.0).")
45
49
  gp.add_argument("--noise-std", type=float, default=0.2)
46
- gp.add_argument("--seed", type=int, default=42)
50
+ gp.add_argument("--seed", type=int, default=13)
47
51
  gp.add_argument("--save-ground-truth", help="Path to save ground-truth module labels CSV.")
48
52
 
49
53
  # ------------------------------------------------------------------
@@ -57,6 +61,37 @@ def build_parser() -> argparse.ArgumentParser:
57
61
  bp.add_argument("--batch-size", type=int, default=128)
58
62
  bp.add_argument("--no-cuda", action="store_true", default=False)
59
63
 
64
+ # ------------------------------------------------------------------
65
+ # init-config
66
+ # ------------------------------------------------------------------
67
+ cp = subs.add_parser("init-config", help="Write a starter simulation config file.")
68
+ cp.add_argument("--output", default="sim.yaml", help="Destination config path.")
69
+
70
+ # ------------------------------------------------------------------
71
+ # generate-grid
72
+ # ------------------------------------------------------------------
73
+ gp2 = subs.add_parser("generate-grid", help="Generate a scenario grid from config.")
74
+ gp2.add_argument("--config", required=True, help="Simulation config path.")
75
+ gp2.add_argument("--outdir", required=True, help="Output directory for scenario grid.")
76
+ gp2.add_argument("--reps", type=int, default=None, help="Override replicates per scenario.")
77
+ gp2.add_argument("--base-seed", type=int, default=None, help="Override base seed for all runs.")
78
+
79
+ # ------------------------------------------------------------------
80
+ # generate-scenario
81
+ # ------------------------------------------------------------------
82
+ sp = subs.add_parser("generate-scenario", help="Generate a single scenario/replicate from config.")
83
+ sp.add_argument("--config", required=True, help="Simulation config path.")
84
+ sp.add_argument("--scenario-id", required=True, help="Scenario identifier from config expansion.")
85
+ sp.add_argument("--rep", type=int, default=0, help="Replicate index.")
86
+ sp.add_argument("--outdir", required=True, help="Output directory for scenario grid.")
87
+ sp.add_argument("--base-seed", type=int, default=None, help="Override base seed for this run.")
88
+
89
+ # ------------------------------------------------------------------
90
+ # validate-grid
91
+ # ------------------------------------------------------------------
92
+ vp = subs.add_parser("validate-grid", help="Validate scenario grid output files.")
93
+ vp.add_argument("--grid-dir", required=True, help="Scenario grid output directory.")
94
+
60
95
  return parser
61
96
 
62
97
 
@@ -144,6 +179,58 @@ def handle_benchmark(args, logger: logging.Logger) -> None:
144
179
  logger.info("Benchmark metrics saved to %s", out)
145
180
 
146
181
 
182
+ def handle_init_config(args, logger: logging.Logger) -> None:
183
+ from bsvae.simulation.scenario import write_starter_config
184
+
185
+ write_starter_config(args.output)
186
+ logger.info("Starter simulation config written to %s", args.output)
187
+
188
+
189
+ def handle_generate_grid(args, logger: logging.Logger) -> None:
190
+ from bsvae.simulation.scenario import load_config, generate_grid
191
+
192
+ config = load_config(args.config)
193
+ summary = generate_grid(
194
+ config=config,
195
+ outdir=args.outdir,
196
+ reps=args.reps,
197
+ base_seed=args.base_seed,
198
+ )
199
+ logger.info(
200
+ "Grid complete: %d scenarios × %d reps = %d runs",
201
+ summary["n_scenarios"],
202
+ summary["n_reps"],
203
+ summary["n_runs"],
204
+ )
205
+ logger.info("Manifest: %s", summary["manifest"])
206
+
207
+
208
+ def handle_generate_scenario(args, logger: logging.Logger) -> None:
209
+ from bsvae.simulation.scenario import load_config, generate_scenario
210
+
211
+ config = load_config(args.config)
212
+ run_dir = generate_scenario(
213
+ config=config,
214
+ scenario_id=args.scenario_id,
215
+ rep=args.rep,
216
+ outdir=args.outdir,
217
+ base_seed=args.base_seed,
218
+ )
219
+ logger.info("Scenario generated: %s", run_dir)
220
+
221
+
222
+ def handle_validate_grid(args, logger: logging.Logger) -> None:
223
+ from bsvae.simulation.scenario import validate_grid
224
+
225
+ result = validate_grid(args.grid_dir)
226
+ logger.info(
227
+ "Grid validation: checked %d/%d rows, all required files present=%s",
228
+ result["checked_rows"],
229
+ result["manifest_rows"],
230
+ result["all_required_files_present"],
231
+ )
232
+
233
+
147
234
  def cli(argv=None):
148
235
  parser = build_parser()
149
236
  args = parser.parse_args(argv)
@@ -153,6 +240,14 @@ def cli(argv=None):
153
240
  handle_generate(args, logger)
154
241
  elif args.command == "benchmark":
155
242
  handle_benchmark(args, logger)
243
+ elif args.command == "init-config":
244
+ handle_init_config(args, logger)
245
+ elif args.command == "generate-grid":
246
+ handle_generate_grid(args, logger)
247
+ elif args.command == "generate-scenario":
248
+ handle_generate_scenario(args, logger)
249
+ elif args.command == "validate-grid":
250
+ handle_validate_grid(args, logger)
156
251
  else:
157
252
  parser.error(f"Unknown command: {args.command}")
158
253