SigProfilerExtractor 1.4.1__tar.gz → 1.5.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.
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/CHANGELOG.md +26 -1
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/PKG-INFO +5 -5
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/README.md +4 -4
- sigprofilerextractor-1.5.0/SigProfilerExtractor/_version.py +1 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor/controllers/cli_controller.py +16 -5
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor/nmf_cpu.py +20 -6
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor/nmf_gpu.py +110 -15
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor/sigpro.py +151 -33
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor/subroutines.py +248 -218
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor.egg-info/PKG-INFO +5 -5
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor.egg-info/SOURCES.txt +16 -1
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor.egg-info/scm_file_list.json +70 -55
- sigprofilerextractor-1.5.0/SigProfilerExtractor.egg-info/scm_version.json +8 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/docs/3. Using the Tool - Input.md +129 -129
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/docs/4. Using the Tool - Output.md +6 -3
- sigprofilerextractor-1.5.0/tests/test_activity_export.py +18 -0
- sigprofilerextractor-1.5.0/tests/test_assignment_forwarding.py +44 -0
- sigprofilerextractor-1.5.0/tests/test_context_labels.py +22 -0
- sigprofilerextractor-1.5.0/tests/test_default_parity.py +23 -0
- sigprofilerextractor-1.5.0/tests/test_import_side_effects.py +45 -0
- sigprofilerextractor-1.5.0/tests/test_input_samples.py +37 -0
- sigprofilerextractor-1.5.0/tests/test_matrix_normalization.py +30 -0
- sigprofilerextractor-1.5.0/tests/test_nmf_floor.py +69 -0
- sigprofilerextractor-1.5.0/tests/test_normalization_cutoff.py +27 -0
- sigprofilerextractor-1.5.0/tests/test_output_directory.py +34 -0
- sigprofilerextractor-1.5.0/tests/test_precision.py +22 -0
- sigprofilerextractor-1.5.0/tests/test_replicate_batches.py +25 -0
- sigprofilerextractor-1.5.0/tests/test_replicate_diagnostics.py +30 -0
- sigprofilerextractor-1.5.0/tests/test_seed_file.py +32 -0
- sigprofilerextractor-1.5.0/tests/test_silhouette_stability.py +44 -0
- sigprofilerextractor-1.5.0/tests/test_sv_output_path.py +16 -0
- sigprofilerextractor-1.4.1/SigProfilerExtractor/_version.py +0 -1
- sigprofilerextractor-1.4.1/SigProfilerExtractor.egg-info/scm_version.json +0 -8
- sigprofilerextractor-1.4.1/docs/assets/images/osf/h3tvx.png +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/.github/workflows/ci.yml +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/.github/workflows/create-version-tag.yml +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/.github/workflows/docs.yml +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/.github/workflows/release.yml +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/.github/workflows/static.yml +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/.gitignore +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/Dockerfile +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/LICENSE.txt +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/MANIFEST.in +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor/__init__.py +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor/data/CNVInput/Battenberg_test.tsv +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor/data/CSVInput/csv_example.csv +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor/data/MatObjInput/21_breast_WGS_substitutions.mat +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor/data/ReferenceFiles/CNV_features.tsv +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor/data/ReferenceFiles/CN_classes_dictionary.txt +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor/data/TextInput/Samples_CNV.txt +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor/data/TextInput/Samples_DBS.txt +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor/data/TextInput/Samples_ID.txt +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor/data/TextInput/Samples_SBS.txt +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor/data/TextInput/Samples_SV.txt +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor/data/VCFInput/PD3851a.vcf +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor/data/VCFInput/PD3890a.vcf +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor/data/VCFInput/PD3904a.vcf +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor/data/VCFInput/PD3905a.vcf +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor/data/VCFInput/PD3945a.vcf +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor/estimate_best_solution.py +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor/nndsvd.py +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor/sigprofilerextractor_cli.py +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor/version.py +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor.egg-info/dependency_links.txt +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor.egg-info/entry_points.txt +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor.egg-info/not-zip-safe +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor.egg-info/requires.txt +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/SigProfilerExtractor.egg-info/top_level.txt +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/docs/1. Installation.md +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/docs/2. Quick Start Example.md +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/docs/5. Output - All Solutions.md +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/docs/6. Output - Suggested Solution.md +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/docs/assets/images/osf/4hnaj.png +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/docs/assets/images/osf/5f8ep.png +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/docs/assets/images/osf/5tr9y.png +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/docs/assets/images/osf/5u7v8.png +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/docs/assets/images/osf/9uf4s.png +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/docs/assets/images/osf/a7gkf.png +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/docs/assets/images/osf/akef5.png +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/docs/assets/images/osf/c5hzf.png +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/docs/assets/images/osf/dahws.png +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/docs/assets/images/osf/h796w.png +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/docs/assets/images/osf/kb4w2.png +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/docs/assets/images/osf/ku93g.png +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/docs/assets/images/osf/p4cfj.png +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/docs/assets/images/osf/r9wcm.png +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/docs/assets/images/osf/rz3ue.png +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/docs/assets/images/osf/whn8t.png +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/docs/assets/images/osf/xcm95.png +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/docs/assets/images/osf/xqzfh.png +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/docs/assets/images/osf/yhqnw.png +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/docs/assets/images/osf/z98fk.png +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/docs/assets/images/osf/zu6sm.png +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/docs/assets/stylesheets/extra.css +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/docs/home.md +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/docs/index.md +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/install_genome.py +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/mkdocs.yml +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/mkdocs_hooks.py +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/pyproject.toml +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/setup.cfg +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/setup.py +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/test.py +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/tests/test_cosmic_version.py +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/wikies/CLI-Usage.md +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/wikies/Citation.md +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/wikies/Home.md +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/wikies/Installation.md +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/wikies/Output.md +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/wikies/Python-API.md +0 -0
- {sigprofilerextractor-1.4.1 → sigprofilerextractor-1.5.0}/wikies/_Sidebar.md +0 -0
|
@@ -5,11 +5,36 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
|
|
6
6
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [
|
|
8
|
+
## [1.5.0] - 2026-09-28
|
|
9
9
|
|
|
10
10
|
### Changed
|
|
11
11
|
- Updated the default COSMIC reference signature version from 3.5 to 3.6.
|
|
12
12
|
- Updated the minimum SigProfilerAssignment version to 1.1.5 for COSMIC v3.6 support.
|
|
13
|
+
- Changed the CLI default for `--maximum_signatures` from 10 to 25 so it matches the Python API.
|
|
14
|
+
- GPU batches now give every NMF replicate its own resampling and initialization generators, sample totals, and convergence stopping point, matching CPU and single-replicate GPU behavior.
|
|
15
|
+
- A seeds file now contains exactly one root seed. All random generators used by a run are derived reproducibly from that seed.
|
|
16
|
+
- Existing results for a mutation context are moved to `<context>_previous_<timestamp>` before a rerun writes new results, preventing files from different runs from being mixed.
|
|
17
|
+
- Importing SigProfilerExtractor no longer suppresses warnings globally or changes the host program's multiprocessing start method.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- Added a lower bound (1e-16) to W, H and W @ H in the KL multiplicative updates, on CPU and GPU. A multiplicative update cannot move an entry away from exactly zero, so zeros from the NNDSVD initialization (about half of W and H) or from underflow stayed zero for the whole run. The fit could then stall and pass the convergence test early at a worse solution; with `nmf_init="nndsvd"` this could happen at the first allowed convergence check. Results and run times can change, including with the default random initialization.
|
|
21
|
+
- Fixed the KL loss returning NaN when the matrix passed to NMF contains a zero (`0 * log 0`). The extraction pipeline raises every value to at least 1e-4 before NMF, so this affected only direct use of `nmf_cpu` and `nmf_gpu`.
|
|
22
|
+
- If a cell of W @ H reached zero, the update produced inf or NaN that spread to all of W and H with no error. An NMF replicate whose W or H contains NaN or inf now raises `FloatingPointError` instead of entering clustering.
|
|
23
|
+
- CPU extraction now runs the requested number of NMF replicates when `batch_size` is greater than 1. The setting only controls GPU batching.
|
|
24
|
+
- Forwarded `nnls_add_penalty`, `nnls_remove_penalty`, `initial_remove_penalty`, and `collapse_to_SBS96` to SigProfilerAssignment.
|
|
25
|
+
- Recomputed the signature-rank range for each mutation context so a context with fewer samples cannot narrow the range used by subsequent contexts.
|
|
26
|
+
- Seeded the Gaussian mixture model used to choose the normalization cutoff, making it reproducible from `Seeds.txt`.
|
|
27
|
+
- Stopped treating failed silhouette calculations as perfect stability. Rank one remains stable by convention; invalid higher-rank calculations now raise a clear error.
|
|
28
|
+
- Removed two unused per-replicate arrays that could each consume several gigabytes for large mutation matrices.
|
|
29
|
+
- Validated `matrix_normalization`. Accepted values are `"gmm"`, `"100X"`, `"log2"`, `"none"`, or a positive integer cutoff; unsupported values now raise `ValueError`.
|
|
30
|
+
- Samples containing some missing values now raise a clear error instead of being dropped silently. Completely empty columns and samples with zero mutations are reported and removed.
|
|
31
|
+
- Preserved input values as float64 throughout NMF when `precision="double"` is selected.
|
|
32
|
+
- Labelled matrices with unrecognized channel counts as `CH<n>` instead of incorrectly labelling every such matrix as SBS.
|
|
33
|
+
- Fixed loading `Seeds.txt` with current NumPy versions and added clear validation for a missing `Seed` column or more than one root seed.
|
|
34
|
+
- A run now raises an error instead of reporting success when none of the requested mutation contexts can be analyzed.
|
|
35
|
+
- Rounded exported NMF activities to the nearest integer instead of truncating them.
|
|
36
|
+
- Made the `SV_Matrices` output location independent of whether a BEDPE input directory has a trailing slash.
|
|
37
|
+
- Unified CPU and GPU per-replicate diagnostics so both paths calculate them from the matrix actually fitted by NMF.
|
|
13
38
|
|
|
14
39
|
## [1.2.7] - 2026-01-22
|
|
15
40
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: SigProfilerExtractor
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.0
|
|
4
4
|
Summary: Extracts mutational signatures from mutational catalogues
|
|
5
5
|
Home-page: https://github.com/SigProfilerSuite/SigProfilerExtractor.git
|
|
6
6
|
Author: S Mishu Ashiqul Islam
|
|
@@ -121,7 +121,7 @@ Extracts mutational signatures from an array of samples.
|
|
|
121
121
|
|
|
122
122
|
```python
|
|
123
123
|
sigProfilerExtractor(input_type, out_put, input_data, reference_genome="GRCh37", opportunity_genome = "GRCh37", cosmic_version=3.6, context_type = "default", exome = False,
|
|
124
|
-
minimum_signatures=1, maximum_signatures=
|
|
124
|
+
minimum_signatures=1, maximum_signatures=25, nmf_replicates=100, resample = True, batch_size=1, cpu=-1, gpu=False,
|
|
125
125
|
nmf_init="random", precision= "single", matrix_normalization= "gmm", seeds= "random",
|
|
126
126
|
min_nmf_iterations= 10000, max_nmf_iterations=1000000, nmf_test_conv= 10000, nmf_tolerance= 1e-15, get_all_signature_matrices= False)
|
|
127
127
|
```
|
|
@@ -141,9 +141,9 @@ sigProfilerExtractor(input_type, out_put, input_data, reference_genome="GRCh37",
|
|
|
141
141
|
| | **maximum_signatures** | Positive Integer | The maximum number of signatures to be extracted (default: `25`). |
|
|
142
142
|
| | **nmf_replicates** | Positive Integer | The number of iteration to be performed to extract each number signature (default: `100`). |
|
|
143
143
|
| | **resample** | Boolean | If `True`, add poisson noise to samples by resampling (default: `True`). |
|
|
144
|
-
| | **seeds** | String | Ensures reproducible NMF
|
|
144
|
+
| | **seeds** | String | Ensures reproducible NMF resampling and initialization. Provide the path to a `Seeds.txt` file containing exactly one root seed in the `Seed` column (the file generated by a previous analysis can be reused) to reproduce results (default: `"random"`). |
|
|
145
145
|
| **NMF Engines** | | | |
|
|
146
|
-
| | **matrix_normalization** | String | Method of normalizing the genome matrix before it is analyzed by NMF (default: `"gmm"`). Options are
|
|
146
|
+
| | **matrix_normalization** | String | Method of normalizing the genome matrix before it is analyzed by NMF (default: `"gmm"`). Options are `"100X"`, `"log2"`, `"none"`, or a positive integer used as a manual cutoff (for example `5000`). |
|
|
147
147
|
| | **nmf_init** | String | The initialization algorithm for W and H matrix of NMF (default: `"random"`). Options are `"random"`, `"nndsvd"`, `"nndsvda"`, `"nndsvdar"` and `"nndsvd_min"`. |
|
|
148
148
|
| | **precision** | String | Values should be single or double (default: `"single"`). |
|
|
149
149
|
| | **min_nmf_iterations** | Integer | Value defines the minimum number of iterations to be completed before NMF converges (default: `10000`). |
|
|
@@ -154,7 +154,7 @@ sigProfilerExtractor(input_type, out_put, input_data, reference_genome="GRCh37",
|
|
|
154
154
|
| | **cpu** | Integer | The number of processors to be used to extract the signatures (default: all processors). |
|
|
155
155
|
| | **assignment_cpu** | Integer | Number of processors to be used by SigProfilerAssignment for the final signature assignment step (default: all available). This is independent of the `cpu` parameter. |
|
|
156
156
|
| | **gpu** | Boolean | Defines if the GPU resource will used if available (default: `False`). If `True`, the GPU resources will be used in the computation. *Note: All available CPU processors are used by default, which may cause a memory error. This error can be resolved by reducing the number of CPU processes through the `cpu` parameter.*|
|
|
157
|
-
| | **batch_size** | Integer |
|
|
157
|
+
| | **batch_size** | Integer | Effective only when the GPU is used. Defines how many NMF replicates are grouped into each GPU batch (default: `1`). Every replicate has its own resampling and initialization generators, sample totals, and convergence stopping point, whether it runs alone or in a batch. |
|
|
158
158
|
| **Solution Estimation Thresholds** | | | |
|
|
159
159
|
| | **stability** | Float | The cutoff thresh-hold of the average stability (default: `0.8`). Solutions with average stabilities below this thresh-hold will not be considered. |
|
|
160
160
|
| | **min_stability** | Float | The cutoff thresh-hold of the minimum stability (default: `0.2`). Solutions with minimum stabilities below this thresh-hold will not be considered. |
|
|
@@ -89,7 +89,7 @@ Extracts mutational signatures from an array of samples.
|
|
|
89
89
|
|
|
90
90
|
```python
|
|
91
91
|
sigProfilerExtractor(input_type, out_put, input_data, reference_genome="GRCh37", opportunity_genome = "GRCh37", cosmic_version=3.6, context_type = "default", exome = False,
|
|
92
|
-
minimum_signatures=1, maximum_signatures=
|
|
92
|
+
minimum_signatures=1, maximum_signatures=25, nmf_replicates=100, resample = True, batch_size=1, cpu=-1, gpu=False,
|
|
93
93
|
nmf_init="random", precision= "single", matrix_normalization= "gmm", seeds= "random",
|
|
94
94
|
min_nmf_iterations= 10000, max_nmf_iterations=1000000, nmf_test_conv= 10000, nmf_tolerance= 1e-15, get_all_signature_matrices= False)
|
|
95
95
|
```
|
|
@@ -109,9 +109,9 @@ sigProfilerExtractor(input_type, out_put, input_data, reference_genome="GRCh37",
|
|
|
109
109
|
| | **maximum_signatures** | Positive Integer | The maximum number of signatures to be extracted (default: `25`). |
|
|
110
110
|
| | **nmf_replicates** | Positive Integer | The number of iteration to be performed to extract each number signature (default: `100`). |
|
|
111
111
|
| | **resample** | Boolean | If `True`, add poisson noise to samples by resampling (default: `True`). |
|
|
112
|
-
| | **seeds** | String | Ensures reproducible NMF
|
|
112
|
+
| | **seeds** | String | Ensures reproducible NMF resampling and initialization. Provide the path to a `Seeds.txt` file containing exactly one root seed in the `Seed` column (the file generated by a previous analysis can be reused) to reproduce results (default: `"random"`). |
|
|
113
113
|
| **NMF Engines** | | | |
|
|
114
|
-
| | **matrix_normalization** | String | Method of normalizing the genome matrix before it is analyzed by NMF (default: `"gmm"`). Options are
|
|
114
|
+
| | **matrix_normalization** | String | Method of normalizing the genome matrix before it is analyzed by NMF (default: `"gmm"`). Options are `"100X"`, `"log2"`, `"none"`, or a positive integer used as a manual cutoff (for example `5000`). |
|
|
115
115
|
| | **nmf_init** | String | The initialization algorithm for W and H matrix of NMF (default: `"random"`). Options are `"random"`, `"nndsvd"`, `"nndsvda"`, `"nndsvdar"` and `"nndsvd_min"`. |
|
|
116
116
|
| | **precision** | String | Values should be single or double (default: `"single"`). |
|
|
117
117
|
| | **min_nmf_iterations** | Integer | Value defines the minimum number of iterations to be completed before NMF converges (default: `10000`). |
|
|
@@ -122,7 +122,7 @@ sigProfilerExtractor(input_type, out_put, input_data, reference_genome="GRCh37",
|
|
|
122
122
|
| | **cpu** | Integer | The number of processors to be used to extract the signatures (default: all processors). |
|
|
123
123
|
| | **assignment_cpu** | Integer | Number of processors to be used by SigProfilerAssignment for the final signature assignment step (default: all available). This is independent of the `cpu` parameter. |
|
|
124
124
|
| | **gpu** | Boolean | Defines if the GPU resource will used if available (default: `False`). If `True`, the GPU resources will be used in the computation. *Note: All available CPU processors are used by default, which may cause a memory error. This error can be resolved by reducing the number of CPU processes through the `cpu` parameter.*|
|
|
125
|
-
| | **batch_size** | Integer |
|
|
125
|
+
| | **batch_size** | Integer | Effective only when the GPU is used. Defines how many NMF replicates are grouped into each GPU batch (default: `1`). Every replicate has its own resampling and initialization generators, sample totals, and convergence stopping point, whether it runs alone or in a batch. |
|
|
126
126
|
| **Solution Estimation Thresholds** | | | |
|
|
127
127
|
| | **stability** | Float | The cutoff thresh-hold of the average stability (default: `0.8`). Solutions with average stabilities below this thresh-hold will not be considered. |
|
|
128
128
|
| | **min_stability** | Float | The cutoff thresh-hold of the minimum stability (default: `0.2`). Solutions with minimum stabilities below this thresh-hold will not be considered. |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
version = '1.5.0'
|
|
@@ -77,8 +77,11 @@ def parse_arguments_extractor(args: List[str], description: str) -> argparse.Nam
|
|
|
77
77
|
parser.add_argument(
|
|
78
78
|
"--maximum_signatures",
|
|
79
79
|
type=int,
|
|
80
|
-
default=
|
|
81
|
-
help=
|
|
80
|
+
default=sigpro.DEFAULT_MAXIMUM_SIGNATURES,
|
|
81
|
+
help=(
|
|
82
|
+
"Maximum number of signatures to be extracted "
|
|
83
|
+
f"(default: {sigpro.DEFAULT_MAXIMUM_SIGNATURES})."
|
|
84
|
+
),
|
|
82
85
|
)
|
|
83
86
|
parser.add_argument(
|
|
84
87
|
"--nmf_replicates",
|
|
@@ -97,13 +100,21 @@ def parse_arguments_extractor(args: List[str], description: str) -> argparse.Nam
|
|
|
97
100
|
parser.add_argument(
|
|
98
101
|
"--seeds",
|
|
99
102
|
default="random",
|
|
100
|
-
help=
|
|
103
|
+
help=(
|
|
104
|
+
"Root seed source for reproducible NMF resampling and initialization: "
|
|
105
|
+
"a path to a Seeds.txt file containing one root seed in the Seed "
|
|
106
|
+
"column, or 'random' (default: 'random')."
|
|
107
|
+
),
|
|
101
108
|
)
|
|
102
109
|
parser.add_argument(
|
|
103
110
|
"--batch_size",
|
|
104
111
|
type=int,
|
|
105
112
|
default=1,
|
|
106
|
-
help=
|
|
113
|
+
help=(
|
|
114
|
+
"Number of NMF replicates grouped into each GPU batch. GPU only; "
|
|
115
|
+
"replicates keep independent resampling and initialization generators, "
|
|
116
|
+
"sample totals, and convergence stopping points (default: 1)."
|
|
117
|
+
),
|
|
107
118
|
)
|
|
108
119
|
parser.add_argument(
|
|
109
120
|
"--cpu",
|
|
@@ -138,7 +149,7 @@ def parse_arguments_extractor(args: List[str], description: str) -> argparse.Nam
|
|
|
138
149
|
parser.add_argument(
|
|
139
150
|
"--matrix_normalization",
|
|
140
151
|
default="gmm",
|
|
141
|
-
help="Method of normalizing the genome matrix before it is analyzed by NMF (default: 'gmm'). Options are '
|
|
152
|
+
help="Method of normalizing the genome matrix before it is analyzed by NMF (default: 'gmm'). Options are 'gmm', '100X', 'log2', 'none', or a positive integer used as a manual cutoff (for example 5000).",
|
|
142
153
|
)
|
|
143
154
|
parser.add_argument(
|
|
144
155
|
"--min_nmf_iterations",
|
|
@@ -17,6 +17,15 @@ import torch
|
|
|
17
17
|
import torch.nn
|
|
18
18
|
from torch import nn
|
|
19
19
|
|
|
20
|
+
# Lower bound for W, H and W @ H in the KL multiplicative updates. A multiplicative
|
|
21
|
+
# update cannot move an entry away from exactly zero, so zeros from the NNDSVD
|
|
22
|
+
# initialization or from underflow stay locked for the whole run; the fit can then
|
|
23
|
+
# stall and pass the convergence test early at a worse solution. If a cell of W @ H
|
|
24
|
+
# reaches zero, V / (W @ H) gives inf (NaN where V is also zero), which the next
|
|
25
|
+
# matrix product spreads to every entry of W and H. The original CPU engine (nimfa)
|
|
26
|
+
# applied an equivalent floor after every iteration.
|
|
27
|
+
EPSILON = 1e-16
|
|
28
|
+
|
|
20
29
|
|
|
21
30
|
class NMF:
|
|
22
31
|
def __init__(
|
|
@@ -178,10 +187,13 @@ class NMF:
|
|
|
178
187
|
@property
|
|
179
188
|
def _kl_loss(self):
|
|
180
189
|
# calculate kl_loss in double precision for better convergence criteria
|
|
190
|
+
# xlogy gives 0 where V == 0, where V * log(V / WH) would give NaN. pnmf()
|
|
191
|
+
# raises V to at least 1e-4, so this matters only when NMF is called directly.
|
|
192
|
+
reconstruction = self.reconstruction.clamp(min=EPSILON)
|
|
181
193
|
return (
|
|
182
|
-
(self._V
|
|
194
|
+
torch.xlogy(self._V, self._V / reconstruction).sum(dtype=torch.float64)
|
|
183
195
|
- self._V.sum(dtype=torch.float64)
|
|
184
|
-
+
|
|
196
|
+
+ reconstruction.sum(dtype=torch.float64)
|
|
185
197
|
)
|
|
186
198
|
|
|
187
199
|
@property
|
|
@@ -245,15 +257,17 @@ class NMF:
|
|
|
245
257
|
ones = torch.ones(self._V.shape).type(self._tensor_type)
|
|
246
258
|
for self._iter in range(self.max_iterations):
|
|
247
259
|
ht = self.H.transpose(1, 2)
|
|
248
|
-
numerator = (self._V / (self.W @ self.H)) @ ht
|
|
260
|
+
numerator = (self._V / (self.W @ self.H).clamp(min=EPSILON)) @ ht
|
|
249
261
|
|
|
250
|
-
denomenator = ones @ ht
|
|
262
|
+
denomenator = (ones @ ht).clamp(min=EPSILON)
|
|
251
263
|
self._W *= numerator / denomenator
|
|
264
|
+
self._W.clamp_(min=EPSILON)
|
|
252
265
|
|
|
253
266
|
wt = self.W.transpose(1, 2)
|
|
254
|
-
numerator = wt @ (self._V / (self.W @ self.H))
|
|
255
|
-
denomenator = wt @ ones
|
|
267
|
+
numerator = wt @ (self._V / (self.W @ self.H).clamp(min=EPSILON))
|
|
268
|
+
denomenator = (wt @ ones).clamp(min=EPSILON)
|
|
256
269
|
self._H *= numerator / denomenator
|
|
270
|
+
self._H.clamp_(min=EPSILON)
|
|
257
271
|
if stop_iterations()[0]:
|
|
258
272
|
self._conv = stop_iterations()[1]
|
|
259
273
|
break
|
|
@@ -10,6 +10,15 @@ import torch
|
|
|
10
10
|
import torch.nn
|
|
11
11
|
from torch import nn
|
|
12
12
|
|
|
13
|
+
# Lower bound for W, H and W @ H in the KL multiplicative updates. A multiplicative
|
|
14
|
+
# update cannot move an entry away from exactly zero, so zeros from the NNDSVD
|
|
15
|
+
# initialization or from underflow stay locked for the whole run; the fit can then
|
|
16
|
+
# stall and pass the convergence test early at a worse solution. If a cell of W @ H
|
|
17
|
+
# reaches zero, V / (W @ H) gives inf (NaN where V is also zero), which the next
|
|
18
|
+
# matrix product spreads to every entry of W and H. The original CPU engine (nimfa)
|
|
19
|
+
# applied an equivalent floor after every iteration.
|
|
20
|
+
EPSILON = 1e-16
|
|
21
|
+
|
|
13
22
|
|
|
14
23
|
class NMF:
|
|
15
24
|
def __init__(
|
|
@@ -79,7 +88,32 @@ class NMF:
|
|
|
79
88
|
"""
|
|
80
89
|
Initialise basis and coefficient matrices according to `init_method`
|
|
81
90
|
"""
|
|
82
|
-
if init_method == "random":
|
|
91
|
+
if init_method == "random" and isinstance(self._generator, (list, tuple)):
|
|
92
|
+
# each replicate draws its W and then its H from its own generator, in
|
|
93
|
+
# the same order as nmf_cpu, so it starts from the same point on CPU
|
|
94
|
+
# and GPU however it is batched
|
|
95
|
+
if len(self._generator) != self._V.shape[0]:
|
|
96
|
+
raise ValueError("One generator per replicate is required.")
|
|
97
|
+
W = np.stack(
|
|
98
|
+
[
|
|
99
|
+
g.random((self._V.shape[1], self._rank), dtype=np.float64)
|
|
100
|
+
for g in self._generator
|
|
101
|
+
]
|
|
102
|
+
)
|
|
103
|
+
H = np.stack(
|
|
104
|
+
[
|
|
105
|
+
g.random((self._rank, self._V.shape[2]), dtype=np.float64)
|
|
106
|
+
for g in self._generator
|
|
107
|
+
]
|
|
108
|
+
)
|
|
109
|
+
W = torch.from_numpy(W).cuda()
|
|
110
|
+
H = torch.from_numpy(H).cuda()
|
|
111
|
+
if self._np_dtype is np.float32:
|
|
112
|
+
W = W.float()
|
|
113
|
+
H = H.float()
|
|
114
|
+
return W, H
|
|
115
|
+
|
|
116
|
+
elif init_method == "random":
|
|
83
117
|
W = torch.from_numpy(
|
|
84
118
|
self._generator.random(
|
|
85
119
|
(self._V.shape[0], self._V.shape[1], self._rank), dtype=np.float64
|
|
@@ -102,7 +136,7 @@ class NMF:
|
|
|
102
136
|
for i in range(self._V.shape[0]):
|
|
103
137
|
vin = self._V.cpu().numpy()[i]
|
|
104
138
|
W[i, :, :], H[i, :, :] = nv.initialize(
|
|
105
|
-
vin, self._rank, options={"flag": 0, "generator": self.
|
|
139
|
+
vin, self._rank, options={"flag": 0, "generator": self._replicate_generator(i)}
|
|
106
140
|
)
|
|
107
141
|
|
|
108
142
|
elif init_method == "nndsvda":
|
|
@@ -112,7 +146,7 @@ class NMF:
|
|
|
112
146
|
for i in range(self._V.shape[0]):
|
|
113
147
|
vin = self._V.cpu().numpy()[i]
|
|
114
148
|
W[i, :, :], H[i, :, :] = nv.initialize(
|
|
115
|
-
vin, self._rank, options={"flag": 1, "generator": self.
|
|
149
|
+
vin, self._rank, options={"flag": 1, "generator": self._replicate_generator(i)}
|
|
116
150
|
)
|
|
117
151
|
|
|
118
152
|
elif init_method == "nndsvdar":
|
|
@@ -122,7 +156,7 @@ class NMF:
|
|
|
122
156
|
for i in range(self._V.shape[0]):
|
|
123
157
|
vin = self._V.cpu().numpy()[i]
|
|
124
158
|
W[i, :, :], H[i, :, :] = nv.initialize(
|
|
125
|
-
vin, self._rank, options={"flag": 2, "generator": self.
|
|
159
|
+
vin, self._rank, options={"flag": 2, "generator": self._replicate_generator(i)}
|
|
126
160
|
)
|
|
127
161
|
elif init_method == "nndsvd_min":
|
|
128
162
|
W = np.zeros([self._V.shape[0], self._V.shape[1], self._rank])
|
|
@@ -130,7 +164,7 @@ class NMF:
|
|
|
130
164
|
nv = Nndsvd()
|
|
131
165
|
for i in range(self._V.shape[0]):
|
|
132
166
|
vin = self._V.cpu().numpy()[i]
|
|
133
|
-
w, h = nv.initialize(vin, self._rank, options={"flag": 2, "generator": self.
|
|
167
|
+
w, h = nv.initialize(vin, self._rank, options={"flag": 2, "generator": self._replicate_generator(i)})
|
|
134
168
|
min_X = np.min(vin[vin > 0])
|
|
135
169
|
h[h <= min_X] = min_X
|
|
136
170
|
w[w <= min_X] = min_X
|
|
@@ -143,6 +177,12 @@ class NMF:
|
|
|
143
177
|
H = torch.from_numpy(H).type(self._tensor_type).cuda(self._gpu_id)
|
|
144
178
|
return W, H
|
|
145
179
|
|
|
180
|
+
def _replicate_generator(self, i):
|
|
181
|
+
"""Generator of replicate i: its own one, or the one shared by the batch."""
|
|
182
|
+
if isinstance(self._generator, (list, tuple)):
|
|
183
|
+
return self._generator[i]
|
|
184
|
+
return self._generator
|
|
185
|
+
|
|
146
186
|
@property
|
|
147
187
|
def reconstruction(self):
|
|
148
188
|
return self.W @ self.H
|
|
@@ -169,10 +209,13 @@ class NMF:
|
|
|
169
209
|
@property
|
|
170
210
|
def _kl_loss(self):
|
|
171
211
|
# calculate kl_loss in double precision for better convergence criteria
|
|
212
|
+
# xlogy gives 0 where V == 0, where V * log(V / WH) would give NaN. pnmf()
|
|
213
|
+
# raises V to at least 1e-4, so this matters only when NMF is called directly.
|
|
214
|
+
reconstruction = self.reconstruction.clamp(min=EPSILON)
|
|
172
215
|
return (
|
|
173
|
-
(self._V
|
|
216
|
+
torch.xlogy(self._V, self._V / reconstruction).sum(dtype=torch.float64)
|
|
174
217
|
- self._V.sum(dtype=torch.float64)
|
|
175
|
-
+
|
|
218
|
+
+ reconstruction.sum(dtype=torch.float64)
|
|
176
219
|
)
|
|
177
220
|
|
|
178
221
|
@property
|
|
@@ -187,6 +230,47 @@ class NMF:
|
|
|
187
230
|
self._prev_loss = self._kl_loss
|
|
188
231
|
return False
|
|
189
232
|
|
|
233
|
+
def _replicate_kl_loss(self, i):
|
|
234
|
+
# _kl_loss of replicate i alone (the same expression, on a batch of one)
|
|
235
|
+
V = self._V[i : i + 1]
|
|
236
|
+
reconstruction = (self.W[i : i + 1] @ self.H[i : i + 1]).clamp(min=EPSILON)
|
|
237
|
+
return (
|
|
238
|
+
torch.xlogy(V, V / reconstruction).sum(dtype=torch.float64)
|
|
239
|
+
- V.sum(dtype=torch.float64)
|
|
240
|
+
+ reconstruction.sum(dtype=torch.float64)
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
def _stop_converged_replicates(self):
|
|
244
|
+
"""
|
|
245
|
+
The single-replicate stopping rule of _loss_converged, applied to every
|
|
246
|
+
replicate of the batch: every test_conv iterations, a replicate stops when
|
|
247
|
+
its relative loss change is below the tolerance and more than
|
|
248
|
+
min_iterations have run. A stopped replicate keeps its W and H, so each
|
|
249
|
+
replicate ends where it would have ended on its own.
|
|
250
|
+
"""
|
|
251
|
+
if self._iter % self._test_conv != 0:
|
|
252
|
+
return
|
|
253
|
+
loss = torch.stack(
|
|
254
|
+
[self._replicate_kl_loss(i) for i in range(self._V.shape[0])]
|
|
255
|
+
)
|
|
256
|
+
if not self._iter:
|
|
257
|
+
self._loss_init = loss
|
|
258
|
+
self._prev_loss = loss
|
|
259
|
+
return
|
|
260
|
+
converged = ((self._prev_loss - loss) / self._loss_init) < self._tolerance
|
|
261
|
+
self._prev_loss = torch.where(converged, self._prev_loss, loss)
|
|
262
|
+
stop = converged & self._active & (self._iter > self.min_iterations)
|
|
263
|
+
self._replicate_conv[stop.cpu()] = self._iter
|
|
264
|
+
self._active &= ~stop
|
|
265
|
+
|
|
266
|
+
@property
|
|
267
|
+
def replicate_conv(self):
|
|
268
|
+
"""Iteration at which each replicate converged (0 if it did not)."""
|
|
269
|
+
try:
|
|
270
|
+
return [int(c) for c in self._replicate_conv]
|
|
271
|
+
except AttributeError:
|
|
272
|
+
return [int(self.conv)] * self._V.shape[0]
|
|
273
|
+
|
|
190
274
|
def fit(self, beta=1):
|
|
191
275
|
"""
|
|
192
276
|
Fit the basis (W) and coefficient (H) matrices to the input matrix (V) using multiplicative updates and
|
|
@@ -232,20 +316,31 @@ class NMF:
|
|
|
232
316
|
ones = (
|
|
233
317
|
torch.ones(self._V.shape).type(self._tensor_type).cuda(self._gpu_id)
|
|
234
318
|
)
|
|
319
|
+
self._active = torch.ones(
|
|
320
|
+
self._V.shape[0], dtype=torch.bool, device=self._V.device
|
|
321
|
+
)
|
|
322
|
+
self._replicate_conv = torch.zeros(self._V.shape[0], dtype=torch.long)
|
|
235
323
|
for self._iter in range(self.max_iterations):
|
|
324
|
+
# replicates that have already converged keep their W and H
|
|
325
|
+
active = self._active[:, None, None]
|
|
236
326
|
ht = self.H.transpose(1, 2)
|
|
237
|
-
numerator = (self._V / (self.W @ self.H)) @ ht
|
|
327
|
+
numerator = (self._V / (self.W @ self.H).clamp(min=EPSILON)) @ ht
|
|
238
328
|
|
|
239
|
-
denomenator = ones @ ht
|
|
240
|
-
self.
|
|
329
|
+
denomenator = (ones @ ht).clamp(min=EPSILON)
|
|
330
|
+
W = (self.W * (numerator / denomenator)).clamp(min=EPSILON)
|
|
331
|
+
self._W = torch.where(active, W, self._W)
|
|
241
332
|
|
|
242
333
|
wt = self.W.transpose(1, 2)
|
|
243
|
-
numerator = wt @ (self._V / (self.W @ self.H))
|
|
244
|
-
denomenator = wt @ ones
|
|
245
|
-
self.
|
|
246
|
-
|
|
247
|
-
|
|
334
|
+
numerator = wt @ (self._V / (self.W @ self.H).clamp(min=EPSILON))
|
|
335
|
+
denomenator = (wt @ ones).clamp(min=EPSILON)
|
|
336
|
+
H = (self.H * (numerator / denomenator)).clamp(min=EPSILON)
|
|
337
|
+
self._H = torch.where(active, H, self._H)
|
|
338
|
+
|
|
339
|
+
self._stop_converged_replicates()
|
|
340
|
+
if not self._active.any():
|
|
248
341
|
break
|
|
342
|
+
if self._V.shape[0] == 1 and self._replicate_conv[0] > 0:
|
|
343
|
+
self._conv = int(self._replicate_conv[0])
|
|
249
344
|
|
|
250
345
|
else:
|
|
251
346
|
for self._iter in range(self.max_iterations):
|