diffbio 0.1.9__tar.gz → 0.1.10__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.
- {diffbio-0.1.9 → diffbio-0.1.10}/PKG-INFO +8 -6
- {diffbio-0.1.9 → diffbio-0.1.10}/pyproject.toml +12 -9
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/losses/__init__.py +2 -0
- diffbio-0.1.10/src/diffbio/losses/_negative_binomial.py +388 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/losses/statistical_losses.py +16 -45
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/statistical/nb_glm.py +5 -24
- {diffbio-0.1.9 → diffbio-0.1.10}/.gitignore +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/LICENSE +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/README.md +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/benchmarks/README.md +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/benchmarks/crossmodality/README.md +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/examples/README.md +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/configs.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/constants.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/base_operators.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/data_types.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/gnn_components.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/graph_utils.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/neural_components.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/optimal_transport.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/_projections_permutahedron.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/_projections_simplex.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/_projections_transport.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/_sorting_network.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/_types.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/_utils.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/autograd_safe.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/comparison.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/elementwise.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/logical.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/normalization.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/quantile.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/selection.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/sorting.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/straight_through.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/uncertainty.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/evaluation/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/evaluation/adapters.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/evaluation/graders.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/evaluation/problem.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/evaluation/runner.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/losses/alignment_losses.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/losses/biological_regularization.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/losses/metric_losses.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/losses/self_supervised_losses.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/losses/singlecell_losses.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/_count_vae.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/_loss_balancing.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/_masked_gene_transformer.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/_transformer_validation.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/alignment/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/alignment/profile_hmm.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/alignment/scoring.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/alignment/smith_waterman.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/alignment/soft_msa.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/assembly/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/assembly/gnn_assembly.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/assembly/metagenomic_binning.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/crispr/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/crispr/guide_scoring.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/drug_discovery/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/drug_discovery/_graph_utils.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/drug_discovery/admet_predictor.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/drug_discovery/attentive_fp.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/drug_discovery/dti.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/drug_discovery/fingerprint.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/drug_discovery/maccs_keys.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/drug_discovery/message_passing.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/drug_discovery/primitives.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/drug_discovery/property_predictor.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/drug_discovery/similarity.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/epigenomics/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/epigenomics/chromatin_state.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/epigenomics/contextual.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/epigenomics/fno_peak_calling.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/epigenomics/peak_calling.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/foundation_models/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/foundation_models/adapters.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/foundation_models/contracts.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/foundation_models/embedding_probe.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/foundation_models/experimental.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/foundation_models/foundation_model.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/foundation_models/frozen.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/foundation_models/precomputed.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/foundation_models/transformer_encoder.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/mapping/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/mapping/neural_mapper.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/metabolomics/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/metabolomics/isotope_envelope.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/metabolomics/soft_centroiding.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/metabolomics/spectral_similarity.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/molecular_dynamics/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/molecular_dynamics/force_field.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/molecular_dynamics/integrator.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/molecular_dynamics/primitives.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/multiomics/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/multiomics/hic_contact.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/multiomics/multiomics_vae.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/multiomics/spatial_deconvolution.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/multiomics/spatial_gene_detection.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/normalization/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/normalization/arcsinh_cofactor.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/normalization/differentiable_pca.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/normalization/embedding.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/normalization/learnable_normalization.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/normalization/learnable_orthogonal_projection.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/normalization/learnable_projection.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/normalization/matrix_free_pca.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/normalization/phate.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/normalization/scaling.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/normalization/soft_pca.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/normalization/umap.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/normalization/vae_normalizer.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/population/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/population/ancestry_estimation.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/preprocessing/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/preprocessing/adapter_removal.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/preprocessing/duplicate_filter.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/preprocessing/error_correction.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/protein/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/protein/secondary_structure.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/quality_filter.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/rna_structure/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/rna_structure/rna_folding.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/rnaseq/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/rnaseq/motif_discovery.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/rnaseq/splicing_psi.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/ambient_removal.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/archetypes.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/batch_correction.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/cell_annotation.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/communication.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/differential_distribution.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/doublet_detection.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/downsampling.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/enhanced_batch_correction.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/grn_inference.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/imputation.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/knockdown_filter.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/ot_trajectory.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/simulation.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/sindy_grn.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/soft_clustering.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/soft_hvg.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/spatial_domains.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/stochastic_gate_selector.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/switch_de.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/trajectory.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/velocity.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/statistical/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/statistical/em_quantification.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/statistical/hmm.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/variant/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/variant/classifier.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/variant/cnn_classifier.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/variant/cnv_segmentation.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/variant/deepvariant_pileup.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/variant/learnable_pileup.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/variant/pileup.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/variant/quality_recalibration.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/pipelines/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/pipelines/adapters.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/pipelines/differential_expression.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/pipelines/enhanced_variant_calling.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/pipelines/joint_preprocessing.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/pipelines/joint_training.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/pipelines/minibatch_training.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/pipelines/perturbation.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/pipelines/preprocessing.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/pipelines/single_cell.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/pipelines/variant_calling.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/reductions/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/reductions/base.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/reductions/pca_reduction.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/reductions/tfidf_reduction.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/samplers/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/samplers/perturbation_sampler.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sequences/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sequences/dna.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sequences/kmer.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/_anndata_shared.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/_batch_iteration.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/_benchmark_source.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/_indexed_batch_source.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/_utils.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/anndata_interop.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/anndata_source.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/archive_ii.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/balifam.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/bam.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/bengrn_ground_truth.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/contextual_epigenomics.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/dti.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/embeddings.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/encode_peaks.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/fasta.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/immune_human.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/indexed_embeddings.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/indexed_view.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/molnet.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/multiomics.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/pancreas.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/perturbation/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/perturbation/_types.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/perturbation/_utils.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/perturbation/concat_source.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/perturbation/control_mapping.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/perturbation/experiment_config.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/perturbation/h5_metadata_cache.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/perturbation/output_space.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/perturbation/perturbation_source.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/seqfish.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/sequence_foundation.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/singlecell_foundation.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/splitters/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/splitters/base.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/splitters/molecular.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/splitters/perturbation.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/splitters/random.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/splitters/sequence.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/utils/__init__.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/utils/dependency_runtime.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/utils/nn_utils.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/utils/quality.py +0 -0
- {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/utils/training.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: diffbio
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.10
|
|
4
4
|
Summary: End-to-end differentiable bioinformatics for JAX/Flax NNX: alignment, mapping, assembly, variant calling, RNA-seq, single-cell, epigenomics, CRISPR, metabolomics, multi-omics, protein and RNA structure, molecular dynamics and drug-discovery operators composed into trainable pipelines on datarax, artifex, opifex and calibrax
|
|
5
5
|
Project-URL: Bug Tracker, https://github.com/avitai/DiffBio/issues
|
|
6
6
|
Project-URL: Documentation, https://diffbio.readthedocs.io
|
|
@@ -45,22 +45,22 @@ Classifier: Topic :: Software Development :: Libraries
|
|
|
45
45
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
46
46
|
Requires-Python: <3.14,>=3.12
|
|
47
47
|
Requires-Dist: anndata>=0.9.1
|
|
48
|
-
Requires-Dist: avitai-artifex>=0.1.
|
|
48
|
+
Requires-Dist: avitai-artifex>=0.1.15
|
|
49
49
|
Requires-Dist: beartype>=0.14.1
|
|
50
50
|
Requires-Dist: biopython>=1.81
|
|
51
|
-
Requires-Dist: calibrax>=0.1.
|
|
51
|
+
Requires-Dist: calibrax>=0.1.17
|
|
52
52
|
Requires-Dist: chex>=0.1.7
|
|
53
|
-
Requires-Dist: datarax
|
|
53
|
+
Requires-Dist: datarax<0.1.18,>=0.1.17
|
|
54
54
|
Requires-Dist: flax>=0.12.9
|
|
55
55
|
Requires-Dist: h5py>=3.7
|
|
56
56
|
Requires-Dist: jax-md>=0.2.27
|
|
57
57
|
Requires-Dist: jax>=0.11.1
|
|
58
58
|
Requires-Dist: jaxtyping>=0.2.20
|
|
59
59
|
Requires-Dist: numpy>=1.24
|
|
60
|
-
Requires-Dist: opifex>=0.2.
|
|
60
|
+
Requires-Dist: opifex>=0.2.10
|
|
61
61
|
Requires-Dist: optax>=0.2.8
|
|
62
62
|
Requires-Dist: scipy>=1.10
|
|
63
|
-
Requires-Dist: substrax>=0.1.
|
|
63
|
+
Requires-Dist: substrax>=0.1.25
|
|
64
64
|
Provides-Extra: all
|
|
65
65
|
Requires-Dist: bandit[toml]>=1.8.6; extra == 'all'
|
|
66
66
|
Requires-Dist: beartype>=0.14.1; extra == 'all'
|
|
@@ -83,6 +83,7 @@ Requires-Dist: mkdocs-material>=9.6.7; extra == 'all'
|
|
|
83
83
|
Requires-Dist: mkdocs>=1.6.1; extra == 'all'
|
|
84
84
|
Requires-Dist: mkdocstrings-python>=1.1.2; extra == 'all'
|
|
85
85
|
Requires-Dist: mkdocstrings>=0.28.3; extra == 'all'
|
|
86
|
+
Requires-Dist: mpmath>=1.3; extra == 'all'
|
|
86
87
|
Requires-Dist: optimistix>=0.0.9; extra == 'all'
|
|
87
88
|
Requires-Dist: ott-jax>=0.5.0; extra == 'all'
|
|
88
89
|
Requires-Dist: pandas>=2.0; extra == 'all'
|
|
@@ -199,6 +200,7 @@ Requires-Dist: ott-jax>=0.5.0; extra == 'soft-ops-ot'
|
|
|
199
200
|
Provides-Extra: test
|
|
200
201
|
Requires-Dist: beartype>=0.14.1; extra == 'test'
|
|
201
202
|
Requires-Dist: coverage>=7; extra == 'test'
|
|
203
|
+
Requires-Dist: mpmath>=1.3; extra == 'test'
|
|
202
204
|
Requires-Dist: pytest-asyncio>=0.23; extra == 'test'
|
|
203
205
|
Requires-Dist: pytest-benchmark>=4; extra == 'test'
|
|
204
206
|
Requires-Dist: pytest-cov>=6.1.1; extra == 'test'
|
|
@@ -36,17 +36,18 @@ classifiers = [
|
|
|
36
36
|
"License :: OSI Approved :: MIT License"
|
|
37
37
|
]
|
|
38
38
|
dependencies = [
|
|
39
|
-
# Datarax - operator, source, and pipeline contracts
|
|
40
|
-
|
|
39
|
+
# Datarax - operator, source, and pipeline contracts. Capped below 0.1.18, which removes the
|
|
40
|
+
# eager source helpers diffbio.sources imports; the release that ports the sources raises it.
|
|
41
|
+
"datarax>=0.1.17,<0.1.18",
|
|
41
42
|
# Artifex - modeling, modality, and transformer substrate
|
|
42
|
-
"avitai-artifex>=0.1.
|
|
43
|
+
"avitai-artifex>=0.1.15",
|
|
43
44
|
# Opifex - scientific ML, operator learning, and advanced optimization
|
|
44
|
-
"opifex>=0.2.
|
|
45
|
+
"opifex>=0.2.10",
|
|
45
46
|
# Calibrax - benchmarking, comparison, profiling, and regression control
|
|
46
|
-
"calibrax>=0.1.
|
|
47
|
+
"calibrax>=0.1.17",
|
|
47
48
|
# Substrax - device identity, meshes, runtime settings and checkpoint store shared by the
|
|
48
|
-
# siblings; 0.1.
|
|
49
|
-
"substrax>=0.1.
|
|
49
|
+
# siblings; calibrax 0.1.17 requires 0.1.25.
|
|
50
|
+
"substrax>=0.1.25",
|
|
50
51
|
# JAX ecosystem
|
|
51
52
|
"beartype>=0.14.1",
|
|
52
53
|
"chex>=0.1.7",
|
|
@@ -71,7 +72,7 @@ license = {file = "LICENSE"}
|
|
|
71
72
|
name = "diffbio"
|
|
72
73
|
readme = "README.md"
|
|
73
74
|
requires-python = ">=3.12,<3.14"
|
|
74
|
-
version = "0.1.
|
|
75
|
+
version = "0.1.10"
|
|
75
76
|
|
|
76
77
|
[project.optional-dependencies]
|
|
77
78
|
all = ["diffbio[benchmark,chem,cuda12,dev,docs,genomics,metal,soft-ops-advanced,soft-ops-ot,test,torch-io]"]
|
|
@@ -148,7 +149,9 @@ test = [
|
|
|
148
149
|
"pytest-timeout>=2.1",
|
|
149
150
|
"pytest-xdist>=3.6",
|
|
150
151
|
# tests/test_ci_shards.py reads the CI workflow.
|
|
151
|
-
"pyyaml>=6"
|
|
152
|
+
"pyyaml>=6",
|
|
153
|
+
# The exact references of tests/losses/test_count_likelihood_precision.py.
|
|
154
|
+
"mpmath>=1.3"
|
|
152
155
|
]
|
|
153
156
|
torch-io = ["torch>=1.13.0"]
|
|
154
157
|
|
|
@@ -4,6 +4,7 @@ This module provides loss functions for training differentiable bioinformatics
|
|
|
4
4
|
pipelines, including biological regularization to prevent adversarial optimization.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
from diffbio.losses._negative_binomial import nb_log_prob
|
|
7
8
|
from diffbio.losses.alignment_losses import (
|
|
8
9
|
AlignmentConsistencyLoss,
|
|
9
10
|
AlignmentScoreLoss,
|
|
@@ -58,6 +59,7 @@ __all__ = [
|
|
|
58
59
|
"HMMLikelihoodLoss",
|
|
59
60
|
"NegativeBinomialLoss",
|
|
60
61
|
"VAELoss",
|
|
62
|
+
"nb_log_prob",
|
|
61
63
|
"zinb_negative_log_likelihood",
|
|
62
64
|
# Self-supervised losses
|
|
63
65
|
"MaskedValueLoss",
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
"""The negative binomial log-probability by Loader's saddle-point method, with closed-form partials.
|
|
2
|
+
|
|
3
|
+
Catherine Loader, "Fast and Accurate Computation of Binomial Probabilities" (2002, revising
|
|
4
|
+
2000) writes the binomial mass through the Stirling remainder ``delta`` (eq. 4) and the
|
|
5
|
+
deviance ``bd0`` (eqs. 3, 6), so no two large logarithms are ever subtracted. The negative
|
|
6
|
+
binomial follows from the identity
|
|
7
|
+
|
|
8
|
+
NB(x; theta, mu) = theta / (x + theta) * b(theta; x + theta, theta / (theta + mu)),
|
|
9
|
+
|
|
10
|
+
which with ``n = x + theta`` reduces to
|
|
11
|
+
|
|
12
|
+
log NB = delta(n) - delta(theta) - delta(x) - bd0(theta, n P) - bd0(x, n Q)
|
|
13
|
+
- (log(2 pi) + log(x) + log1p(x / theta)) / 2,
|
|
14
|
+
|
|
15
|
+
``P = theta / (theta + mu)`` and ``Q = mu / (theta + mu)``; it is Loader's Poisson form (eq. 7)
|
|
16
|
+
as ``theta`` grows. The direct formula subtracts ``lgamma`` values of size ~7e7 at 5e6 counts,
|
|
17
|
+
where float32 resolves 4 (issue #36). A zero count takes R's ``dnbinom_mu`` form,
|
|
18
|
+
``theta log(theta / (theta + mu))`` through ``log1p`` of the small ratio.
|
|
19
|
+
|
|
20
|
+
Two choices follow from float32 rather than from the paper, which works in float64:
|
|
21
|
+
|
|
22
|
+
- The Stirling series is used from the argument where its first omitted term falls below the
|
|
23
|
+
dtype's ``eps`` relative to ``delta``, and ``lgamma`` below it.
|
|
24
|
+
- ``bd0`` is Welinder's ``-x log1pmx((m - x) / x)`` (R bug PR#15628), with the difference
|
|
25
|
+
``x - m`` computed in closed form, ``theta (mu - x) / (theta + mu)``. Loader's direct form
|
|
26
|
+
``x log(x / m) + m - x`` cancels beyond his series radius and takes the log of a quotient,
|
|
27
|
+
which XLA's GPU float32 division rounds to about one ULP rather than to the nearest.
|
|
28
|
+
|
|
29
|
+
The derivatives are not obtained by differentiating these branches: every branch would run in
|
|
30
|
+
the backward pass, and autodiff of the formula cancels. A ``jax.custom_jvp`` gives the exact
|
|
31
|
+
partials instead, each written without a cancelling difference:
|
|
32
|
+
|
|
33
|
+
d/dmu = theta (x - mu) / (mu (theta + mu)),
|
|
34
|
+
d/dtheta = log1pmx(t) + x / (2 theta (theta + x)) + delta'(theta + x) - delta'(theta),
|
|
35
|
+
d/dx = log1p(s) - (2x + theta) / (2x (x + theta)) + delta'(x + theta) - delta'(x),
|
|
36
|
+
|
|
37
|
+
with ``t = (x - mu) / (theta + mu)``, ``s = theta (mu - x) / (x (theta + mu))`` and
|
|
38
|
+
``log1pmx(t) = log1p(t) - t``. They follow from ``psi(z) = delta'(z) + log(z) - 1 / (2z)``
|
|
39
|
+
applied to the textbook partials ``psi(x + theta) - psi(theta) + log(P) + (mu - x) / (theta +
|
|
40
|
+
mu)`` and ``psi(x + theta) - psi(x + 1) + log(Q)``, whose terms are of size ``1 / theta`` while
|
|
41
|
+
the partial in ``theta`` is of size ``1 / theta**2``. No published source states them; each piece
|
|
42
|
+
is published: ``log1pmx`` by its artanh series (as R's ``log1pmx`` evaluates it), and
|
|
43
|
+
``delta'`` by the asymptotic series of the digamma function (DLMF 5.11.2).
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
import functools
|
|
47
|
+
import math
|
|
48
|
+
|
|
49
|
+
import jax
|
|
50
|
+
import jax.numpy as jnp
|
|
51
|
+
from jax.custom_derivatives import SymbolicZero
|
|
52
|
+
from jax.scipy.special import digamma, gammaln
|
|
53
|
+
from jaxtyping import Array, Float
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
_HALF_LOG_TWO_PI = 0.5 * math.log(2.0 * math.pi)
|
|
57
|
+
|
|
58
|
+
# Stirling's series for log(z!) - log(sqrt(2 pi z) (z / e)**z): B_{2k} / (2k (2k - 1)) z**(1-2k)
|
|
59
|
+
# (Abramowitz and Stegun 6.1.40; Loader eq. 4 lists the first three).
|
|
60
|
+
_STIRLING_COEFFICIENTS = (
|
|
61
|
+
1.0 / 12.0,
|
|
62
|
+
-1.0 / 360.0,
|
|
63
|
+
1.0 / 1260.0,
|
|
64
|
+
-1.0 / 1680.0,
|
|
65
|
+
1.0 / 1188.0,
|
|
66
|
+
-691.0 / 360360.0,
|
|
67
|
+
1.0 / 156.0,
|
|
68
|
+
)
|
|
69
|
+
# The first omitted coefficient, -3617 / 122400, bounds the truncation error of the alternating
|
|
70
|
+
# series above (Whittaker and Watson, section 12.33).
|
|
71
|
+
_STIRLING_FIRST_OMITTED = 3617.0 / 122400.0
|
|
72
|
+
|
|
73
|
+
# Integer counts below this, where theta is below delta'(z)'s series start too, take the partial
|
|
74
|
+
# in theta as an exact harmonic sum: one division per term, so a few at most.
|
|
75
|
+
_HARMONIC_COUNTS = 4
|
|
76
|
+
|
|
77
|
+
# log1pmx's artanh series runs on this interval of t and log(1 + t) - t outside it, where it
|
|
78
|
+
# loses at most log2(0.5 / 0.193), 1.4 bits, at t = -0.5.
|
|
79
|
+
_LOG1PMX_SERIES_INTERVAL = (-0.5, 1.0)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _stirling_series_start(dtype: jnp.dtype) -> int:
|
|
83
|
+
"""The smallest ``z`` where the series' truncation error is below ``eps * delta(z)``.
|
|
84
|
+
|
|
85
|
+
``delta(z) > 1 / (12 z + 1)`` and the truncation error is below the first omitted term
|
|
86
|
+
``c z**-15``, so ``c z**-15 <= eps / (12 z + 1)`` holds from ``z = (13 c / eps)**(1/14)``.
|
|
87
|
+
"""
|
|
88
|
+
eps = float(jnp.finfo(dtype).eps)
|
|
89
|
+
return math.ceil((13.0 * _STIRLING_FIRST_OMITTED / eps) ** (1.0 / 14.0))
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _exact_stirling_remainders(dtype: jnp.dtype) -> dict[int, float]:
|
|
93
|
+
"""``delta(n)`` at the integers below the series start, evaluated in float64.
|
|
94
|
+
|
|
95
|
+
R's ``stirlerr`` keeps such exact values for small arguments: below the series start
|
|
96
|
+
``delta`` otherwise comes from ``lgamma`` minus terms many times its size (at ``n = 2``,
|
|
97
|
+
0.041 left from 0.69 and 1.73). Rounded once to ``dtype``, each value is correctly rounded.
|
|
98
|
+
"""
|
|
99
|
+
return {
|
|
100
|
+
n: math.lgamma(n + 1.0) - (n + 0.5) * math.log(n) + n - _HALF_LOG_TWO_PI
|
|
101
|
+
for n in range(1, _stirling_series_start(dtype))
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _stirling_derivative_series_start(dtype: jnp.dtype) -> int:
|
|
106
|
+
"""The smallest ``z`` where the derivative series' truncation is below ``eps * |delta'(z)|``.
|
|
107
|
+
|
|
108
|
+
The series of ``delta'`` is that of ``psi(z) - log(z) + 1 / (2z)`` (DLMF 5.11.2), whose
|
|
109
|
+
remainder for real ``z > 0`` is below the first omitted term (DLMF 5.11(ii)),
|
|
110
|
+
``15 c z**-16``. Its terms alternate and decrease for ``z >= 1``, so ``|delta'(z)| >= (1 -
|
|
111
|
+
1 / 10) / (12 z**2) > 1 / (14 z**2)``, and ``15 c z**-16 <= eps / (14 z**2)`` holds from
|
|
112
|
+
``z = (210 c / eps)**(1/14)``.
|
|
113
|
+
"""
|
|
114
|
+
eps = float(jnp.finfo(dtype).eps)
|
|
115
|
+
order = 2 * len(_STIRLING_COEFFICIENTS) + 1
|
|
116
|
+
return math.ceil((14.0 * order * _STIRLING_FIRST_OMITTED / eps) ** (1.0 / 14.0))
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _log1pmx_truncation(t: float, terms: int) -> float:
|
|
120
|
+
"""The bound ``2 |r| y**(K+1) / ((2K + 3)(1 - y))`` on dropping ``sum_{k >= K}``."""
|
|
121
|
+
r = t / (2.0 + t)
|
|
122
|
+
y = r * r
|
|
123
|
+
return 2.0 * abs(r) * y ** (terms + 1) / ((2 * terms + 3) * (1.0 - y))
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _log1pmx_series_terms(dtype: jnp.dtype) -> int:
|
|
127
|
+
"""Terms of ``sum_k y**k / (2k + 3)`` that reach ``eps`` relative to ``log1pmx``.
|
|
128
|
+
|
|
129
|
+
Checked at the interval's ends, where ``|r|`` and so the truncation are largest.
|
|
130
|
+
"""
|
|
131
|
+
eps = float(jnp.finfo(dtype).eps)
|
|
132
|
+
terms = 1
|
|
133
|
+
while any(
|
|
134
|
+
_log1pmx_truncation(t, terms) > eps * abs(math.log1p(t) - t)
|
|
135
|
+
for t in _LOG1PMX_SERIES_INTERVAL
|
|
136
|
+
):
|
|
137
|
+
terms += 1
|
|
138
|
+
return terms
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def stirling_remainder(z: Float[Array, "..."]) -> Float[Array, "..."]:
|
|
142
|
+
"""``delta(z) = log(z!) - log(sqrt(2 pi z) (z / e)**z)`` for ``z > 0`` (Loader eq. 4).
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
z: Positive arguments.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
The remainder of Stirling's approximation, elementwise; exact values at the integers
|
|
149
|
+
below the series start.
|
|
150
|
+
"""
|
|
151
|
+
start = _stirling_series_start(z.dtype)
|
|
152
|
+
large = z >= start
|
|
153
|
+
# Each branch sees an argument in its own domain, so the side the where drops stays finite.
|
|
154
|
+
z_series = jnp.where(large, z, start)
|
|
155
|
+
inverse_square = 1.0 / (z_series * z_series)
|
|
156
|
+
series = jnp.zeros_like(z)
|
|
157
|
+
for coefficient in reversed(_STIRLING_COEFFICIENTS):
|
|
158
|
+
series = series * inverse_square + coefficient
|
|
159
|
+
series = series / z_series
|
|
160
|
+
z_direct = jnp.where(large, 1.0, z)
|
|
161
|
+
direct = gammaln(z_direct + 1.0) - (z_direct + 0.5) * jnp.log(z_direct) + z_direct
|
|
162
|
+
result = jnp.where(large, series, direct - _HALF_LOG_TWO_PI)
|
|
163
|
+
for n, exact in _exact_stirling_remainders(z.dtype).items():
|
|
164
|
+
result = jnp.where(z == n, exact, result)
|
|
165
|
+
return result
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def stirling_remainder_derivative(z: Float[Array, "..."]) -> Float[Array, "..."]:
|
|
169
|
+
"""``delta'(z) = psi(z + 1) - log(z) - 1 / (2z)`` for ``z > 0``.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
z: Positive arguments.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
The derivative of the Stirling remainder, by DLMF 5.11.2's series where it reaches the
|
|
176
|
+
dtype's ``eps`` and by the digamma function below.
|
|
177
|
+
"""
|
|
178
|
+
start = _stirling_derivative_series_start(z.dtype)
|
|
179
|
+
large = z >= start
|
|
180
|
+
# The second derivatives differentiate both sides, so each stays in its own domain.
|
|
181
|
+
z_series = jnp.where(large, z, start)
|
|
182
|
+
inverse_square = 1.0 / (z_series * z_series)
|
|
183
|
+
series = jnp.zeros_like(z)
|
|
184
|
+
for order, coefficient in reversed(list(enumerate(_STIRLING_COEFFICIENTS, start=1))):
|
|
185
|
+
series = series * inverse_square - (2 * order - 1) * coefficient
|
|
186
|
+
series = series * inverse_square
|
|
187
|
+
z_direct = jnp.where(large, 1.0, z)
|
|
188
|
+
direct = digamma(z_direct + 1.0) - jnp.log(z_direct) - 0.5 / z_direct
|
|
189
|
+
return jnp.where(large, series, direct)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def log1pmx(t: Float[Array, "..."], one_plus_t: Float[Array, "..."]) -> Float[Array, "..."]:
|
|
193
|
+
"""``log(1 + t) - t`` for ``t > -1``.
|
|
194
|
+
|
|
195
|
+
Inside ``[-0.5, 1]`` it is ``r (2 y S(y) - t)`` with ``r = t / (2 + t)``, ``y = r**2`` and
|
|
196
|
+
``S(y) = sum_k y**k / (2k + 3)``, from ``log(1 + t) = 2 artanh(r)`` with the cancelling
|
|
197
|
+
``2r - t`` formed as ``-r t``; outside, ``log(1 + t) - t`` loses at most 1.4 bits.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
t: Arguments above -1.
|
|
201
|
+
one_plus_t: ``1 + t`` computed in closed form by the caller, so that it keeps its
|
|
202
|
+
relative accuracy as ``t`` approaches -1.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
``log(1 + t) - t``, elementwise.
|
|
206
|
+
"""
|
|
207
|
+
lower, upper = _LOG1PMX_SERIES_INTERVAL
|
|
208
|
+
inside = (t >= lower) & (t <= upper)
|
|
209
|
+
t_series = jnp.where(inside, t, 0.0)
|
|
210
|
+
r = t_series / (2.0 + t_series)
|
|
211
|
+
y = r * r
|
|
212
|
+
total = jnp.zeros_like(y)
|
|
213
|
+
for k in reversed(range(_log1pmx_series_terms(t.dtype))):
|
|
214
|
+
total = total * y + 1.0 / (2 * k + 3)
|
|
215
|
+
series = r * (2.0 * y * total - t_series)
|
|
216
|
+
one_plus_t_direct = jnp.where(inside, 1.0, one_plus_t)
|
|
217
|
+
return jnp.where(inside, series, jnp.log(one_plus_t_direct) - t)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def binomial_deviance(
|
|
221
|
+
x: Float[Array, "..."], m: Float[Array, "..."], difference: Float[Array, "..."]
|
|
222
|
+
) -> Float[Array, "..."]:
|
|
223
|
+
"""``bd0(x, m) = x log(x / m) + m - x`` for ``x, m > 0`` (Loader eq. 6).
|
|
224
|
+
|
|
225
|
+
It is ``-x log1pmx(t)`` with ``t = (m - x) / x``, M. Welinder's form (R bug PR#15628; DPQ's
|
|
226
|
+
``bd0_l1pm``): from ``m = x (1 + t)``, ``x log(x / m) = -x log1p(t)`` and ``m - x = x t``.
|
|
227
|
+
Nothing in it cancels, and for small ``|t|`` it is ``log1pmx``'s artanh series, the series of
|
|
228
|
+
Loader's eq. 6. Loader's direct ``x log(x / m) + (m - x)``, used beyond his series radius,
|
|
229
|
+
cancels and takes the log of ``x / m``, a quotient XLA's GPU float32 division rounds to
|
|
230
|
+
about one ULP rather than to the nearest: at ``(x, mu, theta) = (1e5, 5e4, 1e5)`` on an RTX
|
|
231
|
+
4090 that put 20 ULP of error on ``bd0(theta, n P)``, against 0.4 ULP on a CPU.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
x: Positive first arguments.
|
|
235
|
+
m: Positive second arguments.
|
|
236
|
+
difference: ``x - m``, computed without subtracting the two.
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
The deviance term, elementwise.
|
|
240
|
+
"""
|
|
241
|
+
return -x * log1pmx(-difference / x, m / x)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@jax.custom_jvp
|
|
245
|
+
def nb_log_prob(
|
|
246
|
+
counts: Float[Array, "..."], mu: Float[Array, "..."], theta: Float[Array, "..."]
|
|
247
|
+
) -> Float[Array, "..."]:
|
|
248
|
+
"""``log NB(x | mu, theta)`` in the mean and inverse-dispersion parameterisation.
|
|
249
|
+
|
|
250
|
+
Differentiable in all three arguments through the closed-form partials of the module
|
|
251
|
+
docstring; in ``counts``, of the continuous extension through the gamma function.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
counts: Non-negative counts ``x``.
|
|
255
|
+
mu: Positive means, broadcastable to ``counts``.
|
|
256
|
+
theta: Positive inverse dispersions, broadcastable to ``counts``.
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
The log-probability, elementwise.
|
|
260
|
+
"""
|
|
261
|
+
# No broadcast up front: a term in theta alone, such as delta(theta), runs at theta's own
|
|
262
|
+
# shape (one value per gene for NegativeBinomialLoss) and broadcasts only when combined.
|
|
263
|
+
positive = counts > 0
|
|
264
|
+
# The saddle-point branch sees a count of one at a zero count, where the where drops it.
|
|
265
|
+
x = jnp.where(positive, counts, 1.0)
|
|
266
|
+
# n P and n Q are each one product, as R's dnbinom_mu forms them: as x + (theta - n P) the
|
|
267
|
+
# mean term would subtract nearly equal numbers and lose mu once mu / theta is below eps.
|
|
268
|
+
# theta - n P = theta (mu - x) / (theta + mu) = x - n Q, so the deviances also see their
|
|
269
|
+
# arguments' difference in closed form rather than by subtraction.
|
|
270
|
+
total = theta + mu
|
|
271
|
+
n = x + theta
|
|
272
|
+
difference = theta * (mu - x) / total
|
|
273
|
+
log_positive = (
|
|
274
|
+
stirling_remainder(n)
|
|
275
|
+
- stirling_remainder(theta)
|
|
276
|
+
- stirling_remainder(x)
|
|
277
|
+
- binomial_deviance(theta, n * (theta / total), difference)
|
|
278
|
+
- binomial_deviance(x, n * (mu / total), -difference)
|
|
279
|
+
- 0.5 * (2.0 * _HALF_LOG_TWO_PI + jnp.log(x) + jnp.log1p(x / theta))
|
|
280
|
+
)
|
|
281
|
+
return jnp.where(positive, log_positive, _log_prob_of_zero(mu, theta))
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def _log_prob_of_zero(mu: Float[Array, "..."], theta: Float[Array, "..."]) -> Float[Array, "..."]:
|
|
285
|
+
"""``log NB(0) = theta log(theta / (theta + mu))``, as R's ``dnbinom_mu`` forms it.
|
|
286
|
+
|
|
287
|
+
The log takes the small one of ``theta / (theta + mu)`` and ``mu / (theta + mu)``, each a
|
|
288
|
+
single quotient: ``log`` of the first when ``theta < mu``, ``log1p`` of minus the second
|
|
289
|
+
otherwise, where the first rounds towards one.
|
|
290
|
+
"""
|
|
291
|
+
total = theta + mu
|
|
292
|
+
return theta * jnp.where(theta < mu, jnp.log(theta / total), jnp.log1p(-mu / total))
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _log_mean_fraction(mu: Float[Array, "..."], theta: Float[Array, "..."]) -> Float[Array, "..."]:
|
|
296
|
+
"""``log(mu / (theta + mu))``, as ``log1p(-theta / (theta + mu))`` when ``theta < mu``."""
|
|
297
|
+
total = theta + mu
|
|
298
|
+
return jnp.where(theta < mu, jnp.log1p(-theta / total), jnp.log(mu / total))
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _partial_in_mu(
|
|
302
|
+
counts: Float[Array, "..."], mu: Float[Array, "..."], theta: Float[Array, "..."]
|
|
303
|
+
) -> Float[Array, "..."]:
|
|
304
|
+
"""``theta (x - mu) / (mu (theta + mu))``: ``x / mu - (x + theta) / (theta + mu)`` combined."""
|
|
305
|
+
return theta * (counts - mu) / (mu * (theta + mu))
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _partial_in_theta(
|
|
309
|
+
counts: Float[Array, "..."], mu: Float[Array, "..."], theta: Float[Array, "..."]
|
|
310
|
+
) -> Float[Array, "..."]:
|
|
311
|
+
"""``log1pmx(t) + x / (2 theta (theta + x)) + delta'(theta + x) - delta'(theta)``.
|
|
312
|
+
|
|
313
|
+
At ``x = 0`` the rational term is 0 and the two ``delta'`` agree exactly, leaving
|
|
314
|
+
``log1pmx(-mu / (theta + mu))``, the derivative of ``theta log(theta / (theta + mu))``.
|
|
315
|
+
The ``delta'`` difference is formed first: added one at a time, each ``delta'`` would absorb
|
|
316
|
+
a ``log1pmx`` far below its own size before the two cancel.
|
|
317
|
+
|
|
318
|
+
Below ``delta'``'s series start in ``theta``, ``delta'(theta)`` rests on digamma, whose
|
|
319
|
+
float32 error then bounds the result where it is small. There an integer count below
|
|
320
|
+
``_HARMONIC_COUNTS`` takes ``psi(theta + x) - psi(theta) = sum_{j < x} 1 / (theta + j)``
|
|
321
|
+
(DLMF 5.5.2), which jax also uses for its hyp1f1 parameter derivatives: the partial is
|
|
322
|
+
``sum_{j < x} (mu - j) / ((theta + j)(theta + mu)) + log1pmx(-mu / (theta + mu))``, exact
|
|
323
|
+
term by term. From the series start on, the general form is the more accurate: its terms
|
|
324
|
+
are of the size of the score's ``1 / theta**2``, the harmonic terms of ``1 / theta``.
|
|
325
|
+
"""
|
|
326
|
+
total = theta + mu
|
|
327
|
+
stirling_difference = stirling_remainder_derivative(
|
|
328
|
+
theta + counts
|
|
329
|
+
) - stirling_remainder_derivative(theta)
|
|
330
|
+
general = (
|
|
331
|
+
log1pmx((counts - mu) / total, (theta + counts) / total)
|
|
332
|
+
+ counts / (2.0 * theta * (theta + counts))
|
|
333
|
+
+ stirling_difference
|
|
334
|
+
)
|
|
335
|
+
harmonic = log1pmx(-mu / total, theta / total)
|
|
336
|
+
for j in range(_HARMONIC_COUNTS - 1):
|
|
337
|
+
harmonic = harmonic + jnp.where(j < counts, (mu - j) / ((theta + j) * total), 0.0)
|
|
338
|
+
is_harmonic = (
|
|
339
|
+
(counts < _HARMONIC_COUNTS)
|
|
340
|
+
& (counts == jnp.floor(counts))
|
|
341
|
+
& (theta < _stirling_derivative_series_start(theta.dtype))
|
|
342
|
+
)
|
|
343
|
+
return jnp.where(is_harmonic, harmonic, general)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def _partial_in_counts(
|
|
347
|
+
counts: Float[Array, "..."], mu: Float[Array, "..."], theta: Float[Array, "..."]
|
|
348
|
+
) -> Float[Array, "..."]:
|
|
349
|
+
"""``psi(x + theta) - psi(x + 1) + log(mu / (theta + mu))``.
|
|
350
|
+
|
|
351
|
+
For ``x >= 1``, ``log1p(s) - (2x + theta) / (2x (x + theta)) + delta'(x + theta) -
|
|
352
|
+
delta'(x)``, ``s = theta (mu - x) / (x (theta + mu))``, with ``log`` of ``1 + s = mu (x +
|
|
353
|
+
theta) / (x (theta + mu))`` near ``s = -1`` and the ``delta'`` difference formed first, as in
|
|
354
|
+
``_partial_in_theta``: ``psi(x + theta)`` and ``log(theta + mu)`` would otherwise cancel to
|
|
355
|
+
leave a result of size one when ``theta`` is large. Below one count, where the ``1 / x``
|
|
356
|
+
terms grow, the digamma functions directly.
|
|
357
|
+
"""
|
|
358
|
+
large = counts >= 1.0
|
|
359
|
+
x = jnp.where(large, counts, 1.0)
|
|
360
|
+
total = theta + mu
|
|
361
|
+
ratio_minus_one = theta * (mu - x) / (x * total)
|
|
362
|
+
log_ratio = jnp.where(
|
|
363
|
+
ratio_minus_one < -0.5,
|
|
364
|
+
jnp.log(mu * (x + theta) / (x * total)),
|
|
365
|
+
jnp.log1p(ratio_minus_one),
|
|
366
|
+
)
|
|
367
|
+
stirling_difference = stirling_remainder_derivative(x + theta) - stirling_remainder_derivative(
|
|
368
|
+
x
|
|
369
|
+
)
|
|
370
|
+
identity = log_ratio - (2.0 * x + theta) / (2.0 * x * (x + theta)) + stirling_difference
|
|
371
|
+
x_direct = jnp.where(large, 1.0, counts)
|
|
372
|
+
direct = digamma(x_direct + theta) - digamma(x_direct + 1.0) + _log_mean_fraction(mu, theta)
|
|
373
|
+
return jnp.where(large, identity, direct)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
@functools.partial(nb_log_prob.defjvp, symbolic_zeros=True)
|
|
377
|
+
def _nb_log_prob_jvp(
|
|
378
|
+
primals: tuple[Array, Array, Array], tangents: tuple[Array, Array, Array]
|
|
379
|
+
) -> tuple[Array, Array]:
|
|
380
|
+
counts, mu, theta = primals
|
|
381
|
+
value = nb_log_prob(counts, mu, theta)
|
|
382
|
+
tangent = jnp.zeros_like(value)
|
|
383
|
+
partials = (_partial_in_counts, _partial_in_mu, _partial_in_theta)
|
|
384
|
+
for partial, tangent_in in zip(partials, tangents, strict=True):
|
|
385
|
+
# An argument nobody differentiates arrives as a symbolic zero: its partial is skipped.
|
|
386
|
+
if not isinstance(tangent_in, SymbolicZero):
|
|
387
|
+
tangent = tangent + partial(counts, mu, theta) * tangent_in
|
|
388
|
+
return value, tangent
|
|
@@ -15,38 +15,7 @@ import jax.numpy as jnp
|
|
|
15
15
|
from flax import nnx
|
|
16
16
|
from jaxtyping import Array, Float, Int
|
|
17
17
|
|
|
18
|
-
from diffbio.
|
|
19
|
-
from diffbio.core import soft_ops
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def _nb_log_prob(
|
|
23
|
-
counts: Float[Array, "..."],
|
|
24
|
-
mu: Float[Array, "..."],
|
|
25
|
-
theta: Float[Array, "..."],
|
|
26
|
-
eps: float = EPSILON,
|
|
27
|
-
) -> Float[Array, "..."]:
|
|
28
|
-
"""Per-element negative binomial log-probability ``log NB(x | mu, theta)``.
|
|
29
|
-
|
|
30
|
-
Shared core of :func:`zinb_negative_log_likelihood` and
|
|
31
|
-
:class:`NegativeBinomialLoss`.
|
|
32
|
-
|
|
33
|
-
Args:
|
|
34
|
-
counts: Observed counts ``x``.
|
|
35
|
-
mu: Mean parameter (broadcastable to ``counts``).
|
|
36
|
-
theta: Dispersion parameter (broadcastable to ``counts``).
|
|
37
|
-
eps: Numerical stability constant.
|
|
38
|
-
|
|
39
|
-
Returns:
|
|
40
|
-
Elementwise log-probability, same shape as ``counts``.
|
|
41
|
-
"""
|
|
42
|
-
log_theta_mu = jnp.log(theta + mu + eps)
|
|
43
|
-
return (
|
|
44
|
-
jax.scipy.special.gammaln(counts + theta)
|
|
45
|
-
- jax.scipy.special.gammaln(theta)
|
|
46
|
-
- jax.scipy.special.gammaln(counts + 1.0)
|
|
47
|
-
+ theta * (jnp.log(theta + eps) - log_theta_mu)
|
|
48
|
-
+ counts * (jnp.log(mu + eps) - log_theta_mu)
|
|
49
|
-
)
|
|
18
|
+
from diffbio.losses._negative_binomial import nb_log_prob
|
|
50
19
|
|
|
51
20
|
|
|
52
21
|
def zinb_negative_log_likelihood(
|
|
@@ -81,24 +50,26 @@ def zinb_negative_log_likelihood(
|
|
|
81
50
|
"""
|
|
82
51
|
mu = jnp.exp(log_rate)
|
|
83
52
|
theta = jnp.exp(jnp.clip(log_theta, -10.0, 10.0))
|
|
84
|
-
eps = EPSILON
|
|
85
53
|
|
|
86
54
|
# Log-space sigmoid computations (numerically stable)
|
|
87
55
|
softplus_pi = jax.nn.softplus(-pi_logit) # = -log(sigmoid(pi_logit))
|
|
88
|
-
log_theta_mu = jnp.log(theta + mu + eps)
|
|
89
|
-
|
|
90
|
-
# NB(0) in log-space combined with dropout logit
|
|
91
|
-
pi_theta_log = -pi_logit + theta * (jnp.log(theta + eps) - log_theta_mu)
|
|
92
56
|
|
|
93
|
-
#
|
|
94
|
-
|
|
57
|
+
# log NB(x); at a zero count it is log NB(0) = -theta log1p(mu / theta), which stays
|
|
58
|
+
# accurate when theta >> mu, where log(theta) - log(theta + mu) cancels.
|
|
59
|
+
log_nb = nb_log_prob(counts, mu, theta)
|
|
95
60
|
|
|
96
|
-
# Case x
|
|
97
|
-
#
|
|
98
|
-
|
|
61
|
+
# Case x == 0: log[pi + (1 - pi) NB(0)] = log(1 - y) with y = (1 - pi)(1 - NB(0)). Both
|
|
62
|
+
# 1 - NB(0) and 1 - y go through Machler's log1mexp ("Accurately Computing log(1 - exp(-|a|))",
|
|
63
|
+
# 2012; jax.nn.log1mexp), since log(pi + (1 - pi) NB(0)) as softplus(-pi + log NB(0)) -
|
|
64
|
+
# softplus(-pi) subtracts two nearly equal terms once NB(0) is close to one.
|
|
65
|
+
log_y = -jax.nn.softplus(pi_logit) + jax.nn.log1mexp(-log_nb)
|
|
66
|
+
case_zero = jax.nn.log1mexp(-log_y)
|
|
67
|
+
# Case x > 0: log[(1 - pi) NB(x)]
|
|
68
|
+
case_nonzero = -pi_logit + log_nb - softplus_pi
|
|
99
69
|
|
|
100
|
-
|
|
101
|
-
|
|
70
|
+
# The counts are observed data, so the branch is chosen exactly, as scvi-tools'
|
|
71
|
+
# log_zinb_positive does; nothing differentiates the selection.
|
|
72
|
+
log_prob = jnp.where(counts == 0, case_zero, case_nonzero)
|
|
102
73
|
|
|
103
74
|
return -jnp.sum(log_prob)
|
|
104
75
|
|
|
@@ -159,7 +130,7 @@ class NegativeBinomialLoss(nnx.Module):
|
|
|
159
130
|
mu = jnp.maximum(mu, self.eps)
|
|
160
131
|
theta = jnp.maximum(theta, self.eps)
|
|
161
132
|
|
|
162
|
-
ll =
|
|
133
|
+
ll = nb_log_prob(counts, mu, theta[None, :])
|
|
163
134
|
|
|
164
135
|
# Return mean negative log-likelihood
|
|
165
136
|
return -jnp.mean(ll)
|
|
@@ -19,6 +19,9 @@ from datarax.core.operator import OperatorModule
|
|
|
19
19
|
from flax import nnx
|
|
20
20
|
from jaxtyping import Array, Float, PyTree
|
|
21
21
|
|
|
22
|
+
from diffbio.losses._negative_binomial import nb_log_prob
|
|
23
|
+
|
|
24
|
+
|
|
22
25
|
logger = logging.getLogger(__name__)
|
|
23
26
|
|
|
24
27
|
|
|
@@ -160,30 +163,8 @@ class DifferentiableNBGLM(OperatorModule):
|
|
|
160
163
|
Total log probability (scalar).
|
|
161
164
|
"""
|
|
162
165
|
mu = self.predict_mean(design, size_factor)
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
# Negative binomial log probability
|
|
166
|
-
# NB(k; mu, r) where r = dispersion
|
|
167
|
-
# log P(k) = log(Gamma(k + r)) - log(Gamma(k + 1)) - log(Gamma(r))
|
|
168
|
-
# + r * log(r / (r + mu)) + k * log(mu / (r + mu))
|
|
169
|
-
|
|
170
|
-
r = dispersion
|
|
171
|
-
k = counts
|
|
172
|
-
|
|
173
|
-
# Compute log probability using the NB PMF
|
|
174
|
-
# Use jax.scipy.special functions for numerical stability
|
|
175
|
-
log_prob = (
|
|
176
|
-
jax.scipy.special.gammaln(k + r)
|
|
177
|
-
- jax.scipy.special.gammaln(k + 1)
|
|
178
|
-
- jax.scipy.special.gammaln(r)
|
|
179
|
-
+ r * jnp.log(r / (r + mu + 1e-8))
|
|
180
|
-
+ k * jnp.log(mu / (r + mu + 1e-8) + 1e-8)
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
# Sum over features
|
|
184
|
-
total_log_prob = jnp.sum(log_prob)
|
|
185
|
-
|
|
186
|
-
return total_log_prob
|
|
166
|
+
# NB(k; mu, r) with r = dispersion, the inverse dispersion: variance mu + mu**2 / r.
|
|
167
|
+
return jnp.sum(nb_log_prob(counts, mu, self.get_dispersion()))
|
|
187
168
|
|
|
188
169
|
def batch_log_likelihood(
|
|
189
170
|
self,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|