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.
Files changed (231) hide show
  1. {diffbio-0.1.9 → diffbio-0.1.10}/PKG-INFO +8 -6
  2. {diffbio-0.1.9 → diffbio-0.1.10}/pyproject.toml +12 -9
  3. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/losses/__init__.py +2 -0
  4. diffbio-0.1.10/src/diffbio/losses/_negative_binomial.py +388 -0
  5. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/losses/statistical_losses.py +16 -45
  6. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/statistical/nb_glm.py +5 -24
  7. {diffbio-0.1.9 → diffbio-0.1.10}/.gitignore +0 -0
  8. {diffbio-0.1.9 → diffbio-0.1.10}/LICENSE +0 -0
  9. {diffbio-0.1.9 → diffbio-0.1.10}/README.md +0 -0
  10. {diffbio-0.1.9 → diffbio-0.1.10}/benchmarks/README.md +0 -0
  11. {diffbio-0.1.9 → diffbio-0.1.10}/benchmarks/crossmodality/README.md +0 -0
  12. {diffbio-0.1.9 → diffbio-0.1.10}/examples/README.md +0 -0
  13. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/__init__.py +0 -0
  14. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/configs.py +0 -0
  15. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/constants.py +0 -0
  16. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/__init__.py +0 -0
  17. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/base_operators.py +0 -0
  18. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/data_types.py +0 -0
  19. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/gnn_components.py +0 -0
  20. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/graph_utils.py +0 -0
  21. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/neural_components.py +0 -0
  22. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/optimal_transport.py +0 -0
  23. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/__init__.py +0 -0
  24. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/_projections_permutahedron.py +0 -0
  25. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/_projections_simplex.py +0 -0
  26. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/_projections_transport.py +0 -0
  27. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/_sorting_network.py +0 -0
  28. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/_types.py +0 -0
  29. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/_utils.py +0 -0
  30. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/autograd_safe.py +0 -0
  31. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/comparison.py +0 -0
  32. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/elementwise.py +0 -0
  33. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/logical.py +0 -0
  34. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/normalization.py +0 -0
  35. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/quantile.py +0 -0
  36. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/selection.py +0 -0
  37. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/sorting.py +0 -0
  38. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/soft_ops/straight_through.py +0 -0
  39. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/core/uncertainty.py +0 -0
  40. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/evaluation/__init__.py +0 -0
  41. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/evaluation/adapters.py +0 -0
  42. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/evaluation/graders.py +0 -0
  43. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/evaluation/problem.py +0 -0
  44. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/evaluation/runner.py +0 -0
  45. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/losses/alignment_losses.py +0 -0
  46. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/losses/biological_regularization.py +0 -0
  47. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/losses/metric_losses.py +0 -0
  48. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/losses/self_supervised_losses.py +0 -0
  49. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/losses/singlecell_losses.py +0 -0
  50. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/__init__.py +0 -0
  51. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/_count_vae.py +0 -0
  52. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/_loss_balancing.py +0 -0
  53. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/_masked_gene_transformer.py +0 -0
  54. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/_transformer_validation.py +0 -0
  55. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/alignment/__init__.py +0 -0
  56. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/alignment/profile_hmm.py +0 -0
  57. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/alignment/scoring.py +0 -0
  58. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/alignment/smith_waterman.py +0 -0
  59. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/alignment/soft_msa.py +0 -0
  60. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/assembly/__init__.py +0 -0
  61. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/assembly/gnn_assembly.py +0 -0
  62. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/assembly/metagenomic_binning.py +0 -0
  63. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/crispr/__init__.py +0 -0
  64. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/crispr/guide_scoring.py +0 -0
  65. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/drug_discovery/__init__.py +0 -0
  66. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/drug_discovery/_graph_utils.py +0 -0
  67. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/drug_discovery/admet_predictor.py +0 -0
  68. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/drug_discovery/attentive_fp.py +0 -0
  69. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/drug_discovery/dti.py +0 -0
  70. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/drug_discovery/fingerprint.py +0 -0
  71. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/drug_discovery/maccs_keys.py +0 -0
  72. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/drug_discovery/message_passing.py +0 -0
  73. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/drug_discovery/primitives.py +0 -0
  74. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/drug_discovery/property_predictor.py +0 -0
  75. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/drug_discovery/similarity.py +0 -0
  76. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/epigenomics/__init__.py +0 -0
  77. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/epigenomics/chromatin_state.py +0 -0
  78. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/epigenomics/contextual.py +0 -0
  79. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/epigenomics/fno_peak_calling.py +0 -0
  80. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/epigenomics/peak_calling.py +0 -0
  81. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/foundation_models/__init__.py +0 -0
  82. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/foundation_models/adapters.py +0 -0
  83. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/foundation_models/contracts.py +0 -0
  84. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/foundation_models/embedding_probe.py +0 -0
  85. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/foundation_models/experimental.py +0 -0
  86. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/foundation_models/foundation_model.py +0 -0
  87. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/foundation_models/frozen.py +0 -0
  88. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/foundation_models/precomputed.py +0 -0
  89. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/foundation_models/transformer_encoder.py +0 -0
  90. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/mapping/__init__.py +0 -0
  91. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/mapping/neural_mapper.py +0 -0
  92. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/metabolomics/__init__.py +0 -0
  93. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/metabolomics/isotope_envelope.py +0 -0
  94. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/metabolomics/soft_centroiding.py +0 -0
  95. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/metabolomics/spectral_similarity.py +0 -0
  96. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/molecular_dynamics/__init__.py +0 -0
  97. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/molecular_dynamics/force_field.py +0 -0
  98. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/molecular_dynamics/integrator.py +0 -0
  99. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/molecular_dynamics/primitives.py +0 -0
  100. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/multiomics/__init__.py +0 -0
  101. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/multiomics/hic_contact.py +0 -0
  102. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/multiomics/multiomics_vae.py +0 -0
  103. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/multiomics/spatial_deconvolution.py +0 -0
  104. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/multiomics/spatial_gene_detection.py +0 -0
  105. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/normalization/__init__.py +0 -0
  106. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/normalization/arcsinh_cofactor.py +0 -0
  107. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/normalization/differentiable_pca.py +0 -0
  108. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/normalization/embedding.py +0 -0
  109. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/normalization/learnable_normalization.py +0 -0
  110. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/normalization/learnable_orthogonal_projection.py +0 -0
  111. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/normalization/learnable_projection.py +0 -0
  112. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/normalization/matrix_free_pca.py +0 -0
  113. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/normalization/phate.py +0 -0
  114. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/normalization/scaling.py +0 -0
  115. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/normalization/soft_pca.py +0 -0
  116. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/normalization/umap.py +0 -0
  117. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/normalization/vae_normalizer.py +0 -0
  118. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/population/__init__.py +0 -0
  119. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/population/ancestry_estimation.py +0 -0
  120. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/preprocessing/__init__.py +0 -0
  121. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/preprocessing/adapter_removal.py +0 -0
  122. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/preprocessing/duplicate_filter.py +0 -0
  123. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/preprocessing/error_correction.py +0 -0
  124. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/protein/__init__.py +0 -0
  125. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/protein/secondary_structure.py +0 -0
  126. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/quality_filter.py +0 -0
  127. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/rna_structure/__init__.py +0 -0
  128. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/rna_structure/rna_folding.py +0 -0
  129. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/rnaseq/__init__.py +0 -0
  130. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/rnaseq/motif_discovery.py +0 -0
  131. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/rnaseq/splicing_psi.py +0 -0
  132. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/__init__.py +0 -0
  133. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/ambient_removal.py +0 -0
  134. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/archetypes.py +0 -0
  135. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/batch_correction.py +0 -0
  136. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/cell_annotation.py +0 -0
  137. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/communication.py +0 -0
  138. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/differential_distribution.py +0 -0
  139. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/doublet_detection.py +0 -0
  140. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/downsampling.py +0 -0
  141. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/enhanced_batch_correction.py +0 -0
  142. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/grn_inference.py +0 -0
  143. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/imputation.py +0 -0
  144. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/knockdown_filter.py +0 -0
  145. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/ot_trajectory.py +0 -0
  146. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/simulation.py +0 -0
  147. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/sindy_grn.py +0 -0
  148. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/soft_clustering.py +0 -0
  149. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/soft_hvg.py +0 -0
  150. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/spatial_domains.py +0 -0
  151. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/stochastic_gate_selector.py +0 -0
  152. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/switch_de.py +0 -0
  153. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/trajectory.py +0 -0
  154. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/singlecell/velocity.py +0 -0
  155. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/statistical/__init__.py +0 -0
  156. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/statistical/em_quantification.py +0 -0
  157. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/statistical/hmm.py +0 -0
  158. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/variant/__init__.py +0 -0
  159. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/variant/classifier.py +0 -0
  160. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/variant/cnn_classifier.py +0 -0
  161. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/variant/cnv_segmentation.py +0 -0
  162. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/variant/deepvariant_pileup.py +0 -0
  163. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/variant/learnable_pileup.py +0 -0
  164. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/variant/pileup.py +0 -0
  165. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/operators/variant/quality_recalibration.py +0 -0
  166. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/pipelines/__init__.py +0 -0
  167. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/pipelines/adapters.py +0 -0
  168. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/pipelines/differential_expression.py +0 -0
  169. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/pipelines/enhanced_variant_calling.py +0 -0
  170. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/pipelines/joint_preprocessing.py +0 -0
  171. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/pipelines/joint_training.py +0 -0
  172. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/pipelines/minibatch_training.py +0 -0
  173. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/pipelines/perturbation.py +0 -0
  174. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/pipelines/preprocessing.py +0 -0
  175. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/pipelines/single_cell.py +0 -0
  176. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/pipelines/variant_calling.py +0 -0
  177. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/reductions/__init__.py +0 -0
  178. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/reductions/base.py +0 -0
  179. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/reductions/pca_reduction.py +0 -0
  180. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/reductions/tfidf_reduction.py +0 -0
  181. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/samplers/__init__.py +0 -0
  182. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/samplers/perturbation_sampler.py +0 -0
  183. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sequences/__init__.py +0 -0
  184. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sequences/dna.py +0 -0
  185. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sequences/kmer.py +0 -0
  186. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/__init__.py +0 -0
  187. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/_anndata_shared.py +0 -0
  188. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/_batch_iteration.py +0 -0
  189. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/_benchmark_source.py +0 -0
  190. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/_indexed_batch_source.py +0 -0
  191. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/_utils.py +0 -0
  192. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/anndata_interop.py +0 -0
  193. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/anndata_source.py +0 -0
  194. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/archive_ii.py +0 -0
  195. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/balifam.py +0 -0
  196. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/bam.py +0 -0
  197. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/bengrn_ground_truth.py +0 -0
  198. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/contextual_epigenomics.py +0 -0
  199. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/dti.py +0 -0
  200. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/embeddings.py +0 -0
  201. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/encode_peaks.py +0 -0
  202. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/fasta.py +0 -0
  203. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/immune_human.py +0 -0
  204. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/indexed_embeddings.py +0 -0
  205. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/indexed_view.py +0 -0
  206. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/molnet.py +0 -0
  207. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/multiomics.py +0 -0
  208. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/pancreas.py +0 -0
  209. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/perturbation/__init__.py +0 -0
  210. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/perturbation/_types.py +0 -0
  211. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/perturbation/_utils.py +0 -0
  212. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/perturbation/concat_source.py +0 -0
  213. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/perturbation/control_mapping.py +0 -0
  214. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/perturbation/experiment_config.py +0 -0
  215. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/perturbation/h5_metadata_cache.py +0 -0
  216. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/perturbation/output_space.py +0 -0
  217. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/perturbation/perturbation_source.py +0 -0
  218. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/seqfish.py +0 -0
  219. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/sequence_foundation.py +0 -0
  220. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/sources/singlecell_foundation.py +0 -0
  221. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/splitters/__init__.py +0 -0
  222. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/splitters/base.py +0 -0
  223. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/splitters/molecular.py +0 -0
  224. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/splitters/perturbation.py +0 -0
  225. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/splitters/random.py +0 -0
  226. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/splitters/sequence.py +0 -0
  227. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/utils/__init__.py +0 -0
  228. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/utils/dependency_runtime.py +0 -0
  229. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/utils/nn_utils.py +0 -0
  230. {diffbio-0.1.9 → diffbio-0.1.10}/src/diffbio/utils/quality.py +0 -0
  231. {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.9
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.10
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.8
51
+ Requires-Dist: calibrax>=0.1.17
52
52
  Requires-Dist: chex>=0.1.7
53
- Requires-Dist: datarax>=0.1.13
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.7
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.11
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
- "datarax>=0.1.13",
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.10",
43
+ "avitai-artifex>=0.1.15",
43
44
  # Opifex - scientific ML, operator learning, and advanced optimization
44
- "opifex>=0.2.7",
45
+ "opifex>=0.2.10",
45
46
  # Calibrax - benchmarking, comparison, profiling, and regression control
46
- "calibrax>=0.1.8",
47
+ "calibrax>=0.1.17",
47
48
  # Substrax - device identity, meshes, runtime settings and checkpoint store shared by the
48
- # siblings; 0.1.6 adds substrax.runtime, substrax.artifacts and substrax.testing.
49
- "substrax>=0.1.11",
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.9"
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.constants import EPSILON
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
- # Case x == 0: log[sigmoid(pi) + (1 - sigmoid(pi)) * NB(0)]
94
- case_zero = jax.nn.softplus(pi_theta_log) - softplus_pi
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 > 0: log[(1 - sigmoid(pi)) * NB(x)]. The bracketed term equals
97
- # ``-pi_logit + log NB(x)`` once ``pi_theta_log`` is expanded.
98
- case_nonzero = -softplus_pi - pi_logit + _nb_log_prob(counts, mu, theta, eps)
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
- is_zero = soft_ops.less(counts, jnp.array(eps), softness=0.01)
101
- log_prob = is_zero * case_zero + (1.0 - is_zero) * case_nonzero
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 = _nb_log_prob(counts, mu, theta[None, :], self.eps)
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
- dispersion = self.get_dispersion()
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