bend2 1.0.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 (53) hide show
  1. bend2/__init__.py +12 -0
  2. bend2/__main__.py +3 -0
  3. bend2/_combine.py +191 -0
  4. bend2/_data/atlas-candidate-activities.parquet +0 -0
  5. bend2/_data/atlas-population.parquet +0 -0
  6. bend2/_data/atlas-programs.csv +17 -0
  7. bend2/_data/atlas-source-activities.parquet +0 -0
  8. bend2/_data/gtex-genes.tsv +19283 -0
  9. bend2/_data/gtex-targets.npz +0 -0
  10. bend2/_data/prior-panel.parquet +0 -0
  11. bend2/_data/prior-query-seeds.json +2063 -0
  12. bend2/_data/siraj-numeric-reference.npz +0 -0
  13. bend2/_data/siraj_selective.parquet +0 -0
  14. bend2/_examples/custom.py +26 -0
  15. bend2/_examples/dnabert2.py +10 -0
  16. bend2/_examples/huggingface.py +17 -0
  17. bend2/_examples/ntv3_prior.py +12 -0
  18. bend2/_linear.py +141 -0
  19. bend2/_rescoring/__init__.py +1 -0
  20. bend2/_rescoring/interventions.py +257 -0
  21. bend2/_rescoring/panels.py +272 -0
  22. bend2/_tasks/__init__.py +14 -0
  23. bend2/_tasks/_siraj.py +33 -0
  24. bend2/_tasks/atlas_profile.py +189 -0
  25. bend2/_tasks/background.py +56 -0
  26. bend2/_tasks/cell_selective.py +113 -0
  27. bend2/_tasks/context.py +28 -0
  28. bend2/_tasks/opensplice.py +33 -0
  29. bend2/_tasks/prior.py +90 -0
  30. bend2/_tasks/repair_percentile.py +72 -0
  31. bend2/_tasks/tissue_identification.py +182 -0
  32. bend2/assets.json +147 -0
  33. bend2/cache.py +212 -0
  34. bend2/catalog.py +52 -0
  35. bend2/cli.py +444 -0
  36. bend2/datasets.py +565 -0
  37. bend2/github.py +384 -0
  38. bend2/huggingface.py +183 -0
  39. bend2/model.py +215 -0
  40. bend2/planning.py +251 -0
  41. bend2/readouts.py +180 -0
  42. bend2/reference_models.py +433 -0
  43. bend2/results.py +186 -0
  44. bend2/reviews.py +365 -0
  45. bend2/runner.py +362 -0
  46. bend2/submissions.py +632 -0
  47. bend2-1.0.0.dist-info/METADATA +145 -0
  48. bend2-1.0.0.dist-info/RECORD +53 -0
  49. bend2-1.0.0.dist-info/WHEEL +5 -0
  50. bend2-1.0.0.dist-info/entry_points.txt +2 -0
  51. bend2-1.0.0.dist-info/licenses/LICENSE +21 -0
  52. bend2-1.0.0.dist-info/licenses/docs/data.md +107 -0
  53. bend2-1.0.0.dist-info/top_level.txt +1 -0
bend2/__init__.py ADDED
@@ -0,0 +1,12 @@
1
+ """BEND 2.0: a benchmark for DNA models."""
2
+
3
+ __version__ = "1.0.0"
4
+
5
+ from bend2.catalog import tasks
6
+ from bend2.model import Model
7
+ from bend2.planning import plan
8
+ from bend2.results import Run
9
+ from bend2.runner import evaluate
10
+ from bend2._combine import combine
11
+
12
+ __all__ = ["Model", "Run", "evaluate", "plan", "combine", "tasks"]
bend2/__main__.py ADDED
@@ -0,0 +1,3 @@
1
+ from bend2.cli import main
2
+
3
+ raise SystemExit(main())
bend2/_combine.py ADDED
@@ -0,0 +1,191 @@
1
+ """Combine task results from separate evaluations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import copy
6
+ import hashlib
7
+ import json
8
+ import shutil
9
+ from contextlib import ExitStack
10
+ from datetime import UTC, datetime
11
+ from pathlib import Path
12
+
13
+ from filelock import FileLock
14
+
15
+ from bend2 import __version__
16
+ from bend2.catalog import CATALOG
17
+ from bend2.datasets import task_identity
18
+ from bend2.results import Run, digest, write_json
19
+ from bend2.submissions import _PROTOCOLS, _TABLES, _data_summary, _subset
20
+
21
+
22
+ def _hash(value):
23
+ return hashlib.sha256(json.dumps(value, sort_keys=True).encode()).hexdigest()
24
+
25
+
26
+ def _merge_model(results):
27
+ models = [result["identity_spec"]["model"] for result in results]
28
+ merged = copy.deepcopy(models[0])
29
+ revision = merged.get("revision", "").strip()
30
+ if not revision or revision.lower() in {"main", "master", "latest", "head", "default"}:
31
+ raise ValueError("Declare a stable model revision before combining independent runs")
32
+ for model in models[1:]:
33
+ for field in merged:
34
+ if field != "adapter" and model[field] != merged[field]:
35
+ raise ValueError(f"Cannot combine runs with different model {field}")
36
+ for capability in merged["capabilities"]:
37
+ if (
38
+ merged["adapter"][capability]["inference_sha256"]
39
+ != model["adapter"][capability]["inference_sha256"]
40
+ ):
41
+ raise ValueError(f"Cannot combine different {capability} adapter implementations")
42
+ return merged
43
+
44
+
45
+ def _check_task(task, row):
46
+ """Check task protocols, data identities and evidence files."""
47
+ _subset(_PROTOCOLS[task], row["details"], f"{task}.protocol")
48
+ _subset(task_identity(task), _data_summary(row["data"]), f"{task}.data")
49
+ required = [f"{task}/{table}.parquet" for table in _TABLES[task]]
50
+ required.append(f"{task}/protocol.json")
51
+ if any(path not in row["files"] for path in required):
52
+ raise ValueError(f"Missing prediction/protocol evidence for {task}")
53
+ for relative in row["files"]:
54
+ if Path(relative).parts[0] != task or "\\" in relative:
55
+ raise ValueError(f"Unexpected evidence path for {task}: {relative}")
56
+
57
+
58
+ def _copy_verified(source, destination, expected):
59
+ destination.parent.mkdir(parents=True, exist_ok=True)
60
+ with source.open("rb") as reader, destination.open("xb") as writer:
61
+ shutil.copyfileobj(reader, writer, length=1024 * 1024)
62
+ if digest(destination) != expected:
63
+ raise ValueError(f"Source artifact changed while combining: {source}")
64
+
65
+
66
+ def combine(runs, output) -> Run:
67
+ """Combine task results from the same model.
68
+
69
+ ``runs`` is a sequence of :class:`Run` objects or result-directory paths.
70
+ ``output`` must not exist. Duplicate tasks must have identical results.
71
+ The new directory includes the original environments, adapters and benchmark
72
+ source. To add tasks later, evaluate them separately and combine again.
73
+ """
74
+ if isinstance(runs, (str, Path, Run)):
75
+ raise TypeError("Pass a list of at least two result directories or Run objects")
76
+ sources = [run if isinstance(run, Run) else Run(run) for run in runs]
77
+ if len(sources) < 2:
78
+ raise ValueError("Choose at least two runs to combine")
79
+ if len({run.directory for run in sources}) != len(sources):
80
+ raise ValueError("The same result directory was supplied more than once")
81
+ destination = Path(output).expanduser().resolve()
82
+ if destination.exists():
83
+ raise FileExistsError(f"Combined output already exists: {destination}; choose a new directory")
84
+ with ExitStack() as stack:
85
+ for run in sorted(sources, key=lambda item: str(item.directory)):
86
+ if not (run.directory / "result.json").is_file():
87
+ raise ValueError(f"No BEND result.json in {run.directory}")
88
+ stack.enter_context(FileLock(run.directory / ".run.lock", timeout=0))
89
+ results = [run.validate() for run in sources]
90
+ merged_model = _merge_model(results)
91
+ rows, owners, origins = {}, {}, []
92
+ for run, result in zip(sources, results, strict=True):
93
+ source_result_hash = digest(run.directory / "result.json")
94
+ # The manifest hash distinguishes separate executions of the same model.
95
+ origin_key = source_result_hash
96
+ origin = {
97
+ "run_identity": result["identity"],
98
+ "result_sha256": source_result_hash,
99
+ "manifest": f"implementation/origins/{origin_key}/result.json",
100
+ "created_at": result["created_at"],
101
+ "status": result["status"],
102
+ "package_version": result["version"],
103
+ "environment": result.get("environment", {}),
104
+ "tasks": list(result["tasks"]),
105
+ }
106
+ origins.append(origin)
107
+ for task, row in result["tasks"].items():
108
+ if CATALOG[task][1] not in merged_model["capabilities"]:
109
+ raise ValueError(f"Model does not declare the capability required for {task}")
110
+ if task in merged_model.get("unsupported_tasks", {}):
111
+ raise ValueError(f"Model declares evaluated task {task} unsupported")
112
+ _check_task(task, row)
113
+ if task in rows:
114
+ keys = ("metric", "details", "data", "files")
115
+ if any(rows[task][key] != row[key] for key in keys):
116
+ raise ValueError(
117
+ f"Conflicting results for {task}; combine complementary runs or "
118
+ "identical evidence, never selected scores"
119
+ )
120
+ rows[task]["origins"].append(copy.deepcopy(origin))
121
+ else:
122
+ rows[task] = copy.deepcopy(row)
123
+ rows[task].pop("invocation", None) # Resolved in each original manifest below.
124
+ rows[task]["origins"] = [copy.deepcopy(origin)]
125
+ owners[task] = run
126
+ if not rows:
127
+ raise ValueError("No completed tasks are available to combine")
128
+ requested = list(
129
+ dict.fromkeys(task for result in results for task in result["requested_tasks"])
130
+ )
131
+ skipped = {}
132
+ for result in results:
133
+ for task, reason in result.get("skipped", {}).items():
134
+ if task not in rows:
135
+ if task in skipped and skipped[task] != reason:
136
+ raise ValueError(f"Conflicting skipped-task explanations for {task}")
137
+ skipped[task] = reason
138
+ pending = set(requested) - set(rows) - set(skipped)
139
+ identity_spec = {
140
+ "model": merged_model,
141
+ "combined_from": [
142
+ {key: item[key] for key in ("run_identity", "result_sha256")}
143
+ for item in origins
144
+ ],
145
+ }
146
+ data = {
147
+ "schema_version": 1,
148
+ "benchmark": "BEND 2.0",
149
+ "version": __version__,
150
+ "identity": _hash(identity_spec),
151
+ "identity_spec": identity_spec,
152
+ "model": {key: value for key, value in merged_model.items() if key != "adapter"},
153
+ "created_at": datetime.now(UTC).isoformat(),
154
+ "status": "incomplete" if pending else "complete",
155
+ "combined_from": origins,
156
+ "requested_tasks": requested,
157
+ "skipped": skipped,
158
+ "tasks": {task: rows[task] for task in CATALOG if task in rows},
159
+ "implementation_files": {},
160
+ }
161
+ destination.parent.mkdir(parents=True, exist_ok=True)
162
+ destination.mkdir()
163
+ try:
164
+ for task, row in data["tasks"].items():
165
+ for relative, checksum in row["files"].items():
166
+ _copy_verified(owners[task].directory / relative, destination / relative, checksum)
167
+ for run, result, origin in zip(sources, results, origins, strict=True):
168
+ prefix = Path(origin["manifest"]).parent
169
+ files = {"result.json": origin["result_sha256"]}
170
+ files.update(result.get("implementation_files", {}))
171
+ if result.get("adapter_file"):
172
+ files["model.py"] = result["adapter_file"]
173
+ for relative, checksum in files.items():
174
+ copied = (prefix / relative).as_posix()
175
+ if copied in data["implementation_files"]:
176
+ continue # Byte-identical source manifests share one snapshot.
177
+ _copy_verified(run.directory / relative, destination / copied, checksum)
178
+ data["implementation_files"][copied] = checksum
179
+ # Keep an adapter at the top level as well as in the source snapshots.
180
+ for run, result in zip(sources, results, strict=True):
181
+ if result.get("adapter_file"):
182
+ _copy_verified(
183
+ run.directory / "model.py", destination / "model.py", result["adapter_file"]
184
+ )
185
+ data["adapter_file"] = result["adapter_file"]
186
+ break
187
+ write_json(destination / "result.json", data)
188
+ return Run(destination)
189
+ except BaseException:
190
+ shutil.rmtree(destination)
191
+ raise
Binary file
@@ -0,0 +1,17 @@
1
+ program,on_cells,off_cells
2
+ 786_O|GM12878|SKNSH,786_O|GM12878|SKNSH,NT2_D1|WERI_Rb1|SJCRH30|HepG2|K562|MCF7|HeLaS3|HEK293|HMC3
3
+ 786_O|MCF7,786_O|MCF7,NT2_D1|GM12878|SKNSH|WERI_Rb1|SJCRH30|HepG2|K562|HeLaS3|HEK293|HMC3
4
+ GM12878|HepG2|NT2_D1,GM12878|HepG2|NT2_D1,786_O|SKNSH|WERI_Rb1|SJCRH30|K562|MCF7|HeLaS3|HEK293|HMC3
5
+ GM12878|K562,GM12878|K562,NT2_D1|786_O|SKNSH|WERI_Rb1|SJCRH30|HepG2|MCF7|HeLaS3|HEK293|HMC3
6
+ GM12878|SJCRH30|WERI_Rb1,GM12878|SJCRH30|WERI_Rb1,NT2_D1|786_O|SKNSH|HepG2|K562|MCF7|HeLaS3|HEK293|HMC3
7
+ GM12878|SKNSH,GM12878|SKNSH,NT2_D1|786_O|WERI_Rb1|SJCRH30|HepG2|K562|MCF7|HeLaS3|HEK293|HMC3
8
+ HeLaS3|HepG2,HeLaS3|HepG2,NT2_D1|GM12878|786_O|SKNSH|WERI_Rb1|SJCRH30|K562|MCF7|HEK293|HMC3
9
+ HeLaS3|HepG2|MCF7,HeLaS3|HepG2|MCF7,NT2_D1|GM12878|786_O|SKNSH|WERI_Rb1|SJCRH30|K562|HEK293|HMC3
10
+ HeLaS3|K562|MCF7,HeLaS3|K562|MCF7,NT2_D1|GM12878|786_O|SKNSH|WERI_Rb1|SJCRH30|HepG2|HEK293|HMC3
11
+ HeLaS3|NT2_D1|WERI_Rb1,HeLaS3|NT2_D1|WERI_Rb1,GM12878|786_O|SKNSH|SJCRH30|HepG2|K562|MCF7|HEK293|HMC3
12
+ HeLaS3|SJCRH30|SKNSH,HeLaS3|SJCRH30|SKNSH,NT2_D1|GM12878|786_O|WERI_Rb1|HepG2|K562|MCF7|HEK293|HMC3
13
+ HepG2|K562,HepG2|K562,NT2_D1|GM12878|786_O|SKNSH|WERI_Rb1|SJCRH30|MCF7|HeLaS3|HEK293|HMC3
14
+ HepG2|K562|SKNSH,HepG2|K562|SKNSH,NT2_D1|GM12878|786_O|WERI_Rb1|SJCRH30|MCF7|HeLaS3|HEK293|HMC3
15
+ HepG2|WERI_Rb1,HepG2|WERI_Rb1,NT2_D1|GM12878|786_O|SKNSH|SJCRH30|K562|MCF7|HeLaS3|HEK293|HMC3
16
+ NT2_D1|SKNSH,NT2_D1|SKNSH,GM12878|786_O|WERI_Rb1|SJCRH30|HepG2|K562|MCF7|HeLaS3|HEK293|HMC3
17
+ SJCRH30|SKNSH,SJCRH30|SKNSH,NT2_D1|GM12878|786_O|WERI_Rb1|HepG2|K562|MCF7|HeLaS3|HEK293|HMC3