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.
- diffbio/__init__.py +39 -0
- diffbio/configs.py +75 -0
- diffbio/constants.py +204 -0
- diffbio/core/__init__.py +127 -0
- diffbio/core/base_operators.py +612 -0
- diffbio/core/data_types.py +260 -0
- diffbio/core/gnn_components.py +629 -0
- diffbio/core/graph_utils.py +149 -0
- diffbio/core/neural_components.py +270 -0
- diffbio/core/optimal_transport.py +133 -0
- diffbio/core/soft_ops/__init__.py +216 -0
- diffbio/core/soft_ops/_projections_permutahedron.py +1864 -0
- diffbio/core/soft_ops/_projections_simplex.py +240 -0
- diffbio/core/soft_ops/_projections_transport.py +508 -0
- diffbio/core/soft_ops/_sorting_network.py +204 -0
- diffbio/core/soft_ops/_types.py +15 -0
- diffbio/core/soft_ops/_utils.py +342 -0
- diffbio/core/soft_ops/autograd_safe.py +120 -0
- diffbio/core/soft_ops/comparison.py +235 -0
- diffbio/core/soft_ops/elementwise.py +309 -0
- diffbio/core/soft_ops/logical.py +146 -0
- diffbio/core/soft_ops/quantile.py +376 -0
- diffbio/core/soft_ops/selection.py +236 -0
- diffbio/core/soft_ops/sorting.py +926 -0
- diffbio/core/soft_ops/straight_through.py +261 -0
- diffbio/core/uncertainty.py +279 -0
- diffbio/evaluation/__init__.py +42 -0
- diffbio/evaluation/adapters.py +409 -0
- diffbio/evaluation/graders.py +223 -0
- diffbio/evaluation/problem.py +157 -0
- diffbio/evaluation/runner.py +277 -0
- diffbio/losses/__init__.py +59 -0
- diffbio/losses/alignment_losses.py +222 -0
- diffbio/losses/biological_regularization.py +288 -0
- diffbio/losses/metric_losses.py +139 -0
- diffbio/losses/singlecell_losses.py +387 -0
- diffbio/losses/statistical_losses.py +345 -0
- diffbio/operators/__init__.py +60 -0
- diffbio/operators/_count_vae.py +197 -0
- diffbio/operators/_loss_balancing.py +65 -0
- diffbio/operators/_masked_gene_transformer.py +118 -0
- diffbio/operators/_transformer_validation.py +50 -0
- diffbio/operators/alignment/__init__.py +51 -0
- diffbio/operators/alignment/profile_hmm.py +350 -0
- diffbio/operators/alignment/scoring.py +127 -0
- diffbio/operators/alignment/smith_waterman.py +261 -0
- diffbio/operators/alignment/soft_msa.py +419 -0
- diffbio/operators/assembly/__init__.py +27 -0
- diffbio/operators/assembly/gnn_assembly.py +252 -0
- diffbio/operators/assembly/metagenomic_binning.py +296 -0
- diffbio/operators/crispr/__init__.py +17 -0
- diffbio/operators/crispr/guide_scoring.py +269 -0
- diffbio/operators/drug_discovery/__init__.py +133 -0
- diffbio/operators/drug_discovery/_graph_utils.py +142 -0
- diffbio/operators/drug_discovery/admet_predictor.py +285 -0
- diffbio/operators/drug_discovery/attentive_fp.py +411 -0
- diffbio/operators/drug_discovery/dti.py +261 -0
- diffbio/operators/drug_discovery/fingerprint.py +490 -0
- diffbio/operators/drug_discovery/maccs_keys.py +267 -0
- diffbio/operators/drug_discovery/message_passing.py +200 -0
- diffbio/operators/drug_discovery/primitives.py +242 -0
- diffbio/operators/drug_discovery/property_predictor.py +163 -0
- diffbio/operators/drug_discovery/similarity.py +193 -0
- diffbio/operators/epigenomics/__init__.py +35 -0
- diffbio/operators/epigenomics/chromatin_state.py +491 -0
- diffbio/operators/epigenomics/contextual.py +288 -0
- diffbio/operators/epigenomics/fno_peak_calling.py +153 -0
- diffbio/operators/epigenomics/peak_calling.py +555 -0
- diffbio/operators/foundation_models/__init__.py +119 -0
- diffbio/operators/foundation_models/adapters.py +114 -0
- diffbio/operators/foundation_models/contracts.py +245 -0
- diffbio/operators/foundation_models/embedding_probe.py +83 -0
- diffbio/operators/foundation_models/experimental.py +128 -0
- diffbio/operators/foundation_models/foundation_model.py +332 -0
- diffbio/operators/foundation_models/frozen.py +59 -0
- diffbio/operators/foundation_models/precomputed.py +270 -0
- diffbio/operators/foundation_models/transformer_encoder.py +564 -0
- diffbio/operators/mapping/__init__.py +17 -0
- diffbio/operators/mapping/neural_mapper.py +493 -0
- diffbio/operators/metabolomics/__init__.py +39 -0
- diffbio/operators/metabolomics/spectral_similarity.py +315 -0
- diffbio/operators/molecular_dynamics/__init__.py +51 -0
- diffbio/operators/molecular_dynamics/force_field.py +265 -0
- diffbio/operators/molecular_dynamics/integrator.py +304 -0
- diffbio/operators/molecular_dynamics/primitives.py +115 -0
- diffbio/operators/multiomics/__init__.py +38 -0
- diffbio/operators/multiomics/hic_contact.py +377 -0
- diffbio/operators/multiomics/multiomics_vae.py +325 -0
- diffbio/operators/multiomics/spatial_deconvolution.py +316 -0
- diffbio/operators/multiomics/spatial_gene_detection.py +493 -0
- diffbio/operators/normalization/__init__.py +42 -0
- diffbio/operators/normalization/embedding.py +222 -0
- diffbio/operators/normalization/phate.py +400 -0
- diffbio/operators/normalization/umap.py +261 -0
- diffbio/operators/normalization/vae_normalizer.py +258 -0
- diffbio/operators/population/__init__.py +17 -0
- diffbio/operators/population/ancestry_estimation.py +274 -0
- diffbio/operators/preprocessing/__init__.py +76 -0
- diffbio/operators/preprocessing/adapter_removal.py +311 -0
- diffbio/operators/preprocessing/duplicate_filter.py +317 -0
- diffbio/operators/preprocessing/error_correction.py +287 -0
- diffbio/operators/protein/__init__.py +31 -0
- diffbio/operators/protein/secondary_structure.py +509 -0
- diffbio/operators/quality_filter.py +128 -0
- diffbio/operators/rna_structure/__init__.py +35 -0
- diffbio/operators/rna_structure/rna_folding.py +509 -0
- diffbio/operators/rnaseq/__init__.py +23 -0
- diffbio/operators/rnaseq/motif_discovery.py +251 -0
- diffbio/operators/rnaseq/splicing_psi.py +216 -0
- diffbio/operators/singlecell/__init__.py +193 -0
- diffbio/operators/singlecell/ambient_removal.py +333 -0
- diffbio/operators/singlecell/archetypes.py +191 -0
- diffbio/operators/singlecell/batch_correction.py +288 -0
- diffbio/operators/singlecell/cell_annotation.py +519 -0
- diffbio/operators/singlecell/communication.py +704 -0
- diffbio/operators/singlecell/differential_distribution.py +243 -0
- diffbio/operators/singlecell/doublet_detection.py +657 -0
- diffbio/operators/singlecell/downsampling.py +166 -0
- diffbio/operators/singlecell/enhanced_batch_correction.py +519 -0
- diffbio/operators/singlecell/grn_inference.py +336 -0
- diffbio/operators/singlecell/imputation.py +429 -0
- diffbio/operators/singlecell/knockdown_filter.py +176 -0
- diffbio/operators/singlecell/ot_trajectory.py +277 -0
- diffbio/operators/singlecell/simulation.py +444 -0
- diffbio/operators/singlecell/sindy_grn.py +247 -0
- diffbio/operators/singlecell/soft_clustering.py +211 -0
- diffbio/operators/singlecell/spatial_domains.py +677 -0
- diffbio/operators/singlecell/switch_de.py +184 -0
- diffbio/operators/singlecell/trajectory.py +447 -0
- diffbio/operators/singlecell/velocity.py +361 -0
- diffbio/operators/statistical/__init__.py +35 -0
- diffbio/operators/statistical/em_quantification.py +260 -0
- diffbio/operators/statistical/hmm.py +234 -0
- diffbio/operators/statistical/nb_glm.py +272 -0
- diffbio/operators/variant/__init__.py +64 -0
- diffbio/operators/variant/classifier.py +333 -0
- diffbio/operators/variant/cnn_classifier.py +255 -0
- diffbio/operators/variant/cnv_segmentation.py +678 -0
- diffbio/operators/variant/deepvariant_pileup.py +426 -0
- diffbio/operators/variant/pileup.py +240 -0
- diffbio/operators/variant/quality_recalibration.py +274 -0
- diffbio/pipelines/__init__.py +65 -0
- diffbio/pipelines/differential_expression.py +279 -0
- diffbio/pipelines/enhanced_variant_calling.py +326 -0
- diffbio/pipelines/perturbation.py +407 -0
- diffbio/pipelines/preprocessing.py +267 -0
- diffbio/pipelines/single_cell.py +366 -0
- diffbio/pipelines/variant_calling.py +490 -0
- diffbio/samplers/__init__.py +9 -0
- diffbio/samplers/perturbation_sampler.py +142 -0
- diffbio/sequences/__init__.py +34 -0
- diffbio/sequences/dna.py +239 -0
- diffbio/sources/__init__.py +149 -0
- diffbio/sources/_anndata_shared.py +89 -0
- diffbio/sources/_batch_iteration.py +37 -0
- diffbio/sources/_benchmark_source.py +152 -0
- diffbio/sources/_indexed_batch_source.py +38 -0
- diffbio/sources/_utils.py +45 -0
- diffbio/sources/anndata_interop.py +387 -0
- diffbio/sources/anndata_source.py +361 -0
- diffbio/sources/archive_ii.py +174 -0
- diffbio/sources/balifam.py +207 -0
- diffbio/sources/bam.py +265 -0
- diffbio/sources/bengrn_ground_truth.py +306 -0
- diffbio/sources/contextual_epigenomics.py +242 -0
- diffbio/sources/dti.py +359 -0
- diffbio/sources/embeddings.py +203 -0
- diffbio/sources/encode_peaks.py +223 -0
- diffbio/sources/fasta.py +226 -0
- diffbio/sources/immune_human.py +172 -0
- diffbio/sources/indexed_embeddings.py +128 -0
- diffbio/sources/indexed_view.py +191 -0
- diffbio/sources/molnet.py +493 -0
- diffbio/sources/multiomics.py +279 -0
- diffbio/sources/pancreas.py +108 -0
- diffbio/sources/perturbation/__init__.py +69 -0
- diffbio/sources/perturbation/_types.py +51 -0
- diffbio/sources/perturbation/_utils.py +125 -0
- diffbio/sources/perturbation/concat_source.py +115 -0
- diffbio/sources/perturbation/control_mapping.py +215 -0
- diffbio/sources/perturbation/experiment_config.py +261 -0
- diffbio/sources/perturbation/h5_metadata_cache.py +218 -0
- diffbio/sources/perturbation/output_space.py +52 -0
- diffbio/sources/perturbation/perturbation_source.py +513 -0
- diffbio/sources/seqfish.py +145 -0
- diffbio/sources/sequence_foundation.py +68 -0
- diffbio/sources/singlecell_foundation.py +68 -0
- diffbio/splitters/__init__.py +63 -0
- diffbio/splitters/base.py +251 -0
- diffbio/splitters/molecular.py +330 -0
- diffbio/splitters/perturbation.py +199 -0
- diffbio/splitters/random.py +217 -0
- diffbio/splitters/sequence.py +201 -0
- diffbio/utils/__init__.py +55 -0
- diffbio/utils/dependency_runtime.py +115 -0
- diffbio/utils/nn_utils.py +157 -0
- diffbio/utils/quality.py +45 -0
- diffbio/utils/training.py +585 -0
- diffbio-0.1.0.dist-info/METADATA +480 -0
- diffbio-0.1.0.dist-info/RECORD +202 -0
- diffbio-0.1.0.dist-info/WHEEL +4 -0
- 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
|
+
]
|