diffbio 0.1.0__py3-none-any.whl

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 (202) hide show
  1. diffbio/__init__.py +39 -0
  2. diffbio/configs.py +75 -0
  3. diffbio/constants.py +204 -0
  4. diffbio/core/__init__.py +127 -0
  5. diffbio/core/base_operators.py +612 -0
  6. diffbio/core/data_types.py +260 -0
  7. diffbio/core/gnn_components.py +629 -0
  8. diffbio/core/graph_utils.py +149 -0
  9. diffbio/core/neural_components.py +270 -0
  10. diffbio/core/optimal_transport.py +133 -0
  11. diffbio/core/soft_ops/__init__.py +216 -0
  12. diffbio/core/soft_ops/_projections_permutahedron.py +1864 -0
  13. diffbio/core/soft_ops/_projections_simplex.py +240 -0
  14. diffbio/core/soft_ops/_projections_transport.py +508 -0
  15. diffbio/core/soft_ops/_sorting_network.py +204 -0
  16. diffbio/core/soft_ops/_types.py +15 -0
  17. diffbio/core/soft_ops/_utils.py +342 -0
  18. diffbio/core/soft_ops/autograd_safe.py +120 -0
  19. diffbio/core/soft_ops/comparison.py +235 -0
  20. diffbio/core/soft_ops/elementwise.py +309 -0
  21. diffbio/core/soft_ops/logical.py +146 -0
  22. diffbio/core/soft_ops/quantile.py +376 -0
  23. diffbio/core/soft_ops/selection.py +236 -0
  24. diffbio/core/soft_ops/sorting.py +926 -0
  25. diffbio/core/soft_ops/straight_through.py +261 -0
  26. diffbio/core/uncertainty.py +279 -0
  27. diffbio/evaluation/__init__.py +42 -0
  28. diffbio/evaluation/adapters.py +409 -0
  29. diffbio/evaluation/graders.py +223 -0
  30. diffbio/evaluation/problem.py +157 -0
  31. diffbio/evaluation/runner.py +277 -0
  32. diffbio/losses/__init__.py +59 -0
  33. diffbio/losses/alignment_losses.py +222 -0
  34. diffbio/losses/biological_regularization.py +288 -0
  35. diffbio/losses/metric_losses.py +139 -0
  36. diffbio/losses/singlecell_losses.py +387 -0
  37. diffbio/losses/statistical_losses.py +345 -0
  38. diffbio/operators/__init__.py +60 -0
  39. diffbio/operators/_count_vae.py +197 -0
  40. diffbio/operators/_loss_balancing.py +65 -0
  41. diffbio/operators/_masked_gene_transformer.py +118 -0
  42. diffbio/operators/_transformer_validation.py +50 -0
  43. diffbio/operators/alignment/__init__.py +51 -0
  44. diffbio/operators/alignment/profile_hmm.py +350 -0
  45. diffbio/operators/alignment/scoring.py +127 -0
  46. diffbio/operators/alignment/smith_waterman.py +261 -0
  47. diffbio/operators/alignment/soft_msa.py +419 -0
  48. diffbio/operators/assembly/__init__.py +27 -0
  49. diffbio/operators/assembly/gnn_assembly.py +252 -0
  50. diffbio/operators/assembly/metagenomic_binning.py +296 -0
  51. diffbio/operators/crispr/__init__.py +17 -0
  52. diffbio/operators/crispr/guide_scoring.py +269 -0
  53. diffbio/operators/drug_discovery/__init__.py +133 -0
  54. diffbio/operators/drug_discovery/_graph_utils.py +142 -0
  55. diffbio/operators/drug_discovery/admet_predictor.py +285 -0
  56. diffbio/operators/drug_discovery/attentive_fp.py +411 -0
  57. diffbio/operators/drug_discovery/dti.py +261 -0
  58. diffbio/operators/drug_discovery/fingerprint.py +490 -0
  59. diffbio/operators/drug_discovery/maccs_keys.py +267 -0
  60. diffbio/operators/drug_discovery/message_passing.py +200 -0
  61. diffbio/operators/drug_discovery/primitives.py +242 -0
  62. diffbio/operators/drug_discovery/property_predictor.py +163 -0
  63. diffbio/operators/drug_discovery/similarity.py +193 -0
  64. diffbio/operators/epigenomics/__init__.py +35 -0
  65. diffbio/operators/epigenomics/chromatin_state.py +491 -0
  66. diffbio/operators/epigenomics/contextual.py +288 -0
  67. diffbio/operators/epigenomics/fno_peak_calling.py +153 -0
  68. diffbio/operators/epigenomics/peak_calling.py +555 -0
  69. diffbio/operators/foundation_models/__init__.py +119 -0
  70. diffbio/operators/foundation_models/adapters.py +114 -0
  71. diffbio/operators/foundation_models/contracts.py +245 -0
  72. diffbio/operators/foundation_models/embedding_probe.py +83 -0
  73. diffbio/operators/foundation_models/experimental.py +128 -0
  74. diffbio/operators/foundation_models/foundation_model.py +332 -0
  75. diffbio/operators/foundation_models/frozen.py +59 -0
  76. diffbio/operators/foundation_models/precomputed.py +270 -0
  77. diffbio/operators/foundation_models/transformer_encoder.py +564 -0
  78. diffbio/operators/mapping/__init__.py +17 -0
  79. diffbio/operators/mapping/neural_mapper.py +493 -0
  80. diffbio/operators/metabolomics/__init__.py +39 -0
  81. diffbio/operators/metabolomics/spectral_similarity.py +315 -0
  82. diffbio/operators/molecular_dynamics/__init__.py +51 -0
  83. diffbio/operators/molecular_dynamics/force_field.py +265 -0
  84. diffbio/operators/molecular_dynamics/integrator.py +304 -0
  85. diffbio/operators/molecular_dynamics/primitives.py +115 -0
  86. diffbio/operators/multiomics/__init__.py +38 -0
  87. diffbio/operators/multiomics/hic_contact.py +377 -0
  88. diffbio/operators/multiomics/multiomics_vae.py +325 -0
  89. diffbio/operators/multiomics/spatial_deconvolution.py +316 -0
  90. diffbio/operators/multiomics/spatial_gene_detection.py +493 -0
  91. diffbio/operators/normalization/__init__.py +42 -0
  92. diffbio/operators/normalization/embedding.py +222 -0
  93. diffbio/operators/normalization/phate.py +400 -0
  94. diffbio/operators/normalization/umap.py +261 -0
  95. diffbio/operators/normalization/vae_normalizer.py +258 -0
  96. diffbio/operators/population/__init__.py +17 -0
  97. diffbio/operators/population/ancestry_estimation.py +274 -0
  98. diffbio/operators/preprocessing/__init__.py +76 -0
  99. diffbio/operators/preprocessing/adapter_removal.py +311 -0
  100. diffbio/operators/preprocessing/duplicate_filter.py +317 -0
  101. diffbio/operators/preprocessing/error_correction.py +287 -0
  102. diffbio/operators/protein/__init__.py +31 -0
  103. diffbio/operators/protein/secondary_structure.py +509 -0
  104. diffbio/operators/quality_filter.py +128 -0
  105. diffbio/operators/rna_structure/__init__.py +35 -0
  106. diffbio/operators/rna_structure/rna_folding.py +509 -0
  107. diffbio/operators/rnaseq/__init__.py +23 -0
  108. diffbio/operators/rnaseq/motif_discovery.py +251 -0
  109. diffbio/operators/rnaseq/splicing_psi.py +216 -0
  110. diffbio/operators/singlecell/__init__.py +193 -0
  111. diffbio/operators/singlecell/ambient_removal.py +333 -0
  112. diffbio/operators/singlecell/archetypes.py +191 -0
  113. diffbio/operators/singlecell/batch_correction.py +288 -0
  114. diffbio/operators/singlecell/cell_annotation.py +519 -0
  115. diffbio/operators/singlecell/communication.py +704 -0
  116. diffbio/operators/singlecell/differential_distribution.py +243 -0
  117. diffbio/operators/singlecell/doublet_detection.py +657 -0
  118. diffbio/operators/singlecell/downsampling.py +166 -0
  119. diffbio/operators/singlecell/enhanced_batch_correction.py +519 -0
  120. diffbio/operators/singlecell/grn_inference.py +336 -0
  121. diffbio/operators/singlecell/imputation.py +429 -0
  122. diffbio/operators/singlecell/knockdown_filter.py +176 -0
  123. diffbio/operators/singlecell/ot_trajectory.py +277 -0
  124. diffbio/operators/singlecell/simulation.py +444 -0
  125. diffbio/operators/singlecell/sindy_grn.py +247 -0
  126. diffbio/operators/singlecell/soft_clustering.py +211 -0
  127. diffbio/operators/singlecell/spatial_domains.py +677 -0
  128. diffbio/operators/singlecell/switch_de.py +184 -0
  129. diffbio/operators/singlecell/trajectory.py +447 -0
  130. diffbio/operators/singlecell/velocity.py +361 -0
  131. diffbio/operators/statistical/__init__.py +35 -0
  132. diffbio/operators/statistical/em_quantification.py +260 -0
  133. diffbio/operators/statistical/hmm.py +234 -0
  134. diffbio/operators/statistical/nb_glm.py +272 -0
  135. diffbio/operators/variant/__init__.py +64 -0
  136. diffbio/operators/variant/classifier.py +333 -0
  137. diffbio/operators/variant/cnn_classifier.py +255 -0
  138. diffbio/operators/variant/cnv_segmentation.py +678 -0
  139. diffbio/operators/variant/deepvariant_pileup.py +426 -0
  140. diffbio/operators/variant/pileup.py +240 -0
  141. diffbio/operators/variant/quality_recalibration.py +274 -0
  142. diffbio/pipelines/__init__.py +65 -0
  143. diffbio/pipelines/differential_expression.py +279 -0
  144. diffbio/pipelines/enhanced_variant_calling.py +326 -0
  145. diffbio/pipelines/perturbation.py +407 -0
  146. diffbio/pipelines/preprocessing.py +267 -0
  147. diffbio/pipelines/single_cell.py +366 -0
  148. diffbio/pipelines/variant_calling.py +490 -0
  149. diffbio/samplers/__init__.py +9 -0
  150. diffbio/samplers/perturbation_sampler.py +142 -0
  151. diffbio/sequences/__init__.py +34 -0
  152. diffbio/sequences/dna.py +239 -0
  153. diffbio/sources/__init__.py +149 -0
  154. diffbio/sources/_anndata_shared.py +89 -0
  155. diffbio/sources/_batch_iteration.py +37 -0
  156. diffbio/sources/_benchmark_source.py +152 -0
  157. diffbio/sources/_indexed_batch_source.py +38 -0
  158. diffbio/sources/_utils.py +45 -0
  159. diffbio/sources/anndata_interop.py +387 -0
  160. diffbio/sources/anndata_source.py +361 -0
  161. diffbio/sources/archive_ii.py +174 -0
  162. diffbio/sources/balifam.py +207 -0
  163. diffbio/sources/bam.py +265 -0
  164. diffbio/sources/bengrn_ground_truth.py +306 -0
  165. diffbio/sources/contextual_epigenomics.py +242 -0
  166. diffbio/sources/dti.py +359 -0
  167. diffbio/sources/embeddings.py +203 -0
  168. diffbio/sources/encode_peaks.py +223 -0
  169. diffbio/sources/fasta.py +226 -0
  170. diffbio/sources/immune_human.py +172 -0
  171. diffbio/sources/indexed_embeddings.py +128 -0
  172. diffbio/sources/indexed_view.py +191 -0
  173. diffbio/sources/molnet.py +493 -0
  174. diffbio/sources/multiomics.py +279 -0
  175. diffbio/sources/pancreas.py +108 -0
  176. diffbio/sources/perturbation/__init__.py +69 -0
  177. diffbio/sources/perturbation/_types.py +51 -0
  178. diffbio/sources/perturbation/_utils.py +125 -0
  179. diffbio/sources/perturbation/concat_source.py +115 -0
  180. diffbio/sources/perturbation/control_mapping.py +215 -0
  181. diffbio/sources/perturbation/experiment_config.py +261 -0
  182. diffbio/sources/perturbation/h5_metadata_cache.py +218 -0
  183. diffbio/sources/perturbation/output_space.py +52 -0
  184. diffbio/sources/perturbation/perturbation_source.py +513 -0
  185. diffbio/sources/seqfish.py +145 -0
  186. diffbio/sources/sequence_foundation.py +68 -0
  187. diffbio/sources/singlecell_foundation.py +68 -0
  188. diffbio/splitters/__init__.py +63 -0
  189. diffbio/splitters/base.py +251 -0
  190. diffbio/splitters/molecular.py +330 -0
  191. diffbio/splitters/perturbation.py +199 -0
  192. diffbio/splitters/random.py +217 -0
  193. diffbio/splitters/sequence.py +201 -0
  194. diffbio/utils/__init__.py +55 -0
  195. diffbio/utils/dependency_runtime.py +115 -0
  196. diffbio/utils/nn_utils.py +157 -0
  197. diffbio/utils/quality.py +45 -0
  198. diffbio/utils/training.py +585 -0
  199. diffbio-0.1.0.dist-info/METADATA +480 -0
  200. diffbio-0.1.0.dist-info/RECORD +202 -0
  201. diffbio-0.1.0.dist-info/WHEEL +4 -0
  202. diffbio-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,157 @@
1
+ """Benchmark problem definition and JSON loading.
2
+
3
+ Defines the ``BenchmarkProblem`` dataclass representing a single evaluation
4
+ problem from scBench or SpatialBench, and provides utilities for loading
5
+ problem sets from JSON files.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import logging
12
+ from dataclasses import dataclass, field
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ @dataclass(frozen=True, slots=True, kw_only=True)
20
+ class _TaskSpec:
21
+ """Normalized task specification for a benchmark problem."""
22
+
23
+ type: str
24
+ config: dict[str, Any] = field(default_factory=dict)
25
+
26
+
27
+ @dataclass(frozen=True, slots=True, kw_only=True)
28
+ class _GraderSpec:
29
+ """Normalized grader specification for a benchmark problem."""
30
+
31
+ type: str
32
+ config: dict[str, Any] = field(default_factory=dict)
33
+
34
+
35
+ @dataclass(frozen=True, slots=True, kw_only=True, init=False)
36
+ class BenchmarkProblem:
37
+ """A single benchmark evaluation problem.
38
+
39
+ Public properties:
40
+ problem_id: Unique identifier for the problem.
41
+ task_type: Category of the task (e.g., ``"qc_filtering"``,
42
+ ``"clustering"``, ``"differential_expression"``).
43
+ grader_type: Which grading algorithm to use (e.g.,
44
+ ``"numeric_tolerance"``, ``"multiple_choice"``).
45
+ expected_answer: The ground-truth answer in the format expected
46
+ by the corresponding grader.
47
+ grader_config: Extra parameters for the grader (e.g.,
48
+ ``{"tolerance": 0.05, "mode": "relative"}``).
49
+ task_config: Parameters for the DiffBio operator invocation
50
+ (e.g., ``{"n_clusters": 5, "temperature": 1.0}``).
51
+ data_path: Optional path to the h5ad or other data file.
52
+ description: Human-readable description of the problem.
53
+ source: Origin benchmark suite (``"scbench"`` or ``"spatialbench"``).
54
+ """
55
+
56
+ problem_id: str
57
+ expected_answer: Any
58
+ data_path: str | None
59
+ description: str
60
+ source: str
61
+ _task: _TaskSpec = field(init=False, repr=False)
62
+ _grader: _GraderSpec = field(init=False, repr=False)
63
+
64
+ def __init__(
65
+ self,
66
+ *,
67
+ problem_id: str,
68
+ task_type: str,
69
+ grader_type: str,
70
+ expected_answer: Any,
71
+ grader_config: dict[str, Any] | None = None,
72
+ task_config: dict[str, Any] | None = None,
73
+ data_path: str | None = None,
74
+ description: str = "",
75
+ source: str = "",
76
+ ) -> None:
77
+ """Normalize task and grader state into grouped specs."""
78
+ object.__setattr__(self, "problem_id", problem_id)
79
+ object.__setattr__(self, "expected_answer", expected_answer)
80
+ object.__setattr__(self, "data_path", data_path)
81
+ object.__setattr__(self, "description", description)
82
+ object.__setattr__(self, "source", source)
83
+ object.__setattr__(
84
+ self,
85
+ "_task",
86
+ _TaskSpec(type=task_type, config=dict(task_config or {})),
87
+ )
88
+ object.__setattr__(
89
+ self,
90
+ "_grader",
91
+ _GraderSpec(type=grader_type, config=dict(grader_config or {})),
92
+ )
93
+
94
+ @property
95
+ def task_type(self) -> str:
96
+ """Return the task category."""
97
+ return self._task.type
98
+
99
+ @property
100
+ def task_config(self) -> dict[str, Any]:
101
+ """Return task-specific operator configuration."""
102
+ return self._task.config
103
+
104
+ @property
105
+ def grader_type(self) -> str:
106
+ """Return the grader category."""
107
+ return self._grader.type
108
+
109
+ @property
110
+ def grader_config(self) -> dict[str, Any]:
111
+ """Return grader-specific configuration."""
112
+ return self._grader.config
113
+
114
+
115
+ def load_problems(path: Path | str) -> list[BenchmarkProblem]:
116
+ """Load benchmark problems from a JSON file.
117
+
118
+ The JSON file should contain a list of objects, each with fields matching
119
+ ``BenchmarkProblem`` attributes.
120
+
121
+ Args:
122
+ path: Path to the JSON file.
123
+
124
+ Returns:
125
+ List of parsed BenchmarkProblem instances.
126
+
127
+ Raises:
128
+ FileNotFoundError: If the file does not exist.
129
+ json.JSONDecodeError: If the file is not valid JSON.
130
+ KeyError: If a required field is missing from a problem entry.
131
+ """
132
+ path = Path(path)
133
+ if not path.exists():
134
+ raise FileNotFoundError(f"Benchmark problems file not found: {path}")
135
+
136
+ raw = json.loads(path.read_text(encoding="utf-8"))
137
+
138
+ if isinstance(raw, dict) and "problems" in raw:
139
+ raw = raw["problems"]
140
+
141
+ problems: list[BenchmarkProblem] = []
142
+ for entry in raw:
143
+ problem = BenchmarkProblem(
144
+ problem_id=entry["problem_id"],
145
+ task_type=entry["task_type"],
146
+ grader_type=entry["grader_type"],
147
+ expected_answer=entry["expected_answer"],
148
+ grader_config=entry.get("grader_config", {}),
149
+ task_config=entry.get("task_config", {}),
150
+ data_path=entry.get("data_path"),
151
+ description=entry.get("description", ""),
152
+ source=entry.get("source", ""),
153
+ )
154
+ problems.append(problem)
155
+
156
+ logger.info("Loaded %d benchmark problems from %s", len(problems), path)
157
+ return problems
@@ -0,0 +1,277 @@
1
+ """Evaluation runner for executing benchmark problems and collecting results.
2
+
3
+ Provides ``run_problem`` for single-problem execution, ``run_benchmark``
4
+ for batch execution, and ``summarize`` for aggregating results.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+ import time
11
+ from dataclasses import dataclass, field
12
+ from typing import Any, cast
13
+
14
+ from diffbio.evaluation.adapters import TaskAdapter
15
+ from diffbio.evaluation.graders import (
16
+ GradeResult,
17
+ grade_distribution_comparison,
18
+ grade_label_set_jaccard,
19
+ grade_marker_gene_precision_recall,
20
+ grade_multiple_choice,
21
+ grade_numeric_tolerance,
22
+ )
23
+ from diffbio.evaluation.problem import BenchmarkProblem
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ @dataclass(frozen=True, slots=True, kw_only=True)
29
+ class EvalResult:
30
+ """Result of evaluating a single benchmark problem.
31
+
32
+ Attributes:
33
+ problem_id: Identifier of the evaluated problem.
34
+ task_type: Task category of the problem.
35
+ grade: The grading result from the appropriate grader.
36
+ predicted_answer: The raw answer produced by the adapter.
37
+ quality_metrics: Calibrax quality metrics on operator output
38
+ (e.g., silhouette score for clustering, MMD for batch correction).
39
+ elapsed_seconds: Wall-clock time for the solve step.
40
+ error: Error message if the solve step failed, else empty string.
41
+ """
42
+
43
+ problem_id: str
44
+ task_type: str
45
+ grade: GradeResult
46
+ predicted_answer: Any = None
47
+ quality_metrics: dict[str, float] = field(default_factory=dict)
48
+ elapsed_seconds: float = 0.0
49
+ error: str = ""
50
+
51
+
52
+ @dataclass(frozen=True, slots=True, kw_only=True)
53
+ class BenchmarkSummary:
54
+ """Aggregated summary of a benchmark run.
55
+
56
+ Attributes:
57
+ total: Total number of problems evaluated.
58
+ passed: Number of problems that passed.
59
+ failed: Number of problems that failed.
60
+ errored: Number of problems that encountered errors.
61
+ pass_rate: Fraction of problems that passed (0.0 to 1.0).
62
+ mean_score: Mean score across all evaluated problems.
63
+ by_task_type: Pass rate broken down by task_type.
64
+ """
65
+
66
+ total: int
67
+ passed: int
68
+ failed: int
69
+ errored: int
70
+ pass_rate: float
71
+ mean_score: float
72
+ by_task_type: dict[str, float] = field(default_factory=dict)
73
+
74
+
75
+ def _grade_answer(
76
+ predicted: Any,
77
+ problem: BenchmarkProblem,
78
+ ) -> GradeResult:
79
+ """Apply the appropriate grader to a predicted answer.
80
+
81
+ Args:
82
+ predicted: The answer produced by the TaskAdapter.
83
+ problem: The benchmark problem (contains expected answer and grader config).
84
+
85
+ Returns:
86
+ GradeResult from the matching grader algorithm.
87
+
88
+ Raises:
89
+ ValueError: If grader_type is not recognised.
90
+ """
91
+ grader_type = problem.grader_type
92
+ expected = problem.expected_answer
93
+ config = problem.grader_config
94
+
95
+ if grader_type == "numeric_tolerance":
96
+ return grade_numeric_tolerance(
97
+ float(predicted),
98
+ float(expected),
99
+ tolerance=config.get("tolerance", 0.1),
100
+ mode=config.get("mode", "absolute"),
101
+ )
102
+
103
+ if grader_type == "multiple_choice":
104
+ return grade_multiple_choice(str(predicted), str(expected))
105
+
106
+ if grader_type == "marker_gene_precision_recall":
107
+ return grade_marker_gene_precision_recall(
108
+ list(predicted),
109
+ list(expected),
110
+ k=config.get("k"),
111
+ )
112
+
113
+ if grader_type == "distribution_comparison":
114
+ return grade_distribution_comparison(
115
+ dict(predicted),
116
+ dict(expected),
117
+ tolerance=config.get("tolerance", 0.1),
118
+ )
119
+
120
+ if grader_type == "label_set_jaccard":
121
+ return grade_label_set_jaccard(
122
+ set(predicted) if not isinstance(predicted, set) else predicted,
123
+ set(expected) if not isinstance(expected, set) else expected,
124
+ threshold=config.get("threshold", 0.5),
125
+ )
126
+
127
+ raise ValueError(
128
+ f"Unknown grader_type {grader_type!r}. Supported: "
129
+ "numeric_tolerance, multiple_choice, marker_gene_precision_recall, "
130
+ "distribution_comparison, label_set_jaccard"
131
+ )
132
+
133
+
134
+ def run_problem(
135
+ problem: BenchmarkProblem,
136
+ data_dict: dict[str, Any],
137
+ *,
138
+ adapter: TaskAdapter | None = None,
139
+ collect_metrics: bool = True,
140
+ ) -> EvalResult:
141
+ """Run a single benchmark problem end-to-end.
142
+
143
+ Args:
144
+ problem: The benchmark problem to evaluate.
145
+ data_dict: Operator-ready input data.
146
+ adapter: Optional TaskAdapter instance. Created with defaults if None.
147
+ collect_metrics: Whether to compute calibrax quality metrics.
148
+
149
+ Returns:
150
+ EvalResult with grading outcome and optional quality metrics.
151
+ """
152
+ if adapter is None:
153
+ adapter = TaskAdapter()
154
+
155
+ quality_metrics: dict[str, float] = {}
156
+ try:
157
+ t0 = time.monotonic()
158
+ if collect_metrics:
159
+ predicted, quality_metrics = adapter.solve_with_metrics(problem, data_dict)
160
+ else:
161
+ predicted = adapter.solve(problem, data_dict)
162
+ elapsed = time.monotonic() - t0
163
+ except Exception as exc:
164
+ logger.warning("Problem %s failed: %s", problem.problem_id, exc)
165
+ return EvalResult(
166
+ problem_id=problem.problem_id,
167
+ task_type=problem.task_type,
168
+ grade=GradeResult(passed=False, score=0.0, detail=f"solve error: {exc}"),
169
+ error=str(exc),
170
+ )
171
+
172
+ try:
173
+ grade = _grade_answer(predicted, problem)
174
+ except Exception as exc:
175
+ logger.warning("Grading %s failed: %s", problem.problem_id, exc)
176
+ return EvalResult(
177
+ problem_id=problem.problem_id,
178
+ task_type=problem.task_type,
179
+ grade=GradeResult(passed=False, score=0.0, detail=f"grading error: {exc}"),
180
+ predicted_answer=predicted,
181
+ quality_metrics=quality_metrics,
182
+ elapsed_seconds=elapsed,
183
+ error=str(exc),
184
+ )
185
+
186
+ return EvalResult(
187
+ problem_id=problem.problem_id,
188
+ task_type=problem.task_type,
189
+ grade=grade,
190
+ predicted_answer=predicted,
191
+ quality_metrics=quality_metrics,
192
+ elapsed_seconds=elapsed,
193
+ )
194
+
195
+
196
+ def run_benchmark(
197
+ problems: list[BenchmarkProblem],
198
+ data_loader: Any,
199
+ *,
200
+ adapter: TaskAdapter | None = None,
201
+ ) -> list[EvalResult]:
202
+ """Run a batch of benchmark problems.
203
+
204
+ Args:
205
+ problems: List of problems to evaluate.
206
+ data_loader: Callable ``(problem) -> dict`` that provides operator-ready
207
+ data for each problem. Can also be a dict mapping problem_id to data.
208
+ adapter: Optional shared TaskAdapter.
209
+
210
+ Returns:
211
+ List of EvalResult, one per problem.
212
+ """
213
+ if adapter is None:
214
+ adapter = TaskAdapter()
215
+
216
+ results: list[EvalResult] = []
217
+ for problem in problems:
218
+ data_dict: dict[str, Any]
219
+ if callable(data_loader):
220
+ data_dict = data_loader(problem)
221
+ else:
222
+ data_dict = cast(dict[str, Any], data_loader[problem.problem_id])
223
+ result = run_problem(problem, data_dict, adapter=adapter)
224
+ results.append(result)
225
+ logger.info(
226
+ "Problem %s: %s (score=%.3f, %.2fs)",
227
+ problem.problem_id,
228
+ "PASS" if result.grade.passed else "FAIL",
229
+ result.grade.score,
230
+ result.elapsed_seconds,
231
+ )
232
+
233
+ return results
234
+
235
+
236
+ def summarize(results: list[EvalResult]) -> BenchmarkSummary:
237
+ """Aggregate evaluation results into a summary.
238
+
239
+ Args:
240
+ results: List of EvalResult from a benchmark run.
241
+
242
+ Returns:
243
+ BenchmarkSummary with pass rates and score statistics.
244
+ """
245
+ if not results:
246
+ return BenchmarkSummary(
247
+ total=0,
248
+ passed=0,
249
+ failed=0,
250
+ errored=0,
251
+ pass_rate=0.0,
252
+ mean_score=0.0,
253
+ )
254
+
255
+ passed = sum(1 for r in results if r.grade.passed)
256
+ errored = sum(1 for r in results if r.error)
257
+ failed = len(results) - passed - errored
258
+
259
+ scores = [r.grade.score for r in results]
260
+ mean_score = sum(scores) / len(scores) if scores else 0.0
261
+
262
+ # Per-task-type pass rate
263
+ by_task: dict[str, list[bool]] = {}
264
+ for r in results:
265
+ by_task.setdefault(r.task_type, []).append(r.grade.passed)
266
+
267
+ by_task_rate = {task: sum(passes) / len(passes) for task, passes in by_task.items()}
268
+
269
+ return BenchmarkSummary(
270
+ total=len(results),
271
+ passed=passed,
272
+ failed=failed,
273
+ errored=errored,
274
+ pass_rate=passed / len(results),
275
+ mean_score=mean_score,
276
+ by_task_type=by_task_rate,
277
+ )
@@ -0,0 +1,59 @@
1
+ """Loss functions and regularization for bioinformatics pipelines.
2
+
3
+ This module provides loss functions for training differentiable bioinformatics
4
+ pipelines, including biological regularization to prevent adversarial optimization.
5
+ """
6
+
7
+ from diffbio.losses.alignment_losses import (
8
+ AlignmentConsistencyLoss,
9
+ AlignmentScoreLoss,
10
+ SoftEditDistanceLoss,
11
+ )
12
+ from diffbio.losses.biological_regularization import (
13
+ BiologicalPlausibilityLoss,
14
+ BiologicalRegularizationConfig,
15
+ GapPatternRegularization,
16
+ GCContentRegularization,
17
+ SequenceComplexityLoss,
18
+ )
19
+ from diffbio.losses.singlecell_losses import (
20
+ BatchMixingLoss,
21
+ ClusteringCompactnessLoss,
22
+ ShannonDiversityLoss,
23
+ SimpsonDiversityLoss,
24
+ VelocityConsistencyLoss,
25
+ )
26
+ from diffbio.losses.metric_losses import DifferentiableAUROC, ExactAUROC
27
+ from diffbio.losses.statistical_losses import (
28
+ HMMLikelihoodLoss,
29
+ NegativeBinomialLoss,
30
+ VAELoss,
31
+ zinb_negative_log_likelihood,
32
+ )
33
+
34
+ __all__ = [
35
+ # Alignment losses
36
+ "AlignmentConsistencyLoss",
37
+ "AlignmentScoreLoss",
38
+ "SoftEditDistanceLoss",
39
+ # Biological regularization
40
+ "BiologicalPlausibilityLoss",
41
+ "BiologicalRegularizationConfig",
42
+ "GapPatternRegularization",
43
+ "GCContentRegularization",
44
+ "SequenceComplexityLoss",
45
+ # Metric losses
46
+ "DifferentiableAUROC",
47
+ "ExactAUROC",
48
+ # Single-cell losses
49
+ "BatchMixingLoss",
50
+ "ClusteringCompactnessLoss",
51
+ "ShannonDiversityLoss",
52
+ "SimpsonDiversityLoss",
53
+ "VelocityConsistencyLoss",
54
+ # Statistical losses
55
+ "HMMLikelihoodLoss",
56
+ "NegativeBinomialLoss",
57
+ "VAELoss",
58
+ "zinb_negative_log_likelihood",
59
+ ]