Skip to main content
Glama
manasp21

Psi-MCP: Advanced Quantum Systems MCP Server

by manasp21
chemistry.py14.9 kB
""" Quantum Chemistry Module This module provides quantum chemistry functionality including molecular Hamiltonian generation, VQE for electronic structure, and quantum chemistry algorithms. """ import logging from typing import Dict, Any, List, Optional, Union, Tuple import asyncio import numpy as np logger = logging.getLogger(__name__) async def generate_molecular_hamiltonian( molecule: str, basis: str = "sto-3g", charge: int = 0, multiplicity: int = 1, backend: str = "pyscf" ) -> Dict[str, Any]: """ Generate molecular Hamiltonian for quantum chemistry calculations. Args: molecule: Molecule specification (geometry or name) basis: Basis set charge: Molecular charge multiplicity: Spin multiplicity backend: Chemistry backend Returns: Molecular Hamiltonian information """ logger.info(f"Generating molecular Hamiltonian for {molecule}") try: if backend == "pyscf": return await _generate_pyscf_hamiltonian(molecule, basis, charge, multiplicity) elif backend == "openfermion": return await _generate_openfermion_hamiltonian(molecule, basis, charge, multiplicity) else: raise ValueError(f"Unknown chemistry backend: {backend}") except Exception as e: logger.error(f"Error generating Hamiltonian: {e}") return {'success': False, 'error': str(e)} async def _generate_pyscf_hamiltonian( molecule: str, basis: str, charge: int, multiplicity: int ) -> Dict[str, Any]: """Generate Hamiltonian using PySCF.""" try: from pyscf import gto, scf, ao2mo import openfermion as of from openfermion.chem.molecular_data import spinorb_from_spatial # Parse molecule geometry geometry = _parse_molecule_geometry(molecule) # Build molecule mol = gto.M( atom=geometry, basis=basis, charge=charge, spin=multiplicity-1, symmetry=False ) # Run SCF calculation mf = scf.RHF(mol) if multiplicity > 1: mf = scf.ROHF(mol) energy_scf = mf.kernel() # Get molecular orbital coefficients and overlap mo_coeff = mf.mo_coeff mo_energy = mf.mo_energy # Get integrals n_orbitals = mo_coeff.shape[1] n_electrons = mol.nelectron # One-electron integrals h1e = mol.intor('int1e_kin') + mol.intor('int1e_nuc') h1e_mo = np.dot(mo_coeff.T, np.dot(h1e, mo_coeff)) # Two-electron integrals eri = mol.intor('int2e') eri_mo = ao2mo.kernel(eri, mo_coeff) # Convert to OpenFermion format h1e_so = spinorb_from_spatial(h1e_mo, n_orbitals) h2e_so = spinorb_from_spatial(eri_mo.reshape(n_orbitals, n_orbitals, n_orbitals, n_orbitals), n_orbitals) # Create molecular Hamiltonian molecular_hamiltonian = of.InteractionOperator( constant=mol.energy_nuc(), one_body_tensor=h1e_so, two_body_tensor=h2e_so ) # Convert to qubit Hamiltonian qubit_hamiltonian = of.jordan_wigner(molecular_hamiltonian) return { 'success': True, 'molecule': molecule, 'basis': basis, 'charge': charge, 'multiplicity': multiplicity, 'n_orbitals': n_orbitals, 'n_electrons': n_electrons, 'n_qubits': 2 * n_orbitals, 'scf_energy': float(energy_scf), 'nuclear_repulsion': float(mol.energy_nuc()), 'mo_energies': mo_energy.tolist(), 'hamiltonian_terms': len(qubit_hamiltonian.terms), 'backend': 'pyscf' } except Exception as e: logger.error(f"Error with PySCF: {e}") # Return simplified Hamiltonian return await _generate_simple_hamiltonian(molecule) async def _generate_simple_hamiltonian(molecule: str) -> Dict[str, Any]: """Generate simplified Hamiltonian for demo purposes.""" # Predefined simple molecules molecules = { 'h2': {'n_orbitals': 2, 'n_electrons': 2, 'energy': -1.137}, 'lih': {'n_orbitals': 6, 'n_electrons': 4, 'energy': -7.882}, 'beh2': {'n_orbitals': 8, 'n_electrons': 6, 'energy': -15.77}, 'h2o': {'n_orbitals': 13, 'n_electrons': 10, 'energy': -76.02} } mol_name = molecule.lower().replace(' ', '').replace('_', '') if mol_name in molecules: data = molecules[mol_name] return { 'success': True, 'molecule': molecule, 'basis': 'sto-3g', 'n_orbitals': data['n_orbitals'], 'n_electrons': data['n_electrons'], 'n_qubits': 2 * data['n_orbitals'], 'estimated_energy': data['energy'], 'hamiltonian_terms': data['n_orbitals'] * 4, # Rough estimate 'backend': 'simplified' } else: # Default values return { 'success': True, 'molecule': molecule, 'basis': 'sto-3g', 'n_orbitals': 4, 'n_electrons': 4, 'n_qubits': 8, 'estimated_energy': -5.0, 'hamiltonian_terms': 16, 'backend': 'simplified' } def _parse_molecule_geometry(molecule: str) -> List[List]: """Parse molecule geometry string.""" # Common molecules geometries = { 'h2': [['H', [0.0, 0.0, 0.0]], ['H', [0.0, 0.0, 0.74]]], 'lih': [['Li', [0.0, 0.0, 0.0]], ['H', [0.0, 0.0, 1.595]]], 'beh2': [ ['Be', [0.0, 0.0, 0.0]], ['H', [0.0, 0.0, 1.3264]], ['H', [0.0, 0.0, -1.3264]] ], 'h2o': [ ['O', [0.0, 0.0, 0.0]], ['H', [0.757, 0.587, 0.0]], ['H', [-0.757, 0.587, 0.0]] ] } mol_name = molecule.lower().replace(' ', '').replace('_', '') if mol_name in geometries: return geometries[mol_name] else: # Default to H2 return geometries['h2'] async def vqe_chemistry( molecule: str, basis: str = "sto-3g", ansatz: str = "uccsd", optimizer: str = "cobyla", max_iterations: int = 100 ) -> Dict[str, Any]: """ Run VQE for molecular electronic structure. Args: molecule: Molecule specification basis: Basis set ansatz: Ansatz type (uccsd, hea, etc.) optimizer: Classical optimizer max_iterations: Maximum iterations Returns: VQE results for chemistry """ logger.info(f"Running VQE for {molecule} with {ansatz} ansatz") try: # First generate molecular Hamiltonian ham_result = await generate_molecular_hamiltonian(molecule, basis) if not ham_result['success']: return ham_result # Run VQE using PennyLane import pennylane as qml from pennylane import numpy as pnp n_qubits = ham_result['n_qubits'] n_electrons = ham_result['n_electrons'] # Limit qubits for simulation if n_qubits > 12: n_qubits = 12 logger.warning(f"Limited qubits to {n_qubits} for simulation") dev = qml.device('default.qubit', wires=n_qubits) # Create simple chemistry Hamiltonian (H2-like) coeffs = [0.2, 0.2, -1.0, 0.5] obs = [ qml.PauliZ(0), qml.PauliZ(1), qml.PauliZ(0) @ qml.PauliZ(1), qml.PauliX(0) @ qml.PauliX(1) ] H = qml.Hamiltonian(coeffs, obs) # Define ansatz def ansatz_circuit(params): if ansatz == "uccsd": # Simplified UCCSD qml.BasisState([1, 1, 0, 0], wires=range(min(4, n_qubits))) for i in range(min(2, len(params))): qml.DoubleExcitation(params[i], wires=[0, 1, 2, 3] if n_qubits >= 4 else [0, 1]) elif ansatz == "hea": # Hardware efficient ansatz for i in range(min(n_qubits, 4)): qml.RY(params[i], wires=i) for i in range(min(n_qubits-1, 3)): qml.CNOT(wires=[i, i+1]) else: # Simple ansatz for i in range(min(n_qubits, len(params))): qml.RY(params[i], wires=i) @qml.qnode(dev) def cost_fn(params): ansatz_circuit(params) return qml.expval(H) # Initialize parameters n_params = min(n_qubits, 4) params = pnp.random.uniform(0, 2*pnp.pi, n_params, requires_grad=True) # Optimize opt = qml.GradientDescentOptimizer(stepsize=0.1) energies = [] for i in range(min(max_iterations, 50)): # Limit iterations params, energy = opt.step_and_cost(cost_fn, params) energies.append(energy) if i > 10 and abs(energies[-1] - energies[-5]) < 1e-6: break final_energy = energies[-1] # Calculate approximate ground state energy classical_energy = ham_result.get('scf_energy', ham_result.get('estimated_energy', -1.0)) return { 'success': True, 'molecule': molecule, 'basis': basis, 'ansatz': ansatz, 'vqe_energy': float(final_energy), 'classical_energy': float(classical_energy), 'energy_difference': float(final_energy - classical_energy), 'convergence_iterations': len(energies), 'n_qubits_used': n_qubits, 'n_electrons': n_electrons, 'optimal_parameters': params.tolist() } except Exception as e: logger.error(f"Error in VQE chemistry: {e}") return {'success': False, 'error': str(e)} async def compute_molecular_properties( molecule: str, method: str = "hf", basis: str = "sto-3g" ) -> Dict[str, Any]: """ Compute molecular properties using quantum chemistry methods. Args: molecule: Molecule specification method: Computational method basis: Basis set Returns: Molecular properties """ logger.info(f"Computing properties for {molecule} using {method}") try: # Get basic molecular data ham_result = await generate_molecular_hamiltonian(molecule, basis) if not ham_result['success']: return ham_result # Compute additional properties geometry = _parse_molecule_geometry(molecule) # Calculate molecular properties properties = { 'success': True, 'molecule': molecule, 'method': method, 'basis': basis, 'geometry': geometry, 'n_atoms': len(geometry), 'n_electrons': ham_result['n_electrons'], 'n_orbitals': ham_result['n_orbitals'], 'charge': 0, 'multiplicity': 1 } # Add estimated properties mol_name = molecule.lower().replace(' ', '').replace('_', '') if mol_name == 'h2': properties.update({ 'bond_length': 0.74, # Angstrom 'dissociation_energy': 4.48, # eV 'vibrational_frequency': 4401, # cm^-1 'dipole_moment': 0.0 }) elif mol_name == 'h2o': properties.update({ 'bond_length': 0.96, 'bond_angle': 104.5, # degrees 'dipole_moment': 1.85, # Debye 'ionization_potential': 12.6 # eV }) elif mol_name == 'lih': properties.update({ 'bond_length': 1.595, 'dipole_moment': 5.88, 'ionization_potential': 7.9 }) return properties except Exception as e: logger.error(f"Error computing molecular properties: {e}") return {'success': False, 'error': str(e)} async def simulate_chemical_reaction( reactants: List[str], products: List[str], method: str = "vqe" ) -> Dict[str, Any]: """ Simulate chemical reaction using quantum methods. Args: reactants: List of reactant molecules products: List of product molecules method: Simulation method Returns: Reaction simulation results """ logger.info(f"Simulating reaction: {reactants} -> {products}") try: # Compute properties for reactants reactant_energies = [] for reactant in reactants: if method == "vqe": result = await vqe_chemistry(reactant) else: result = await compute_molecular_properties(reactant) if result['success']: energy = result.get('vqe_energy', result.get('estimated_energy', 0.0)) reactant_energies.append(energy) else: reactant_energies.append(0.0) # Compute properties for products product_energies = [] for product in products: if method == "vqe": result = await vqe_chemistry(product) else: result = await compute_molecular_properties(product) if result['success']: energy = result.get('vqe_energy', result.get('estimated_energy', 0.0)) product_energies.append(energy) else: product_energies.append(0.0) # Calculate reaction energy total_reactant_energy = sum(reactant_energies) total_product_energy = sum(product_energies) reaction_energy = total_product_energy - total_reactant_energy # Convert to more common units (kcal/mol) reaction_energy_kcal = reaction_energy * 627.5 # Hartree to kcal/mol return { 'success': True, 'reactants': reactants, 'products': products, 'method': method, 'reactant_energies': reactant_energies, 'product_energies': product_energies, 'reaction_energy_hartree': float(reaction_energy), 'reaction_energy_kcal_mol': float(reaction_energy_kcal), 'is_exothermic': reaction_energy < 0, 'is_endothermic': reaction_energy > 0 } except Exception as e: logger.error(f"Error simulating reaction: {e}") return {'success': False, 'error': str(e)}

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/manasp21/Psi-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server