""" NVIDIA CUDA-Q VQE Demo ===================== This Gradio application demonstrates the Variational Quantum Eigensolver (VQE) algorithm using NVIDIA's CUDA Quantum library. The demo allows users to: 1. Select from available molecules 2. Adjust bond length parameters 3. Run VQE simulations with real-time visualization 4. View energy convergence and molecular structure in 3D Running the Demo --------------- 1. Ensure you have all dependencies installed: ```bash pip install -r requirements.txt ``` 2. Launch the application: ```bash python app.py ``` 3. Open your browser to the local URL displayed in the terminal (typically http://localhost:7860) Using the Interface ----------------- - Select a molecule from the dropdown - Adjust bond length using the slider (0.5 to 2.5 Å) - Click "Run VQE Simulation" or watch real-time updates as you adjust parameters - View results in three panels: * Simulation Results: Shows final energy and optimization status * VQE Convergence: Plots energy vs. iteration * Molecule Visualization: 3D representation of the molecule GPU Acceleration -------------- The app automatically detects if NVIDIA GPUs are available: - With GPU: Uses CUDA-Q's nvidia backend for acceleration - Without GPU: Falls back to CPU-based quantum simulation References --------- - Installation: See Cuda-Q_install.md - VQE Implementation: See VQE_Example.md - Project Structure: See quantum-demo-blueprint.md """ import spaces from gradio_client import Client from gradio.routes import Request as gr_Request import subprocess import sys import json import os import logging from logging.handlers import RotatingFileHandler import gradio as gr import numpy as np from quantum_utils import run_vqe_simulation from visualization import plot_convergence, create_molecule_viewer, format_molecule_params import plotly.graph_objects as go # Configure logging os.makedirs('logs', exist_ok=True) # Create formatters file_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') console_formatter = logging.Formatter('%(levelname)s - %(message)s') # Set up file handler with rotation file_handler = RotatingFileHandler( 'logs/vqe_simulation.log', maxBytes=1024 * 1024, # 1MB per file backupCount=3 # Keep 3 backup files ) file_handler.setFormatter(file_formatter) file_handler.setLevel(logging.DEBUG) # Set up console handler with less verbose output console_handler = logging.StreamHandler() console_handler.setFormatter(console_formatter) console_handler.setLevel(logging.INFO) # Only show INFO and above in console # Configure root logger logger = logging.getLogger() logger.setLevel(logging.DEBUG) # Remove any existing handlers for handler in logger.handlers[:]: logger.removeHandler(handler) # Add our handlers logger.addHandler(file_handler) logger.addHandler(console_handler) # Disable other loggers that might be too verbose logging.getLogger('httpcore.http11').setLevel(logging.WARNING) logging.getLogger('httpx').setLevel(logging.WARNING) logging.getLogger('gradio').setLevel(logging.WARNING) # Log startup message logger.info("VQE Demo Application Starting") def install_dependencies(): print("""Install required packages from requirements.txt.""") try: subprocess.check_call([sys.executable, "-m", "pip", "install", "-r", "requirements.txt"]) print("Dependencies installed successfully") except subprocess.CalledProcessError as e: print(f"Error installing dependencies: {e}") sys.exit(1) # Test if cudaq is already installed try: import cudaq print("CUDA-Q already installed, skipping dependency installation") except ImportError: print("CUDA-Q not found, installing dependencies...") # Run installation before any other imports install_dependencies() # Load molecule definitions def load_molecules(): """Load molecule definitions from JSON file.""" try: with open('molecules.json', 'r') as f: all_molecules = json.load(f) # Filter to only include enabled molecules enabled_molecules = {k: v for k, v in all_molecules.items() if v.get('enabled', 0) == 1} if not enabled_molecules: print("Warning: No enabled molecules found in molecules.json", file=sys.stderr) return enabled_molecules except Exception as e: print(f"Error loading molecules.json: {e}", file=sys.stderr) return {} # Now import the rest of the modules print("about to import numpy and quantum_utils", file=sys.stderr, flush=True) import numpy as np from quantum_utils import run_vqe_simulation from visualization import plot_convergence, create_molecule_viewer, format_molecule_params # Add immediate logging print("Imported all modules successfully", file=sys.stderr, flush=True) def update_simulation(molecule_choice: str, scale_factor: float) -> tuple: """ Run VQE simulation and update visualizations. Args: molecule_choice: Selected molecule from available options scale_factor: Factor to scale the molecule geometry by (1.0 = original size) Returns: Tuple of (energy text, convergence plot, molecule HTML) """ print("UPDATE_SIMULATION CALLED", file=sys.stderr, flush=True) try: # Get molecule data molecules = load_molecules() if molecule_choice not in molecules: raise ValueError(f"Unknown molecule: {molecule_choice}") molecule_data = molecules[molecule_choice] print(f"Starting simulation with molecule: {molecule_choice}, scale: {scale_factor}", file=sys.stderr, flush=True) # Run VQE simulation results = run_vqe_simulation( molecule_data=molecule_data, scale_factor=scale_factor ) print(f"VQE simulation completed with results: {results}", file=sys.stderr, flush=True) # Create plots print("Creating convergence plot...", file=sys.stderr) convergence_plot = plot_convergence(results) print("Convergence plot created", file=sys.stderr) print("Creating molecule visualization...", file=sys.stderr) molecule_html = create_molecule_viewer(molecule_choice, scale_factor) print("Molecule visualization created", file=sys.stderr) # Format energy output energy_text = f""" Final Energy: {results['final_energy']:.6f} Hartree Optimization Status: {'Success' if results['success'] else 'Failed'} Message: {results['message']} Number of Iterations: {len(results['history'])} Scale Factor: {scale_factor:.2f} """ print("Returning results...", file=sys.stderr) return energy_text, convergence_plot, molecule_html except Exception as e: import traceback error_msg = f"Error in simulation:\n{str(e)}\n\nTraceback:\n{traceback.format_exc()}" print(error_msg, file=sys.stderr) # This will show in the server logs return error_msg, None, None def create_interface(): """Create the Gradio interface for the VQE demo.""" def set_client_for_session(request: gr.Request): """Initialize client with user's IP token to handle rate limiting.""" logger.info(f"Setting client for session with request: {request}") try: # Try to initialize client with HF token from environment variable client = Client("A19grey/Cuda-Quantum-Molecular-Playground", hf_token=os.getenv("HF_TOKEN")) logger.info(f"Client initialized with A19grey HF token") return client except Exception as e: logger.error(f"Error setting client with HF token: {e}") try: # Fallback to using x-ip-token from request headers x_ip_token = request.headers['x-ip-token'] logger.info(f"Client initialized with x-ip-token from request headers") return Client("A19grey/Cuda-Quantum-Molecular-Playground", headers={"x-ip-token": x_ip_token}) except Exception as e: logger.error(f"Error setting client with x-ip-token: {e}") return None # Load available molecules molecules = load_molecules() if not molecules: raise ValueError("No molecules found in molecules.json") with gr.Blocks(title="CUDA-Q VQE Demo") as demo: gr.Markdown(""" # 🔬 NVIDIA CUDA-Q VQE Demo This demo harnesses the power of **NVIDIA's GPU-accelerated CUDA Quantum** library to simulate quantum systems on classical hardware. The process works in two steps: 1. 📊 Generate the [Hamiltonian](https://quantumfrontiers.com/2017/01/31/hamiltonian-an-american-musical-without-americana-or-music/) - *the mathematical description of the system's energy* 2. ⚛️ Use the **Variational Quantum Eigensolver (VQE)** algorithm to calculate the ground state energy ## How to Use > 🔍 Select a molecule below, adjust its parameters, and watch as the simulation computes the lowest > possible energy state using quantum-inspired algorithms accelerated by NVIDIA GPUs. ## Important Note ⚠️ Even for small molecules, the quantum circuits can become quite complex. In this demo, we show only the first terms. > 💡 **Pro Tip:** In real-world applications, even with GPU acceleration, scientists use more advanced methods > that focus on simulating only outer shell electrons for practical quantum chemistry calculations. --- *Inspired by the [NVIDIA CUDA-Q VQE Example](https://nvidia.github.io/cuda-quantum/latest/applications/python/vqe_advanced.html)* """) client = gr.State() state = gr.State({}) # Top section: 3 columns with gr.Row(): with gr.Column(scale=1): molecule_choice = gr.Dropdown( choices=list(molecules.keys()), value=None, label="Select Molecule", interactive=True, allow_custom_value=False ) default_molecule = molecules[list(molecules.keys())[0]] scale_factor = gr.Slider( minimum=default_molecule['scale_range']['min'], maximum=default_molecule['scale_range']['max'], value=default_molecule['default_scale'], step=default_molecule['scale_range']['step'], label="Scale Factor (1.0 = original size)", interactive=True ) with gr.Column(scale=1): params_display = gr.HTML( label="Molecule Parameters", value="
Select a molecule to view its parameters
" ) with gr.Column(scale=1): molecule_viz = gr.Plot( label="3D Molecule Viewer", show_label=True ) # Middle section: Buttons and results in 2 columns with gr.Row(): with gr.Column(scale=1): # Buttons side by side with gr.Row(): generate_ham_button = gr.Button("Generate Hamiltonian") run_vqe_button = gr.Button("Find that ground state!", interactive=False) # Hamiltonian Information simulation_results = gr.HTML( label="Hamiltonian Information", value="
" + "Click 'Generate Hamiltonian' to start.
" ) with gr.Column(scale=1): # VQE Convergence Plot convergence_plot = gr.Plot( label="VQE Convergence", show_label=True ) # Bottom section: Full width circuit visualization circuit_latex_display = gr.Markdown( label="Quantum Circuit Visualization", value="Circuit will be displayed after generation.", elem_classes="circuit-output" ) # Add CSS styling gr.Markdown(""" """) def generate_hamiltonian(molecule_choice: str, scale_factor: float) -> tuple: """Generate Hamiltonian for the selected molecule.""" logger.info(f"Starting Hamiltonian generation for molecule: {molecule_choice}, scale: {scale_factor}") try: # Get molecule data logger.debug("Loading molecule data from molecules dictionary") if molecule_choice not in molecules: logger.error(f"Molecule {molecule_choice} not found in molecules dictionary") return "
Error: Invalid molecule selection
", "", gr.update(interactive=False), None molecule_data = molecules[molecule_choice] logger.debug(f"Loaded molecule data: {molecule_data}") # Generate Hamiltonian only logger.info("Calling run_vqe_simulation with hamiltonian_only=True") results = run_vqe_simulation( molecule_data=molecule_data, scale_factor=scale_factor, hamiltonian_only=True ) logger.debug(f"VQE simulation results: {results}") # Format Hamiltonian results logger.debug("Formatting Hamiltonian results") hamiltonian_html = f"""

Hamiltonian Generated:

Ready to run VQE optimization.

""" # Format circuit output logger.debug("Formatting circuit LaTeX output") circuit_latex = f"""
{results.get('circuit_latex', 'Circuit visualization not available').replace('`', '`')}
""" logger.info("Successfully generated Hamiltonian and circuit visualization") return hamiltonian_html, circuit_latex, gr.update(interactive=True), None except Exception as e: import traceback logger.error(f"Error in generate_hamiltonian: {str(e)}\n{traceback.format_exc()}") error_msg = f"
Error generating Hamiltonian:
{str(e)}
" return error_msg, "Error generating circuit visualization", gr.update(interactive=False), None def run_vqe_optimization(molecule_choice: str, scale_factor: float) -> tuple: """Run VQE optimization after Hamiltonian is generated.""" try: # Get molecule data molecule_data = molecules[molecule_choice] # Run full VQE simulation results = run_vqe_simulation( molecule_data=molecule_data, scale_factor=scale_factor, hamiltonian_only=False ) # Create plots convergence_plot = plot_convergence(results) # Format simulation results without the optimization status simulation_html = f"""

VQE Optimization Results:

{results['message']}

""" return simulation_html, convergence_plot except Exception as e: error_msg = f"
Error in VQE optimization:
{str(e)}
" return error_msg, None def update_molecule_info(molecule_choice): """Update description and scale range when molecule is selected.""" try: logger.info(f"Updating molecule info for: {molecule_choice}") mol_data = molecules[molecule_choice] # Format parameters display params_html = format_molecule_params(mol_data) logger.debug(f"Generated parameter HTML for {molecule_choice}") # Get scale range values with defaults min_scale = mol_data.get('scale_range', {}).get('min', 0.1) max_scale = mol_data.get('scale_range', {}).get('max', 3.0) default_scale = mol_data.get('default_scale', 1.0) step_size = mol_data.get('scale_range', {}).get('step', 0.05) logger.debug(f"Scale parameters - min: {min_scale}, max: {max_scale}, default: {default_scale}") # Create molecule visualization using Plotly logger.info(f"Creating Plotly molecule visualization for {molecule_choice}") mol_plot = create_molecule_viewer(molecule_choice, default_scale) if mol_plot is None: logger.error(f"No molecule visualization created for {molecule_choice}") mol_plot = go.Figure() # Empty figure as fallback return [ params_html, gr.update( minimum=min_scale, maximum=max_scale, value=default_scale, step=step_size ), mol_plot ] except Exception as e: logger.error(f"Error updating molecule info: {str(e)}", exc_info=True) return [ "
Error loading molecule parameters
", gr.update( minimum=0.1, maximum=3.0, value=1.0, step=0.05 ), go.Figure() # Empty figure as fallback ] # Update parameters and 3D view when molecule changes molecule_choice.change( fn=update_molecule_info, inputs=[molecule_choice], outputs=[params_display, scale_factor, molecule_viz] ) # Generate Hamiltonian when button is clicked generate_ham_button.click( fn=generate_hamiltonian, inputs=[molecule_choice, scale_factor], outputs=[simulation_results, circuit_latex_display, run_vqe_button, convergence_plot] ) # Run VQE optimization when button is clicked run_vqe_button.click( fn=run_vqe_optimization, inputs=[molecule_choice, scale_factor], outputs=[simulation_results, convergence_plot] ) # Add load event to set client logger.info("Adding load event to set client") demo.load(set_client_for_session, None, client) return demo if __name__ == "__main__": print("Starting VQE Demo Application") demo = create_interface() print("Launching demo with custom headers support") demo.launch( server_name="0.0.0.0", server_port=7860, ssr_mode=False, show_api=True # Enable API endpoints )