A19grey's picture
added logging for gradio demo loading and molecule geometry creation
2a24c63
"""
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="<div>Select a molecule to view its parameters</div>"
)
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="<div style='padding: 10px; background-color: #f5f5f5; border-radius: 5px; font-size: 0.8em;'>" +
"Click 'Generate Hamiltonian' to start.</div>"
)
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("""
<style>
.circuit-output {
font-family: monospace;
font-size: 0.6em;
white-space: pre;
overflow-x: auto;
padding: 10px;
background-color: #f8f8f8;
width: 100%;
max-width: 100%;
box-sizing: border-box;
display: block;
}
.circuit-output pre {
margin: 0 auto;
background: none !important;
border: none !important;
padding: 0 !important;
width: 100%;
overflow-x: auto;
}
.circuit-output code {
background: none !important;
border: none !important;
padding: 0 !important;
}
.gradio-container .prose {
max-width: 100% !important;
width: 100% !important;
}
.markdown-text {
width: 100% !important;
max-width: none !important;
}
</style>
""")
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 "<div>Error: Invalid molecule selection</div>", "", 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"""
<div style='padding: 10px; background-color: #f5f5f5; border-radius: 5px; font-size: 0.8em;'>
<h3>Hamiltonian Generated:</h3>
<ul style='list-style-type: none; padding-left: 0;'>
<li><b>Number of Qubits:</b> {results['qubit_count']}</li>
<li><b>Number of Electrons:</b> {results['electron_count']}</li>
<li><b>Hamiltonian Terms:</b> {results['hamiltonian_terms']}</li>
<li style='margin-top: 8px; color: #2a6f97;'><b>Quantum Circuit Parameters:</b> {results['parameter_count']} <i>(parameters needed to represent the system in quantum circuit form)</i></li>
<li><b>Scale Factor:</b> {scale_factor:.2f}</li>
</ul>
<p><i>Ready to run VQE optimization.</i></p>
</div>
"""
# Format circuit output
logger.debug("Formatting circuit LaTeX output")
circuit_latex = f"""<div class='circuit-output'><pre>{results.get('circuit_latex', 'Circuit visualization not available').replace('`', '&#96;')}</pre></div>"""
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"<div style='color: red;'>Error generating Hamiltonian:<br>{str(e)}</div>"
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"""
<div style='padding: 10px; background-color: #f5f5f5; border-radius: 5px;'>
<h3>VQE Optimization Results:</h3>
<ul style='list-style-type: none; padding-left: 0;'>
<li><b>Final Energy:</b> {results['final_energy']:.6f} Hartree</li>
<li><b>Parameter Count:</b> {results['parameter_count']}</li>
<li><b>Hamiltonian Terms:</b> {results['hamiltonian_terms']}</li>
<li><b>Iterations:</b> {len(results['history'])}</li>
<li><b>Scale Factor:</b> {scale_factor:.2f}</li>
</ul>
<p><i>{results['message']}</i></p>
</div>
"""
return simulation_html, convergence_plot
except Exception as e:
error_msg = f"<div style='color: red;'>Error in VQE optimization:<br>{str(e)}</div>"
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 [
"<div>Error loading molecule parameters</div>",
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
)