Spaces:
Sleeping
Sleeping
| """src/multiversal/multiversal_protein_computer.py | |
| Multiversal computing for protein folding: run parallel "universes" each with | |
| different random initialization, optimization strategy, or parameter choices. | |
| Note on parallelism: | |
| - The folding/energy inner loops are Python bytecode. | |
| - Threading does not provide true CPU scaling due to the GIL. | |
| This implementation uses ProcessPoolExecutor by default to achieve real CPU | |
| parallelism on multi-core systems. | |
| """ | |
| from __future__ import annotations | |
| import concurrent.futures | |
| import json | |
| import logging | |
| import math | |
| import time | |
| from dataclasses import dataclass, field | |
| from pathlib import Path | |
| from typing import Any, Dict, List, Optional | |
| from .protein_folding_engine import FoldingParameters, ProteinFoldingEngine, ProteinStructure | |
| logger = logging.getLogger(__name__) | |
| class UniverseConfig: | |
| """Configuration for a single universe's protein folding run.""" | |
| universe_id: str | |
| seed: int | |
| steps: int = 5000 | |
| t_start: float = 2.0 | |
| t_end: float = 0.2 | |
| max_torsion_step: float = math.radians(25.0) | |
| max_cartesian_jitter: float = 0.75 | |
| params_override: Optional[Dict[str, Any]] = None | |
| initial_structure: Optional[ProteinStructure] = None | |
| consensus_coords: Optional[List[Tuple[float, float, float]]] = None | |
| class UniverseResult: | |
| """Result from a single universe.""" | |
| universe_id: str | |
| seed: int | |
| best_energy: float | |
| final_energy: float | |
| acceptance_rate: float | |
| runtime_s: float | |
| best_structure: ProteinStructure | |
| trajectory: List[Dict[str, float]] | |
| metadata: Dict[str, Any] = field(default_factory=dict) | |
| class MultiversalResult: | |
| """Aggregate result from all universes.""" | |
| sequence: str | |
| n_universes: int | |
| universes: List[UniverseResult] | |
| best_overall: UniverseResult | |
| energy_mean: float | |
| energy_std: float | |
| total_runtime_s: float | |
| timestamp: float | |
| def to_dict(self) -> Dict[str, Any]: | |
| return { | |
| "sequence": self.sequence, | |
| "n_universes": self.n_universes, | |
| "best_overall_universe_id": self.best_overall.universe_id, | |
| "best_overall_energy": self.best_overall.best_energy, | |
| "energy_mean": self.energy_mean, | |
| "energy_std": self.energy_std, | |
| "total_runtime_s": self.total_runtime_s, | |
| "timestamp": self.timestamp, | |
| "universes": [ | |
| { | |
| "universe_id": u.universe_id, | |
| "seed": u.seed, | |
| "best_energy": u.best_energy, | |
| "final_energy": u.final_energy, | |
| "acceptance_rate": u.acceptance_rate, | |
| "runtime_s": u.runtime_s, | |
| "metadata": u.metadata, | |
| } | |
| for u in self.universes | |
| ], | |
| } | |
| def _fold_universe_worker(sequence: str, config: UniverseConfig, artifacts_dir: str) -> UniverseResult: | |
| """Worker function: fold in a single universe. | |
| Kept at module scope so it can be pickled for multiprocessing. | |
| """ | |
| start = time.time() | |
| params = FoldingParameters() | |
| if config.params_override: | |
| for k, v in config.params_override.items(): | |
| setattr(params, k, v) | |
| engine = ProteinFoldingEngine(artifacts_dir=artifacts_dir, params=params) | |
| if config.initial_structure: | |
| initial = config.initial_structure | |
| else: | |
| initial = engine.initialize_extended_chain(sequence, seed=config.seed) | |
| if config.consensus_coords: | |
| engine.params.consensus_coords = config.consensus_coords | |
| # If consensus is set, we use a small consensus_k by default if not overridden | |
| if engine.params.consensus_k == 0: | |
| engine.params.consensus_k = 0.05 | |
| result = engine.metropolis_anneal( | |
| initial, | |
| steps=config.steps, | |
| t_start=config.t_start, | |
| t_end=config.t_end, | |
| max_torsion_step=config.max_torsion_step, | |
| max_cartesian_jitter=config.max_cartesian_jitter, | |
| seed=config.seed, | |
| ) | |
| runtime = time.time() - start | |
| return UniverseResult( | |
| universe_id=config.universe_id, | |
| seed=config.seed, | |
| best_energy=result["best_energy"], | |
| final_energy=result["final_energy"], | |
| acceptance_rate=result["acceptance_rate"], | |
| runtime_s=runtime, | |
| best_structure=result["best_structure"], | |
| trajectory=result["trajectory"], | |
| metadata={ | |
| "steps": config.steps, | |
| "t_start": config.t_start, | |
| "t_end": config.t_end, | |
| }, | |
| ) | |
| class MultiversalProteinComputer: | |
| """Compute protein folding across parallel universes.""" | |
| def __init__( | |
| self, | |
| artifacts_dir: str | Path = "./protein_folding_artifacts", | |
| log_level: int = logging.INFO, | |
| ): | |
| self.artifacts_dir = Path(artifacts_dir) | |
| self.artifacts_dir.mkdir(parents=True, exist_ok=True) | |
| self._setup_logging(log_level) | |
| def _setup_logging(self, log_level: int): | |
| logging.basicConfig( | |
| level=log_level, | |
| format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", | |
| datefmt="%Y-%m-%d %H:%M:%S", | |
| ) | |
| def fold_multiversal( | |
| self, | |
| sequence: str, | |
| n_universes: int = 4, | |
| steps_per_universe: int = 5000, | |
| t_start: float = 2.0, | |
| t_end: float = 0.2, | |
| base_seed: int = 42, | |
| max_workers: Optional[int] = None, | |
| save_artifacts: bool = True, | |
| n_cycles: int = 5, | |
| ) -> MultiversalResult: | |
| """Fold a sequence across multiple parallel universes with inter-universal consensus sharing.""" | |
| logger.info("==== MULTIVERSAL PROTEIN FOLDING START (V2 - CONSENSUS) ====") | |
| logger.info("Sequence: %s (length=%d)", sequence, len(sequence)) | |
| logger.info("Universes: %d | Total Steps/universe: %d | Cycles: %d", n_universes, steps_per_universe, n_cycles) | |
| logger.info("Temperature: %.2f -> %.2f", t_start, t_end) | |
| start_time = time.time() | |
| steps_per_cycle = max(100, steps_per_universe // n_cycles) | |
| current_structures: List[Optional[ProteinStructure]] = [None] * n_universes | |
| consensus_coords: Optional[List[Tuple[float, float, float]]] = None | |
| all_universe_results: List[UniverseResult] = [] | |
| for cycle in range(n_cycles): | |
| logger.info("--- Cycle %d/%d ---", cycle + 1, n_cycles) | |
| # Linear temperature schedule across cycles | |
| cycle_t_start = t_start + (t_end - t_start) * (cycle / n_cycles) | |
| cycle_t_end = t_start + (t_end - t_start) * ((cycle + 1) / n_cycles) | |
| configs: List[UniverseConfig] = [] | |
| for i in range(n_universes): | |
| configs.append( | |
| UniverseConfig( | |
| universe_id=f"universe_{i:03d}_cycle_{cycle}", | |
| seed=base_seed + i + (cycle * n_universes), | |
| steps=steps_per_cycle, | |
| t_start=cycle_t_start, | |
| t_end=cycle_t_end, | |
| initial_structure=current_structures[i], | |
| consensus_coords=consensus_coords, | |
| params_override={"consensus_k": 0.1 * (cycle / n_cycles)} # Increase consensus bias over time | |
| ) | |
| ) | |
| cycle_results: List[UniverseResult] = [] | |
| with concurrent.futures.ProcessPoolExecutor(max_workers=max_workers) as executor: | |
| future_map = { | |
| executor.submit(_fold_universe_worker, sequence, cfg, str(self.artifacts_dir)): i | |
| for i, cfg in enumerate(configs) | |
| } | |
| for future in concurrent.futures.as_completed(future_map): | |
| idx = future_map[future] | |
| try: | |
| result = future.result() | |
| cycle_results.append(result) | |
| current_structures[idx] = result.best_structure | |
| except Exception as exc: | |
| logger.error("Universe index %d failed in cycle %d: %s", idx, cycle, exc) | |
| if not cycle_results: | |
| logger.error("All universes failed in cycle %d", cycle) | |
| continue | |
| # Update consensus from the best found so far in this cycle | |
| best_in_cycle = min(cycle_results, key=lambda r: r.best_energy) | |
| consensus_coords = best_in_cycle.best_structure.coords | |
| # Store results from final cycle or if it's the best overall | |
| if cycle == n_cycles - 1: | |
| all_universe_results = cycle_results | |
| else: | |
| # Optionally keep track of improvements | |
| pass | |
| total_time = time.time() - start_time | |
| if not all_universe_results: | |
| # Fallback if final cycle failed | |
| raise RuntimeError("Folding failed in all cycles") | |
| best = min(all_universe_results, key=lambda r: r.best_energy) | |
| energies = [r.best_energy for r in all_universe_results] | |
| mean_e = sum(energies) / len(energies) | |
| std_e = (sum((e - mean_e) ** 2 for e in energies) / len(energies)) ** 0.5 | |
| multiversal_result = MultiversalResult( | |
| sequence=sequence, | |
| n_universes=n_universes, | |
| universes=all_universe_results, | |
| best_overall=best, | |
| energy_mean=mean_e, | |
| energy_std=std_e, | |
| total_runtime_s=total_time, | |
| timestamp=time.time(), | |
| ) | |
| logger.info("==== MULTIVERSAL PROTEIN FOLDING COMPLETE ====") | |
| logger.info("Best overall energy: %.6f", best.best_energy) | |
| logger.info("Total runtime: %.3f s", total_time) | |
| if save_artifacts: | |
| self._save_multiversal_artifact(multiversal_result) | |
| return multiversal_result | |
| def _save_multiversal_artifact(self, result: MultiversalResult) -> str: | |
| """Save the multiversal result (all universes) as a JSON artifact.""" | |
| timestamp = int(time.time()) | |
| filename = f"multiversal_fold_{timestamp}.json" | |
| path = self.artifacts_dir / filename | |
| payload = result.to_dict() | |
| best_st = result.best_overall.best_structure | |
| payload["best_structure"] = best_st.to_dict() | |
| with open(path, "w", encoding="utf-8") as f: | |
| json.dump(payload, f, indent=2) | |
| logger.info("Multiversal artifact saved: %s", path) | |
| return str(path) | |