""" SQLite cache for simulation results. Keyed on SHA256 hash of the full parameter vector. Stores all scalar outputs. Does NOT cache history arrays (too large). """ import sqlite3 import hashlib import json import os import time _DEFAULT_DB = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'sweep_cache.db') class SweepCache: """Persistent cache for simulation scalar outputs.""" def __init__(self, db_path=_DEFAULT_DB): self.db_path = db_path 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 ( param_hash TEXT PRIMARY KEY, params_json TEXT NOT NULL, scalars_json TEXT NOT NULL, wall_time_s REAL, created_at REAL ) ''') self._conn.commit() self._hits = 0 self._misses = 0 @staticmethod def _make_key(Pexit_barg, speed_f, Ptank_barg, Psat_barg, ICVparam, DCVparam, pump_geom, proc_param, fluid='h2', flash_eff=0.0): """Hash the full parameter vector to a cache key.""" def _round(v): if isinstance(v, (int, float)): return round(float(v), 8) if isinstance(v, (list, tuple)): return [_round(x) for x in v] return v vec = { 'Pexit_barg': _round(Pexit_barg), 'speed_f': _round(speed_f), 'Ptank_barg': _round(Ptank_barg), 'Psat_barg': _round(Psat_barg), 'ICVparam': _round(list(ICVparam)), 'DCVparam': _round(list(DCVparam)), 'pump_geom': _round(list(pump_geom)), 'proc_param': _round(list(proc_param)), 'fluid': fluid, 'flash_eff': _round(flash_eff), } raw = json.dumps(vec, sort_keys=True, separators=(',', ':')) return hashlib.sha256(raw.encode()).hexdigest(), raw def get(self, **kwargs): """Look up cached scalars. Returns (scalars_dict, wall_time) or None.""" key, _ = self._make_key(**kwargs) row = self._conn.execute( 'SELECT scalars_json, wall_time_s FROM cache WHERE param_hash = ?', (key,) ).fetchone() if row: self._hits += 1 return json.loads(row[0]), row[1] self._misses += 1 return None def put(self, scalars, wall_time_s, **kwargs): """Store simulation scalars in cache.""" key, params_json = self._make_key(**kwargs) clean = {} for k, v in scalars.items(): try: clean[k] = float(v) except (TypeError, ValueError): clean[k] = v self._conn.execute( 'INSERT OR REPLACE INTO cache (param_hash, params_json, scalars_json, wall_time_s, created_at) ' 'VALUES (?, ?, ?, ?, ?)', (key, params_json, json.dumps(clean), wall_time_s, time.time()) ) self._conn.commit() @property def stats(self): total = self._conn.execute('SELECT COUNT(*) FROM cache').fetchone()[0] hit_rate = self._hits / (self._hits + self._misses) if (self._hits + self._misses) > 0 else 0 return { 'total_cached': total, 'session_hits': self._hits, 'session_misses': self._misses, 'hit_rate': hit_rate, } def close(self): self._conn.close()