# molecular_demo2.py # FINAL VERSION with robust display logic import streamlit as st import numpy as np import time from collections import defaultdict import json import io try: from rdkit import Chem from rdkit.Chem import Draw from rdkit.Chem import rdMolDraw2D RDKIT_AVAILABLE = True from PIL import Image except ImportError: RDKIT_AVAILABLE = False from molecular_constraint_solver import MolecularConstraintEncoder, parse_constraints class SparsePhaseCalciumField3SAT: def __init__(self, N_vars, clauses, seed=42, K=0.87, eta=0.045, prune_rate=0.005, noise=0.03, DT=0.003, drive=14.28, solver_steps=300): np.random.seed(seed) self.N, self.M, self.clauses = N_vars, len(clauses), clauses self.K, self.eta, self.prune_rate, self.noise, self.DT = K, eta, prune_rate, noise, DT self.drive, self.max_steps = drive, solver_steps self.phases, self.clause_weights = np.random.uniform(0, 2 * np.pi, N_vars), np.ones(self.M) self.W = defaultdict(dict) for _ in range(min(self.N * 2, 20000)): i, j = np.random.randint(0, self.N, 2) if i != j: self.W[i][j] = np.random.uniform(0.01, 0.05) self.history = {'satisfaction': []} def get_assignment(self): return np.cos(self.phases) > 0 def evaluate_clause(self, clause, assignment): for lit in clause: idx = abs(lit) - 1 if idx >= self.N: continue val = assignment[idx] if (lit > 0 and val) or (lit < 0 and not val): return True return False def compute_satisfaction(self, assignment=None): if assignment is None: assignment = self.get_assignment() if self.M == 0: return 1.0 return sum(1 for c in self.clauses if self.evaluate_clause(c, assignment)) / self.M def step(self): dphi, assignment = np.zeros(self.N), self.get_assignment() for idx, clause in enumerate(self.clauses): if not self.evaluate_clause(clause, assignment): self.clause_weights[idx] = min(self.clause_weights[idx] + 0.02, 5.0) lit = clause[np.random.randint(len(clause))] idx_var = abs(lit) - 1 if idx_var >= self.N: continue target = 0.0 if lit > 0 else np.pi dphi[idx_var] += self.drive * self.clause_weights[idx] * np.sin(target - self.phases[idx_var]) for i in self.W: for j, w in self.W[i].items(): p_diff = self.phases[j] - self.phases[i] dphi[i] += self.K * w * np.sin(p_diff) dphi[j] -= self.K * w * np.sin(p_diff) dphi += self.noise * np.random.randn(self.N) self.phases = np.mod(self.phases + self.DT * dphi, 2 * np.pi) if np.random.rand() < 0.1: for _ in range(20): i, j = np.random.randint(0, self.N, 2) if i != j and np.cos(self.phases[i] - self.phases[j]) > 0.98: self.W[i][j] = min(1.0, self.W[i].get(j, 0.0) + self.eta) if self.W: s = np.random.choice(list(self.W.keys())) if self.W[s]: t = np.random.choice(list(self.W[s].keys())) self.W[s][t] *= (1 - self.prune_rate) if self.W[s][t] < 0.01: del self.W[s][t] self.history['satisfaction'].append(self.compute_satisfaction()) def draw_molecule_from_structure(s_dict): """Draw raw graph with atom labels, falling back to text if needed.""" atoms = s_dict.get('atoms', []) bonds = s_dict.get('bonds', []) # Define the text fallback first def get_text_fallback(): if not atoms: return "No atoms in structure." adj = {a['id']: [] for a in atoms} for b in bonds: # The source of the KeyError is here, so we add a check if b['from'] in adj and b['to'] in adj: adj[b['from']].append(b['to']) adj[b['to']].append(b['from']) lines = [f"{a['id']:02d} {a['element']:>2} -> {', '.join(map(str, adj.get(a['id'], [])))}" for a in atoms] return "\n".join(lines) if not RDKIT_AVAILABLE: return get_text_fallback() try: mol = Chem.RWMol() atom_map = {info['id']: mol.AddAtom(Chem.Atom(info['element'])) for info in atoms} for bond in bonds: a, b = bond['from'], bond['to'] if a in atom_map and b in atom_map: mol.AddBond(atom_map[a], atom_map[b], Chem.BondType.SINGLE) if mol.GetNumAtoms() == 0: return None rdkit_idx_to_original_id = {v: k for k, v in atom_map.items()} drawer = rdMolDraw2D.MolDraw2DCairo(300, 300) opts = drawer.drawOptions() for idx in range(mol.GetNumAtoms()): original_id = rdkit_idx_to_original_id.get(idx, '?') symbol = mol.GetAtomWithIdx(idx).GetSymbol() opts.atomLabels[idx] = f"{original_id}:{symbol}" rdMolDraw2D.PrepareAndDrawMolecule(drawer, mol) drawer.FinishDrawing() png = drawer.GetDrawingText() return Image.open(io.BytesIO(png)) except Exception: return get_text_fallback() # Return text on any RDKit error st.set_page_config(page_title="Molecular Constraint Solver", layout="wide", page_icon="🧬") st.markdown("""""", unsafe_allow_html=True) st.markdown('