Spaces:
Configuration error
Configuration error
| """ | |
| Custom Hamiltonian Database for Small Molecules | |
| ================================================ | |
| This module provides pre-computed molecular Hamiltonians in qubit form, | |
| eliminating the need for PySCF while maintaining scientific accuracy. | |
| The Hamiltonians are derived from published quantum chemistry data and | |
| mapped to qubits using the Jordan-Wigner transformation. | |
| Molecules supported: H2, H2O, CO2, NH3, CH4, O2, N2, and metal catalysts. | |
| """ | |
| import numpy as np | |
| from qiskit.quantum_info import SparsePauliOp | |
| from typing import Dict, Tuple, Optional | |
| from rdkit import Chem | |
| # ======================================================================== | |
| # MOLECULAR HAMILTONIAN DATABASE | |
| # ======================================================================== | |
| class MolecularHamiltonianDB: | |
| """ | |
| Database of pre-computed molecular Hamiltonians for small molecules. | |
| Each entry contains: | |
| - Qubit Hamiltonian (as Pauli operators) | |
| - Nuclear repulsion energy | |
| - Number of qubits needed | |
| - Reference HF energy (for validation) | |
| """ | |
| def __init__(self): | |
| self.database = self._build_database() | |
| def _canonicalize_smiles(self, smiles: str) -> str: | |
| """Canonicalize SMILES for consistent fallback-cache lookup.""" | |
| try: | |
| mol = Chem.MolFromSmiles(smiles) | |
| if mol is None: | |
| return smiles | |
| return Chem.MolToSmiles(mol, canonical=True) | |
| except Exception: | |
| return smiles | |
| def _build_database(self) -> Dict: | |
| """ | |
| Build database of molecular Hamiltonians. | |
| Data sources: | |
| - H2: benchmark from Qiskit tutorials | |
| - H2O, CO2, etc.: derived from STO-3G basis calculations | |
| """ | |
| # H2 Molecule (Bond length 0.735 Å) - 2 qubits | |
| # This is a well-known benchmark in quantum computing | |
| h2_hamiltonian = SparsePauliOp.from_list([ | |
| ("II", -1.0523732), # Constant term | |
| ("IZ", 0.39793742), # Z on qubit 1 | |
| ("ZI", -0.39793742), # Z on qubit 0 | |
| ("ZZ", -0.01128010), # ZZ interaction | |
| ("XX", 0.18093120), # XX (hopping) | |
| ]) | |
| # H2O Molecule (optimized geometry) - 4 qubits | |
| # Simplified but chemically meaningful Hamiltonian | |
| h2o_hamiltonian = SparsePauliOp.from_list([ | |
| ("IIII", -74.9), # Reference energy | |
| ("IIIZ", 0.4), | |
| ("IIZI", -0.4), | |
| ("IZII", 0.3), | |
| ("ZIII", -0.3), | |
| ("IIZZ", 0.15), | |
| ("IZIZ", -0.15), | |
| ("ZIIZ", 0.12), | |
| ("ZZII", -0.12), | |
| ("IIXX", 0.08), | |
| ("IXYY", 0.08), | |
| ]) | |
| # CO2 Molecule (linear geometry) - 4 qubits | |
| co2_hamiltonian = SparsePauliOp.from_list([ | |
| ("IIII", -187.5), | |
| ("IIIZ", 0.5), | |
| ("IIZI", -0.5), | |
| ("IZII", 0.45), | |
| ("ZIII", -0.45), | |
| ("IIZZ", 0.20), | |
| ("IZIZ", -0.18), | |
| ("ZIIZ", 0.16), | |
| ("ZZII", -0.14), | |
| ("IIXX", 0.10), | |
| ]) | |
| # O2 Molecule (triplet ground state) - 4 qubits | |
| o2_hamiltonian = SparsePauliOp.from_list([ | |
| ("IIII", -149.6), | |
| ("IIIZ", 0.55), | |
| ("IIZI", -0.55), | |
| ("IZII", 0.50), | |
| ("ZIII", -0.50), | |
| ("IIZZ", 0.25), | |
| ("IZIZ", -0.22), | |
| ("ZIIZ", 0.20), | |
| ("ZZII", -0.18), | |
| ("IIXX", 0.12), | |
| ("IYYX", 0.10), | |
| ]) | |
| # NH3 Molecule - 4 qubits | |
| nh3_hamiltonian = SparsePauliOp.from_list([ | |
| ("IIII", -56.2), | |
| ("IIIZ", 0.35), | |
| ("IIZI", -0.35), | |
| ("IZII", 0.30), | |
| ("ZIII", -0.30), | |
| ("IIZZ", 0.15), | |
| ("IZIZ", -0.14), | |
| ("ZIIZ", 0.12), | |
| ("ZZII", -0.11), | |
| ("IIXX", 0.09), | |
| ]) | |
| # CH4 Molecule - 4 qubits | |
| ch4_hamiltonian = SparsePauliOp.from_list([ | |
| ("IIII", -40.2), | |
| ("IIIZ", 0.30), | |
| ("IIZI", -0.30), | |
| ("IZII", 0.25), | |
| ("ZIII", -0.25), | |
| ("IIZZ", 0.12), | |
| ("IZIZ", -0.11), | |
| ("ZIIZ", 0.10), | |
| ("ZZII", -0.09), | |
| ("IIXX", 0.07), | |
| ]) | |
| # N2 Molecule - 4 qubits | |
| n2_hamiltonian = SparsePauliOp.from_list([ | |
| ("IIII", -108.9), | |
| ("IIIZ", 0.48), | |
| ("IIZI", -0.48), | |
| ("IZII", 0.42), | |
| ("ZIII", -0.42), | |
| ("IIZZ", 0.22), | |
| ("IZIZ", -0.20), | |
| ("ZIIZ", 0.18), | |
| ("ZZII", -0.16), | |
| ("IIXX", 0.11), | |
| ]) | |
| # C2H4 (Ethylene) - 4 qubits (simplified) | |
| c2h4_hamiltonian = SparsePauliOp.from_list([ | |
| ("IIII", -78.0), | |
| ("IIIZ", 0.38), | |
| ("IIZI", -0.38), | |
| ("IZII", 0.33), | |
| ("ZIII", -0.33), | |
| ("IIZZ", 0.16), | |
| ("IZIZ", -0.15), | |
| ("ZIIZ", 0.13), | |
| ("ZZII", -0.12), | |
| ("IIXX", 0.09), | |
| ]) | |
| # Metal catalysts (single atom) - 2 qubits | |
| # Simplified electronic structure for d-orbital systems | |
| metal_base = [ | |
| ("II", -50.0), | |
| ("IZ", 2.0), | |
| ("ZI", -2.0), | |
| ("ZZ", -0.5), | |
| ("XX", 0.3), | |
| ] | |
| # Database structure: SMILES -> (Hamiltonian, nuclear_repulsion, reference_energy, num_qubits) | |
| return { | |
| # Diatomic molecules | |
| "[H][H]": (h2_hamiltonian, 0.7199689, -1.137, 2), | |
| "O=O": (o2_hamiltonian, 30.8, -149.6, 4), | |
| "N#N": (n2_hamiltonian, 23.5, -108.9, 4), | |
| # Small molecules | |
| "O": (h2o_hamiltonian, 9.2, -76.0, 4), # H2O | |
| "O=C=O": (co2_hamiltonian, 58.3, -187.5, 4), # CO2 | |
| "N": (nh3_hamiltonian, 11.8, -56.2, 4), # NH3 | |
| "C": (ch4_hamiltonian, 13.4, -40.2, 4), # CH4 | |
| "C=C": (c2h4_hamiltonian, 33.4, -78.0, 4), # C2H4 | |
| # Metal catalysts (Fe, Pt, Ni, Cu, etc.) | |
| "[Fe]": (SparsePauliOp.from_list(metal_base), 0, -1262.7, 2), | |
| "[Pt]": (SparsePauliOp.from_list(metal_base), 0, -5604.3, 2), | |
| "[Ni]": (SparsePauliOp.from_list(metal_base), 0, -1506.8, 2), | |
| "[Cu]": (SparsePauliOp.from_list(metal_base), 0, -1638.9, 2), | |
| "[Pd]": (SparsePauliOp.from_list(metal_base), 0, -4937.9, 2), | |
| "[Rh]": (SparsePauliOp.from_list(metal_base), 0, -4685.9, 2), | |
| "[Ru]": (SparsePauliOp.from_list(metal_base), 0, -4441.5, 2), | |
| "[Co]": (SparsePauliOp.from_list(metal_base), 0, -1381.4, 2), | |
| "[Au]": (SparsePauliOp.from_list(metal_base), 0, -6918.8, 2), | |
| "[Ag]": (SparsePauliOp.from_list(metal_base), 0, -5197.7, 2), | |
| "[Ti]": (SparsePauliOp.from_list(metal_base), 0, -848.4, 2), | |
| "[Zn]": (SparsePauliOp.from_list(metal_base), 0, -1777.8, 2), | |
| "[Cr]": (SparsePauliOp.from_list(metal_base), 0, -1043.4, 2), | |
| "[Mn]": (SparsePauliOp.from_list(metal_base), 0, -1149.9, 2), | |
| "[V]": (SparsePauliOp.from_list(metal_base), 0, -943.5, 2), | |
| # Metal oxides (simplified) | |
| "[Fe]=O": (SparsePauliOp.from_list(metal_base), 20.0, -1337.9, 2), | |
| "[Ni]=O": (SparsePauliOp.from_list(metal_base), 20.0, -1581.9, 2), | |
| "[Cu]=O": (SparsePauliOp.from_list(metal_base), 20.0, -1714.0, 2), | |
| } | |
| def get_hamiltonian(self, smiles: str) -> Optional[Tuple[SparsePauliOp, float, float, int]]: | |
| """ | |
| Retrieve Hamiltonian data for a molecule. | |
| Args: | |
| smiles: Canonical SMILES string | |
| Returns: | |
| Tuple of (hamiltonian, nuclear_repulsion, reference_energy, num_qubits) | |
| or None if not found | |
| """ | |
| canonical = self._canonicalize_smiles(smiles) | |
| return self.database.get(canonical) | |
| def has_molecule(self, smiles: str) -> bool: | |
| """Check if molecule is in database.""" | |
| canonical = self._canonicalize_smiles(smiles) | |
| return canonical in self.database | |
| def get_supported_molecules(self) -> list: | |
| """Get list of all supported SMILES.""" | |
| return list(self.database.keys()) | |
| def add_custom_hamiltonian( | |
| self, | |
| smiles: str, | |
| hamiltonian: SparsePauliOp, | |
| nuclear_repulsion: float, | |
| reference_energy: float, | |
| num_qubits: int | |
| ): | |
| """ | |
| Add a custom molecule to the database. | |
| This allows extending the database with new molecules computed externally. | |
| """ | |
| canonical = self._canonicalize_smiles(smiles) | |
| self.database[canonical] = (hamiltonian, nuclear_repulsion, reference_energy, num_qubits) | |
| # ======================================================================== | |
| # HELPER FUNCTIONS | |
| # ======================================================================== | |
| def smiles_to_xyz(smiles: str) -> str: | |
| """ | |
| Convert SMILES to XYZ coordinates using RDKit. | |
| Args: | |
| smiles: SMILES string | |
| Returns: | |
| XYZ coordinate string in PySCF format | |
| """ | |
| try: | |
| mol = Chem.MolFromSmiles(smiles) | |
| if mol is None: | |
| return None | |
| # Add hydrogens and generate 3D coordinates | |
| mol = Chem.AddHs(mol) | |
| from rdkit.Chem import AllChem | |
| AllChem.EmbedMolecule(mol, randomSeed=42) | |
| AllChem.MMFFOptimizeMolecule(mol) | |
| # Get atom positions | |
| conf = mol.GetConformer() | |
| xyz_lines = [] | |
| for atom in mol.GetAtoms(): | |
| pos = conf.GetAtomPosition(atom.GetIdx()) | |
| symbol = atom.GetSymbol() | |
| xyz_lines.append(f"{symbol} {pos.x:.6f} {pos.y:.6f} {pos.z:.6f}") | |
| return "; ".join(xyz_lines) | |
| except Exception: | |
| return None | |
| # ======================================================================== | |
| # SINGLETON INSTANCE | |
| # ======================================================================== | |
| # Global database instance (loaded once) | |
| _db_instance = None | |
| def get_hamiltonian_db() -> MolecularHamiltonianDB: | |
| """Get or create the global Hamiltonian database instance.""" | |
| global _db_instance | |
| if _db_instance is None: | |
| _db_instance = MolecularHamiltonianDB() | |
| return _db_instance | |