Spaces:
Sleeping
Sleeping
| """SQLite-backed cache for simulation sweep results, keyed on SHA256 of params.""" | |
| from __future__ import annotations | |
| import hashlib | |
| import json | |
| import logging | |
| import sqlite3 | |
| from typing import Any, Dict, Optional, Tuple | |
| logger = logging.getLogger(__name__) | |
| SCHEMA_VERSION = 3 | |
| class SweepCache: | |
| """Stores simulation scalar results in a local SQLite database. | |
| Keys are derived from a SHA256 hash of the canonicalised parameter dict, | |
| so identical runs are served from cache instead of re-simulated. | |
| Schema is versioned via ``PRAGMA user_version``. On open, if the stored | |
| version differs from ``SCHEMA_VERSION``, all rows are dropped and the | |
| version is written. | |
| """ | |
| def __init__(self, db_path: str = "sweep_cache.db") -> None: | |
| # check_same_thread=False: Gradio serves requests from a worker pool, | |
| # so the same SweepCache instance is used across threads. Access is | |
| # serialized at the Python level by SQLite's own locking, so cross- | |
| # thread use is safe in practice. | |
| self._conn = sqlite3.connect(db_path, check_same_thread=False) | |
| self._conn.execute("PRAGMA journal_mode=WAL") | |
| self._conn.execute( | |
| "CREATE TABLE IF NOT EXISTS cache (" | |
| " key TEXT PRIMARY KEY," | |
| " scalars TEXT," | |
| " wall_time REAL," | |
| " ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP" | |
| ")" | |
| ) | |
| self._migrate_if_needed() | |
| self._conn.commit() | |
| self._session_hits = 0 | |
| self._session_misses = 0 | |
| def _migrate_if_needed(self) -> None: | |
| """If stored schema version != SCHEMA_VERSION, drop all rows.""" | |
| cur = self._conn.execute("PRAGMA user_version") | |
| current = cur.fetchone()[0] | |
| if current != SCHEMA_VERSION: | |
| logger.warning( | |
| "sweep_cache schema mismatch (stored=%s, expected=%s); " | |
| "wiping cache", | |
| current, SCHEMA_VERSION, | |
| ) | |
| self._conn.execute("DELETE FROM cache") | |
| self._conn.execute(f"PRAGMA user_version = {SCHEMA_VERSION}") | |
| # ------------------------------------------------------------------ | |
| # Internal helpers | |
| # ------------------------------------------------------------------ | |
| def _make_key(params: Dict[str, Any]) -> str: | |
| """Canonical SHA256 key: round floats to 8 dp, sort keys, hash.""" | |
| cleaned: Dict[str, Any] = {} | |
| for k in sorted(params.keys()): | |
| v = params[k] | |
| cleaned[k] = round(v, 8) if isinstance(v, float) else v | |
| blob = json.dumps(cleaned, sort_keys=True).encode() | |
| return hashlib.sha256(blob).hexdigest()[:16] | |
| # ------------------------------------------------------------------ | |
| # Public API | |
| # ------------------------------------------------------------------ | |
| def get(self, params: Dict[str, Any]) -> Optional[Tuple[Dict[str, Any], float]]: | |
| """Return ``(scalars_dict, wall_time)`` or ``None`` on cache miss.""" | |
| key = self._make_key(params) | |
| row = self._conn.execute( | |
| "SELECT scalars, wall_time FROM cache WHERE key = ?", (key,) | |
| ).fetchone() | |
| if row is None: | |
| self._session_misses += 1 | |
| return None | |
| self._session_hits += 1 | |
| return json.loads(row[0]), row[1] | |
| def put( | |
| self, | |
| params: Dict[str, Any], | |
| scalars: Dict[str, Any], | |
| wall_time: float, | |
| ) -> None: | |
| """Insert (or replace) a cache entry.""" | |
| key = self._make_key(params) | |
| self._conn.execute( | |
| "INSERT OR REPLACE INTO cache (key, scalars, wall_time) VALUES (?, ?, ?)", | |
| (key, json.dumps(scalars), wall_time), | |
| ) | |
| self._conn.commit() | |
| def clear(self) -> int: | |
| """Delete all cached entries. Returns the number of rows deleted.""" | |
| count = self._conn.execute("SELECT COUNT(*) FROM cache").fetchone()[0] | |
| self._conn.execute("DELETE FROM cache") | |
| self._conn.commit() | |
| self._session_hits = 0 | |
| self._session_misses = 0 | |
| return count | |
| def stats(self) -> Dict[str, Any]: | |
| """Return cache statistics for the current session.""" | |
| total = self._conn.execute("SELECT COUNT(*) FROM cache").fetchone()[0] | |
| total_lookups = self._session_hits + self._session_misses | |
| return { | |
| "total_cached": total, | |
| "session_hits": self._session_hits, | |
| "session_misses": self._session_misses, | |
| "hit_rate": ( | |
| self._session_hits / total_lookups if total_lookups > 0 else 0.0 | |
| ), | |
| } | |