Download code/causalcelljepa/readout.py from NisargOza/CausalCellJEPA: direct link, hf CLI and curl.
- Browser
- Download file 125 kB
-
https://huggingface.co/NisargOza/CausalCellJEPA/resolve/main/code/causalcelljepa/readout.py
- Command line
-
hf download hf://NisargOza/CausalCellJEPA/code/causalcelljepa/readout.py
-
curl -L -o readout.py https://huggingface.co/NisargOza/CausalCellJEPA/resolve/main/code/causalcelljepa/readout.py
125 kB
| # Leakage-safe normalized-expression caching and latent-to-transcriptome readout. | |
| # The decoder is separate from—and never backpropagates into—the frozen world model. | |
| import json | |
| from collections import Counter, defaultdict | |
| from copy import deepcopy | |
| from hashlib import sha256 | |
| from itertools import product | |
| from pathlib import Path | |
| import anndata as ad | |
| import h5py | |
| import numpy as np | |
| import torch | |
| import yaml | |
| from scipy import sparse | |
| from scipy.stats import rankdata, t, wilcoxon | |
| from torch.utils.data import DataLoader | |
| from causalcelljepa.dynamics import ( | |
| LatentPopulationDataset, | |
| anchored_selected_entry, | |
| build_dynamics_model, | |
| learned_target_id_config, | |
| state_ablation_config, | |
| ) | |
| from causalcelljepa.evaluation import fit_linear_baseline | |
| from causalcelljepa.representations import build_autoencoder | |
| from causalcelljepa.resources import file_sha256 | |
| from causalcelljepa.training import _git_state, _runtime_environment, _runtime_source_hash | |
| def normalized_hvg_expression(counts, hvg_columns, library_size=10_000): | |
| """Normalize by each full measured library before selecting the frozen HVGs.""" | |
| counts = np.asarray(counts, dtype=np.float32) | |
| totals = counts.sum(1) | |
| assert np.isfinite(counts).all() and (counts >= 0).all() and (totals > 0).all() | |
| return np.log1p( | |
| counts[:, hvg_columns] * (np.float32(library_size) / totals)[:, None] | |
| ).astype(np.float32, copy=False) | |
| def write_expression_cache(config, output_path=None, maximum_cells=None, verify_raw=True): | |
| """Stream raw H5AD rows into a latent-aligned normalized 3,000-HVG cache.""" | |
| inputs, cache_config = config["inputs"], config["expression_cache"] | |
| replogle = json.loads(Path(inputs["replogle_manifest_path"]).read_text()) | |
| assert replogle["manifest_sha256"] == inputs["replogle_manifest_sha256"] | |
| replogle_config = yaml.safe_load(Path(inputs["replogle_config_path"]).read_text()) | |
| assert file_sha256(inputs["replogle_config_path"]) == replogle["runtime"]["config_sha256"] | |
| latent_path = Path(inputs["latent_cache_path"]) | |
| assert (latent_path.stat().st_size, file_sha256(latent_path)) == ( | |
| inputs["latent_cache_bytes"], | |
| inputs["latent_cache_sha256"], | |
| ) | |
| output = Path(output_path or cache_config["output_path"]) | |
| assert cache_config["dtype"] == "float32" and not output.exists() | |
| output.parent.mkdir(parents=True, exist_ok=True) | |
| temporary = output.with_suffix(output.suffix + ".tmp") | |
| assert not temporary.exists() | |
| with h5py.File(latent_path, "r") as latent: | |
| cells = min(int(latent.attrs["cells"]), maximum_cells or int(latent.attrs["cells"])) | |
| assert maximum_cells is not None or cells == cache_config["expected_cells"] | |
| contexts = latent["context"].asstr()[:cells] | |
| source_rows = latent["source_row"][:cells] | |
| roles = latent["role"].asstr()[:cells] | |
| with h5py.File(temporary, "w") as destination: | |
| destination.attrs.update( | |
| { | |
| "format_version": 1, | |
| "cells": cells, | |
| "hvg_count": cache_config["hvg_count"], | |
| "dtype": cache_config["dtype"], | |
| "library_size": replogle_config["data"]["library_size"], | |
| "latent_cache_sha256": inputs["latent_cache_sha256"], | |
| "replogle_manifest_sha256": inputs["replogle_manifest_sha256"], | |
| "hvg_sha256": replogle["genes"]["hvg_sha256"], | |
| "role_counts_json": json.dumps(dict(sorted(Counter(roles).items()))), | |
| "provenance_json": json.dumps( | |
| { | |
| "config_sha256": file_sha256("configs/readout.yaml"), | |
| "runtime_source_sha256": _runtime_source_hash(), | |
| "runtime_environment": _runtime_environment(), | |
| "git": _git_state(), | |
| }, | |
| sort_keys=True, | |
| ), | |
| } | |
| ) | |
| expression = destination.create_dataset( | |
| "expression", | |
| (cells, cache_config["hvg_count"]), | |
| dtype="f4", | |
| chunks=(cache_config["block_size"], cache_config["hvg_count"]), | |
| ) | |
| hvg_ids = replogle["genes"]["hvg_gene_ids"] | |
| for context in replogle_config["data"]["contexts"]: | |
| source = replogle_config["data"]["files"][context] | |
| path = Path(inputs["raw_directory"]) / source["filename"] | |
| assert path.stat().st_size == source["bytes"] | |
| if verify_raw: | |
| assert file_sha256(path) == source["sha256"] | |
| positions = np.flatnonzero(contexts == context) | |
| if not len(positions): | |
| continue | |
| assert np.array_equal(positions, np.arange(positions[0], positions[-1] + 1)) | |
| data = ad.read_h5ad(path, backed="r") | |
| columns = np.asarray([data.var_names.get_loc(gene) for gene in hvg_ids]) | |
| rows = source_rows[positions] | |
| assert np.all(rows[1:] > rows[:-1]) | |
| for start in range(0, len(positions), cache_config["block_size"]): | |
| selected = positions[start : start + cache_config["block_size"]] | |
| counts = np.asarray(data.X[source_rows[selected]]) | |
| expression[selected] = normalized_hvg_expression( | |
| counts, columns, replogle_config["data"]["library_size"] | |
| ) | |
| data.file.close() | |
| destination.flush() | |
| temporary.replace(output) | |
| return { | |
| "output": str(output), | |
| "bytes": output.stat().st_size, | |
| "sha256": file_sha256(output), | |
| "cells": cells, | |
| "hvg_count": cache_config["hvg_count"], | |
| "role_counts": dict(sorted(Counter(roles).items())), | |
| } | |
| def decoder_split(cell_ids, roles, fit_roles, seed, validation_fraction, maximum_cells=None): | |
| """Create an exact deterministic cell split restricted to representation-visible roles.""" | |
| eligible = np.flatnonzero(np.isin(roles, fit_roles)) | |
| ranked = sorted( | |
| eligible, | |
| key=lambda index: sha256(f"{seed}\0readout\0{cell_ids[index]}".encode()).digest(), | |
| ) | |
| if maximum_cells is not None: | |
| ranked = ranked[:maximum_cells] | |
| validation_cells = max(1, round(validation_fraction * len(ranked))) | |
| return np.sort(ranked[validation_cells:]), np.sort(ranked[:validation_cells]) | |
| def sufficient_statistics(latent, expression, indices, mean, scale, block_size): | |
| """Accumulate the exact multivariate linear-regression sufficient statistics.""" | |
| width, genes = latent.shape[1] + 1, expression.shape[1] | |
| gram = np.zeros((width, width), dtype=np.float64) | |
| cross = np.zeros((width, genes), dtype=np.float64) | |
| response_square = 0.0 | |
| for start in range(0, len(indices), block_size): | |
| selected = indices[start : start + block_size] | |
| design = np.empty((len(selected), width), dtype=np.float32) | |
| design[:, :-1] = (latent[selected] - mean) / scale | |
| design[:, -1] = 1 | |
| response = expression[selected].astype(np.float32) | |
| gram += design.T @ design | |
| cross += design.T @ response | |
| response_square += float(np.square(response.astype(np.float64)).sum()) | |
| return {"cells": len(indices), "gram": gram, "cross": cross, "response_square": response_square} | |
| def ridge_solution(statistics, alpha): | |
| """Solve the linear decoder while leaving the intercept unpenalized.""" | |
| penalty = np.eye(statistics["gram"].shape[0], dtype=np.float64) * alpha | |
| penalty[-1, -1] = 0 | |
| return np.linalg.solve(statistics["gram"] + penalty, statistics["cross"]) | |
| def regression_mse(statistics, solution): | |
| """Evaluate an un-clipped linear decoder exactly from sufficient statistics.""" | |
| residual = ( | |
| statistics["response_square"] | |
| - 2 * np.sum(solution * statistics["cross"]) | |
| + np.sum(solution * (statistics["gram"] @ solution)) | |
| ) | |
| return max(0.0, float(residual)) / (statistics["cells"] * statistics["cross"].shape[1]) | |
| def fit_readout(config, maximum_cells=None): | |
| """Select and refit the decoder using only explicitly permitted cell roles.""" | |
| inputs, decoder = config["inputs"], config["decoder"] | |
| assert decoder["architecture"] == "linear" | |
| dynamics = json.loads(Path(inputs["dynamics_manifest_path"]).read_text()) | |
| assert dynamics["manifest_sha256"] == inputs["dynamics_manifest_sha256"] | |
| latent_mean = np.asarray(dynamics["normalization"]["latent_mean"], dtype=np.float32) | |
| latent_scale = ( | |
| np.asarray(dynamics["normalization"]["latent_std"], dtype=np.float32) | |
| * dynamics["normalization"]["dimension_scale"] | |
| ) | |
| with h5py.File(inputs["latent_cache_path"], "r") as latent, h5py.File( | |
| config["expression_cache"]["output_path"], "r" | |
| ) as expression_cache: | |
| cells = int(expression_cache.attrs["cells"]) | |
| assert expression_cache.attrs["latent_cache_sha256"] == inputs["latent_cache_sha256"] | |
| cell_ids, roles = latent["cell_id"].asstr()[:cells], latent["role"].asstr()[:cells] | |
| train, validation = decoder_split( | |
| cell_ids, | |
| roles, | |
| decoder["fit_roles"], | |
| config["seed"], | |
| decoder["validation_fraction"], | |
| maximum_cells, | |
| ) | |
| train_stats = sufficient_statistics( | |
| latent["latent"], | |
| expression_cache["expression"], | |
| train, | |
| latent_mean, | |
| latent_scale, | |
| decoder["block_size"], | |
| ) | |
| validation_stats = sufficient_statistics( | |
| latent["latent"], | |
| expression_cache["expression"], | |
| validation, | |
| latent_mean, | |
| latent_scale, | |
| decoder["block_size"], | |
| ) | |
| candidates = [] | |
| for alpha in decoder["ridge_candidates"]: | |
| solution = ridge_solution(train_stats, alpha) | |
| candidates.append((regression_mse(validation_stats, solution), alpha)) | |
| validation_mse, selected_alpha = min(candidates) | |
| combined = { | |
| key: train_stats[key] + validation_stats[key] | |
| for key in ("cells", "gram", "cross", "response_square") | |
| } | |
| solution = ridge_solution(combined, selected_alpha) | |
| baseline_mean = train_stats["cross"][-1] / train_stats["cells"] | |
| baseline = np.vstack( | |
| (np.zeros((solution.shape[0] - 1, solution.shape[1])), baseline_mean) | |
| ) | |
| baseline_mse = regression_mse(validation_stats, baseline) | |
| used_roles = sorted(set(roles[np.concatenate((train, validation))])) | |
| assert used_roles == sorted(decoder["fit_roles"]) | |
| report = { | |
| "architecture": decoder["architecture"], | |
| "fit_roles": used_roles, | |
| "fit_cells": combined["cells"], | |
| "train_cells": train_stats["cells"], | |
| "validation_cells": validation_stats["cells"], | |
| "validation_fraction": decoder["validation_fraction"], | |
| "ridge_candidates": decoder["ridge_candidates"], | |
| "ridge_validation_mse": [value for value, _ in candidates], | |
| "selected_ridge": selected_alpha, | |
| "selected_validation_mse": validation_mse, | |
| "gene_mean_validation_mse": baseline_mse, | |
| "validation_explained_fraction": 1 - validation_mse / baseline_mse, | |
| "split_cell_ids_sha256": { | |
| "train": sha256("\n".join(sorted(cell_ids[train])).encode()).hexdigest(), | |
| "validation": sha256("\n".join(sorted(cell_ids[validation])).encode()).hexdigest(), | |
| }, | |
| } | |
| checkpoint = { | |
| "format_version": 1, | |
| "weights": torch.from_numpy(solution[:-1].astype(np.float32)), | |
| "bias": torch.from_numpy(solution[-1].astype(np.float32)), | |
| "latent_mean": torch.from_numpy(latent_mean), | |
| "latent_scale": torch.from_numpy(latent_scale), | |
| "output_clamp_min": decoder["output_clamp_min"], | |
| "report": report, | |
| "provenance": { | |
| "config_sha256": file_sha256("configs/readout.yaml"), | |
| "latent_cache_sha256": inputs["latent_cache_sha256"], | |
| "expression_cache_sha256": file_sha256(config["expression_cache"]["output_path"]), | |
| "replogle_manifest_sha256": inputs["replogle_manifest_sha256"], | |
| "dynamics_manifest_sha256": inputs["dynamics_manifest_sha256"], | |
| "runtime_source_sha256": _runtime_source_hash(), | |
| "runtime_environment": _runtime_environment(), | |
| "git": _git_state(), | |
| }, | |
| } | |
| assert torch.isfinite(checkpoint["weights"]).all() and torch.isfinite(checkpoint["bias"]).all() | |
| return checkpoint | |
| def decode_normalized_latents(latents, checkpoint): | |
| """Decode dynamics-space latents to nonnegative normalized log expression.""" | |
| return (latents @ checkpoint["weights"] + checkpoint["bias"]).clamp_min( | |
| checkpoint["output_clamp_min"] | |
| ) | |
| def decode_representation_centroids(latents, decoder): | |
| """Map normalized state centroids through one frozen representation-specific readout.""" | |
| if decoder["kind"] == "jepa_linear": | |
| return decode_normalized_latents(latents, decoder["checkpoint"]) | |
| raw = latents * decoder["latent_scale"] + decoder["latent_mean"] | |
| if decoder["kind"] == "pca_inverse": | |
| expression = raw @ decoder["components"] + decoder["expression_mean"] | |
| else: | |
| assert decoder["kind"] == "autoencoder" | |
| expression = decoder["model"].decoder(raw) | |
| return expression.clamp_min(0) | |
| def _bh_adjust(p_values): | |
| order = np.argsort(p_values) | |
| ranked = p_values[order] * len(p_values) / np.arange(1, len(p_values) + 1) | |
| adjusted = np.minimum.accumulate(ranked[::-1])[::-1].clip(max=1) | |
| result = np.empty_like(adjusted) | |
| result[order] = adjusted | |
| return result | |
| def expression_truth(config, regimes=None, maximum_conditions=None): | |
| """Compute batch-matched true effects and batch-level DE calls once per condition.""" | |
| inputs, metric_config = config["inputs"], config["metrics"] | |
| regimes = regimes or config["regimes"] | |
| truth, report = {}, {} | |
| with h5py.File(inputs["latent_cache_path"], "r") as latent, h5py.File( | |
| inputs["expression_cache_path"], "r" | |
| ) as expression_cache: | |
| roles = latent["role"].asstr()[:] | |
| targets = latent["target"].asstr()[:] | |
| batches = latent["source_batch"].asstr()[:] | |
| contexts = latent["context"].asstr()[:] | |
| expression = expression_cache["expression"] | |
| for regime, specification in regimes.items(): | |
| control_indices = np.flatnonzero(roles == specification["control_role"]) | |
| assert set(contexts[control_indices]) == {specification["context"]} | |
| control_means = { | |
| batch: expression[np.sort(control_indices[batches[control_indices] == batch])].mean( | |
| 0, dtype=np.float64 | |
| ) | |
| for batch in sorted(set(batches[control_indices])) | |
| } | |
| outcome_indices = np.flatnonzero(roles == specification["outcome_role"]) | |
| assert set(contexts[outcome_indices]) == {specification["context"]} | |
| condition_targets = sorted(set(targets[outcome_indices])) | |
| condition_targets = condition_targets[: maximum_conditions or len(condition_targets)] | |
| report[regime] = { | |
| "context": specification["context"], | |
| "outcome_role": specification["outcome_role"], | |
| "targets": len(condition_targets), | |
| "conditions_with_no_degs": 0, | |
| } | |
| for target in condition_targets: | |
| indices = np.sort(outcome_indices[targets[outcome_indices] == target]) | |
| observed = expression[indices].astype(np.float64) | |
| target_batches = batches[indices] | |
| batch_effects, matched_control = [], np.zeros(observed.shape[1]) | |
| for batch in sorted(set(target_batches)): | |
| selected = target_batches == batch | |
| batch_effects.append(observed[selected].mean(0) - control_means[batch]) | |
| matched_control += selected.sum() * control_means[batch] | |
| batch_effects = np.stack(batch_effects) | |
| effect = observed.mean(0) - matched_control / len(indices) | |
| if len(batch_effects) > 1: | |
| batch_mean = batch_effects.mean(0) | |
| standard_error = batch_effects.std(0, ddof=1) / np.sqrt(len(batch_effects)) | |
| statistic = np.divide( | |
| np.abs(batch_mean), | |
| standard_error, | |
| out=np.where(batch_mean == 0, 0.0, np.inf), | |
| where=standard_error > 0, | |
| ) | |
| q_value = _bh_adjust(2 * t.sf(statistic, len(batch_effects) - 1)) | |
| else: | |
| q_value = np.ones(observed.shape[1]) | |
| deg = (q_value <= metric_config["deg_batch_fdr"]) & ( | |
| np.abs(effect) >= metric_config["deg_min_abs_effect"] | |
| ) | |
| report[regime]["conditions_with_no_degs"] += int(not deg.any()) | |
| truth[regime, target] = { | |
| "effect": effect.astype(np.float32), | |
| "deg": deg, | |
| "q_value": q_value.astype(np.float32), | |
| "batches": len(batch_effects), | |
| "cells": len(indices), | |
| } | |
| return truth, report | |
| def _correlation(predicted, observed, rank=False): | |
| if np.std(predicted) <= 1e-12 or np.std(observed) <= 1e-12: | |
| return None | |
| if rank: | |
| predicted, observed = rankdata(predicted), rankdata(observed) | |
| predicted, observed = predicted - predicted.mean(), observed - observed.mean() | |
| return float(predicted @ observed / (np.linalg.norm(predicted) * np.linalg.norm(observed))) | |
| def _binary_ranking_metrics(scores, labels): | |
| positives, negatives = int(labels.sum()), int((~labels).sum()) | |
| if not positives or not negatives: | |
| return None, None | |
| ranks = rankdata(scores) | |
| auroc = (ranks[labels].sum() - positives * (positives + 1) / 2) / ( | |
| positives * negatives | |
| ) | |
| order = np.argsort(-scores, kind="stable") | |
| sorted_scores, sorted_labels = scores[order], labels[order] | |
| cumulative = np.cumsum(sorted_labels) | |
| group_ends = np.r_[np.flatnonzero(sorted_scores[1:] != sorted_scores[:-1]), len(scores) - 1] | |
| true_positives = cumulative[group_ends] | |
| precision = true_positives / (group_ends + 1) | |
| average_precision = np.sum(np.diff(np.r_[0, true_positives]) * precision) / positives | |
| return float(average_precision), float(auroc) | |
| def gene_effect_metrics(predicted, observed, target_index, deg, top_genes=(20, 50, 100)): | |
| """Evaluate one perturbation-condition, explicitly labeling outcome-selected scopes.""" | |
| result = {} | |
| scopes = {"all": np.arange(len(observed))} | |
| excluded = np.arange(len(observed)) if target_index is None else np.delete( | |
| np.arange(len(observed)), target_index | |
| ) | |
| scopes["target_excluded"] = excluded | |
| for scope, indices in scopes.items(): | |
| predicted_scope, observed_scope = predicted[indices], observed[indices] | |
| predicted_magnitude, observed_magnitude = ( | |
| np.linalg.norm(predicted_scope), | |
| np.linalg.norm(observed_scope), | |
| ) | |
| result[f"{scope}_effect_pearson"] = _correlation(predicted_scope, observed_scope) | |
| result[f"{scope}_effect_spearman"] = _correlation( | |
| predicted_scope, observed_scope, rank=True | |
| ) | |
| result[f"{scope}_direction_cosine"] = ( | |
| float(predicted_scope @ observed_scope / (predicted_magnitude * observed_magnitude)) | |
| if predicted_magnitude > 1e-12 and observed_magnitude > 1e-12 | |
| else None | |
| ) | |
| result[f"{scope}_magnitude_ratio"] = float( | |
| predicted_magnitude / max(observed_magnitude, 1e-12) | |
| ) | |
| result[f"{scope}_magnitude_absolute_error"] = float( | |
| abs(predicted_magnitude - observed_magnitude) | |
| ) | |
| ranked_true = np.argsort(-np.abs(observed), kind="stable") | |
| ranked_predicted = np.argsort(-np.abs(predicted), kind="stable") | |
| for top in top_genes: | |
| indices = ranked_true[:top] | |
| result[f"retrospective_top{top}_effect_pearson"] = _correlation( | |
| predicted[indices], observed[indices] | |
| ) | |
| result[f"retrospective_top{top}_effect_spearman"] = _correlation( | |
| predicted[indices], observed[indices], rank=True | |
| ) | |
| result[f"retrospective_top{top}_overlap"] = float( | |
| len(set(indices) & set(ranked_predicted[:top])) / top | |
| ) | |
| auprc, auroc = _binary_ranking_metrics(np.abs(predicted), deg) | |
| result["deg_auprc"], result["deg_auroc"] = auprc, auroc | |
| result["deg_sign_accuracy"] = ( | |
| float(np.mean(np.sign(predicted[deg]) == np.sign(observed[deg]))) if deg.any() else None | |
| ) | |
| return result | |
| def _load_pathway_matrix(path, hvg_genes): | |
| gene_index = {gene: index for index, gene in enumerate(hvg_genes)} | |
| rows, columns, labels = [], [], [] | |
| with Path(path).open() as handle: | |
| for row, line in enumerate(handle): | |
| fields = line.rstrip("\n").split("\t") | |
| indices = sorted({gene_index[gene] for gene in fields[2:] if gene in gene_index}) | |
| labels.append(fields[0]) | |
| rows.extend([row] * len(indices)) | |
| columns.extend(indices) | |
| matrix = sparse.csr_matrix( | |
| (np.ones(len(rows), dtype=np.float32), (rows, columns)), | |
| shape=(len(labels), len(hvg_genes)), | |
| ) | |
| return matrix, labels | |
| def pathway_scores(effect, matrix): | |
| """Return an analytic size-normalized signed rank-enrichment score.""" | |
| ranks = rankdata(effect).astype(np.float32) | |
| if ranks.std() == 0: | |
| return np.zeros(matrix.shape[0], dtype=np.float32) | |
| ranks = (ranks - ranks.mean()) / ranks.std() | |
| sizes = np.asarray(matrix.sum(1)).ravel() | |
| denominator = np.sqrt(sizes * (len(effect) - sizes) / (len(effect) - 1)) | |
| return np.asarray(matrix @ ranks).ravel() / denominator | |
| def pathway_agreement(predicted, observed, matrix, top_k=(10, 25, 50)): | |
| predicted_score, observed_score = pathway_scores(predicted, matrix), pathway_scores( | |
| observed, matrix | |
| ) | |
| result = { | |
| "pathway_nes_pearson": _correlation(predicted_score, observed_score), | |
| "pathway_rank_spearman": _correlation(predicted_score, observed_score, rank=True), | |
| "pathway_nes_rmse": float(np.sqrt(np.mean(np.square(predicted_score - observed_score)))), | |
| } | |
| predicted_order, observed_order = ( | |
| np.argsort(-np.abs(predicted_score)), | |
| np.argsort(-np.abs(observed_score)), | |
| ) | |
| for top in top_k: | |
| if np.std(predicted) <= 1e-12: | |
| result[f"pathway_top{top}_jaccard"] = None | |
| else: | |
| intersection = len(set(predicted_order[:top]) & set(observed_order[:top])) | |
| result[f"pathway_top{top}_jaccard"] = float( | |
| intersection / (2 * top - intersection) | |
| ) | |
| return result | |
| _TRANSCRIPTOMIC_METADATA = { | |
| "regime", | |
| "context", | |
| "outcome_role", | |
| "target", | |
| "repeat", | |
| "model", | |
| "truth_batches", | |
| "truth_cells", | |
| "true_deg_count", | |
| "target_in_hvg", | |
| } | |
| def _condition_summary(records, resamples, seed): | |
| target_values = defaultdict(list) | |
| for record in records: | |
| for metric in set(record) - _TRANSCRIPTOMIC_METADATA: | |
| if record[metric] is not None: | |
| target_values[record["regime"], record["model"], record["target"], metric].append( | |
| record[metric] | |
| ) | |
| grouped = defaultdict(list) | |
| for (regime, model, target, metric), values in target_values.items(): | |
| grouped[regime, model, metric].append(float(np.mean(values))) | |
| summary = [] | |
| for key, values in sorted(grouped.items()): | |
| values = np.asarray(values) | |
| generator = np.random.default_rng( | |
| int.from_bytes(sha256("\0".join(key).encode()).digest()[:8], "little") + seed | |
| ) | |
| bootstrap = values[ | |
| generator.integers(0, len(values), (resamples, len(values))) | |
| ].mean(1) | |
| summary.append( | |
| { | |
| "regime": key[0], | |
| "model": key[1], | |
| "metric": key[2], | |
| "targets": len(values), | |
| "mean": float(values.mean()), | |
| "median": float(np.median(values)), | |
| "mean_bootstrap_95ci": [ | |
| float(value) for value in np.quantile(bootstrap, (0.025, 0.975)) | |
| ], | |
| } | |
| ) | |
| return summary | |
| def _paired_comparisons( | |
| records, | |
| resamples, | |
| seed, | |
| baselines=("no_change", "mean_effect", "linear_esm", "pseudo_paired"), | |
| ): | |
| target_values = defaultdict(list) | |
| for record in records: | |
| for metric in set(record) - _TRANSCRIPTOMIC_METADATA: | |
| if record[metric] is not None: | |
| target_values[record["regime"], record["model"], record["target"], metric].append( | |
| record[metric] | |
| ) | |
| averaged = {key: float(np.mean(values)) for key, values in target_values.items()} | |
| comparisons = [] | |
| for regime in sorted({record["regime"] for record in records}): | |
| metrics = sorted({key[3] for key in averaged if key[:2] == (regime, "causalcelljepa")}) | |
| for baseline in baselines: | |
| for metric in metrics: | |
| targets = sorted( | |
| key[2] | |
| for key in averaged | |
| if key[:2] == (regime, "causalcelljepa") | |
| and key[3] == metric | |
| and (regime, baseline, key[2], metric) in averaged | |
| ) | |
| if not targets: | |
| continue | |
| model = np.asarray([averaged[regime, "causalcelljepa", x, metric] for x in targets]) | |
| reference = np.asarray([averaged[regime, baseline, x, metric] for x in targets]) | |
| if metric.endswith("magnitude_ratio"): | |
| improvement, direction = np.abs(reference - 1) - np.abs(model - 1), "closer_to_one_is_better" | |
| elif metric.endswith(("absolute_error", "rmse")): | |
| improvement, direction = reference - model, "lower_is_better" | |
| else: | |
| improvement, direction = model - reference, "higher_is_better" | |
| generator = np.random.default_rng( | |
| (int.from_bytes(sha256(f"{regime}\0{baseline}\0{metric}".encode()).digest()[:8], "little") + seed) | |
| % (1 << 64) | |
| ) | |
| bootstrap = improvement[ | |
| generator.integers(0, len(improvement), (resamples, len(improvement))) | |
| ].mean(1) | |
| p_value = 1.0 if np.all(improvement == 0) else float( | |
| wilcoxon(improvement, alternative="two-sided", method="approx").pvalue | |
| ) | |
| comparisons.append( | |
| { | |
| "regime": regime, | |
| "baseline": baseline, | |
| "metric": metric, | |
| "direction": direction, | |
| "targets": len(targets), | |
| "causalcelljepa_mean": float(model.mean()), | |
| "baseline_mean": float(reference.mean()), | |
| "mean_improvement": float(improvement.mean()), | |
| "mean_improvement_bootstrap_95ci": [ | |
| float(value) for value in np.quantile(bootstrap, (0.025, 0.975)) | |
| ], | |
| "wilcoxon_two_sided_p": p_value, | |
| } | |
| ) | |
| q_values = _bh_adjust(np.asarray([item["wilcoxon_two_sided_p"] for item in comparisons])) | |
| for comparison, q_value in zip(comparisons, q_values): | |
| comparison["benjamini_hochberg_q"] = float(q_value) | |
| return comparisons | |
| def _paired_transcriptomic_models(records, pairs, resamples, seed): | |
| """Compare arbitrary models on matched perturbations using transcriptomic metric directions.""" | |
| target_values = defaultdict(list) | |
| for record in records: | |
| for metric in set(record) - _TRANSCRIPTOMIC_METADATA: | |
| if record[metric] is not None: | |
| target_values[record["regime"], record["model"], record["target"], metric].append( | |
| record[metric] | |
| ) | |
| averaged = {key: float(np.mean(values)) for key, values in target_values.items()} | |
| comparisons = [] | |
| for regime in sorted({record["regime"] for record in records}): | |
| for pair in pairs: | |
| candidate, reference = pair["candidate"], pair["reference"] | |
| metrics = sorted({ | |
| key[3] | |
| for key in averaged | |
| if key[:2] == (regime, candidate) | |
| and (regime, reference, key[2], key[3]) in averaged | |
| }) | |
| for metric in metrics: | |
| targets = sorted( | |
| key[2] | |
| for key in averaged | |
| if key[:2] == (regime, candidate) | |
| and key[3] == metric | |
| and (regime, reference, key[2], metric) in averaged | |
| ) | |
| candidate_values = np.asarray( | |
| [averaged[regime, candidate, target, metric] for target in targets] | |
| ) | |
| reference_values = np.asarray( | |
| [averaged[regime, reference, target, metric] for target in targets] | |
| ) | |
| if metric.endswith("magnitude_ratio"): | |
| improvement = np.abs(reference_values - 1) - np.abs(candidate_values - 1) | |
| direction = "closer_to_one_is_better" | |
| elif metric.endswith(("absolute_error", "rmse")): | |
| improvement, direction = reference_values - candidate_values, "lower_is_better" | |
| else: | |
| improvement, direction = candidate_values - reference_values, "higher_is_better" | |
| key = f"{regime}\0{candidate}\0{reference}\0{metric}" | |
| generator = np.random.default_rng( | |
| (int.from_bytes(sha256(key.encode()).digest()[:8], "little") + seed) | |
| % (1 << 64) | |
| ) | |
| bootstrap = improvement[ | |
| generator.integers(0, len(improvement), (resamples, len(improvement))) | |
| ].mean(1) | |
| p_value = 1.0 if np.all(improvement == 0) else float( | |
| wilcoxon(improvement, alternative="two-sided", method="approx").pvalue | |
| ) | |
| comparisons.append( | |
| { | |
| "regime": regime, | |
| "candidate": candidate, | |
| "reference": reference, | |
| "hypothesis": pair["hypothesis"], | |
| "metric": metric, | |
| "direction": direction, | |
| "targets": len(targets), | |
| "candidate_mean": float(candidate_values.mean()), | |
| "reference_mean": float(reference_values.mean()), | |
| "mean_improvement": float(improvement.mean()), | |
| "median_improvement": float(np.median(improvement)), | |
| "mean_improvement_bootstrap_95ci": [ | |
| float(value) for value in np.quantile(bootstrap, (0.025, 0.975)) | |
| ], | |
| "wilcoxon_two_sided_p": p_value, | |
| } | |
| ) | |
| q_values = _bh_adjust(np.asarray([item["wilcoxon_two_sided_p"] for item in comparisons])) | |
| for comparison, q_value in zip(comparisons, q_values): | |
| comparison["benjamini_hochberg_q"] = float(q_value) | |
| return comparisons | |
| def run_transcriptomic_evaluation( | |
| config, regimes=None, repeats=None, maximum_conditions=None, output_directory=None | |
| ): | |
| """Decode all frozen models and evaluate gene effects without fitting on outcomes.""" | |
| inputs = config["inputs"] | |
| for kind in ( | |
| "latent_cache", | |
| "expression_cache", | |
| "action_cache", | |
| "checkpoint", | |
| "pseudo_paired_checkpoint", | |
| "readout_checkpoint", | |
| "go_gmt", | |
| ): | |
| assert file_sha256(inputs[f"{kind}_path"]) == inputs[f"{kind}_sha256"] | |
| for kind in ("readout", "go"): | |
| manifest = json.loads(Path(inputs[f"{kind}_manifest_path"]).read_text()) | |
| declared = manifest.pop("manifest_sha256") | |
| assert declared == inputs[f"{kind}_manifest_sha256"] == sha256( | |
| json.dumps(manifest, sort_keys=True, separators=(",", ":")).encode() | |
| ).hexdigest() | |
| primary = torch.load(inputs["checkpoint_path"], map_location="cpu", weights_only=False) | |
| pseudo = torch.load( | |
| inputs["pseudo_paired_checkpoint_path"], map_location="cpu", weights_only=False | |
| ) | |
| readout = torch.load(inputs["readout_checkpoint_path"], map_location="cpu", weights_only=False) | |
| model = build_dynamics_model(primary["configuration"]).eval() | |
| model.load_state_dict(primary["model"]) | |
| pseudo_model = build_dynamics_model(pseudo["configuration"]).eval() | |
| pseudo_model.load_state_dict(pseudo["model"]) | |
| assert pseudo["configuration"]["objective"] == "pseudo_paired_mse" | |
| linear_effect, mean_effect, baseline_report = fit_linear_baseline(config) | |
| replogle = json.loads(Path(inputs["replogle_manifest_path"]).read_text()) | |
| hvg_genes = replogle["genes"]["hvg_gene_names"] | |
| hvg_index = {gene: index for index, gene in enumerate(hvg_genes)} | |
| pathway_matrix, pathway_labels = _load_pathway_matrix(inputs["go_gmt_path"], hvg_genes) | |
| assert len(pathway_labels) == 4328 | |
| regimes = regimes or config["regimes"] | |
| repeats = repeats or config["sampling"]["repeats"] | |
| truth, truth_report = expression_truth(config, regimes, maximum_conditions) | |
| output = Path(output_directory or config["output_directory"]) | |
| assert not output.exists() | |
| output.mkdir(parents=True) | |
| records, signatures = [], defaultdict(lambda: [0, None]) | |
| for regime, specification in regimes.items(): | |
| dataset = LatentPopulationDataset( | |
| inputs["latent_cache_path"], | |
| inputs["action_cache_path"], | |
| inputs["dynamics_manifest_path"], | |
| regime, | |
| config["sampling"]["population_size"], | |
| config["seed"], | |
| specification["outcome_role"], | |
| specification["control_role"], | |
| specification["context"], | |
| ) | |
| indices = range(min(len(dataset), maximum_conditions or len(dataset))) | |
| for repeat in range(repeats): | |
| dataset.set_epoch(repeat) | |
| loader = DataLoader( | |
| dataset, | |
| batch_size=config["sampling"]["batch_size"], | |
| sampler=list(indices), | |
| num_workers=config["sampling"]["num_workers"], | |
| ) | |
| for batch in loader: | |
| control = batch["control"] | |
| predictions = { | |
| "causalcelljepa": model(control, batch["action"], batch["action_known"]), | |
| "pseudo_paired": pseudo_model( | |
| control, batch["action"], batch["action_known"] | |
| ), | |
| "no_change": control, | |
| "mean_effect": control + torch.from_numpy(mean_effect)[None, None], | |
| "linear_esm": control | |
| + torch.from_numpy(np.stack([linear_effect[x] for x in batch["target"]]))[ | |
| :, None | |
| ], | |
| } | |
| control_expression = decode_normalized_latents(control.mean(1), readout) | |
| for baseline, predicted in predictions.items(): | |
| predicted_effect = ( | |
| decode_normalized_latents(predicted.mean(1), readout) - control_expression | |
| ).numpy() | |
| for index, target in enumerate(batch["target"]): | |
| observed = truth[regime, target] | |
| metrics = gene_effect_metrics( | |
| predicted_effect[index], | |
| observed["effect"], | |
| hvg_index.get(target), | |
| observed["deg"], | |
| config["metrics"]["retrospective_top_genes"], | |
| ) | |
| record = { | |
| "regime": regime, | |
| "context": specification["context"], | |
| "outcome_role": specification["outcome_role"], | |
| "target": target, | |
| "repeat": repeat, | |
| "model": baseline, | |
| "truth_batches": observed["batches"], | |
| "truth_cells": observed["cells"], | |
| "true_deg_count": int(observed["deg"].sum()), | |
| "target_in_hvg": target in hvg_index, | |
| **metrics, | |
| } | |
| records.append(record) | |
| key = (regime, baseline, target) | |
| signatures[key][0] += 1 | |
| signatures[key][1] = ( | |
| predicted_effect[index] | |
| if signatures[key][1] is None | |
| else signatures[key][1] + predicted_effect[index] | |
| ) | |
| pathway_records, retrieval = [], [] | |
| for regime in regimes: | |
| targets = sorted(target for key, target in truth if key == regime) | |
| true_signatures = np.stack([truth[regime, target]["effect"] for target in targets]) | |
| normalized_truth = true_signatures / np.linalg.norm( | |
| true_signatures, axis=1, keepdims=True | |
| ).clip(1e-12) | |
| for baseline in sorted({record["model"] for record in records}): | |
| predicted_signatures = np.stack( | |
| [ | |
| signatures[regime, baseline, target][1] | |
| / signatures[regime, baseline, target][0] | |
| for target in targets | |
| ] | |
| ) | |
| normalized_prediction = predicted_signatures / np.linalg.norm( | |
| predicted_signatures, axis=1, keepdims=True | |
| ).clip(1e-12) | |
| similarity = normalized_prediction @ normalized_truth.T | |
| ranks = np.asarray( | |
| [ | |
| 1 | |
| + np.count_nonzero(row > row[index]) | |
| + 0.5 * (np.count_nonzero(row == row[index]) - 1) | |
| for index, row in enumerate(similarity) | |
| ] | |
| ) | |
| retrieval.append( | |
| { | |
| "regime": regime, | |
| "model": baseline, | |
| "targets": len(targets), | |
| "top_1": float(np.mean(ranks <= 1)), | |
| "top_5": float(np.mean(ranks <= 5)), | |
| "mean_reciprocal_rank": float(np.mean(1 / ranks)), | |
| "median_rank": float(np.median(ranks)), | |
| } | |
| ) | |
| for index, target in enumerate(targets): | |
| pathway_records.append( | |
| { | |
| "regime": regime, | |
| "context": regimes[regime]["context"], | |
| "outcome_role": regimes[regime]["outcome_role"], | |
| "target": target, | |
| "repeat": 0, | |
| "model": baseline, | |
| "truth_batches": truth[regime, target]["batches"], | |
| "truth_cells": truth[regime, target]["cells"], | |
| "true_deg_count": int(truth[regime, target]["deg"].sum()), | |
| "target_in_hvg": target in hvg_index, | |
| **pathway_agreement( | |
| predicted_signatures[index], | |
| true_signatures[index], | |
| pathway_matrix, | |
| config["metrics"]["pathway_top_k"], | |
| ), | |
| } | |
| ) | |
| resamples = config["metrics"]["bootstrap_resamples"] | |
| summary = { | |
| "condition_metrics": _condition_summary(records, resamples, config["seed"]), | |
| "pathway_metrics": _condition_summary(pathway_records, resamples, config["seed"]), | |
| "retrieval": retrieval, | |
| } | |
| paired = { | |
| "condition_comparisons": _paired_comparisons(records, resamples, config["seed"]), | |
| "pathway_comparisons": _paired_comparisons( | |
| pathway_records, resamples, config["seed"] | |
| ), | |
| } | |
| provenance = { | |
| "config": deepcopy(config), | |
| "executed_regimes": regimes, | |
| "executed_repeats": repeats, | |
| "maximum_conditions_per_regime": maximum_conditions, | |
| "checkpoint_provenance": primary["provenance"], | |
| "pseudo_paired_checkpoint_provenance": pseudo["provenance"], | |
| "readout_provenance": readout["provenance"], | |
| "file_sha256": { | |
| kind: inputs[f"{kind}_sha256"] | |
| for kind in ( | |
| "latent_cache", | |
| "expression_cache", | |
| "action_cache", | |
| "checkpoint", | |
| "pseudo_paired_checkpoint", | |
| "readout_checkpoint", | |
| "go_gmt", | |
| ) | |
| }, | |
| "runtime_source_sha256": _runtime_source_hash(), | |
| "runtime_environment": _runtime_environment(), | |
| "git": _git_state(), | |
| } | |
| for filename, payload in ( | |
| ("summary.json", summary), | |
| ("paired_comparisons.json", paired), | |
| ("truth_report.json", truth_report), | |
| ("baseline_report.json", baseline_report), | |
| ("provenance.json", provenance), | |
| ): | |
| (output / filename).write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n") | |
| for filename, values in ( | |
| ("condition_metrics.jsonl", records), | |
| ("pathway_metrics.jsonl", pathway_records), | |
| ): | |
| with (output / filename).open("w") as handle: | |
| for value in values: | |
| handle.write(json.dumps(value, sort_keys=True) + "\n") | |
| return summary, paired, truth_report, provenance | |
| def run_remaining_comparator_evaluation( | |
| config, base_config, regimes=None, repeats=None, maximum_conditions=None, output_directory=None | |
| ): | |
| """Evaluate frozen action/state comparators through leakage-safe representation readouts.""" | |
| inputs = config["inputs"] | |
| assert file_sha256(config["base_transcriptomics_config_path"]) == config[ | |
| "base_transcriptomics_config_sha256" | |
| ] | |
| model_source = config.get("model_source", "comparator") | |
| manifest_kinds = { | |
| "comparator": "comparator", | |
| "stage2_replication": "replication_training", | |
| "anchored": "anchored_training", | |
| "multiteacher": "multiteacher_training", | |
| "contextual_multiteacher": "contextual_multiteacher_training", | |
| } | |
| assert model_source in manifest_kinds | |
| model_manifest_kind = manifest_kinds[model_source] | |
| manifests = {} | |
| for name in ("base", model_manifest_kind): | |
| payload = json.loads(Path(inputs[f"{name}_manifest_path"]).read_text()) | |
| declared = payload.pop("manifest_sha256") | |
| assert declared == inputs[f"{name}_manifest_sha256"] == sha256( | |
| json.dumps(payload, sort_keys=True, separators=(",", ":")).encode() | |
| ).hexdigest() | |
| manifests[name] = payload | |
| selected_candidate = selected_entry = selection_manifest_sha256 = None | |
| frozen_selection_sources = {"anchored", "multiteacher", "contextual_multiteacher"} | |
| if model_source in frozen_selection_sources: | |
| selection = json.loads( | |
| Path(inputs[f"{model_source}_selection_manifest_path"]).read_text() | |
| ) | |
| selection_manifest_sha256 = selection.pop("manifest_sha256") | |
| assert ( | |
| selection_manifest_sha256 | |
| == inputs[f"{model_source}_selection_manifest_sha256"] | |
| == sha256( | |
| json.dumps(selection, sort_keys=True, separators=(",", ":")).encode() | |
| ).hexdigest() | |
| ) | |
| selected_candidate, selected_entry = anchored_selected_entry( | |
| manifests[model_manifest_kind], selection | |
| ) | |
| assert config["models"] == {f"{model_source}_selected": selected_candidate} | |
| base_artifacts = manifests["base"]["artifacts"]["predictive_evaluation"] | |
| for name in ("condition_metrics", "pathway_metrics", "summary", "provenance"): | |
| path = Path(inputs[f"base_{name}_path"]) | |
| assert (path.stat().st_size, file_sha256(path)) == ( | |
| inputs[f"base_{name}_bytes"], | |
| inputs[f"base_{name}_sha256"], | |
| ) | |
| assert base_artifacts[name]["sha256"] == inputs[f"base_{name}_sha256"] | |
| assert base_artifacts["condition_metrics"]["records"] == inputs[ | |
| "base_condition_metrics_records" | |
| ] | |
| assert base_artifacts["pathway_metrics"]["records"] == inputs[ | |
| "base_pathway_metrics_records" | |
| ] | |
| base_provenance = json.loads(Path(inputs["base_provenance_path"]).read_text()) | |
| assert base_provenance["config"] == base_config and base_provenance["git"]["dirty"] is False | |
| reference_models = set(config.get("reference_models", [])) | |
| reference_summary = {"condition_metrics": [], "pathway_metrics": [], "retrieval": []} | |
| if reference_models: | |
| for name in ("condition_metrics", "pathway_metrics", "summary"): | |
| path = Path(inputs[f"reference_{name}_path"]) | |
| assert (path.stat().st_size, file_sha256(path)) == ( | |
| inputs[f"reference_{name}_bytes"], | |
| inputs[f"reference_{name}_sha256"], | |
| ) | |
| assert len( | |
| Path(inputs["reference_condition_metrics_path"]).read_text().splitlines() | |
| ) == inputs["reference_condition_metrics_records"] | |
| assert len( | |
| Path(inputs["reference_pathway_metrics_path"]).read_text().splitlines() | |
| ) == inputs["reference_pathway_metrics_records"] | |
| reference_summary = json.loads(Path(inputs["reference_summary_path"]).read_text()) | |
| for kind in ("latent_cache", "expression_cache", "readout_checkpoint", "go_gmt"): | |
| assert file_sha256(base_config["inputs"][f"{kind}_path"]) == base_config["inputs"][ | |
| f"{kind}_sha256" | |
| ] | |
| readout = torch.load( | |
| base_config["inputs"]["readout_checkpoint_path"], map_location="cpu", weights_only=False | |
| ) | |
| assert readout["report"]["fit_roles"] == [ | |
| "control_inference", | |
| "control_train", | |
| "dynamics_train", | |
| ] | |
| models, decoders, model_configs, checkpoint_provenance = {}, {}, {}, {} | |
| representation_provenance = {"jepa_linear": readout["provenance"]} | |
| model_manifest = manifests[model_manifest_kind] | |
| assert model_manifest["protocol"]["rpe1_perturbed_outcomes_used_for_fit_or_selection"] is False | |
| assert model_manifest["protocol"]["sealed_test_outcomes_used_for_fit_or_selection"] is False | |
| for name, path in config["models"].items(): | |
| if model_source == "stage2_replication": | |
| entry = model_manifest["artifacts"]["seeds"][str(path)]["best_checkpoint"] | |
| decoder = {"kind": "jepa_linear", "checkpoint": readout} | |
| elif model_source in frozen_selection_sources: | |
| entry = selected_entry["best_checkpoint"] | |
| decoder = {"kind": "jepa_linear", "checkpoint": readout} | |
| elif name == "learned_target_id": | |
| model_config, _, representation_manifest = learned_target_id_config(path) | |
| policy = representation_manifest["policy"] | |
| assert policy["heldout_target_identity_used_for_fit"] is False | |
| assert policy["validation_target_identity_used_for_fit"] is False | |
| decoder = {"kind": "jepa_linear", "checkpoint": readout} | |
| else: | |
| model_config, specification, representation_manifest = state_ablation_config(path) | |
| policy = representation_manifest["policy"] | |
| assert policy["rpe1_perturbed_outcomes_used_for_fit"] is False | |
| assert policy["sealed_test_outcomes_used_for_fit"] is False | |
| dynamics_manifest = json.loads( | |
| Path(model_config["inputs"]["dynamics_manifest_path"]).read_text() | |
| ) | |
| normalization = dynamics_manifest["normalization"] | |
| mean = torch.tensor(normalization["latent_mean"], dtype=torch.float32) | |
| scale = torch.tensor(normalization["latent_std"], dtype=torch.float32) * normalization[ | |
| "dimension_scale" | |
| ] | |
| if name == "pca_state": | |
| with h5py.File(model_config["inputs"]["latent_cache_path"], "r") as cache: | |
| components = np.ascontiguousarray(cache["component"][:]) | |
| expression_mean = np.ascontiguousarray(cache["expression_mean"][:]) | |
| artifact = representation_manifest["artifact"] | |
| assert sha256(components.tobytes()).hexdigest() == artifact["component_sha256"] | |
| assert sha256(expression_mean.tobytes()).hexdigest() == artifact[ | |
| "expression_mean_sha256" | |
| ] | |
| decoder = { | |
| "kind": "pca_inverse", | |
| "latent_mean": mean, | |
| "latent_scale": scale, | |
| "components": torch.from_numpy(components), | |
| "expression_mean": torch.from_numpy(expression_mean), | |
| } | |
| else: | |
| assert name == "autoencoder_state" | |
| artifact = representation_manifest["training"]["best_checkpoint"] | |
| assert (Path(artifact["path"]).stat().st_size, file_sha256(artifact["path"])) == ( | |
| artifact["bytes"], | |
| artifact["sha256"], | |
| ) | |
| representation = torch.load( | |
| artifact["path"], map_location="cpu", weights_only=False | |
| ) | |
| assert representation["configuration"] == specification | |
| autoencoder = build_autoencoder(specification).eval() | |
| autoencoder.load_state_dict(representation["model"]) | |
| decoder = { | |
| "kind": "autoencoder", | |
| "latent_mean": mean, | |
| "latent_scale": scale, | |
| "model": autoencoder, | |
| } | |
| representation_provenance[name] = representation["provenance"] | |
| if model_source == "comparator": | |
| entry = model_manifest["experiments"][name]["best_checkpoint"] | |
| assert (Path(entry["path"]).stat().st_size, file_sha256(entry["path"])) == ( | |
| entry["bytes"], | |
| entry["sha256"], | |
| ) | |
| checkpoint = torch.load(entry["path"], map_location="cpu", weights_only=False) | |
| if model_source == "stage2_replication": | |
| model_config = checkpoint["configuration"] | |
| assert model_config["seed"] == path | |
| assert model_config["replication"]["model_and_sampling_seed"] == path | |
| assert model_config["replication"]["target_split_seed"] == base_config["seed"] | |
| assert checkpoint["state"]["best_validation_epoch"] == model_manifest["artifacts"][ | |
| "seeds" | |
| ][str(path)]["full_run"]["best_validation_epoch"] | |
| elif model_source in frozen_selection_sources: | |
| model_config = checkpoint["configuration"] | |
| assert model_config["revision"]["candidate"] == path == selected_candidate | |
| assert checkpoint["state"]["best_validation_epoch"] == selected_entry["full_run"][ | |
| "best_validation_epoch" | |
| ] | |
| else: | |
| assert checkpoint["configuration"] == model_config | |
| assert checkpoint["provenance"]["git"]["dirty"] is False | |
| model = build_dynamics_model(model_config).eval() | |
| model.load_state_dict(checkpoint["model"]) | |
| models[name], decoders[name], model_configs[name] = model, decoder, model_config | |
| checkpoint_provenance[name] = checkpoint["provenance"] | |
| if model_source == "comparator": | |
| representation_provenance[f"{name}_manifest_sha256"] = representation_manifest[ | |
| "manifest_sha256" | |
| ] | |
| replogle = json.loads(Path(base_config["inputs"]["replogle_manifest_path"]).read_text()) | |
| hvg_genes = replogle["genes"]["hvg_gene_names"] | |
| hvg_index = {gene: index for index, gene in enumerate(hvg_genes)} | |
| pathway_matrix, pathway_labels = _load_pathway_matrix( | |
| base_config["inputs"]["go_gmt_path"], hvg_genes | |
| ) | |
| assert len(pathway_labels) == 4328 | |
| regimes = regimes or base_config["regimes"] | |
| repeats = repeats or base_config["sampling"]["repeats"] | |
| truth, truth_report = expression_truth(base_config, regimes, maximum_conditions) | |
| output = Path(output_directory or config["output_directory"]) | |
| assert not output.exists() | |
| output.mkdir(parents=True) | |
| records, signatures = [], defaultdict(lambda: [0, None]) | |
| for regime, regime_config in regimes.items(): | |
| for name, model in models.items(): | |
| model_config = model_configs[name] | |
| dataset = LatentPopulationDataset( | |
| model_config["inputs"]["latent_cache_path"], | |
| model_config["inputs"]["action_cache_path"], | |
| model_config["inputs"]["dynamics_manifest_path"], | |
| regime, | |
| base_config["sampling"]["population_size"], | |
| base_config["seed"], | |
| regime_config["outcome_role"], | |
| regime_config["control_role"], | |
| regime_config["context"], | |
| ) | |
| indices = range(min(len(dataset), maximum_conditions or len(dataset))) | |
| for repeat in range(repeats): | |
| dataset.set_epoch(repeat) | |
| loader = DataLoader( | |
| dataset, | |
| batch_size=base_config["sampling"]["batch_size"], | |
| sampler=list(indices), | |
| num_workers=base_config["sampling"]["num_workers"], | |
| ) | |
| for batch in loader: | |
| control = batch["control"] | |
| predicted = model(control, batch["action"], batch["action_known"]) | |
| predicted_effect = ( | |
| decode_representation_centroids(predicted.mean(1), decoders[name]) | |
| - decode_representation_centroids(control.mean(1), decoders[name]) | |
| ).numpy() | |
| for index, target in enumerate(batch["target"]): | |
| observed = truth[regime, target] | |
| records.append( | |
| { | |
| "regime": regime, | |
| "context": regime_config["context"], | |
| "outcome_role": regime_config["outcome_role"], | |
| "target": target, | |
| "repeat": repeat, | |
| "model": name, | |
| "truth_batches": observed["batches"], | |
| "truth_cells": observed["cells"], | |
| "true_deg_count": int(observed["deg"].sum()), | |
| "target_in_hvg": target in hvg_index, | |
| **gene_effect_metrics( | |
| predicted_effect[index], | |
| observed["effect"], | |
| hvg_index.get(target), | |
| observed["deg"], | |
| base_config["metrics"]["retrospective_top_genes"], | |
| ), | |
| } | |
| ) | |
| key = (regime, name, target) | |
| signatures[key][0] += 1 | |
| signatures[key][1] = ( | |
| predicted_effect[index] | |
| if signatures[key][1] is None | |
| else signatures[key][1] + predicted_effect[index] | |
| ) | |
| pathway_records, retrieval = [], [] | |
| for regime, regime_config in regimes.items(): | |
| targets = sorted(target for key, target in truth if key == regime) | |
| true_signatures = np.stack([truth[regime, target]["effect"] for target in targets]) | |
| normalized_truth = true_signatures / np.linalg.norm( | |
| true_signatures, axis=1, keepdims=True | |
| ).clip(1e-12) | |
| for name in models: | |
| predicted_signatures = np.stack( | |
| [signatures[regime, name, target][1] / signatures[regime, name, target][0] for target in targets] | |
| ) | |
| normalized_prediction = predicted_signatures / np.linalg.norm( | |
| predicted_signatures, axis=1, keepdims=True | |
| ).clip(1e-12) | |
| similarity = normalized_prediction @ normalized_truth.T | |
| ranks = np.asarray( | |
| [ | |
| 1 | |
| + np.count_nonzero(row > row[index]) | |
| + 0.5 * (np.count_nonzero(row == row[index]) - 1) | |
| for index, row in enumerate(similarity) | |
| ] | |
| ) | |
| retrieval.append( | |
| { | |
| "regime": regime, | |
| "model": name, | |
| "targets": len(targets), | |
| "top_1": float(np.mean(ranks <= 1)), | |
| "top_5": float(np.mean(ranks <= 5)), | |
| "mean_reciprocal_rank": float(np.mean(1 / ranks)), | |
| "median_rank": float(np.median(ranks)), | |
| } | |
| ) | |
| for index, target in enumerate(targets): | |
| observed = truth[regime, target] | |
| pathway_records.append( | |
| { | |
| "regime": regime, | |
| "context": regime_config["context"], | |
| "outcome_role": regime_config["outcome_role"], | |
| "target": target, | |
| "repeat": 0, | |
| "model": name, | |
| "truth_batches": observed["batches"], | |
| "truth_cells": observed["cells"], | |
| "true_deg_count": int(observed["deg"].sum()), | |
| "target_in_hvg": target in hvg_index, | |
| **pathway_agreement( | |
| predicted_signatures[index], | |
| true_signatures[index], | |
| pathway_matrix, | |
| base_config["metrics"]["pathway_top_k"], | |
| ), | |
| } | |
| ) | |
| base_summary = json.loads(Path(inputs["base_summary_path"]).read_text()) | |
| resamples = base_config["metrics"]["bootstrap_resamples"] | |
| summary = { | |
| "condition_metrics": sorted( | |
| base_summary["condition_metrics"] | |
| + [ | |
| item | |
| for item in reference_summary["condition_metrics"] | |
| if item["model"] in reference_models | |
| ] | |
| + _condition_summary(records, resamples, base_config["seed"]), | |
| key=lambda item: (item["regime"], item["model"], item["metric"]), | |
| ), | |
| "pathway_metrics": sorted( | |
| base_summary["pathway_metrics"] | |
| + [ | |
| item | |
| for item in reference_summary["pathway_metrics"] | |
| if item["model"] in reference_models | |
| ] | |
| + _condition_summary(pathway_records, resamples, base_config["seed"]), | |
| key=lambda item: (item["regime"], item["model"], item["metric"]), | |
| ), | |
| "retrieval": sorted( | |
| base_summary["retrieval"] | |
| + [ | |
| item | |
| for item in reference_summary["retrieval"] | |
| if item["model"] in reference_models | |
| ] | |
| + retrieval, | |
| key=lambda item: (item["regime"], item["model"]), | |
| ), | |
| } | |
| base_models = { | |
| model | |
| for pair in config["comparisons"] | |
| for model in (pair["candidate"], pair["reference"]) | |
| if model not in models | |
| } | |
| base_condition_lines = Path(inputs["base_condition_metrics_path"]).read_text().splitlines() | |
| assert len(base_condition_lines) == inputs["base_condition_metrics_records"] | |
| if reference_models: | |
| base_condition_lines += Path(inputs["reference_condition_metrics_path"]).read_text().splitlines() | |
| base_records = [] | |
| for line in base_condition_lines: | |
| record = json.loads(line) | |
| if record["model"] in base_models: | |
| base_records.append(record) | |
| base_pathway_lines = Path(inputs["base_pathway_metrics_path"]).read_text().splitlines() | |
| assert len(base_pathway_lines) == inputs["base_pathway_metrics_records"] | |
| if reference_models: | |
| base_pathway_lines += Path(inputs["reference_pathway_metrics_path"]).read_text().splitlines() | |
| base_pathway_records = [] | |
| for line in base_pathway_lines: | |
| record = json.loads(line) | |
| if record["model"] in base_models: | |
| base_pathway_records.append(record) | |
| paired = { | |
| "condition_comparisons": _paired_transcriptomic_models( | |
| base_records + records, config["comparisons"], resamples, base_config["seed"] | |
| ), | |
| "pathway_comparisons": _paired_transcriptomic_models( | |
| base_pathway_records + pathway_records, | |
| config["comparisons"], | |
| resamples, | |
| base_config["seed"], | |
| ), | |
| } | |
| provenance = { | |
| "config": deepcopy(config), | |
| "base_transcriptomics_config": deepcopy(base_config), | |
| "executed_regimes": regimes, | |
| "executed_repeats": repeats, | |
| "maximum_conditions_per_regime": maximum_conditions, | |
| "checkpoint_provenance": checkpoint_provenance, | |
| "representation_provenance": representation_provenance, | |
| "base_artifact_sha256": { | |
| name: inputs[f"base_{name}_sha256"] | |
| for name in ("condition_metrics", "pathway_metrics", "summary", "provenance") | |
| }, | |
| "base_manifest_sha256": inputs["base_manifest_sha256"], | |
| "runtime_source_sha256": _runtime_source_hash(), | |
| "runtime_environment": _runtime_environment(), | |
| "git": _git_state(), | |
| } | |
| provenance[f"{model_manifest_kind}_manifest_sha256"] = inputs[ | |
| f"{model_manifest_kind}_manifest_sha256" | |
| ] | |
| if selection_manifest_sha256 is not None: | |
| provenance[f"{model_source}_selection_manifest_sha256"] = selection_manifest_sha256 | |
| if reference_models: | |
| provenance["reference_artifact_sha256"] = { | |
| name: inputs[f"reference_{name}_sha256"] | |
| for name in ("condition_metrics", "pathway_metrics", "summary") | |
| } | |
| expected_records = repeats * len(models) * sum(item["targets"] for item in truth_report.values()) | |
| assert len(records) == expected_records | |
| assert all( | |
| value is None or not isinstance(value, float) or np.isfinite(value) | |
| for record in records + pathway_records | |
| for value in record.values() | |
| ) | |
| for filename, payload in ( | |
| ("summary.json", summary), | |
| ("paired_comparisons.json", paired), | |
| ("truth_report.json", truth_report), | |
| ("provenance.json", provenance), | |
| ): | |
| (output / filename).write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n") | |
| for filename, values in ( | |
| ("condition_metrics.jsonl", records), | |
| ("pathway_metrics.jsonl", pathway_records), | |
| ): | |
| with (output / filename).open("w") as handle: | |
| for value in values: | |
| handle.write(json.dumps(value, sort_keys=True) + "\n") | |
| return summary, paired, truth_report, provenance | |
| def run_readout_oracle(config, regimes=None, maximum_conditions=None, output_directory=None): | |
| """Audit the decoder ceiling using observed outcome latents, never as a predictor.""" | |
| inputs = config["inputs"] | |
| for kind in ("latent_cache", "expression_cache", "readout_checkpoint", "go_gmt"): | |
| assert file_sha256(inputs[f"{kind}_path"]) == inputs[f"{kind}_sha256"] | |
| readout = torch.load(inputs["readout_checkpoint_path"], map_location="cpu", weights_only=False) | |
| replogle = json.loads(Path(inputs["replogle_manifest_path"]).read_text()) | |
| hvg_genes = replogle["genes"]["hvg_gene_names"] | |
| hvg_index = {gene: index for index, gene in enumerate(hvg_genes)} | |
| pathway_matrix, pathway_labels = _load_pathway_matrix(inputs["go_gmt_path"], hvg_genes) | |
| assert len(pathway_labels) == 4328 | |
| regimes = regimes or config["regimes"] | |
| truth, truth_report = expression_truth(config, regimes, maximum_conditions) | |
| records, pathway_records = [], [] | |
| with h5py.File(inputs["latent_cache_path"], "r") as latent: | |
| roles = latent["role"].asstr()[:] | |
| targets = latent["target"].asstr()[:] | |
| batches = latent["source_batch"].asstr()[:] | |
| contexts = latent["context"].asstr()[:] | |
| mean, scale = readout["latent_mean"].numpy(), readout["latent_scale"].numpy() | |
| for regime, specification in regimes.items(): | |
| control_indices = np.flatnonzero(roles == specification["control_role"]) | |
| assert set(contexts[control_indices]) == {specification["context"]} | |
| control_means = { | |
| batch: (latent["latent"][np.sort(control_indices[batches[control_indices] == batch])].mean(0) - mean) | |
| / scale | |
| for batch in sorted(set(batches[control_indices])) | |
| } | |
| outcome_indices = np.flatnonzero(roles == specification["outcome_role"]) | |
| condition_targets = sorted(set(targets[outcome_indices])) | |
| condition_targets = condition_targets[: maximum_conditions or len(condition_targets)] | |
| for target in condition_targets: | |
| indices = np.sort(outcome_indices[targets[outcome_indices] == target]) | |
| target_batches = batches[indices] | |
| observed_latent = (latent["latent"][indices].mean(0) - mean) / scale | |
| matched_control = sum( | |
| np.count_nonzero(target_batches == batch) * control_means[batch] | |
| for batch in set(target_batches) | |
| ).astype(np.float32) / len(indices) | |
| decoded_effect = ( | |
| decode_normalized_latents(torch.from_numpy(observed_latent[None]), readout) | |
| - decode_normalized_latents(torch.from_numpy(matched_control[None]), readout) | |
| )[0].numpy() | |
| observed = truth[regime, target] | |
| metadata = { | |
| "regime": regime, | |
| "context": specification["context"], | |
| "outcome_role": specification["outcome_role"], | |
| "target": target, | |
| "repeat": 0, | |
| "model": "observed_latent_readout", | |
| "truth_batches": observed["batches"], | |
| "truth_cells": observed["cells"], | |
| "true_deg_count": int(observed["deg"].sum()), | |
| "target_in_hvg": target in hvg_index, | |
| } | |
| records.append( | |
| { | |
| **metadata, | |
| **gene_effect_metrics( | |
| decoded_effect, | |
| observed["effect"], | |
| hvg_index.get(target), | |
| observed["deg"], | |
| config["metrics"]["retrospective_top_genes"], | |
| ), | |
| } | |
| ) | |
| pathway_records.append( | |
| { | |
| **metadata, | |
| **pathway_agreement( | |
| decoded_effect, | |
| observed["effect"], | |
| pathway_matrix, | |
| config["metrics"]["pathway_top_k"], | |
| ), | |
| } | |
| ) | |
| summary = { | |
| "condition_metrics": _condition_summary( | |
| records, config["metrics"]["bootstrap_resamples"], config["seed"] | |
| ), | |
| "pathway_metrics": _condition_summary( | |
| pathway_records, config["metrics"]["bootstrap_resamples"], config["seed"] | |
| ), | |
| } | |
| provenance = { | |
| "diagnostic_only": True, | |
| "observed_outcomes_used_as_model_inputs": True, | |
| "valid_predictive_baseline": False, | |
| "executed_regimes": regimes, | |
| "maximum_conditions_per_regime": maximum_conditions, | |
| "readout_provenance": readout["provenance"], | |
| "runtime_source_sha256": _runtime_source_hash(), | |
| "runtime_environment": _runtime_environment(), | |
| "git": _git_state(), | |
| } | |
| output = Path(output_directory or "artifacts/readout_oracle") | |
| assert not output.exists() | |
| output.mkdir(parents=True) | |
| for filename, payload in ( | |
| ("summary.json", summary), | |
| ("truth_report.json", truth_report), | |
| ("provenance.json", provenance), | |
| ): | |
| (output / filename).write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n") | |
| for filename, values in ( | |
| ("condition_metrics.jsonl", records), | |
| ("pathway_metrics.jsonl", pathway_records), | |
| ): | |
| with (output / filename).open("w") as handle: | |
| for value in values: | |
| handle.write(json.dumps(value, sort_keys=True) + "\n") | |
| return summary, truth_report, provenance | |
| def _expression_role_effects(config, roles, maximum_targets=None): | |
| inputs = config["inputs"] | |
| with h5py.File(inputs["latent_cache_path"], "r") as latent, h5py.File( | |
| inputs["expression_cache_path"], "r" | |
| ) as expression_cache: | |
| cache_roles = latent["role"].asstr()[:] | |
| targets = latent["target"].asstr()[:] | |
| batches = latent["source_batch"].asstr()[:] | |
| contexts = latent["context"].asstr()[:] | |
| control_indices = np.flatnonzero(cache_roles == "control_train") | |
| assert set(contexts[control_indices]) == {"K562"} | |
| expression = expression_cache["expression"] | |
| control_means = { | |
| batch: expression[np.sort(control_indices[batches[control_indices] == batch])].mean( | |
| 0, dtype=np.float64 | |
| ) | |
| for batch in sorted(set(batches[control_indices])) | |
| } | |
| effects = {} | |
| for role in roles: | |
| outcome_indices = np.flatnonzero(cache_roles == role) | |
| assert set(contexts[outcome_indices]) == {"K562"} | |
| role_targets = sorted(set(targets[outcome_indices])) | |
| role_targets = role_targets[: maximum_targets or len(role_targets)] | |
| effects[role] = {} | |
| for target in role_targets: | |
| indices = np.sort(outcome_indices[targets[outcome_indices] == target]) | |
| target_batches = batches[indices] | |
| matched_control = sum( | |
| np.count_nonzero(target_batches == batch) * control_means[batch] | |
| for batch in set(target_batches) | |
| ) / len(indices) | |
| effects[role][target] = ( | |
| expression[indices].mean(0, dtype=np.float64) - matched_control | |
| ).astype(np.float32) | |
| return effects | |
| def fit_direct_gene_baseline(config, maximum_targets=None): | |
| """Fit ESM-to-expression effects with K562 train/validation outcomes only.""" | |
| direct, base = config["direct_gene"], config["transcriptomics"] | |
| effects = _expression_role_effects( | |
| base, | |
| (direct["fit_outcome_role"], direct["selection_outcome_role"]), | |
| maximum_targets, | |
| ) | |
| action = torch.load(base["inputs"]["action_cache_path"], map_location="cpu", weights_only=True) | |
| action_map = { | |
| target: (action["embedding"][index].numpy(), bool(action["known"][index])) | |
| for index, target in enumerate(action["targets"]) | |
| } | |
| train = effects[direct["fit_outcome_role"]] | |
| known_targets = [target for target in sorted(train) if action_map[target][1]] | |
| x = np.stack([action_map[target][0] for target in known_targets]).astype(np.float64) | |
| y = np.stack([train[target] for target in known_targets]).astype(np.float64) | |
| x_mean, x_std = x.mean(0), x.std(0).clip(1e-8) | |
| y_mean = y.mean(0) | |
| _, _, components = np.linalg.svd(y - y_mean, full_matrices=False) | |
| components = components[: min(direct["rank"], len(known_targets))] | |
| scores = (y - y_mean) @ components.T | |
| standardized = (x - x_mean) / x_std | |
| gram, cross = standardized.T @ standardized, standardized.T @ scores | |
| validation = effects[direct["selection_outcome_role"]] | |
| validation_targets = sorted(validation) | |
| validation_x = np.stack([action_map[target][0] for target in validation_targets]) | |
| validation_y = np.stack([validation[target] for target in validation_targets]) | |
| candidates = [] | |
| for alpha in direct["ridge_candidates"]: | |
| weights = np.linalg.solve(gram + alpha * np.eye(gram.shape[0]), cross) | |
| prediction = ((validation_x - x_mean) / x_std) @ weights @ components + y_mean | |
| candidates.append((float(np.mean((prediction - validation_y) ** 2)), alpha, weights)) | |
| validation_mse, alpha, weights = min(candidates, key=lambda item: (item[0], item[1])) | |
| checkpoint = { | |
| "format_version": 1, | |
| "architecture": "direct_gene_esm_low_rank_ridge", | |
| "x_mean": torch.from_numpy(x_mean.astype(np.float32)), | |
| "x_std": torch.from_numpy(x_std.astype(np.float32)), | |
| "y_mean": torch.from_numpy(y_mean.astype(np.float32)), | |
| "components": torch.from_numpy(components.astype(np.float32)), | |
| "weights": torch.from_numpy(weights.astype(np.float32)), | |
| "report": { | |
| "fit_outcome_role": direct["fit_outcome_role"], | |
| "selection_outcome_role": direct["selection_outcome_role"], | |
| "fit_targets": len(train), | |
| "fit_targets_with_known_action": len(known_targets), | |
| "selection_targets": len(validation_targets), | |
| "rank": len(components), | |
| "selected_ridge": alpha, | |
| "selection_mse": validation_mse, | |
| "ridge_validation_mse": [value for value, _, _ in candidates], | |
| }, | |
| "provenance": { | |
| "config_sha256": file_sha256("configs/direct_gene.yaml"), | |
| "latent_cache_sha256": base["inputs"]["latent_cache_sha256"], | |
| "expression_cache_sha256": base["inputs"]["expression_cache_sha256"], | |
| "action_cache_sha256": base["inputs"]["action_cache_sha256"], | |
| "runtime_source_sha256": _runtime_source_hash(), | |
| "runtime_environment": _runtime_environment(), | |
| "git": _git_state(), | |
| }, | |
| } | |
| return checkpoint | |
| def direct_gene_predictions(checkpoint, action_path): | |
| action = torch.load(action_path, map_location="cpu", weights_only=True) | |
| standardized = (action["embedding"] - checkpoint["x_mean"]) / checkpoint["x_std"] | |
| predicted = ( | |
| standardized @ checkpoint["weights"] @ checkpoint["components"] | |
| + checkpoint["y_mean"] | |
| ) | |
| predicted[~action["known"]] = checkpoint["y_mean"] | |
| return {target: predicted[index].numpy() for index, target in enumerate(action["targets"])} | |
| def fit_kernel_gene_student(config, maximum_targets=None): | |
| """Select a low-rank action kernel using only K562 train/validation effects.""" | |
| kernel, base = config["kernel_gene"], config["transcriptomics"] | |
| assert kernel["experimental_status"] == "exploratory_post_test_requires_external_confirmation" | |
| for prefix in ("direct_checkpoint", "action"): | |
| path = Path(kernel[f"{prefix}_path"]) | |
| assert (path.stat().st_size, file_sha256(path)) == ( | |
| kernel[f"{prefix}_bytes"], | |
| kernel[f"{prefix}_sha256"], | |
| ) | |
| if "action_manifest_path" in kernel: | |
| action_manifest = json.loads(Path(kernel["action_manifest_path"]).read_text()) | |
| declared = action_manifest.pop("manifest_sha256") | |
| assert declared == kernel["action_manifest_sha256"] == sha256( | |
| json.dumps(action_manifest, sort_keys=True, separators=(",", ":")).encode() | |
| ).hexdigest() | |
| assert action_manifest["artifact"]["sha256"] == kernel["action_sha256"] | |
| direct = torch.load(kernel["direct_checkpoint_path"], map_location="cpu", weights_only=True) | |
| action = torch.load(kernel["action_path"], map_location="cpu", weights_only=True) | |
| assert direct["architecture"] == "direct_gene_esm_low_rank_ridge" | |
| effects = _expression_role_effects( | |
| base, | |
| (kernel["fit_outcome_role"], kernel["selection_outcome_role"]), | |
| maximum_targets, | |
| ) | |
| action_map = { | |
| target: (action["embedding"][index].numpy(), bool(action["known"][index])) | |
| for index, target in enumerate(action["targets"]) | |
| } | |
| train = effects[kernel["fit_outcome_role"]] | |
| train_targets = [target for target in sorted(train) if action_map[target][1]] | |
| validation = effects[kernel["selection_outcome_role"]] | |
| validation_targets = sorted(validation) | |
| assert all(action_map[target][1] for target in validation_targets) | |
| train_y = np.stack([train[target] for target in train_targets]).astype(np.float64) | |
| validation_y = np.stack([validation[target] for target in validation_targets]) | |
| components = direct["components"].numpy().astype(np.float64) | |
| y_mean = direct["y_mean"].numpy().astype(np.float64) | |
| train_scores = (train_y - y_mean) @ components.T | |
| candidates, solutions = [], [] | |
| for block, (start, end) in kernel["feature_blocks"].items(): | |
| train_x = np.stack( | |
| [action_map[target][0][start:end] for target in train_targets] | |
| ).astype(np.float64) | |
| validation_x = np.stack( | |
| [action_map[target][0][start:end] for target in validation_targets] | |
| ).astype(np.float64) | |
| mean, std = train_x.mean(0), train_x.std(0).clip(1e-8) | |
| train_x, validation_x = (train_x - mean) / std, (validation_x - mean) / std | |
| for kind in kernel["kernels"]: | |
| if kind == "linear": | |
| problems = [ | |
| ( | |
| None, | |
| None, | |
| validation_x, | |
| train_x.T @ train_x, | |
| train_x.T @ train_scores, | |
| kernel["linear_ridge_candidates"], | |
| ) | |
| ] | |
| else: | |
| assert kind == "rbf" | |
| train_square = np.square(train_x).sum(1) | |
| train_distance = np.maximum( | |
| train_square[:, None] + train_square[None, :] - 2 * train_x @ train_x.T, | |
| 0, | |
| ) | |
| median_distance = float(np.median(train_distance[train_distance > 0])) | |
| validation_distance = np.maximum( | |
| np.square(validation_x).sum(1)[:, None] | |
| + train_square[None, :] | |
| - 2 * validation_x @ train_x.T, | |
| 0, | |
| ) | |
| problems = [ | |
| ( | |
| float(multiplier), | |
| median_distance, | |
| np.exp(-multiplier * validation_distance / median_distance), | |
| np.exp(-multiplier * train_distance / median_distance), | |
| train_scores, | |
| kernel["rbf_ridge_candidates"], | |
| ) | |
| for multiplier in kernel["rbf_bandwidth_multipliers"] | |
| ] | |
| for multiplier, median, design, gram, cross, ridges in problems: | |
| values, vectors = np.linalg.eigh(gram) | |
| projected = vectors.T @ cross | |
| for alpha in ridges: | |
| coefficient = vectors @ (projected / (values[:, None] + alpha)) | |
| prediction = design @ coefficient @ components + y_mean | |
| candidates.append( | |
| { | |
| "feature_block": block, | |
| "kernel": kind, | |
| "bandwidth_multiplier": multiplier, | |
| "ridge": float(alpha), | |
| "selection_mse": float(np.mean((prediction - validation_y) ** 2)), | |
| "selection_mean_effect_pearson": float( | |
| np.mean( | |
| [ | |
| np.corrcoef(predicted, observed)[0, 1] | |
| for predicted, observed in zip(prediction, validation_y) | |
| ] | |
| ) | |
| ), | |
| "median_squared_distance": median, | |
| } | |
| ) | |
| solutions.append( | |
| {"weights": coefficient} | |
| if kind == "linear" | |
| else {"dual": coefficient, "train_x": train_x} | |
| ) | |
| selected_index = min( | |
| range(len(candidates)), | |
| key=lambda index: (candidates[index]["selection_mse"], index), | |
| ) | |
| selected, solution = candidates[selected_index], solutions[selected_index] | |
| start, end = kernel["feature_blocks"][selected["feature_block"]] | |
| selected_x = np.stack( | |
| [action_map[target][0][start:end] for target in train_targets] | |
| ).astype(np.float64) | |
| mean, std = selected_x.mean(0), selected_x.std(0).clip(1e-8) | |
| return { | |
| "format_version": 1, | |
| "architecture": "kernel_gene_low_rank_student", | |
| "feature_block": selected["feature_block"], | |
| "feature_slice": [start, end], | |
| "kernel": selected["kernel"], | |
| "x_mean": torch.from_numpy(mean.astype(np.float32)), | |
| "x_std": torch.from_numpy(std.astype(np.float32)), | |
| "y_mean": direct["y_mean"], | |
| "components": direct["components"], | |
| **{ | |
| name: torch.from_numpy(value.astype(np.float32)) | |
| for name, value in solution.items() | |
| }, | |
| "report": { | |
| "experimental_status": kernel["experimental_status"], | |
| "fit_outcome_role": kernel["fit_outcome_role"], | |
| "selection_outcome_role": kernel["selection_outcome_role"], | |
| "fit_targets": len(train), | |
| "fit_targets_with_known_action": len(train_targets), | |
| "selection_targets": len(validation_targets), | |
| "rank": len(components), | |
| "selection_rule": "minimum validation MSE; config order breaks exact ties", | |
| "selected": selected, | |
| "candidates": candidates, | |
| "sealed_test_outcomes_used_for_fit_or_selection": False, | |
| "rpe1_perturbed_outcomes_used_for_fit_or_selection": False, | |
| }, | |
| "provenance": { | |
| "config_sha256": file_sha256(kernel.get("config_path", "configs/kernel_gene.yaml")), | |
| "direct_checkpoint_sha256": kernel["direct_checkpoint_sha256"], | |
| "action_sha256": kernel["action_sha256"], | |
| "latent_cache_sha256": base["inputs"]["latent_cache_sha256"], | |
| "expression_cache_sha256": base["inputs"]["expression_cache_sha256"], | |
| "runtime_source_sha256": _runtime_source_hash(), | |
| "runtime_environment": _runtime_environment(), | |
| "git": _git_state(), | |
| }, | |
| } | |
| def fit_external_response_student(config, maximum_targets=None): | |
| """Fit a multi-view action student without validation or sealed-target outcome leakage.""" | |
| from causalcelljepa.external_evaluation import grouped_expression_moments | |
| specification, base = config["external_response"], config["transcriptomics"] | |
| assert specification["experimental_status"] == "exploratory_post_test_not_confirmatory" | |
| assert specification["fit_outcome_role"] == "dynamics_train" | |
| assert specification["selection_outcome_role"] == "perturbation_ood_validation" | |
| for name in ("action", "reference_checkpoint"): | |
| path = Path(specification[f"{name}_path"]) | |
| assert (path.stat().st_size, file_sha256(path)) == ( | |
| specification[f"{name}_bytes"], | |
| specification[f"{name}_sha256"], | |
| ) | |
| external_config_path = Path(specification["external_config_path"]) | |
| assert file_sha256(external_config_path) == specification["external_config_sha256"] | |
| external_config = yaml.safe_load(external_config_path.read_text()) | |
| external_manifest = json.loads(Path(specification["external_manifest_path"]).read_text()) | |
| declared = external_manifest.pop("manifest_sha256") | |
| assert declared == specification["external_manifest_sha256"] == sha256( | |
| json.dumps(external_manifest, sort_keys=True, separators=(",", ":")).encode() | |
| ).hexdigest() | |
| assert external_manifest["protocol"]["role"] == "external_test_only" | |
| effects = _expression_role_effects( | |
| base, | |
| (specification["fit_outcome_role"], specification["selection_outcome_role"]), | |
| maximum_targets, | |
| ) | |
| train, validation = ( | |
| effects[specification["fit_outcome_role"]], | |
| effects[specification["selection_outcome_role"]], | |
| ) | |
| action = torch.load(specification["action_path"], map_location="cpu", weights_only=True) | |
| action_map = { | |
| target: (action["embedding"][index].numpy(), bool(action["known"][index])) | |
| for index, target in enumerate(action["targets"]) | |
| } | |
| train_targets = [target for target in sorted(train) if action_map[target][1]] | |
| validation_targets = sorted(validation) | |
| assert all(action_map[target][1] for target in validation_targets) | |
| with h5py.File(base["inputs"]["latent_cache_path"], "r") as cache: | |
| roles, cache_targets = cache["role"].asstr()[:], cache["target"].asstr()[:] | |
| excluded_targets = set().union( | |
| *(set(cache_targets[roles == role]) for role in specification["excluded_outcome_roles"]) | |
| ) | |
| assert not set(train_targets).intersection(excluded_targets) | |
| replogle = json.loads(Path(base["inputs"]["replogle_manifest_path"]).read_text()) | |
| hvg_genes = replogle["genes"]["hvg_gene_names"] | |
| hvg_index = {gene: index for index, gene in enumerate(hvg_genes)} | |
| train_y = np.stack([train[target] for target in train_targets]).astype(np.float64) | |
| validation_y = np.stack([validation[target] for target in validation_targets]) | |
| y_mean, centered_y = train_y.mean(0), train_y - train_y.mean(0) | |
| target_index = {target: index for index, target in enumerate(train_targets)} | |
| external_blocks, external_report = {}, {} | |
| for context in specification["contexts"]: | |
| source = external_config["source"]["files"][context] | |
| path = Path(external_config["source"]["raw_directory"]) / source["filename"] | |
| assert path.stat().st_size == source["bytes"] | |
| assert file_sha256(path) == external_manifest["contexts"][context]["source_sha256"] | |
| data = ad.read_h5ad(path, backed="r") | |
| labels = data.obs[external_config["source"]["perturbation_column"]].astype(str).to_numpy() | |
| eligible = set(external_manifest["contexts"][context]["eligible_targets"]) | |
| admitted = sorted(set(train_targets).intersection(eligible)) | |
| assert not set(admitted).intersection(excluded_targets) | |
| present_genes = [gene for gene in hvg_genes if gene in data.var_names] | |
| columns = np.asarray([data.var_names.get_loc(gene) for gene in present_genes]) | |
| groups = [external_config["source"]["control_label"], *admitted] | |
| means, _, counts = grouped_expression_moments( | |
| data.X, labels, groups, columns, specification["source_block_size"] | |
| ) | |
| data.file.close() | |
| assert counts[0] == source["expected_controls"] | |
| assert counts[1:].min() >= external_config["protocol"]["population_size"] | |
| observed = means[1:] - means[0] | |
| observed -= observed.mean(0) | |
| rows = np.zeros((len(train_targets), len(present_genes)), dtype=np.float64) | |
| positions = np.asarray([target_index[target] for target in admitted]) | |
| rows[positions] = observed | |
| gene_positions = np.asarray([hvg_index[gene] for gene in present_genes]) | |
| reference_rms = np.sqrt(np.mean(centered_y[np.ix_(positions, gene_positions)] ** 2)) | |
| source_rms = np.sqrt(np.mean(observed**2)) | |
| scale = float(reference_rms / source_rms) | |
| external_blocks[context] = rows * scale | |
| external_report[context] = { | |
| "fit_targets": len(admitted), | |
| "fit_target_sha256": sha256("\n".join(admitted).encode()).hexdigest(), | |
| "genes": len(present_genes), | |
| "cells": int(counts.sum()), | |
| "effect_rms_scale_to_k562": scale, | |
| "source_sha256": external_manifest["contexts"][context]["source_sha256"], | |
| } | |
| start, end = specification["feature_slice"] | |
| train_x = np.stack( | |
| [action_map[target][0][start:end] for target in train_targets] | |
| ).astype(np.float64) | |
| validation_x = np.stack( | |
| [action_map[target][0][start:end] for target in validation_targets] | |
| ).astype(np.float64) | |
| x_mean, x_std = train_x.mean(0), train_x.std(0).clip(1e-8) | |
| train_x, validation_x = (train_x - x_mean) / x_std, (validation_x - x_mean) / x_std | |
| train_square = np.square(train_x).sum(1) | |
| train_distance = np.maximum( | |
| train_square[:, None] + train_square[None, :] - 2 * train_x @ train_x.T, 0 | |
| ) | |
| median_distance = float(np.median(train_distance[train_distance > 0])) | |
| validation_distance = np.maximum( | |
| np.square(validation_x).sum(1)[:, None] | |
| + train_square[None, :] | |
| - 2 * validation_x @ train_x.T, | |
| 0, | |
| ) | |
| kernels = {} | |
| for bandwidth in specification["rbf_bandwidth_multipliers"]: | |
| gram = np.exp(-bandwidth * train_distance / median_distance) | |
| values, vectors = np.linalg.eigh(gram) | |
| kernels[bandwidth] = ( | |
| values, | |
| vectors, | |
| np.exp(-bandwidth * validation_distance / median_distance), | |
| ) | |
| reference = torch.load( | |
| specification["reference_checkpoint_path"], map_location="cpu", weights_only=True | |
| ) | |
| reference_prediction = kernel_gene_predictions(reference, specification["action_path"]) | |
| reference_y = np.stack([reference_prediction[target] for target in validation_targets]) | |
| reference_metrics = { | |
| "mse": float(np.mean((reference_y - validation_y) ** 2)), | |
| "mean_effect_pearson": float( | |
| np.mean( | |
| [ | |
| np.corrcoef(predicted, observed)[0, 1] | |
| for predicted, observed in zip(reference_y, validation_y) | |
| ] | |
| ) | |
| ), | |
| "mean_magnitude_absolute_error": float( | |
| np.mean( | |
| np.abs( | |
| np.linalg.norm(reference_y, axis=1) | |
| - np.linalg.norm(validation_y, axis=1) | |
| ) | |
| ) | |
| ), | |
| } | |
| candidates, solutions = [], [] | |
| weight_grid = specification["external_view_weights"] | |
| for weights in ( | |
| dict(zip(specification["contexts"], values, strict=True)) | |
| for values in product(weight_grid, repeat=len(specification["contexts"])) | |
| ): | |
| joined = np.concatenate( | |
| [centered_y] | |
| + [weights[context] * external_blocks[context] for context in specification["contexts"]], | |
| axis=1, | |
| ) | |
| _, _, components = np.linalg.svd(joined, full_matrices=False) | |
| for rank in specification["ranks"]: | |
| rank = min(rank, len(train_targets)) | |
| selected_components = components[:rank] | |
| scores = joined @ selected_components.T | |
| gene_head = selected_components[:, : train_y.shape[1]] | |
| for bandwidth, (values, vectors, design) in kernels.items(): | |
| projected = vectors.T @ scores | |
| for ridge in specification["rbf_ridge_candidates"]: | |
| dual = vectors @ (projected / (values[:, None] + ridge)) | |
| unscaled = design @ dual @ gene_head + y_mean | |
| correlation = float( | |
| np.mean( | |
| [ | |
| np.corrcoef(predicted, observed)[0, 1] | |
| for predicted, observed in zip(unscaled, validation_y) | |
| ] | |
| ) | |
| ) | |
| for output_scale in specification["output_scale_candidates"]: | |
| prediction = output_scale * unscaled | |
| candidate = { | |
| "external_view_weights": weights, | |
| "rank": rank, | |
| "bandwidth_multiplier": float(bandwidth), | |
| "ridge": float(ridge), | |
| "output_scale": float(output_scale), | |
| "median_squared_distance": median_distance, | |
| "selection_mse": float(np.mean((prediction - validation_y) ** 2)), | |
| "selection_mean_effect_pearson": correlation, | |
| "selection_mean_magnitude_absolute_error": float( | |
| np.mean( | |
| np.abs( | |
| np.linalg.norm(prediction, axis=1) | |
| - np.linalg.norm(validation_y, axis=1) | |
| ) | |
| ) | |
| ), | |
| } | |
| candidates.append(candidate) | |
| solutions.append((dual, gene_head)) | |
| gates = specification["validation_gates"] | |
| admissible = [ | |
| index | |
| for index, item in enumerate(candidates) | |
| if sum(item["external_view_weights"].values()) > 0 | |
| and item["selection_mse"] | |
| <= reference_metrics["mse"] * (1 - gates["minimum_relative_mse_improvement"]) | |
| and item["selection_mean_effect_pearson"] | |
| >= reference_metrics["mean_effect_pearson"] + gates["minimum_pearson_improvement"] | |
| and item["selection_mean_magnitude_absolute_error"] | |
| <= reference_metrics["mean_magnitude_absolute_error"] | |
| + gates["maximum_magnitude_error_degradation"] | |
| ] | |
| selected_index = min( | |
| admissible or range(len(candidates)), | |
| key=lambda index: (candidates[index]["selection_mse"], index), | |
| ) | |
| selected, (dual, gene_head) = candidates[selected_index], solutions[selected_index] | |
| selected["passes_validation_gate"] = bool(admissible) | |
| internal_control = min( | |
| (item for item in candidates if sum(item["external_view_weights"].values()) == 0), | |
| key=lambda item: item["selection_mse"], | |
| ) | |
| report = { | |
| "experimental_status": specification["experimental_status"], | |
| "protocol_amendment": specification["protocol_amendment"], | |
| "fit_outcome_role": specification["fit_outcome_role"], | |
| "selection_outcome_role": specification["selection_outcome_role"], | |
| "fit_targets": len(train_targets), | |
| "selection_targets": len(validation_targets), | |
| "external": external_report, | |
| "excluded_outcome_roles": specification["excluded_outcome_roles"], | |
| "excluded_target_count": len(excluded_targets), | |
| "excluded_target_sha256": sha256("\n".join(sorted(excluded_targets)).encode()).hexdigest(), | |
| "reference": reference_metrics, | |
| "internal_no_external_control": internal_control, | |
| "selected": selected, | |
| "candidate_count": len(candidates), | |
| "validation_gates": gates, | |
| "sealed_test_outcomes_used_for_fit_or_selection": False, | |
| "rpe1_perturbed_outcomes_used_for_fit_or_selection": False, | |
| "prior_external_results_make_this_confirmatory": False, | |
| } | |
| return { | |
| "format_version": 1, | |
| "architecture": "external_response_multiview_kernel_gene_student", | |
| "feature_slice": specification["feature_slice"], | |
| "kernel": "rbf", | |
| "x_mean": torch.from_numpy(x_mean.astype(np.float32)), | |
| "x_std": torch.from_numpy(x_std.astype(np.float32)), | |
| "y_mean": torch.from_numpy(y_mean.astype(np.float32)), | |
| "components": torch.from_numpy(gene_head.astype(np.float32)), | |
| "train_x": torch.from_numpy(train_x.astype(np.float32)), | |
| "dual": torch.from_numpy(dual.astype(np.float32)), | |
| "output_scale": selected["output_scale"], | |
| "report": report, | |
| "provenance": { | |
| "config_sha256": file_sha256(specification["config_path"]), | |
| "external_config_sha256": specification["external_config_sha256"], | |
| "external_manifest_sha256": declared, | |
| "reference_checkpoint_sha256": specification["reference_checkpoint_sha256"], | |
| "action_sha256": specification["action_sha256"], | |
| "latent_cache_sha256": base["inputs"]["latent_cache_sha256"], | |
| "expression_cache_sha256": base["inputs"]["expression_cache_sha256"], | |
| "runtime_source_sha256": _runtime_source_hash(), | |
| "runtime_environment": _runtime_environment(), | |
| "git": _git_state(), | |
| }, | |
| } | |
| def kernel_gene_predictions(checkpoint, action_path): | |
| """Apply a frozen linear or RBF action student without outcome access.""" | |
| action = torch.load(action_path, map_location="cpu", weights_only=True) | |
| start, end = checkpoint["feature_slice"] | |
| standardized = ( | |
| action["embedding"][:, start:end] - checkpoint["x_mean"] | |
| ) / checkpoint["x_std"] | |
| if checkpoint["kernel"] == "linear": | |
| scores = standardized @ checkpoint["weights"] | |
| else: | |
| distance = torch.cdist(standardized, checkpoint["train_x"]).square() | |
| selected = checkpoint["report"]["selected"] | |
| similarity = torch.exp( | |
| -selected["bandwidth_multiplier"] | |
| * distance | |
| / selected["median_squared_distance"] | |
| ) | |
| scores = similarity @ checkpoint["dual"] | |
| predicted = checkpoint.get("output_scale", 1.0) * ( | |
| scores @ checkpoint["components"] + checkpoint["y_mean"] | |
| ) | |
| predicted[~action["known"]] = checkpoint.get("output_scale", 1.0) * checkpoint[ | |
| "y_mean" | |
| ] | |
| return {target: predicted[index].numpy() for index, target in enumerate(action["targets"])} | |
| def score_effect_predictions(base, model_name, predictions, maximum_conditions=None): | |
| """Score frozen gene-effect predictions through the common reporting-only endpoint.""" | |
| truth, truth_report = expression_truth(base, maximum_conditions=maximum_conditions) | |
| replogle = json.loads(Path(base["inputs"]["replogle_manifest_path"]).read_text()) | |
| hvg_genes = replogle["genes"]["hvg_gene_names"] | |
| hvg_index = {gene: index for index, gene in enumerate(hvg_genes)} | |
| pathway_matrix, pathway_labels = _load_pathway_matrix( | |
| base["inputs"]["go_gmt_path"], hvg_genes | |
| ) | |
| assert len(pathway_labels) == 4328 | |
| records, signatures, seen = [], defaultdict(lambda: [0, None]), Counter() | |
| for regime, target, repeat, predicted in predictions: | |
| assert (regime, target) in truth and repeat >= 0 | |
| predicted = np.asarray(predicted, dtype=np.float32) | |
| assert predicted.shape == (len(hvg_genes),) and np.isfinite(predicted).all() | |
| observed = truth[regime, target] | |
| specification = base["regimes"][regime] | |
| metadata = { | |
| "regime": regime, | |
| "context": specification["context"], | |
| "outcome_role": specification["outcome_role"], | |
| "target": target, | |
| "repeat": int(repeat), | |
| "model": model_name, | |
| "truth_batches": observed["batches"], | |
| "truth_cells": observed["cells"], | |
| "true_deg_count": int(observed["deg"].sum()), | |
| "target_in_hvg": target in hvg_index, | |
| } | |
| records.append( | |
| { | |
| **metadata, | |
| **gene_effect_metrics( | |
| predicted, | |
| observed["effect"], | |
| hvg_index.get(target), | |
| observed["deg"], | |
| base["metrics"]["retrospective_top_genes"], | |
| ), | |
| } | |
| ) | |
| key = regime, model_name, target | |
| signatures[key][0] += 1 | |
| signatures[key][1] = predicted if signatures[key][1] is None else signatures[key][1] + predicted | |
| seen[regime, target, int(repeat)] += 1 | |
| assert {(regime, target) for regime, target, _ in seen} == set(truth) | |
| assert set(seen.values()) == {1} | |
| pathway_records, retrieval = [], [] | |
| for regime, specification in base["regimes"].items(): | |
| targets = sorted(target for key, target in truth if key == regime) | |
| predicted_signatures = np.stack( | |
| [signatures[regime, model_name, target][1] / signatures[regime, model_name, target][0] for target in targets] | |
| ) | |
| true_signatures = np.stack([truth[regime, target]["effect"] for target in targets]) | |
| normalized_prediction = predicted_signatures / np.linalg.norm( | |
| predicted_signatures, axis=1, keepdims=True | |
| ).clip(1e-12) | |
| normalized_truth = true_signatures / np.linalg.norm( | |
| true_signatures, axis=1, keepdims=True | |
| ).clip(1e-12) | |
| similarity = normalized_prediction @ normalized_truth.T | |
| ranks = np.asarray( | |
| [ | |
| 1 | |
| + np.count_nonzero(row > row[index]) | |
| + 0.5 * (np.count_nonzero(row == row[index]) - 1) | |
| for index, row in enumerate(similarity) | |
| ] | |
| ) | |
| retrieval.append( | |
| { | |
| "regime": regime, | |
| "model": model_name, | |
| "targets": len(targets), | |
| "top_1": float(np.mean(ranks <= 1)), | |
| "top_5": float(np.mean(ranks <= 5)), | |
| "mean_reciprocal_rank": float(np.mean(1 / ranks)), | |
| "median_rank": float(np.median(ranks)), | |
| } | |
| ) | |
| for index, target in enumerate(targets): | |
| observed = truth[regime, target] | |
| pathway_records.append( | |
| { | |
| "regime": regime, | |
| "context": specification["context"], | |
| "outcome_role": specification["outcome_role"], | |
| "target": target, | |
| "repeat": 0, | |
| "model": model_name, | |
| "truth_batches": observed["batches"], | |
| "truth_cells": observed["cells"], | |
| "true_deg_count": int(observed["deg"].sum()), | |
| "target_in_hvg": target in hvg_index, | |
| **pathway_agreement( | |
| predicted_signatures[index], | |
| true_signatures[index], | |
| pathway_matrix, | |
| base["metrics"]["pathway_top_k"], | |
| ), | |
| } | |
| ) | |
| return records, pathway_records, retrieval, truth_report | |
| def run_state_baseline_evaluation(config, base): | |
| """Evaluate validation-frozen State predictions and pair them with frozen references.""" | |
| evaluation, inputs = config["evaluation"], config["inputs"] | |
| prediction_report = json.loads(Path(config["prediction"]["report_path"]).read_text()) | |
| prediction_path = Path(prediction_report["path"]) | |
| assert (prediction_path.stat().st_size, file_sha256(prediction_path)) == ( | |
| prediction_report["bytes"], | |
| prediction_report["sha256"], | |
| ) | |
| assert (Path(inputs["checkpoint_path"]).stat().st_size, file_sha256(inputs["checkpoint_path"])) == ( | |
| inputs["checkpoint_bytes"], | |
| inputs["checkpoint_sha256"], | |
| ) | |
| with h5py.File(prediction_path, "r") as prediction: | |
| assert prediction.attrs["checkpoint_sha256"] == inputs["checkpoint_sha256"] | |
| assert not prediction.attrs["test_outcome_expression_read"] | |
| assert len(prediction["predicted_effect"]) == prediction_report["records"] | |
| predictions = ( | |
| ( | |
| prediction["regime"].asstr()[index], | |
| prediction["target"].asstr()[index], | |
| int(prediction["repeat"][index]), | |
| prediction["predicted_effect"][index], | |
| ) | |
| for index in range(prediction_report["records"]) | |
| ) | |
| records, pathways, retrieval, truth_report = score_effect_predictions( | |
| base, config["prediction"]["model_name"], predictions | |
| ) | |
| reference_records, reference_pathways = [], [] | |
| for reference in evaluation["references"]: | |
| for name, destination in ( | |
| ("condition_metrics", reference_records), | |
| ("pathway_metrics", reference_pathways), | |
| ): | |
| path = Path(reference[f"{name}_path"]) | |
| assert (path.stat().st_size, file_sha256(path)) == ( | |
| reference[f"{name}_bytes"], | |
| reference[f"{name}_sha256"], | |
| ) | |
| lines = path.read_text().splitlines() | |
| assert len(lines) == reference[f"{name}_records"] | |
| destination.extend( | |
| value | |
| for value in map(json.loads, lines) | |
| if value["model"] in reference["models"] | |
| ) | |
| resamples = base["metrics"]["bootstrap_resamples"] | |
| summary = { | |
| "condition_metrics": _condition_summary(records, resamples, base["seed"]), | |
| "pathway_metrics": _condition_summary(pathways, resamples, base["seed"]), | |
| "retrieval": retrieval, | |
| } | |
| paired = { | |
| "condition_comparisons": _paired_transcriptomic_models( | |
| reference_records + records, evaluation["comparisons"], resamples, base["seed"] | |
| ), | |
| "pathway_comparisons": _paired_transcriptomic_models( | |
| reference_pathways + pathways, evaluation["comparisons"], resamples, base["seed"] | |
| ), | |
| } | |
| provenance = { | |
| "config": deepcopy(config), | |
| "base_transcriptomics_config": deepcopy(base), | |
| "reporting_only": True, | |
| "checkpoint_selected_on": "K562 perturbation_ood_validation only", | |
| "rpe1_perturbed_outcomes_used_for_fit_or_selection": False, | |
| "sealed_test_outcomes_used_for_fit_or_selection": False, | |
| "prediction_report": prediction_report, | |
| "checkpoint_sha256": inputs["checkpoint_sha256"], | |
| "runtime_source_sha256": _runtime_source_hash(), | |
| "runtime_environment": _runtime_environment(), | |
| "git": _git_state(), | |
| } | |
| output = Path(evaluation["output_directory"]) | |
| assert not output.exists() | |
| output.mkdir(parents=True) | |
| for filename, payload in ( | |
| ("summary.json", summary), | |
| ("paired_comparisons.json", paired), | |
| ("truth_report.json", truth_report), | |
| ("provenance.json", provenance), | |
| ): | |
| (output / filename).write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n") | |
| for filename, values in (("condition_metrics.jsonl", records), ("pathway_metrics.jsonl", pathways)): | |
| with (output / filename).open("w") as handle: | |
| for value in values: | |
| handle.write(json.dumps(value, sort_keys=True) + "\n") | |
| return summary, paired, truth_report, provenance | |
| def run_kernel_gene_evaluation(config, maximum_conditions=None, output_directory=None): | |
| """Report a selection-frozen kernel student against immutable references.""" | |
| kernel, evaluation, base = config["kernel_gene"], config["evaluation"], config["transcriptomics"] | |
| references, reference_evaluation = [], evaluation | |
| while "reference_config_path" in reference_evaluation: | |
| assert file_sha256(reference_evaluation["reference_config_path"]) == reference_evaluation[ | |
| "reference_config_sha256" | |
| ] | |
| parent = yaml.safe_load(Path(reference_evaluation["reference_config_path"]).read_text())[ | |
| "evaluation" | |
| ] | |
| manifest = json.loads(Path(reference_evaluation["reference_manifest_path"]).read_text()) | |
| declared_reference = manifest.pop("manifest_sha256") | |
| assert declared_reference == reference_evaluation["reference_manifest_sha256"] == sha256( | |
| json.dumps(manifest, sort_keys=True, separators=(",", ":")).encode() | |
| ).hexdigest() | |
| references.append( | |
| { | |
| "models": [parent["model_name"]], | |
| **{ | |
| f"{name}_{field}": manifest["artifacts"][name][field] | |
| for name in ("condition_metrics", "pathway_metrics") | |
| for field in ("path", "bytes", "records", "sha256") | |
| }, | |
| } | |
| ) | |
| reference_evaluation = parent | |
| references = reference_evaluation["references"] + references | |
| comparisons = [ | |
| {**pair, "candidate": evaluation["model_name"]} for pair in evaluation["comparisons"] | |
| ] | |
| selection = json.loads(Path(kernel["selection_manifest_path"]).read_text()) | |
| declared = selection.pop("manifest_sha256") | |
| assert declared == kernel["selection_manifest_sha256"] == sha256( | |
| json.dumps(selection, sort_keys=True, separators=(",", ":")).encode() | |
| ).hexdigest() | |
| checkpoint_artifact = selection["artifacts"]["checkpoint"] | |
| checkpoint_path = Path(checkpoint_artifact["path"]) | |
| assert (checkpoint_path.stat().st_size, file_sha256(checkpoint_path)) == ( | |
| checkpoint_artifact["bytes"], | |
| checkpoint_artifact["sha256"], | |
| ) | |
| action_path = Path(kernel["action_path"]) | |
| assert (action_path.stat().st_size, file_sha256(action_path)) == ( | |
| kernel["action_bytes"], | |
| kernel["action_sha256"], | |
| ) | |
| checkpoint = torch.load(checkpoint_path, map_location="cpu", weights_only=True) | |
| selected = checkpoint["report"]["selected"] | |
| frozen = selection["selection"]["selected"] | |
| if checkpoint["architecture"] == "external_response_multiview_kernel_gene_student": | |
| assert { | |
| "external_view_weights": selected["external_view_weights"], | |
| "rank": selected["rank"], | |
| "bandwidth_multiplier": selected["bandwidth_multiplier"], | |
| "ridge": selected["ridge"], | |
| "output_scale": selected["output_scale"], | |
| "mse": selected["selection_mse"], | |
| } == { | |
| key: frozen[key] | |
| for key in ( | |
| "external_view_weights", | |
| "rank", | |
| "bandwidth_multiplier", | |
| "ridge", | |
| "output_scale", | |
| "mse", | |
| ) | |
| } | |
| else: | |
| assert ( | |
| selected["feature_block"], | |
| selected["kernel"], | |
| selected["bandwidth_multiplier"], | |
| selected["ridge"], | |
| selected["selection_mse"], | |
| ) == ( | |
| frozen["feature_block"], | |
| frozen["kernel"], | |
| frozen["bandwidth_multiplier"], | |
| frozen["ridge"], | |
| frozen["mse"], | |
| ) | |
| predicted = kernel_gene_predictions(checkpoint, action_path) | |
| gate_report = None | |
| if "control_gate" in kernel: | |
| gate_config = kernel["control_gate"] | |
| reference_selection = json.loads(Path(gate_config["reference_manifest_path"]).read_text()) | |
| reference_declared = reference_selection.pop("manifest_sha256") | |
| assert reference_declared == gate_config["reference_manifest_sha256"] == sha256( | |
| json.dumps(reference_selection, sort_keys=True, separators=(",", ":")).encode() | |
| ).hexdigest() | |
| reference_artifact = reference_selection["artifacts"]["checkpoint"] | |
| reference_path = Path(reference_artifact["path"]) | |
| assert (reference_path.stat().st_size, file_sha256(reference_path)) == ( | |
| reference_artifact["bytes"], reference_artifact["sha256"] | |
| ) | |
| reference_checkpoint = torch.load(reference_path, map_location="cpu", weights_only=True) | |
| reference_prediction = kernel_gene_predictions(reference_checkpoint, action_path) | |
| gate_manifest = json.loads(Path(gate_config["gate_manifest_path"]).read_text()) | |
| gate_declared = gate_manifest.pop("manifest_sha256") | |
| assert gate_declared == gate_config["gate_manifest_sha256"] == sha256( | |
| json.dumps(gate_manifest, sort_keys=True, separators=(",", ":")).encode() | |
| ).hexdigest() | |
| gate_artifact = gate_manifest["artifact"] | |
| gate_path = Path(gate_artifact["path"]) | |
| assert (gate_path.stat().st_size, file_sha256(gate_path)) == ( | |
| gate_artifact["bytes"], gate_artifact["sha256"] | |
| ) | |
| gate = torch.load(gate_path, map_location="cpu", weights_only=True) | |
| dynamics_manifest = json.loads(Path(base["inputs"]["dynamics_manifest_path"]).read_text()) | |
| normalization = dynamics_manifest["normalization"] | |
| latent_mean = np.asarray(normalization["latent_mean"]) | |
| latent_scale = np.asarray(normalization["latent_std"]) * normalization["dimension_scale"] | |
| with h5py.File(base["inputs"]["latent_cache_path"], "r") as latent: | |
| roles, targets = latent["role"].asstr()[:], latent["target"].asstr()[:] | |
| if "control_gate" in kernel: | |
| confidences = {} | |
| for role in sorted({value["control_role"] for value in base["regimes"].values()}): | |
| control = ((latent["latent"][roles == role] - latent_mean) / latent_scale).mean(0) | |
| score = float(np.square((control - gate["center"].numpy()) / gate["scale"].numpy()).mean()) | |
| confidence = float( | |
| np.exp(-max(0, score - gate["threshold"]) / gate["temperature"]) | |
| ) | |
| confidences[role] = {"score": score, "reference_confidence": confidence} | |
| assert confidences["control_train"]["reference_confidence"] >= gate_config[ | |
| "minimum_in_domain_confidence" | |
| ] | |
| assert confidences["control_inference"]["reference_confidence"] <= gate_config[ | |
| "maximum_shifted_context_confidence" | |
| ] | |
| gate_report = { | |
| "formula": "reference_confidence * STRING_GO + (1-reference_confidence) * external_response", | |
| "controls": confidences, | |
| "reference_manifest_sha256": reference_declared, | |
| "gate_manifest_sha256": gate_declared, | |
| } | |
| rows = [] | |
| for regime, specification in base["regimes"].items(): | |
| regime_targets = sorted(set(targets[roles == specification["outcome_role"]])) | |
| regime_targets = regime_targets[: maximum_conditions or len(regime_targets)] | |
| confidence = ( | |
| gate_report["controls"][specification["control_role"]]["reference_confidence"] | |
| if gate_report | |
| else 0.0 | |
| ) | |
| rows.extend( | |
| ( | |
| regime, | |
| target, | |
| 0, | |
| confidence * reference_prediction[target] + (1 - confidence) * predicted[target] | |
| if gate_report | |
| else predicted[target], | |
| ) | |
| for target in regime_targets | |
| ) | |
| records, pathways, retrieval, truth_report = score_effect_predictions( | |
| base, evaluation["model_name"], rows, maximum_conditions | |
| ) | |
| reference_records, reference_pathways = [], [] | |
| for reference in references: | |
| for name, destination in ( | |
| ("condition_metrics", reference_records), | |
| ("pathway_metrics", reference_pathways), | |
| ): | |
| path = Path(reference[f"{name}_path"]) | |
| assert (path.stat().st_size, file_sha256(path)) == ( | |
| reference[f"{name}_bytes"], | |
| reference[f"{name}_sha256"], | |
| ) | |
| lines = path.read_text().splitlines() | |
| assert len(lines) == reference[f"{name}_records"] | |
| destination.extend( | |
| value for value in map(json.loads, lines) if value["model"] in reference["models"] | |
| ) | |
| resamples = base["metrics"]["bootstrap_resamples"] | |
| summary = { | |
| "condition_metrics": _condition_summary(records, resamples, base["seed"]), | |
| "pathway_metrics": _condition_summary(pathways, resamples, base["seed"]), | |
| "retrieval": retrieval, | |
| } | |
| paired = { | |
| "condition_comparisons": _paired_transcriptomic_models( | |
| reference_records + records, comparisons, resamples, base["seed"] | |
| ), | |
| "pathway_comparisons": _paired_transcriptomic_models( | |
| reference_pathways + pathways, comparisons, resamples, base["seed"] | |
| ), | |
| } | |
| provenance = { | |
| "config": deepcopy(config), | |
| "experimental_status": selection["experimental_status"], | |
| "reporting_only": True, | |
| "selection_manifest_sha256": declared, | |
| "selection_checkpoint_frozen_before_test_evaluation": True, | |
| "mixture_defined_after_component_test_evaluation": gate_report is not None, | |
| "rpe1_perturbed_outcomes_used_for_fit_or_selection": False, | |
| "sealed_test_outcomes_used_for_fit_or_selection": False, | |
| "checkpoint_provenance": checkpoint["provenance"], | |
| "control_gate": gate_report, | |
| "maximum_conditions_per_regime": maximum_conditions, | |
| "runtime_source_sha256": _runtime_source_hash(), | |
| "runtime_environment": _runtime_environment(), | |
| "git": _git_state(), | |
| } | |
| output = Path(output_directory or evaluation["output_directory"]) | |
| assert not output.exists() | |
| output.mkdir(parents=True) | |
| for filename, payload in ( | |
| ("summary.json", summary), | |
| ("paired_comparisons.json", paired), | |
| ("truth_report.json", truth_report), | |
| ("provenance.json", provenance), | |
| ): | |
| (output / filename).write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n") | |
| for filename, values in (("condition_metrics.jsonl", records), ("pathway_metrics.jsonl", pathways)): | |
| with (output / filename).open("w") as handle: | |
| for value in values: | |
| handle.write(json.dumps(value, sort_keys=True) + "\n") | |
| return summary, paired, truth_report, provenance | |
| def run_direct_gene_evaluation(config, checkpoint): | |
| """Evaluate the frozen direct gene-space baseline against the frozen five-model result.""" | |
| base, direct = config["transcriptomics"], config["direct_gene"] | |
| manifest = json.loads(Path(config["transcriptomics_manifest_path"]).read_text()) | |
| declared = manifest.pop("manifest_sha256") | |
| assert declared == config["transcriptomics_manifest_sha256"] == sha256( | |
| json.dumps(manifest, sort_keys=True, separators=(",", ":")).encode() | |
| ).hexdigest() | |
| prior_directory = Path(base["output_directory"]) | |
| prior_hash = manifest["artifacts"]["predictive_evaluation"]["condition_metrics"][ | |
| "sha256" | |
| ] | |
| assert file_sha256(prior_directory / "condition_metrics.jsonl") == prior_hash | |
| predictions = direct_gene_predictions(checkpoint, base["inputs"]["action_cache_path"]) | |
| truth, truth_report = expression_truth(base) | |
| replogle = json.loads(Path(base["inputs"]["replogle_manifest_path"]).read_text()) | |
| hvg_genes = replogle["genes"]["hvg_gene_names"] | |
| hvg_index = {gene: index for index, gene in enumerate(hvg_genes)} | |
| pathway_matrix, pathway_labels = _load_pathway_matrix( | |
| base["inputs"]["go_gmt_path"], hvg_genes | |
| ) | |
| assert len(pathway_labels) == 4328 | |
| records, pathway_records, retrieval = [], [], [] | |
| for regime, specification in base["regimes"].items(): | |
| targets = sorted(target for key, target in truth if key == regime) | |
| predicted_signatures = np.stack([predictions[target] for target in targets]) | |
| true_signatures = np.stack([truth[regime, target]["effect"] for target in targets]) | |
| normalized_prediction = predicted_signatures / np.linalg.norm( | |
| predicted_signatures, axis=1, keepdims=True | |
| ).clip(1e-12) | |
| normalized_truth = true_signatures / np.linalg.norm( | |
| true_signatures, axis=1, keepdims=True | |
| ).clip(1e-12) | |
| similarity = normalized_prediction @ normalized_truth.T | |
| ranks = np.asarray( | |
| [ | |
| 1 | |
| + np.count_nonzero(row > row[index]) | |
| + 0.5 * (np.count_nonzero(row == row[index]) - 1) | |
| for index, row in enumerate(similarity) | |
| ] | |
| ) | |
| retrieval.append( | |
| { | |
| "regime": regime, | |
| "model": "direct_gene_esm", | |
| "targets": len(targets), | |
| "top_1": float(np.mean(ranks <= 1)), | |
| "top_5": float(np.mean(ranks <= 5)), | |
| "mean_reciprocal_rank": float(np.mean(1 / ranks)), | |
| "median_rank": float(np.median(ranks)), | |
| } | |
| ) | |
| for index, target in enumerate(targets): | |
| observed = truth[regime, target] | |
| metadata = { | |
| "regime": regime, | |
| "context": specification["context"], | |
| "outcome_role": specification["outcome_role"], | |
| "target": target, | |
| "repeat": 0, | |
| "model": "direct_gene_esm", | |
| "truth_batches": observed["batches"], | |
| "truth_cells": observed["cells"], | |
| "true_deg_count": int(observed["deg"].sum()), | |
| "target_in_hvg": target in hvg_index, | |
| } | |
| records.append( | |
| { | |
| **metadata, | |
| **gene_effect_metrics( | |
| predicted_signatures[index], | |
| observed["effect"], | |
| hvg_index.get(target), | |
| observed["deg"], | |
| base["metrics"]["retrospective_top_genes"], | |
| ), | |
| } | |
| ) | |
| pathway_records.append( | |
| { | |
| **metadata, | |
| **pathway_agreement( | |
| predicted_signatures[index], | |
| observed["effect"], | |
| pathway_matrix, | |
| base["metrics"]["pathway_top_k"], | |
| ), | |
| } | |
| ) | |
| prior_records = [ | |
| json.loads(line) | |
| for line in (prior_directory / "condition_metrics.jsonl").read_text().splitlines() | |
| if '"model": "causalcelljepa"' in line | |
| ] | |
| prior_pathways = [ | |
| json.loads(line) | |
| for line in (prior_directory / "pathway_metrics.jsonl").read_text().splitlines() | |
| if '"model": "causalcelljepa"' in line | |
| ] | |
| resamples = base["metrics"]["bootstrap_resamples"] | |
| summary = { | |
| "condition_metrics": _condition_summary(records, resamples, base["seed"]), | |
| "pathway_metrics": _condition_summary(pathway_records, resamples, base["seed"]), | |
| "retrieval": retrieval, | |
| } | |
| paired = { | |
| "condition_comparisons": _paired_comparisons( | |
| prior_records + records, | |
| resamples, | |
| base["seed"], | |
| baselines=("direct_gene_esm",), | |
| ), | |
| "pathway_comparisons": _paired_comparisons( | |
| prior_pathways + pathway_records, | |
| resamples, | |
| base["seed"], | |
| baselines=("direct_gene_esm",), | |
| ), | |
| } | |
| provenance = { | |
| "config": deepcopy(config), | |
| "checkpoint_provenance": checkpoint["provenance"], | |
| "prior_transcriptomics_manifest_sha256": declared, | |
| "runtime_source_sha256": _runtime_source_hash(), | |
| "runtime_environment": _runtime_environment(), | |
| "git": _git_state(), | |
| } | |
| output = Path(direct["output_directory"]) | |
| assert not output.exists() | |
| output.mkdir(parents=True) | |
| for filename, payload in ( | |
| ("summary.json", summary), | |
| ("paired_comparisons.json", paired), | |
| ("truth_report.json", truth_report), | |
| ("fit_report.json", checkpoint["report"]), | |
| ("provenance.json", provenance), | |
| ): | |
| (output / filename).write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n") | |
| for filename, values in ( | |
| ("condition_metrics.jsonl", records), | |
| ("pathway_metrics.jsonl", pathway_records), | |
| ): | |
| with (output / filename).open("w") as handle: | |
| for value in values: | |
| handle.write(json.dumps(value, sort_keys=True) + "\n") | |
| return summary, paired, truth_report, provenance | |