Commit
·
0f5f6d3
0
Parent(s):
Updating to new 3D visualizer molgallery3D and need to restart space to install
Browse files- .cursorrules +8 -0
- .gitattributes +35 -0
- .gitignore +79 -0
- README.md +68 -0
- app.py +308 -0
- docref/Example_cudaq_logical_aim_agk.py +712 -0
- docref/Example_plotly_code.md +0 -0
- docref/H2example.md +0 -0
- docref/VQE_Example.md +345 -0
- docref/molgallery_demo.md +39 -0
- docref/py3dmol_demo.md +45 -0
- docref/quantum-demo-blueprint.md +254 -0
- gitpush.sh +29 -0
- history.md +159 -0
- install.py +8 -0
- molecules.json +157 -0
- packages.txt +3 -0
- quantum_utils.py +332 -0
- requirements.txt +31 -0
- visualization.py +189 -0
.cursorrules
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Write out code in a way that is easy to understand and follow.
|
2 |
+
|
3 |
+
For each large task update make an update in @history.md file with the date and the changes made.
|
4 |
+
New Entries should be added to the top of the file. This will provide a history of the changes made to the code.
|
5 |
+
|
6 |
+
If things are unclear ask for clarification.
|
7 |
+
|
8 |
+
If you are unsure about the best way to do something, propose a solution and ask for clarification.
|
.gitattributes
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Python
|
2 |
+
__pycache__/
|
3 |
+
*.py[cod]
|
4 |
+
*$py.class
|
5 |
+
*.so
|
6 |
+
.Python
|
7 |
+
build/
|
8 |
+
develop-eggs/
|
9 |
+
dist/
|
10 |
+
downloads/
|
11 |
+
eggs/
|
12 |
+
.eggs/
|
13 |
+
lib/
|
14 |
+
lib64/
|
15 |
+
parts/
|
16 |
+
sdist/
|
17 |
+
var/
|
18 |
+
wheels/
|
19 |
+
*.egg-info/
|
20 |
+
.installed.cfg
|
21 |
+
*.egg
|
22 |
+
|
23 |
+
# Virtual Environment
|
24 |
+
venv/
|
25 |
+
env/
|
26 |
+
ENV/
|
27 |
+
.env
|
28 |
+
.venv
|
29 |
+
env.bak/
|
30 |
+
venv.bak/
|
31 |
+
|
32 |
+
# IDE specific files
|
33 |
+
.idea/
|
34 |
+
.vscode/
|
35 |
+
*.swp
|
36 |
+
*.swo
|
37 |
+
.project
|
38 |
+
.pydevproject
|
39 |
+
.settings/
|
40 |
+
|
41 |
+
# Jupyter Notebook
|
42 |
+
.ipynb_checkpoints
|
43 |
+
*.ipynb_checkpoints/
|
44 |
+
|
45 |
+
# Distribution / packaging
|
46 |
+
.Python
|
47 |
+
*.manifest
|
48 |
+
*.spec
|
49 |
+
|
50 |
+
# Logs and databases
|
51 |
+
*.log
|
52 |
+
*.sqlite
|
53 |
+
*.db
|
54 |
+
|
55 |
+
# OS generated files
|
56 |
+
.DS_Store
|
57 |
+
.DS_Store?
|
58 |
+
._*
|
59 |
+
.Spotlight-V100
|
60 |
+
.Trashes
|
61 |
+
ehthumbs.db
|
62 |
+
Thumbs.db
|
63 |
+
|
64 |
+
# Testing
|
65 |
+
.coverage
|
66 |
+
.tox/
|
67 |
+
.pytest_cache/
|
68 |
+
htmlcov/
|
69 |
+
.coverage.*
|
70 |
+
coverage.xml
|
71 |
+
*.cover
|
72 |
+
|
73 |
+
# Documentation
|
74 |
+
docs/_build/
|
75 |
+
docs/generated/
|
76 |
+
|
77 |
+
# Local development settings
|
78 |
+
local_settings.py
|
79 |
+
*.env
|
README.md
ADDED
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
---
|
2 |
+
title: Cuda Quantum Molecular Playground
|
3 |
+
emoji: 📈
|
4 |
+
colorFrom: pink
|
5 |
+
colorTo: gray
|
6 |
+
sdk: gradio
|
7 |
+
sdk_version: 5.9.1
|
8 |
+
app_file: app.py
|
9 |
+
pinned: false
|
10 |
+
license: apache-2.0
|
11 |
+
suggested_hardware: t4-small
|
12 |
+
short_description: Nvidia Cuda Quantum accelerator to demo to simulate bonds
|
13 |
+
---
|
14 |
+
|
15 |
+
# CUDA Quantum Molecular Playground
|
16 |
+
|
17 |
+
An interactive web demo using NVIDIA's CUDA Quantum Library (CUDA-Q) for quantum computations. This project focuses on the Variational Quantum Eigensolver (VQE) algorithm to estimate molecular energy, providing an educational interface for quantum control and simulation.
|
18 |
+
|
19 |
+
## Interface Features
|
20 |
+
|
21 |
+
1. **Molecule Selection & Parameters**
|
22 |
+
- Choose from available molecules
|
23 |
+
- View detailed molecular parameters (formula, orbitals, charge, basis)
|
24 |
+
- Adjust bond length using an interactive scale factor slider
|
25 |
+
|
26 |
+
2. **Simulation Controls**
|
27 |
+
- Run VQE simulations with real-time visualization
|
28 |
+
- View simulation results including:
|
29 |
+
- Final energy
|
30 |
+
- Parameter count
|
31 |
+
- Hamiltonian terms
|
32 |
+
- Optimization status
|
33 |
+
- Iteration count
|
34 |
+
|
35 |
+
3. **Visualization**
|
36 |
+
- Interactive 3D molecular structure viewer
|
37 |
+
- Real-time energy convergence plots
|
38 |
+
- Detailed simulation results display
|
39 |
+
|
40 |
+
## Installation
|
41 |
+
|
42 |
+
```bash
|
43 |
+
pip install -r requirements.txt
|
44 |
+
```
|
45 |
+
|
46 |
+
## Running the Demo
|
47 |
+
|
48 |
+
1. Launch the application:
|
49 |
+
```bash
|
50 |
+
python app.py
|
51 |
+
```
|
52 |
+
|
53 |
+
2. Open your browser to the local URL displayed in the terminal (typically http://localhost:7860)
|
54 |
+
|
55 |
+
## Key Technologies
|
56 |
+
|
57 |
+
- **CUDA-Q**: Quantum computations and hardware acceleration
|
58 |
+
- **OpenFermion**: Quantum simulations and electronic structure calculations
|
59 |
+
- **PySCF**: Molecular Hamiltonian preparation and quantum chemistry simulations
|
60 |
+
- **Gradio**: Interactive web interface
|
61 |
+
- **Plotly**: Data visualization
|
62 |
+
- **py3Dmol**: Molecular structure visualization
|
63 |
+
|
64 |
+
## Hardware Requirements
|
65 |
+
|
66 |
+
The demo automatically detects available hardware:
|
67 |
+
- With NVIDIA GPU: Uses CUDA-Q's nvidia backend for acceleration
|
68 |
+
- Without GPU: Falls back to CPU-based quantum simulation
|
app.py
ADDED
@@ -0,0 +1,308 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
NVIDIA CUDA-Q VQE Demo
|
3 |
+
=====================
|
4 |
+
|
5 |
+
This Gradio application demonstrates the Variational Quantum Eigensolver (VQE)
|
6 |
+
algorithm using NVIDIA's CUDA Quantum library. The demo allows users to:
|
7 |
+
|
8 |
+
1. Select from available molecules
|
9 |
+
2. Adjust bond length parameters
|
10 |
+
3. Run VQE simulations with real-time visualization
|
11 |
+
4. View energy convergence and molecular structure in 3D
|
12 |
+
|
13 |
+
Running the Demo
|
14 |
+
---------------
|
15 |
+
1. Ensure you have all dependencies installed:
|
16 |
+
```bash
|
17 |
+
pip install -r requirements.txt
|
18 |
+
```
|
19 |
+
|
20 |
+
2. Launch the application:
|
21 |
+
```bash
|
22 |
+
python app.py
|
23 |
+
```
|
24 |
+
|
25 |
+
3. Open your browser to the local URL displayed in the terminal
|
26 |
+
(typically http://localhost:7860)
|
27 |
+
|
28 |
+
Using the Interface
|
29 |
+
-----------------
|
30 |
+
- Select a molecule from the dropdown
|
31 |
+
- Adjust bond length using the slider (0.5 to 2.5 Å)
|
32 |
+
- Click "Run VQE Simulation" or watch real-time updates as you adjust parameters
|
33 |
+
- View results in three panels:
|
34 |
+
* Simulation Results: Shows final energy and optimization status
|
35 |
+
* VQE Convergence: Plots energy vs. iteration
|
36 |
+
* Molecule Visualization: 3D representation of the molecule
|
37 |
+
|
38 |
+
GPU Acceleration
|
39 |
+
--------------
|
40 |
+
The app automatically detects if NVIDIA GPUs are available:
|
41 |
+
- With GPU: Uses CUDA-Q's nvidia backend for acceleration
|
42 |
+
- Without GPU: Falls back to CPU-based quantum simulation
|
43 |
+
|
44 |
+
References
|
45 |
+
---------
|
46 |
+
- Installation: See Cuda-Q_install.md
|
47 |
+
- VQE Implementation: See VQE_Example.md
|
48 |
+
- Project Structure: See quantum-demo-blueprint.md
|
49 |
+
"""
|
50 |
+
|
51 |
+
import subprocess
|
52 |
+
import sys
|
53 |
+
import json
|
54 |
+
import os
|
55 |
+
|
56 |
+
# Load molecule definitions
|
57 |
+
def load_molecules():
|
58 |
+
"""Load molecule definitions from JSON file."""
|
59 |
+
try:
|
60 |
+
with open('molecules.json', 'r') as f:
|
61 |
+
return json.load(f)
|
62 |
+
except Exception as e:
|
63 |
+
print(f"Error loading molecules.json: {e}", file=sys.stderr)
|
64 |
+
return {}
|
65 |
+
|
66 |
+
# Test if cudaq is already installed
|
67 |
+
try:
|
68 |
+
import cudaq
|
69 |
+
print("CUDA-Q already installed, skipping dependency installation")
|
70 |
+
except ImportError:
|
71 |
+
print("CUDA-Q not found, installing dependencies...")
|
72 |
+
# Run installation before any other imports
|
73 |
+
install_dependencies()
|
74 |
+
|
75 |
+
# Now import the rest of the modules
|
76 |
+
import gradio as gr
|
77 |
+
import numpy as np
|
78 |
+
from quantum_utils import run_vqe_simulation
|
79 |
+
from visualization import plot_convergence, create_molecule_viewer, format_molecule_params
|
80 |
+
import spaces
|
81 |
+
|
82 |
+
# Add immediate logging
|
83 |
+
print("Imported all modules successfully", file=sys.stderr, flush=True)
|
84 |
+
|
85 |
+
def update_simulation(molecule_choice: str, scale_factor: float) -> tuple:
|
86 |
+
"""
|
87 |
+
Run VQE simulation and update visualizations.
|
88 |
+
|
89 |
+
Args:
|
90 |
+
molecule_choice: Selected molecule from available options
|
91 |
+
scale_factor: Factor to scale the molecule geometry by (1.0 = original size)
|
92 |
+
Returns:
|
93 |
+
Tuple of (energy text, convergence plot, molecule HTML)
|
94 |
+
"""
|
95 |
+
print("UPDATE_SIMULATION CALLED", file=sys.stderr, flush=True)
|
96 |
+
try:
|
97 |
+
# Get molecule data
|
98 |
+
molecules = load_molecules()
|
99 |
+
if molecule_choice not in molecules:
|
100 |
+
raise ValueError(f"Unknown molecule: {molecule_choice}")
|
101 |
+
|
102 |
+
molecule_data = molecules[molecule_choice]
|
103 |
+
print(f"Starting simulation with molecule: {molecule_choice}, scale: {scale_factor}",
|
104 |
+
file=sys.stderr, flush=True)
|
105 |
+
|
106 |
+
# Run VQE simulation
|
107 |
+
results = run_vqe_simulation(
|
108 |
+
molecule_data=molecule_data,
|
109 |
+
scale_factor=scale_factor
|
110 |
+
)
|
111 |
+
|
112 |
+
print(f"VQE simulation completed with results: {results}",
|
113 |
+
file=sys.stderr, flush=True)
|
114 |
+
|
115 |
+
# Create plots
|
116 |
+
print("Creating convergence plot...", file=sys.stderr)
|
117 |
+
convergence_plot = plot_convergence(results)
|
118 |
+
print("Convergence plot created", file=sys.stderr)
|
119 |
+
|
120 |
+
print("Creating molecule visualization...", file=sys.stderr)
|
121 |
+
molecule_html = create_molecule_viewer(molecule_choice, scale_factor)
|
122 |
+
print("Molecule visualization created", file=sys.stderr)
|
123 |
+
|
124 |
+
# Format energy output
|
125 |
+
energy_text = f"""
|
126 |
+
Final Energy: {results['final_energy']:.6f} Hartree
|
127 |
+
Optimization Status: {'Success' if results['success'] else 'Failed'}
|
128 |
+
Message: {results['message']}
|
129 |
+
Number of Iterations: {len(results['history'])}
|
130 |
+
Scale Factor: {scale_factor:.2f}
|
131 |
+
"""
|
132 |
+
|
133 |
+
print("Returning results...", file=sys.stderr)
|
134 |
+
return energy_text, convergence_plot, molecule_html
|
135 |
+
|
136 |
+
except Exception as e:
|
137 |
+
import traceback
|
138 |
+
error_msg = f"Error in simulation:\n{str(e)}\n\nTraceback:\n{traceback.format_exc()}"
|
139 |
+
print(error_msg, file=sys.stderr) # This will show in the server logs
|
140 |
+
return error_msg, None, None
|
141 |
+
|
142 |
+
def create_interface():
|
143 |
+
"""Create the Gradio interface for the VQE demo."""
|
144 |
+
|
145 |
+
# Load available molecules
|
146 |
+
molecules = load_molecules()
|
147 |
+
if not molecules:
|
148 |
+
raise ValueError("No molecules found in molecules.json")
|
149 |
+
|
150 |
+
with gr.Blocks(title="CUDA-Q VQE Demo") as demo:
|
151 |
+
gr.Markdown("# NVIDIA CUDA-Q VQE Demo")
|
152 |
+
gr.Markdown("""
|
153 |
+
This demo shows a Variational Quantum Eigensolver (VQE) simulation
|
154 |
+
using NVIDIA's CUDA Quantum library.
|
155 |
+
""")
|
156 |
+
|
157 |
+
state = gr.State({}) # Store current molecule data
|
158 |
+
|
159 |
+
with gr.Row():
|
160 |
+
with gr.Column(scale=1):
|
161 |
+
# Molecule selection controls
|
162 |
+
molecule_choice = gr.Dropdown(
|
163 |
+
choices=list(molecules.keys()),
|
164 |
+
value=list(molecules.keys())[0],
|
165 |
+
label="Select Molecule"
|
166 |
+
)
|
167 |
+
|
168 |
+
# Get scale range from selected molecule
|
169 |
+
default_molecule = molecules[list(molecules.keys())[0]]
|
170 |
+
scale_factor = gr.Slider(
|
171 |
+
minimum=default_molecule['scale_range']['min'],
|
172 |
+
maximum=default_molecule['scale_range']['max'],
|
173 |
+
value=default_molecule['default_scale'],
|
174 |
+
step=default_molecule['scale_range']['step'],
|
175 |
+
label="Scale Factor (1.0 = original size)",
|
176 |
+
interactive=True
|
177 |
+
)
|
178 |
+
|
179 |
+
run_button = gr.Button("Run VQE Simulation")
|
180 |
+
|
181 |
+
with gr.Column(scale=1):
|
182 |
+
# Molecule parameters display
|
183 |
+
params_display = gr.HTML(
|
184 |
+
label="Molecule Parameters",
|
185 |
+
value=format_molecule_params(default_molecule)
|
186 |
+
)
|
187 |
+
|
188 |
+
# Simulation results box below controls
|
189 |
+
with gr.Row():
|
190 |
+
simulation_results = gr.HTML(
|
191 |
+
label="Simulation Results",
|
192 |
+
value="<div style='padding: 10px; background-color: #f5f5f5; border-radius: 5px;'>" +
|
193 |
+
"Click 'Run VQE Simulation' to start the calculation.</div>"
|
194 |
+
)
|
195 |
+
|
196 |
+
# Plots and visualization
|
197 |
+
with gr.Row():
|
198 |
+
convergence_plot = gr.Plot(
|
199 |
+
label="VQE Convergence",
|
200 |
+
show_label=True
|
201 |
+
)
|
202 |
+
# Create MolGallery3D component
|
203 |
+
molecule_viz = MolGallery3D(
|
204 |
+
columns=1,
|
205 |
+
height=400,
|
206 |
+
label="3D Molecule Viewer",
|
207 |
+
automatic_rotation=True
|
208 |
+
)
|
209 |
+
|
210 |
+
def update_molecule_info(molecule_choice):
|
211 |
+
"""Update description and scale range when molecule is selected."""
|
212 |
+
try:
|
213 |
+
mol_data = molecules[molecule_choice]
|
214 |
+
|
215 |
+
# Format parameters display
|
216 |
+
params_html = format_molecule_params(mol_data)
|
217 |
+
|
218 |
+
# Get scale range values with defaults
|
219 |
+
min_scale = mol_data.get('scale_range', {}).get('min', 0.1)
|
220 |
+
max_scale = mol_data.get('scale_range', {}).get('max', 3.0)
|
221 |
+
default_scale = mol_data.get('default_scale', 1.0)
|
222 |
+
step_size = mol_data.get('scale_range', {}).get('step', 0.05)
|
223 |
+
|
224 |
+
# Create molecule visualization
|
225 |
+
mol_list = create_molecule_viewer(molecule_choice, default_scale)
|
226 |
+
if mol_list is None:
|
227 |
+
mol_list = [] # Empty list if molecule creation failed
|
228 |
+
|
229 |
+
return [
|
230 |
+
params_html, # Update params display
|
231 |
+
gr.update( # Update slider
|
232 |
+
minimum=min_scale,
|
233 |
+
maximum=max_scale,
|
234 |
+
value=default_scale,
|
235 |
+
step=step_size
|
236 |
+
),
|
237 |
+
mol_list # Update 3D view
|
238 |
+
]
|
239 |
+
except Exception as e:
|
240 |
+
print(f"Error updating molecule info: {e}", file=sys.stderr)
|
241 |
+
return [
|
242 |
+
"<div>Error loading molecule parameters</div>",
|
243 |
+
gr.update(
|
244 |
+
minimum=0.1,
|
245 |
+
maximum=3.0,
|
246 |
+
value=1.0,
|
247 |
+
step=0.05
|
248 |
+
),
|
249 |
+
[] # Empty list for molecule visualization
|
250 |
+
]
|
251 |
+
|
252 |
+
def update_simulation_with_details(molecule_choice: str, scale_factor: float) -> tuple:
|
253 |
+
"""Enhanced simulation update with parameter count and Hamiltonian terms."""
|
254 |
+
try:
|
255 |
+
# Get molecule data
|
256 |
+
molecule_data = molecules[molecule_choice]
|
257 |
+
|
258 |
+
# Run VQE simulation
|
259 |
+
results = run_vqe_simulation(
|
260 |
+
molecule_data=molecule_data,
|
261 |
+
scale_factor=scale_factor
|
262 |
+
)
|
263 |
+
|
264 |
+
# Create plots
|
265 |
+
convergence_plot = plot_convergence(results)
|
266 |
+
|
267 |
+
# Format simulation results with additional details
|
268 |
+
simulation_html = f"""
|
269 |
+
<div style='padding: 10px; background-color: #f5f5f5; border-radius: 5px;'>
|
270 |
+
<h3>Simulation Results:</h3>
|
271 |
+
<ul style='list-style-type: none; padding-left: 0;'>
|
272 |
+
<li><b>Final Energy:</b> {results['final_energy']:.6f} Hartree</li>
|
273 |
+
<li><b>Parameter Count:</b> {results.get('parameter_count', 'N/A')}</li>
|
274 |
+
<li><b>Hamiltonian Terms:</b> {results.get('hamiltonian_terms', 'N/A')}</li>
|
275 |
+
<li><b>Optimization Status:</b> {'Success' if results.get('success', False) else 'Failed'}</li>
|
276 |
+
<li><b>Iterations:</b> {len(results['history'])}</li>
|
277 |
+
<li><b>Scale Factor:</b> {scale_factor:.2f}</li>
|
278 |
+
</ul>
|
279 |
+
<p><i>{results.get('message', '')}</i></p>
|
280 |
+
</div>
|
281 |
+
"""
|
282 |
+
|
283 |
+
return simulation_html, convergence_plot
|
284 |
+
|
285 |
+
except Exception as e:
|
286 |
+
import traceback
|
287 |
+
error_msg = f"<div style='color: red;'>Error in simulation:<br>{str(e)}</div>"
|
288 |
+
return error_msg, None
|
289 |
+
|
290 |
+
# Update parameters and 3D view when molecule changes
|
291 |
+
molecule_choice.change(
|
292 |
+
fn=update_molecule_info,
|
293 |
+
inputs=[molecule_choice],
|
294 |
+
outputs=[params_display, scale_factor, molecule_viz]
|
295 |
+
)
|
296 |
+
|
297 |
+
# Run simulation when button is clicked
|
298 |
+
run_button.click(
|
299 |
+
fn=update_simulation_with_details,
|
300 |
+
inputs=[molecule_choice, scale_factor],
|
301 |
+
outputs=[simulation_results, convergence_plot]
|
302 |
+
)
|
303 |
+
|
304 |
+
return demo
|
305 |
+
|
306 |
+
if __name__ == "__main__":
|
307 |
+
demo = create_interface()
|
308 |
+
demo.launch(share=False)
|
docref/Example_cudaq_logical_aim_agk.py
ADDED
@@ -0,0 +1,712 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: utf-8 -*-
|
2 |
+
"""Copy of cudaq_logical_aim_agk.ipynb
|
3 |
+
|
4 |
+
Automatically generated by Colab.
|
5 |
+
|
6 |
+
Original file is located at
|
7 |
+
https://colab.research.google.com/drive/1Jsf9Le8eoCJgddNzYvfQO4E7g--N-DyC
|
8 |
+
|
9 |
+
# AIM: Logical Anderson Impurity Model on Sqale using CUDA-Q
|
10 |
+
|
11 |
+
[](https://colab.research.google.com/github/Infleqtion/client-superstaq/blob/main/docs/source/apps/cudaq_logical_aim.ipynb) [](https://mybinder.org/v2/gh/Infleqtion/client-superstaq/HEAD?labpath=docs/source/apps/cudaq_logical_aim.ipynb)
|
12 |
+
|
13 |
+
Ground state quantum chemistry—computing total energies of molecular configurations to within chemical accuracy—is perhaps the most highly-touted industrial application of fault-tolerant quantum computers. Strongly correlated materials, for example, are particularly interesting, and tools like dynamical mean-field theory (DMFT) allow one to account for the effect of their strong, localized electronic correlations. These DMFT models help predict material properties by approximating the system as a single site impurity inside a “bath” that encompasses the rest of the system. Simulating such dynamics can be a tough task using classical methods, but can be done efficiently on a quantum computer via quantum simulation.
|
14 |
+
|
15 |
+
In this notebook, we showcase a workflow for preparing the ground state of the minimal single-impurity Anderson model (SIAM) using the Hamiltonian Variational Ansatz for a range of realistic parameters. As a first step towards running DMFT on a fault-tolerant quantum computer, we will use logical qubits encoded in the `[[4, 2, 2]]` code. Using this workflow, we will obtain the ground state energy estimates via noisy simulation, and then also execute the corresponding optimized circuits on Infleqtion's gate-based neutral-atom quantum computer, making the benefits of logical qubits apparent. More details can be found in our [paper](https://arxiv.org/abs/2412.07670).
|
16 |
+
|
17 |
+
[agk] - 2024-12-23 Reference chats to understand AIM model - https://gemini.google.com/app/fce0538c8dc2b6ac and https://x.com/i/grok/share/AV9UkWb4LNtfqm1HfLPEQ78HG
|
18 |
+
|
19 |
+
This demo notebook uses CUDA-Q (`cudaq`) and a CUDA-QX library, `cudaq-solvers`; let us first begin by importing (and installing as needed) these packages:
|
20 |
+
"""
|
21 |
+
|
22 |
+
# Commented out IPython magic to ensure Python compatibility.
|
23 |
+
try:
|
24 |
+
import cudaq_solvers as solvers
|
25 |
+
import cudaq
|
26 |
+
import matplotlib.pyplot as plt
|
27 |
+
except ImportError:
|
28 |
+
print("Installing required packages...")
|
29 |
+
# %pip install --quiet 'cudaq-solvers' 'matplotlib'
|
30 |
+
print("Installed `cudaq`, `cudaq-solvers`, and `matplotlib` packages.")
|
31 |
+
print("You may need to restart the kernel to import newly installed packages.")
|
32 |
+
import cudaq_solvers as solvers
|
33 |
+
import cudaq
|
34 |
+
import matplotlib.pyplot as plt
|
35 |
+
|
36 |
+
from collections.abc import Mapping, Sequence
|
37 |
+
import numpy as np
|
38 |
+
from scipy.optimize import minimize
|
39 |
+
import os
|
40 |
+
|
41 |
+
from IPython.core.display import HTML
|
42 |
+
print("restarting Kernel")
|
43 |
+
HTML("<script>Jupyter.notebook.kernel.restart()</script>")
|
44 |
+
print("Kernel restarted. Probably, I don't know... I'm not magic")
|
45 |
+
|
46 |
+
"""## Performing logical Variational Quantum Eigensolver (VQE) with CUDA-QX
|
47 |
+
|
48 |
+
To prepare our ground state quantum Anderson impurity model circuits (referred to as AIM circuits in this notebook for short), we use VQE to train an ansatz to minimize a Hamiltonian and obtain optimal angles that can be used to set the AIM circuits. As described in our [paper](https://arxiv.org/abs/2412.07670), the associated restricted Hamiltonian for our SIAM can be reduced to,
|
49 |
+
$$
|
50 |
+
\begin{equation}
|
51 |
+
H_{(U, V)} = U (Z_0 Z_2 - 1) / 4 + V (X_0 + X_2),
|
52 |
+
\end{equation}
|
53 |
+
$$
|
54 |
+
where $U$ is the Coulomb interaction and $V$ the hybridization strength. In this notebook workflow, we will optimize over a 2-dimensional grid of Hamiltonian parameter values, namely $U\in \{1, 5, 9\}$ and $V\in \{-9, -1, 7\}$ (with all values assumed to be in units of eV), to ensure that the ansatz is generally trainable and expressive, and obtain 9 different circuit layers identified by the key $(U, V)$. We will simulate the VQE on GPU (or optionally on CPU if you do not have GPU access), enabled by CUDA-Q, in the absence of noise:
|
55 |
+
"""
|
56 |
+
|
57 |
+
if cudaq.num_available_gpus() == 0:
|
58 |
+
cudaq.set_target("qpp-cpu", option="fp64")
|
59 |
+
|
60 |
+
print("Using CPU no Cuda gpu found")
|
61 |
+
else:
|
62 |
+
cudaq.set_target("nvidia", option="fp64")
|
63 |
+
print("Using GPU")
|
64 |
+
|
65 |
+
"""This workflow can be easily defined in CUDA-Q as shown in the cell below, using the CUDA-QX Solvers library (which accelerates quantum algorithms like the VQE):"""
|
66 |
+
|
67 |
+
def ansatz(n_qubits: int) -> cudaq.Kernel:
|
68 |
+
# Create a CUDA-Q parameterized kernel
|
69 |
+
paramterized_ansatz, variational_angles = cudaq.make_kernel(list)
|
70 |
+
qubits = paramterized_ansatz.qalloc(n_qubits)
|
71 |
+
|
72 |
+
# Using |+> as the initial state:
|
73 |
+
paramterized_ansatz.h(qubits[0])
|
74 |
+
paramterized_ansatz.cx(qubits[0], qubits[1])
|
75 |
+
|
76 |
+
paramterized_ansatz.rx(variational_angles[0], qubits[0])
|
77 |
+
paramterized_ansatz.cx(qubits[0], qubits[1])
|
78 |
+
paramterized_ansatz.rz(variational_angles[1], qubits[1])
|
79 |
+
paramterized_ansatz.cx(qubits[0], qubits[1])
|
80 |
+
return paramterized_ansatz
|
81 |
+
|
82 |
+
|
83 |
+
def run_logical_vqe(cudaq_hamiltonian: cudaq.SpinOperator) -> tuple[float, list[float]]:
|
84 |
+
# Set seed for easier reproduction
|
85 |
+
np.random.seed(42)
|
86 |
+
|
87 |
+
# Initial angles for the optimizer
|
88 |
+
init_angles = np.random.random(2) * 1e-1
|
89 |
+
|
90 |
+
# Obtain CUDA-Q Ansatz
|
91 |
+
num_qubits = cudaq_hamiltonian.get_qubit_count()
|
92 |
+
variational_kernel = ansatz(num_qubits)
|
93 |
+
|
94 |
+
# Perform VQE optimization
|
95 |
+
energy, params, _ = solvers.vqe(
|
96 |
+
variational_kernel,
|
97 |
+
cudaq_hamiltonian,
|
98 |
+
init_angles,
|
99 |
+
optimizer=minimize,
|
100 |
+
method="SLSQP",
|
101 |
+
tol=1e-10,
|
102 |
+
)
|
103 |
+
return energy, params
|
104 |
+
|
105 |
+
"""## Constructing circuits in the `[[4,2,2]]` encoding
|
106 |
+
|
107 |
+
The `[[4,2,2]]` code is a quantum error detection code that uses four physical qubits to encode two logical qubits. In this notebook, we will construct two variants of quantum circuits: physical (bare, unencoded) and logical (encoded). These circuits will be informed by the Hamiltonian Variational Ansatz described earlier. To measure all the terms in our Hamiltonian, we will measure the data qubits in both the $Z$- and $X$-basis, as allowed by the `[[4,2,2]]` logical gateset. Full details on the circuit constructions are outlined in our [paper](https://arxiv.org/abs/2412.07670).
|
108 |
+
|
109 |
+
Below, we create functions to build our CUDA-Q AIM circuits, both physical and logical versions. As we consider noisy simulation in this notebook, we will include some noisy gates. Here, for simplicity, we will just register a custom identity gate -- to be later used as a noisy operation to model readout error:
|
110 |
+
"""
|
111 |
+
|
112 |
+
cudaq.register_operation("meas_id", np.identity(2))
|
113 |
+
|
114 |
+
def aim_physical_circuit(
|
115 |
+
angles: list[float], basis: str, *, ignore_meas_id: bool = False
|
116 |
+
) -> cudaq.Kernel:
|
117 |
+
kernel = cudaq.make_kernel()
|
118 |
+
qubits = kernel.qalloc(2)
|
119 |
+
|
120 |
+
# Bell state prep
|
121 |
+
kernel.h(qubits[0])
|
122 |
+
kernel.cx(qubits[0], qubits[1])
|
123 |
+
|
124 |
+
# Rx Gate
|
125 |
+
kernel.rx(angles[0], qubits[0])
|
126 |
+
|
127 |
+
# ZZ rotation
|
128 |
+
kernel.cx(qubits[0], qubits[1])
|
129 |
+
kernel.rz(angles[1], qubits[1])
|
130 |
+
kernel.cx(qubits[0], qubits[1])
|
131 |
+
|
132 |
+
if basis == "z_basis":
|
133 |
+
if not ignore_meas_id:
|
134 |
+
kernel.for_loop(
|
135 |
+
start=0, stop=2, function=lambda q_idx: getattr(kernel, "meas_id")(qubits[q_idx])
|
136 |
+
)
|
137 |
+
kernel.mz(qubits)
|
138 |
+
elif basis == "x_basis":
|
139 |
+
kernel.h(qubits)
|
140 |
+
if not ignore_meas_id:
|
141 |
+
kernel.for_loop(
|
142 |
+
start=0, stop=2, function=lambda q_idx: getattr(kernel, "meas_id")(qubits[q_idx])
|
143 |
+
)
|
144 |
+
kernel.mz(qubits)
|
145 |
+
else:
|
146 |
+
raise ValueError("Unsupported basis provided:", basis)
|
147 |
+
return kernel
|
148 |
+
|
149 |
+
def aim_logical_circuit(
|
150 |
+
angles: list[float], basis: str, *, ignore_meas_id: bool = False
|
151 |
+
) -> cudaq.Kernel:
|
152 |
+
kernel = cudaq.make_kernel()
|
153 |
+
qubits = kernel.qalloc(6)
|
154 |
+
|
155 |
+
kernel.for_loop(start=0, stop=3, function=lambda idx: kernel.h(qubits[idx]))
|
156 |
+
kernel.cx(qubits[1], qubits[4])
|
157 |
+
kernel.cx(qubits[2], qubits[3])
|
158 |
+
kernel.cx(qubits[0], qubits[1])
|
159 |
+
kernel.cx(qubits[0], qubits[3])
|
160 |
+
|
161 |
+
# Rx teleportation
|
162 |
+
kernel.rx(angles[0], qubits[0])
|
163 |
+
|
164 |
+
kernel.cx(qubits[0], qubits[1])
|
165 |
+
kernel.cx(qubits[0], qubits[3])
|
166 |
+
kernel.h(qubits[0])
|
167 |
+
|
168 |
+
if basis == "z_basis":
|
169 |
+
if not ignore_meas_id:
|
170 |
+
kernel.for_loop(
|
171 |
+
start=0, stop=5, function=lambda idx: getattr(kernel, "meas_id")(qubits[idx])
|
172 |
+
)
|
173 |
+
kernel.mz(qubits)
|
174 |
+
elif basis == "x_basis":
|
175 |
+
# ZZ rotation and teleportation
|
176 |
+
kernel.cx(qubits[3], qubits[5])
|
177 |
+
kernel.cx(qubits[2], qubits[5])
|
178 |
+
kernel.rz(angles[1], qubits[5])
|
179 |
+
kernel.cx(qubits[1], qubits[5])
|
180 |
+
kernel.cx(qubits[4], qubits[5])
|
181 |
+
kernel.for_loop(start=1, stop=5, function=lambda idx: kernel.h(qubits[idx]))
|
182 |
+
if not ignore_meas_id:
|
183 |
+
kernel.for_loop(
|
184 |
+
start=0, stop=6, function=lambda idx: getattr(kernel, "meas_id")(qubits[idx])
|
185 |
+
)
|
186 |
+
kernel.mz(qubits)
|
187 |
+
else:
|
188 |
+
raise ValueError("Unsupported basis provided:", basis)
|
189 |
+
return kernel
|
190 |
+
|
191 |
+
"""With the circuit definitions above, we can now define a function that automatically runs the VQE and constructs a dictionary containing all the AIM circuits we want to submit to hardware (or noisily simulate):"""
|
192 |
+
|
193 |
+
def generate_circuit_set(ignore_meas_id: bool = False) -> object:
|
194 |
+
u_vals = [1, 5, 9]
|
195 |
+
v_vals = [-9, -1, 7]
|
196 |
+
circuit_dict = {}
|
197 |
+
for u in u_vals:
|
198 |
+
for v in v_vals:
|
199 |
+
qubit_hamiltonian = (
|
200 |
+
0.25 * u * cudaq.spin.z(0) * cudaq.spin.z(1)
|
201 |
+
- 0.25 * u
|
202 |
+
+ v * cudaq.spin.x(0)
|
203 |
+
+ v * cudaq.spin.x(1)
|
204 |
+
)
|
205 |
+
_, opt_params = run_logical_vqe(qubit_hamiltonian)
|
206 |
+
angles = [float(angle) for angle in opt_params]
|
207 |
+
print(f"Computed optimal angles={angles} for U={u}, V={v}")
|
208 |
+
|
209 |
+
tmp_physical_dict = {}
|
210 |
+
tmp_logical_dict = {}
|
211 |
+
for basis in ("z_basis", "x_basis"):
|
212 |
+
tmp_physical_dict[basis] = aim_physical_circuit(
|
213 |
+
angles, basis, ignore_meas_id=ignore_meas_id
|
214 |
+
)
|
215 |
+
tmp_logical_dict[basis] = aim_logical_circuit(
|
216 |
+
angles, basis, ignore_meas_id=ignore_meas_id
|
217 |
+
)
|
218 |
+
|
219 |
+
circuit_dict[f"{u}:{v}"] = {
|
220 |
+
"physical": tmp_physical_dict,
|
221 |
+
"logical": tmp_logical_dict,
|
222 |
+
}
|
223 |
+
print("\nFinished building optimized circuits!")
|
224 |
+
return circuit_dict
|
225 |
+
|
226 |
+
sim_circuit_dict = generate_circuit_set()
|
227 |
+
circuit_layers = sim_circuit_dict.keys()
|
228 |
+
|
229 |
+
"""## Setting up submission and decoding workflow
|
230 |
+
|
231 |
+
In this section, we define various helper functions that will play a role in generating the associated energies of the AIM circuits based on the circuit samples (in the different bases), as well as decode the logical circuits with post-selection informed by the `[[4,2,2]]` code:
|
232 |
+
"""
|
233 |
+
|
234 |
+
def _num_qubits(counts: Mapping[str, float]) -> int:
|
235 |
+
for key in counts:
|
236 |
+
if key.isdecimal():
|
237 |
+
return len(key)
|
238 |
+
return 0
|
239 |
+
|
240 |
+
|
241 |
+
def process_counts(
|
242 |
+
counts: Mapping[str, float],
|
243 |
+
data_qubits: Sequence[int],
|
244 |
+
flag_qubits: Sequence[int] = (),
|
245 |
+
) -> dict[str, float]:
|
246 |
+
new_data: dict[str, float] = {}
|
247 |
+
for key, val in counts.items():
|
248 |
+
if not all(key[i] == "0" for i in flag_qubits):
|
249 |
+
continue
|
250 |
+
|
251 |
+
new_key = "".join(key[i] for i in data_qubits)
|
252 |
+
|
253 |
+
if not set("01").issuperset(new_key):
|
254 |
+
continue
|
255 |
+
|
256 |
+
new_data.setdefault(new_key, 0)
|
257 |
+
new_data[new_key] += val
|
258 |
+
|
259 |
+
return new_data
|
260 |
+
|
261 |
+
|
262 |
+
def decode(counts: Mapping[str, float]) -> dict[str, float]:
|
263 |
+
"""Decode physical counts into logical counts. Should be called after `process_counts`."""
|
264 |
+
|
265 |
+
if not counts:
|
266 |
+
return {}
|
267 |
+
|
268 |
+
num_qubits = _num_qubits(counts)
|
269 |
+
assert num_qubits % 4 == 0
|
270 |
+
|
271 |
+
physical_to_logical = {
|
272 |
+
"0000": "00",
|
273 |
+
"1111": "00",
|
274 |
+
"0011": "01",
|
275 |
+
"1100": "01",
|
276 |
+
"0101": "10",
|
277 |
+
"1010": "10",
|
278 |
+
"0110": "11",
|
279 |
+
"1001": "11",
|
280 |
+
}
|
281 |
+
|
282 |
+
new_data: dict[str, float] = {}
|
283 |
+
for key, val in counts.items():
|
284 |
+
physical_keys = [key[i : i + 4] for i in range(0, num_qubits, 4)]
|
285 |
+
logical_keys = [physical_to_logical.get(physical_key) for physical_key in physical_keys]
|
286 |
+
if None not in logical_keys:
|
287 |
+
new_key = "".join(logical_keys)
|
288 |
+
new_data.setdefault(new_key, 0)
|
289 |
+
new_data[new_key] += val
|
290 |
+
|
291 |
+
return new_data
|
292 |
+
|
293 |
+
|
294 |
+
def ev_x(counts: Mapping[str, float]) -> float:
|
295 |
+
ev = 0.0
|
296 |
+
|
297 |
+
for k, val in counts.items():
|
298 |
+
ev += val * ((-1) ** int(k[0]) + (-1) ** int(k[1]))
|
299 |
+
|
300 |
+
total = sum(counts.values())
|
301 |
+
ev /= total
|
302 |
+
return ev
|
303 |
+
|
304 |
+
|
305 |
+
def ev_xx(counts: Mapping[str, float]) -> float:
|
306 |
+
ev = 0.0
|
307 |
+
|
308 |
+
for k, val in counts.items():
|
309 |
+
ev += val * (-1) ** k.count("1")
|
310 |
+
|
311 |
+
total = sum(counts.values())
|
312 |
+
ev /= total
|
313 |
+
return ev
|
314 |
+
|
315 |
+
|
316 |
+
def ev_zz(counts: Mapping[str, float]) -> float:
|
317 |
+
ev = 0.0
|
318 |
+
|
319 |
+
for k, val in counts.items():
|
320 |
+
ev += val * (-1) ** k.count("1")
|
321 |
+
|
322 |
+
total = sum(counts.values())
|
323 |
+
ev /= total
|
324 |
+
return ev
|
325 |
+
|
326 |
+
|
327 |
+
def aim_logical_energies(
|
328 |
+
data_ordering: object, counts_list: Sequence[dict[str, float]]
|
329 |
+
) -> tuple[dict[tuple[int, int], float], dict[tuple[int, int], float]]:
|
330 |
+
counts_data = {
|
331 |
+
data_ordering[i]: decode(
|
332 |
+
process_counts(
|
333 |
+
counts,
|
334 |
+
data_qubits=[1, 2, 3, 4],
|
335 |
+
flag_qubits=[0, 5],
|
336 |
+
)
|
337 |
+
)
|
338 |
+
for i, counts in enumerate(counts_list)
|
339 |
+
}
|
340 |
+
return _aim_energies(counts_data)
|
341 |
+
|
342 |
+
|
343 |
+
def aim_physical_energies(
|
344 |
+
data_ordering: object, counts_list: Sequence[dict[str, float]]
|
345 |
+
) -> tuple[dict[tuple[int, int], float], dict[tuple[int, int], float]]:
|
346 |
+
counts_data = {
|
347 |
+
data_ordering[i]: process_counts(
|
348 |
+
counts,
|
349 |
+
data_qubits=[0, 1],
|
350 |
+
)
|
351 |
+
for i, counts in enumerate(counts_list)
|
352 |
+
}
|
353 |
+
return _aim_energies(counts_data)
|
354 |
+
|
355 |
+
|
356 |
+
def _aim_energies(
|
357 |
+
counts_data: Mapping[tuple[int, int, str], dict[str, float]],
|
358 |
+
) -> tuple[dict[tuple[int, int], float], dict[tuple[int, int], float]]:
|
359 |
+
evxs: dict[tuple[int, int], float] = {}
|
360 |
+
evxxs: dict[tuple[int, int], float] = {}
|
361 |
+
evzzs: dict[tuple[int, int], float] = {}
|
362 |
+
totals: dict[tuple[int, int], float] = {}
|
363 |
+
|
364 |
+
for key, counts in counts_data.items():
|
365 |
+
h_params, basis = key
|
366 |
+
key_a, key_b = h_params.split(":")
|
367 |
+
u, v = int(key_a), int(key_b)
|
368 |
+
if basis.startswith("x"):
|
369 |
+
evxs[u, v] = ev_x(counts)
|
370 |
+
evxxs[u, v] = ev_xx(counts)
|
371 |
+
else:
|
372 |
+
evzzs[u, v] = ev_zz(counts)
|
373 |
+
|
374 |
+
totals.setdefault((u, v), 0)
|
375 |
+
totals[u, v] += sum(counts.values())
|
376 |
+
|
377 |
+
energies = {}
|
378 |
+
uncertainties = {}
|
379 |
+
for u, v in evxs.keys() & evzzs.keys():
|
380 |
+
string_key = f"{u}:{v}"
|
381 |
+
energies[string_key] = u * (evzzs[u, v] - 1) / 4 + v * evxs[u, v]
|
382 |
+
|
383 |
+
uncertainty_xx = 2 * v**2 * (1 + evxxs[u, v]) - u * v * evxs[u, v] / 2
|
384 |
+
uncertainty_zz = u**2 * (1 - evzzs[u, v]) / 2
|
385 |
+
|
386 |
+
uncertainties[string_key] = np.sqrt(
|
387 |
+
(uncertainty_zz + uncertainty_xx - energies[string_key] ** 2) / (totals[u, v] / 2)
|
388 |
+
)
|
389 |
+
|
390 |
+
return energies, uncertainties
|
391 |
+
|
392 |
+
|
393 |
+
def _get_energy_diff(
|
394 |
+
bf_energies: dict[str, float],
|
395 |
+
physical_energies: dict[str, float],
|
396 |
+
logical_energies: dict[str, float],
|
397 |
+
) -> tuple[list[float], list[float]]:
|
398 |
+
physical_energy_diff = []
|
399 |
+
logical_energy_diff = []
|
400 |
+
|
401 |
+
# Data ordering following `bf_energies` keys
|
402 |
+
for layer in bf_energies.keys():
|
403 |
+
physical_sim_energy = physical_energies[layer]
|
404 |
+
logical_sim_energy = logical_energies[layer]
|
405 |
+
true_energy = bf_energies[layer]
|
406 |
+
u, v = layer.split(":")
|
407 |
+
print(f"Layer=({u}, {v}) has brute-force energy of: {true_energy}")
|
408 |
+
print(f"Physical circuit of layer=({u}, {v}) got an energy of: {physical_sim_energy}")
|
409 |
+
print(f"Logical circuit of layer=({u}, {v}) got an energy of: {logical_sim_energy}")
|
410 |
+
print("-" * 72)
|
411 |
+
|
412 |
+
if logical_sim_energy < physical_sim_energy:
|
413 |
+
print("Logical circuit achieved the lower energy!")
|
414 |
+
else:
|
415 |
+
print("Physical circuit achieved the lower energy")
|
416 |
+
print("-" * 72, "\n")
|
417 |
+
|
418 |
+
physical_energy_diff.append(
|
419 |
+
-1 * (true_energy - physical_sim_energy)
|
420 |
+
) # Multiply by -1 since negative energies
|
421 |
+
logical_energy_diff.append(-1 * (true_energy - logical_sim_energy))
|
422 |
+
return physical_energy_diff, logical_energy_diff
|
423 |
+
|
424 |
+
def submit_aim_circuits(
|
425 |
+
circuit_dict: object,
|
426 |
+
*,
|
427 |
+
folder_path: str = "future_aim_results",
|
428 |
+
shots_count: int = 1000,
|
429 |
+
noise_model: cudaq.mlir._mlir_libs._quakeDialects.cudaq_runtime.NoiseModel | None = None,
|
430 |
+
run_async: bool = False,
|
431 |
+
) -> dict[str, list[dict[str, int]]] | None:
|
432 |
+
if run_async:
|
433 |
+
os.makedirs(folder_path, exist_ok=True)
|
434 |
+
else:
|
435 |
+
aim_results = {"physical": [], "logical": []}
|
436 |
+
|
437 |
+
for layer in circuit_dict.keys():
|
438 |
+
if run_async:
|
439 |
+
print(f"Posting circuits associated with layer=('{layer}')")
|
440 |
+
else:
|
441 |
+
print(f"Running circuits associated with layer=('{layer}')")
|
442 |
+
|
443 |
+
for basis in ("z_basis", "x_basis"):
|
444 |
+
if run_async:
|
445 |
+
u, v = layer.split(":")
|
446 |
+
|
447 |
+
tmp_physical_results = cudaq.sample_async(
|
448 |
+
circuit_dict[layer]["physical"][basis], shots_count=shots_count
|
449 |
+
)
|
450 |
+
file = open(f"{folder_path}/physical_{basis}_job_u={u}_v={v}_result.txt", "w")
|
451 |
+
file.write(str(tmp_physical_results))
|
452 |
+
file.close()
|
453 |
+
|
454 |
+
tmp_logical_results = cudaq.sample_async(
|
455 |
+
circuit_dict[layer]["logical"][basis], shots_count=shots_count
|
456 |
+
)
|
457 |
+
file = open(f"{folder_path}/logical_{basis}_job_u={u}_v={v}_result.txt", "w")
|
458 |
+
file.write(str(tmp_logical_results))
|
459 |
+
file.close()
|
460 |
+
else:
|
461 |
+
tmp_physical_results = cudaq.sample(
|
462 |
+
circuit_dict[layer]["physical"][basis],
|
463 |
+
shots_count=shots_count,
|
464 |
+
noise_model=noise_model,
|
465 |
+
)
|
466 |
+
tmp_logical_results = cudaq.sample(
|
467 |
+
circuit_dict[layer]["logical"][basis],
|
468 |
+
shots_count=shots_count,
|
469 |
+
noise_model=noise_model,
|
470 |
+
)
|
471 |
+
aim_results["physical"].append({k: v for k, v in tmp_physical_results.items()})
|
472 |
+
aim_results["logical"].append({k: v for k, v in tmp_logical_results.items()})
|
473 |
+
if not run_async:
|
474 |
+
print("\nCompleted all circuit sampling!")
|
475 |
+
return aim_results
|
476 |
+
else:
|
477 |
+
print("\nAll circuits submitted for async sampling!")
|
478 |
+
|
479 |
+
def _get_async_results(
|
480 |
+
layers: object, *, folder_path: str = "future_aim_results"
|
481 |
+
) -> dict[str, list[dict[str, int]]]:
|
482 |
+
aim_results = {"physical": [], "logical": []}
|
483 |
+
for layer in layers:
|
484 |
+
print(f"Retrieving all circuits counts associated with layer=('{layer}')")
|
485 |
+
u, v = layer.split(":")
|
486 |
+
for basis in ("z_basis", "x_basis"):
|
487 |
+
file = open(f"{folder_path}/physical_{basis}_job_u={u}_v={v}_result.txt", "r")
|
488 |
+
tmp_physical_results = cudaq.AsyncSampleResult(str(file.read()))
|
489 |
+
physical_counts = tmp_physical_results.get()
|
490 |
+
|
491 |
+
file = open(f"{folder_path}/logical_{basis}_job_u={u}_v={v}_result.txt", "r")
|
492 |
+
tmp_logical_results = cudaq.AsyncSampleResult(str(file.read()))
|
493 |
+
logical_counts = tmp_logical_results.get()
|
494 |
+
|
495 |
+
aim_results["physical"].append({k: v for k, v in physical_counts.items()})
|
496 |
+
aim_results["logical"].append({k: v for k, v in logical_counts.items()})
|
497 |
+
|
498 |
+
print("\nObtained all circuit samples!")
|
499 |
+
return aim_results
|
500 |
+
|
501 |
+
"""## Running a CUDA-Q noisy simulation
|
502 |
+
|
503 |
+
In this section, we will first explore the performance of the physical and logical circuits under the influence of a device noise model. This will help us predict experimental results, as well as understand the dominant error sources at play. Such a simulation can be achieved via CUDA-Q's density matrix simulator:
|
504 |
+
"""
|
505 |
+
|
506 |
+
cudaq.reset_target()
|
507 |
+
cudaq.set_target("density-matrix-cpu")
|
508 |
+
|
509 |
+
|
510 |
+
|
511 |
+
def get_device_noise(
|
512 |
+
depolar_prob_1q: float,
|
513 |
+
depolar_prob_2q: float,
|
514 |
+
*,
|
515 |
+
readout_error_prob: float | None = None,
|
516 |
+
custom_gates: list[str] | None = None,
|
517 |
+
) -> cudaq.mlir._mlir_libs._quakeDialects.cudaq_runtime.NoiseModel:
|
518 |
+
noise = cudaq.NoiseModel()
|
519 |
+
depolar_noise = cudaq.DepolarizationChannel(depolar_prob_1q)
|
520 |
+
|
521 |
+
noisy_ops = ["z", "s", "x", "h", "rx", "rz"]
|
522 |
+
for op in noisy_ops:
|
523 |
+
noise.add_all_qubit_channel(op, depolar_noise)
|
524 |
+
|
525 |
+
if custom_gates:
|
526 |
+
custom_depolar_channel = cudaq.DepolarizationChannel(depolar_prob_1q)
|
527 |
+
for op in custom_gates:
|
528 |
+
noise.add_all_qubit_channel(op, custom_depolar_channel)
|
529 |
+
|
530 |
+
# Two qubit depolarization error
|
531 |
+
p_0 = 1 - depolar_prob_2q
|
532 |
+
p_1 = np.sqrt((1 - p_0**2) / 3)
|
533 |
+
|
534 |
+
k0 = np.array(
|
535 |
+
[[p_0, 0.0, 0.0, 0.0], [0.0, p_0, 0.0, 0.0], [0.0, 0.0, p_0, 0.0], [0.0, 0.0, 0.0, p_0]],
|
536 |
+
dtype=np.complex128,
|
537 |
+
)
|
538 |
+
k1 = np.array(
|
539 |
+
[[0.0, 0.0, p_1, 0.0], [0.0, 0.0, 0.0, p_1], [p_1, 0.0, 0.0, 0.0], [0.0, p_1, 0.0, 0.0]],
|
540 |
+
dtype=np.complex128,
|
541 |
+
)
|
542 |
+
k2 = np.array(
|
543 |
+
[
|
544 |
+
[0.0, 0.0, -1j * p_1, 0.0],
|
545 |
+
[0.0, 0.0, 0.0, -1j * p_1],
|
546 |
+
[1j * p_1, 0.0, 0.0, 0.0],
|
547 |
+
[0.0, 1j * p_1, 0.0, 0.0],
|
548 |
+
],
|
549 |
+
dtype=np.complex128,
|
550 |
+
)
|
551 |
+
k3 = np.array(
|
552 |
+
[[p_1, 0.0, 0.0, 0.0], [0.0, p_1, 0.0, 0.0], [0.0, 0.0, -p_1, 0.0], [0.0, 0.0, 0.0, -p_1]],
|
553 |
+
dtype=np.complex128,
|
554 |
+
)
|
555 |
+
kraus_channel = cudaq.KrausChannel([k0, k1, k2, k3])
|
556 |
+
|
557 |
+
noise.add_all_qubit_channel("cz", kraus_channel)
|
558 |
+
noise.add_all_qubit_channel("cx", kraus_channel)
|
559 |
+
|
560 |
+
if readout_error_prob is not None:
|
561 |
+
# Readout error modeled with a Bit flip channel on identity before measurement
|
562 |
+
bit_flip = cudaq.BitFlipChannel(readout_error_prob)
|
563 |
+
noise.add_all_qubit_channel("meas_id", bit_flip)
|
564 |
+
return noise
|
565 |
+
|
566 |
+
"""Finally, with our example noise model defined above, we can synchronously & noisily sample all of our AIM circuits by passing `noise_model=cudaq_noise_model` to the workflow containing function `submit_aim_circuits()`:"""
|
567 |
+
|
568 |
+
# Example parameters that can model execution on hardware at the high, simulation, level:
|
569 |
+
# Take single-qubit gate depolarization rate: ~0.2% or better (fidelity ≥99.8%)
|
570 |
+
# Take two-qubit gate depolarization rate: ~1–2% (fidelity ~98–99%)
|
571 |
+
cudaq_noise_model = get_device_noise(0.002, 0.02, readout_error_prob=0.02)
|
572 |
+
|
573 |
+
|
574 |
+
|
575 |
+
aim_sim_data = submit_aim_circuits(sim_circuit_dict, noise_model=cudaq_noise_model)
|
576 |
+
|
577 |
+
data_ordering = []
|
578 |
+
for key in circuit_layers:
|
579 |
+
for basis in ("z_basis", "x_basis"):
|
580 |
+
data_ordering.append((key, basis))
|
581 |
+
|
582 |
+
sim_physical_energies, sim_physical_uncertainties = aim_physical_energies(
|
583 |
+
data_ordering, aim_sim_data["physical"]
|
584 |
+
)
|
585 |
+
|
586 |
+
sim_logical_energies, sim_logical_uncertainties = aim_logical_energies(
|
587 |
+
data_ordering, aim_sim_data["logical"]
|
588 |
+
)
|
589 |
+
|
590 |
+
"""To analyze our simulated energy results in the above cells, we will compare them to the brute-force computed exact ground state energies for the AIM Hamiltonian. For simplicity, these are already stored in the dictionary `bf_energies` below:"""
|
591 |
+
|
592 |
+
bf_energies = {
|
593 |
+
"1:-9": -18.251736027394713,
|
594 |
+
"1:-1": -2.265564437074638,
|
595 |
+
"1:7": -14.252231964940428,
|
596 |
+
"5:-9": -19.293350575766127,
|
597 |
+
"5:-1": -3.608495283014149,
|
598 |
+
"5:7": -15.305692796870582,
|
599 |
+
"9:-9": -20.39007993367173,
|
600 |
+
"9:-1": -5.260398644698076,
|
601 |
+
"9:7": -16.429650912487233,
|
602 |
+
}
|
603 |
+
|
604 |
+
"""With the above metric, we can assess the performance of the logical circuits against the physical circuits by considering how far away the respective energies are from the brute-force expected energies. The cell below computes these energy deviations:"""
|
605 |
+
|
606 |
+
sim_physical_energy_diff, sim_logical_energy_diff = _get_energy_diff(
|
607 |
+
bf_energies, sim_physical_energies, sim_logical_energies
|
608 |
+
)
|
609 |
+
|
610 |
+
"""Both physical and logical circuits were subject to the same noise model, but the `[[4,2,2]]` provides additional information that can help overcome some errors. Visualizing the computed energy differences from the above the cell, our noisy simulation provides a preview of the benefits logical qubits can offer:"""
|
611 |
+
|
612 |
+
# Diagnostic block because next code block was getting stuck and throwing many errors
|
613 |
+
print("=== Creating layer labels ===")
|
614 |
+
layer_labels = [(int(key.split(":")[0]), int(key.split(":")[1])) for key in bf_energies.keys()]
|
615 |
+
print("=== ge nerating strings ===")
|
616 |
+
plot_labels = [str(item) for item in layer_labels]
|
617 |
+
|
618 |
+
print("=== Variable Type Check ===")
|
619 |
+
print(f"Type of sim_physical_energy_diff: {type(sim_physical_energy_diff)}")
|
620 |
+
print(f"Type of sim_physical_uncertainties: {type(sim_physical_uncertainties)}")
|
621 |
+
|
622 |
+
print("\n=== Values Preview ===")
|
623 |
+
print(f"bf_energies keys: {list(bf_energies.keys())[:5]}...") # First 5 keys
|
624 |
+
print(f"layer_labels: {layer_labels[:5]}...") # First 5 labels
|
625 |
+
|
626 |
+
print("\n=== Shape/Length Check ===")
|
627 |
+
print(f"Length of plot_labels: {len(plot_labels)}")
|
628 |
+
|
629 |
+
print(f"Length of sim_physical_energy_diff: {len(sim_physical_energy_diff) if hasattr(sim_physical_energy_diff, 'len') else 'N/A'}")
|
630 |
+
|
631 |
+
print(f"Number of uncertainties: {len(sim_physical_uncertainties.values()) if hasattr(sim_physical_uncertainties.values(), 'len') else 'N/A'}")
|
632 |
+
sim_physical_energy_diff
|
633 |
+
|
634 |
+
print("=== Examining sim_physical_uncertainties ===")
|
635 |
+
print(f"Type: {type(sim_physical_uncertainties)}")
|
636 |
+
print("\nFirst few key-value pairs:")
|
637 |
+
for i, (key, value) in enumerate(sim_physical_uncertainties.items()):
|
638 |
+
if i < 5: # Show first 5 items
|
639 |
+
print(f"{key}: {value}")
|
640 |
+
else:
|
641 |
+
break
|
642 |
+
|
643 |
+
print("\n=== Examining sim_physical_energy_diff ===")
|
644 |
+
print(f"Type: {type(sim_physical_energy_diff)}")
|
645 |
+
if hasattr(sim_physical_energy_diff, '__iter__'):
|
646 |
+
print("First few values:")
|
647 |
+
try:
|
648 |
+
print(list(sim_physical_energy_diff)[:5])
|
649 |
+
except:
|
650 |
+
print("Could not convert to list")
|
651 |
+
|
652 |
+
# Convert uncertainties to a list of values
|
653 |
+
physical_uncertainties = list(sim_physical_uncertainties.values())
|
654 |
+
logical_uncertainties = list(sim_logical_uncertainties.values())
|
655 |
+
|
656 |
+
# Convert energy differences if needed
|
657 |
+
if isinstance(sim_physical_energy_diff, dict):
|
658 |
+
energy_diff = list(sim_physical_energy_diff.values())
|
659 |
+
else:
|
660 |
+
energy_diff = sim_physical_energy_diff
|
661 |
+
|
662 |
+
fig, ax = plt.subplots(figsize=(11, 7), dpi=200)
|
663 |
+
print("1")
|
664 |
+
layer_labels = [(int(key.split(":")[0]), int(key.split(":")[1])) for key in bf_energies.keys()]
|
665 |
+
plot_labels = [str(item) for item in layer_labels]
|
666 |
+
print("here")
|
667 |
+
plt.errorbar(
|
668 |
+
plot_labels,
|
669 |
+
sim_physical_energy_diff,
|
670 |
+
yerr=physical_uncertainties,
|
671 |
+
ecolor=(20 / 255.0, 26 / 255.0, 94 / 255.0),
|
672 |
+
color=(20 / 255.0, 26 / 255.0, 94 / 255.0),
|
673 |
+
capsize=4,
|
674 |
+
elinewidth=1.5,
|
675 |
+
fmt="o",
|
676 |
+
markersize=8,
|
677 |
+
markeredgewidth=1,
|
678 |
+
label="Physical",
|
679 |
+
)
|
680 |
+
print("2")
|
681 |
+
|
682 |
+
plt.errorbar(
|
683 |
+
plot_labels,
|
684 |
+
sim_logical_energy_diff,
|
685 |
+
yerr=logical_uncertainties,
|
686 |
+
color=(0, 177 / 255.0, 152 / 255.0),
|
687 |
+
ecolor=(0, 177 / 255.0, 152 / 255.0),
|
688 |
+
capsize=4,
|
689 |
+
elinewidth=1.5,
|
690 |
+
fmt="o",
|
691 |
+
markersize=8,
|
692 |
+
markeredgewidth=1,
|
693 |
+
label="Logical",
|
694 |
+
)
|
695 |
+
print("3")
|
696 |
+
|
697 |
+
ax.set_xlabel("Hamiltonian Parameters (U, V)", fontsize=18)
|
698 |
+
ax.set_ylabel("Energy above true ground state (in eV)", fontsize=18)
|
699 |
+
ax.set_title("CUDA-Q AIM Circuits Simulation (lower is better)", fontsize=20)
|
700 |
+
ax.legend(loc="upper right", fontsize=18.5)
|
701 |
+
plt.xticks(fontsize=16)
|
702 |
+
plt.yticks(fontsize=16)
|
703 |
+
|
704 |
+
print("4")
|
705 |
+
|
706 |
+
ax.axhline(y=0, color="black", linestyle="--", linewidth=2)
|
707 |
+
plt.ylim(
|
708 |
+
top=max(sim_physical_energy_diff) + max(sim_physical_uncertainties.values()) + 0.2, bottom=-0.2
|
709 |
+
)
|
710 |
+
print("5")
|
711 |
+
plt.tight_layout()
|
712 |
+
plt.show()
|
docref/Example_plotly_code.md
ADDED
File without changes
|
docref/H2example.md
ADDED
File without changes
|
docref/VQE_Example.md
ADDED
@@ -0,0 +1,345 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
VQE with gradients, active spaces, and gate fusion¶
|
2 |
+
This tutorial will explore the Variational Quantum Eigensolver, a hybrid quantum classical algorithm for determining the ground state energy of molecules. The first part of this tutorial will walk through the key aspects of the VQE algorithm and how to implement it with CUDA-Q. The following sections explore advanced topics: parallel gradients, active spaces, and gate fusion
|
3 |
+
|
4 |
+
The Basics of VQE¶
|
5 |
+
The VQE algorithm is hybrid quantum-classical algorithm, meaning some subroutines run on a quantum computer or quantum simulator and others run on a traditional (super)computer.
|
6 |
+
|
7 |
+
The goal is to take a parameterized quantum circuit and a qubit form of the molecular Hamiltonian, measure an expectation value that corresponds to the ground state energy of the molecule, and then repeat the process to variationally minimize the energy with respect to the parameters in the quantum circuit. The optimization is performed on a classical device while the expectation values are determined on a quantum device. See the figure below.
|
8 |
+
|
9 |
+
VQE.png
|
10 |
+
|
11 |
+
The next few cells will elaborate on each part of the VQE procedure and show you how to build a VQE simulation to compute the ground state energy of the water molecule.
|
12 |
+
|
13 |
+
Installing/Loading Relevant Packages¶
|
14 |
+
# Install the relevant packages.
|
15 |
+
!pip install pyscf==2.6.2 openfermionpyscf==0.5 matplotlib==3.8.4 openfermion==1.6.1 -q
|
16 |
+
import openfermion
|
17 |
+
import openfermionpyscf
|
18 |
+
from openfermion.transforms import jordan_wigner, get_fermion_operator
|
19 |
+
|
20 |
+
import os
|
21 |
+
import timeit
|
22 |
+
|
23 |
+
import cudaq
|
24 |
+
import matplotlib.pyplot as plt
|
25 |
+
from scipy.optimize import minimize
|
26 |
+
import numpy as np
|
27 |
+
Implementing VQE in CUDA-Q¶
|
28 |
+
Like most quantum chemistry programs, the first step is to specify a molecular geometry, basis set, charge, and multiplicity.
|
29 |
+
|
30 |
+
geometry = [('O', (0.1173, 0.0, 0.0)), ('H', (-0.4691, 0.7570, 0.0)),
|
31 |
+
('H', (-0.4691, -0.7570, 0.0))]
|
32 |
+
basis = 'sto3g'
|
33 |
+
multiplicity = 1
|
34 |
+
charge = 0
|
35 |
+
The VQE procedure requires some classical preprocessing. The code below uses the PySCF package and OpenFermion to compute the Hartree Fock reference state and compute the integrals required for the Hamiltonian.
|
36 |
+
|
37 |
+
molecule = openfermionpyscf.run_pyscf(
|
38 |
+
openfermion.MolecularData(geometry, basis, multiplicity, charge))
|
39 |
+
Next, the Hamiltonian is built using get_molecular_hamiltonian. The Hamiltonian must then be converted to a qubit Hamiltonian consisting of qubit operators. The standard Jordan-Wigner transformation is used to perform this mapping.
|
40 |
+
|
41 |
+
Finally, the Jordan-Wigner qubit Hamiltonian is converted into a CUDA-Q spin operator which can be used to evaluate an expectation value given a quantum circuit.
|
42 |
+
|
43 |
+
molecular_hamiltonian = molecule.get_molecular_hamiltonian()
|
44 |
+
|
45 |
+
fermion_hamiltonian = get_fermion_operator(molecular_hamiltonian)
|
46 |
+
|
47 |
+
qubit_hamiltonian = jordan_wigner(fermion_hamiltonian)
|
48 |
+
|
49 |
+
spin_ham = cudaq.SpinOperator(qubit_hamiltonian)
|
50 |
+
Next, the quantum circuit needs to be defined, which models the wavefunction. This is done in CUDA-Q by specifying a CUDA-Q kernel. The kernel takes as an input the number of qubits, the number of electrons, and the parameters of the circuit ansatz (form of the wavefunction) yet to be defined.
|
51 |
+
|
52 |
+
The number of qubits corresponds to the potential positions of electrons and is therefore twice the number of spatial orbitals constructed with the chosen basis set, as each can be occupied by two electrons.
|
53 |
+
|
54 |
+
The Hartree-Fock reference is constructed by applying
|
55 |
+
bitflip operations to each of the first
|
56 |
+
qubits where
|
57 |
+
is the number of electrons. Next, a parameterized ansatz is chosen. Theoretically, any set of operations can work as an ansatz, however, it is good practice to use an ansatz that captures the underlying physics of the problem. The most common choice for chemistry is the Unitary Coupled Cluster Ansatz with Single and Double excitations (UCCSD). This UCCSD hate operations are automatically added to the kernel with the cudaq.kernels.uccsd(qubits, thetas, electron_num, qubit_num) function.
|
58 |
+
|
59 |
+
The STO-3G water molecuule UCCSD ansatz requires optimization of 140 parameters.
|
60 |
+
|
61 |
+
electron_count = 10
|
62 |
+
qubit_count = 2 * 7
|
63 |
+
|
64 |
+
|
65 |
+
@cudaq.kernel
|
66 |
+
def kernel(qubit_num: int, electron_num: int, thetas: list[float]):
|
67 |
+
qubits = cudaq.qvector(qubit_num)
|
68 |
+
|
69 |
+
for i in range(electron_num):
|
70 |
+
x(qubits[i])
|
71 |
+
|
72 |
+
cudaq.kernels.uccsd(qubits, thetas, electron_num, qubit_num)
|
73 |
+
|
74 |
+
|
75 |
+
parameter_count = cudaq.kernels.uccsd_num_parameters(electron_count,
|
76 |
+
qubit_count)
|
77 |
+
|
78 |
+
print(parameter_count)
|
79 |
+
140
|
80 |
+
The classical optimizer requires a custom cost function which is defined below. The cudaq.observe() function computes an expectation given the Hamiltonian and the kernel defined above.
|
81 |
+
|
82 |
+
def cost(theta):
|
83 |
+
|
84 |
+
exp_val = cudaq.observe(kernel, spin_ham, qubit_count, electron_count,
|
85 |
+
theta).expectation()
|
86 |
+
|
87 |
+
return exp_val
|
88 |
+
|
89 |
+
|
90 |
+
exp_vals = []
|
91 |
+
|
92 |
+
|
93 |
+
def callback(xk):
|
94 |
+
exp_vals.append(cost(xk))
|
95 |
+
|
96 |
+
|
97 |
+
# Initial variational parameters.
|
98 |
+
np.random.seed(42)
|
99 |
+
x0 = np.random.normal(0, 1, parameter_count)
|
100 |
+
The final step is to run the optimization using the scipy minimize function and a selected optimizer, in this case COBYLA.
|
101 |
+
|
102 |
+
cudaq.set_target('nvidia')
|
103 |
+
start_time = timeit.default_timer()
|
104 |
+
result = minimize(cost,
|
105 |
+
x0,
|
106 |
+
method='COBYLA',
|
107 |
+
callback=callback,
|
108 |
+
options={'maxiter': 50})
|
109 |
+
end_time = timeit.default_timer()
|
110 |
+
|
111 |
+
print('UCCSD-VQE energy = ', result.fun)
|
112 |
+
print('Total number of qubits = ', qubit_count)
|
113 |
+
print('Total number of parameters = ', parameter_count)
|
114 |
+
print('Total number of terms in the spin hamiltonian = ',
|
115 |
+
spin_ham.get_term_count())
|
116 |
+
print('Total elapsed time (s) = ', end_time - start_time)
|
117 |
+
|
118 |
+
plt.plot(exp_vals)
|
119 |
+
plt.xlabel('Epochs')
|
120 |
+
plt.ylabel('Energy')
|
121 |
+
plt.title('VQE')
|
122 |
+
plt.show()
|
123 |
+
UCCSD-VQE energy = -70.21455023422772
|
124 |
+
Total number of qubits = 14
|
125 |
+
Total number of parameters = 140
|
126 |
+
Total number of terms in the spin hamiltonian = 1086
|
127 |
+
Total elapsed time (s) = 3.9171073289999185
|
128 |
+
../../_images/applications_python_vqe_advanced_21_1.png
|
129 |
+
The result of this procedure is an estimate of the ground state energy of water. However, the convergence behavior is not perfect, more iterations would greatly improve the result, but would take a few minutes to run.
|
130 |
+
|
131 |
+
Parallel Parameter Shift Gradients¶
|
132 |
+
One way to accelerate VQE is to use an optimizer that accepts a gradient. This can drastically lower the number of VQE iterations required at the cost of computing the gradient on the quantum side of the algorithm.
|
133 |
+
|
134 |
+
The parameter shift rule is a common technique to compute the gradient for parameterized circuits. It is obtained by computing two expectation values for each parameter corresponding to a small forward and backward shift in the ith parameter. These results are used to estimate finite difference contribution to the gradient.
|
135 |
+
|
136 |
+
parametershift.png
|
137 |
+
|
138 |
+
This procedure can become cost prohibitive as the number of parameters becomes large.
|
139 |
+
|
140 |
+
Each of the expectation values needed to evaluate a parameter shift gradient can be computed independently. The CUDA-Q nvidia-mqpu backend is designed for parallel computations across multiple simulated QPUs. The function below uses cudaq.observe_asynch to distribute all of the expectation values evaluations across as many GPUs that are available. First, try it with num_qpus set to 1.
|
141 |
+
|
142 |
+
np.random.seed(42)
|
143 |
+
x0 = np.random.normal(0, 1, parameter_count)
|
144 |
+
|
145 |
+
cudaq.set_target("nvidia-mqpu")
|
146 |
+
|
147 |
+
num_qpus = cudaq.get_target().num_qpus()
|
148 |
+
|
149 |
+
epsilon = np.pi / 4
|
150 |
+
|
151 |
+
|
152 |
+
def batched_gradient_function(kernel, parameters, hamiltonian, epsilon):
|
153 |
+
|
154 |
+
x = np.tile(parameters, (len(parameters), 1))
|
155 |
+
|
156 |
+
xplus = x + (np.eye(x.shape[0]) * epsilon)
|
157 |
+
|
158 |
+
xminus = x - (np.eye(x.shape[0]) * epsilon)
|
159 |
+
|
160 |
+
g_plus = []
|
161 |
+
g_minus = []
|
162 |
+
gradients = []
|
163 |
+
|
164 |
+
qpu_counter = 0 # Iterate over the number of GPU resources available
|
165 |
+
for i in range(x.shape[0]):
|
166 |
+
|
167 |
+
g_plus.append(
|
168 |
+
cudaq.observe_async(kernel,
|
169 |
+
hamiltonian,
|
170 |
+
qubit_count,
|
171 |
+
electron_count,
|
172 |
+
xplus[i],
|
173 |
+
qpu_id=qpu_counter%num_qpus))
|
174 |
+
qpu_counter += 1
|
175 |
+
|
176 |
+
g_minus.append(
|
177 |
+
cudaq.observe_async(kernel,
|
178 |
+
hamiltonian,
|
179 |
+
qubit_count,
|
180 |
+
electron_count,
|
181 |
+
xminus[i],
|
182 |
+
qpu_id=qpu_counter%num_qpus))
|
183 |
+
qpu_counter += 1
|
184 |
+
|
185 |
+
gradients = [
|
186 |
+
(g_plus[i].get().expectation() - g_minus[i].get().expectation()) /
|
187 |
+
(2 * epsilon) for i in range(len(g_minus))
|
188 |
+
]
|
189 |
+
|
190 |
+
assert len(gradients) == len(
|
191 |
+
parameters) == x.shape[0] == xplus.shape[0] == xminus.shape[0]
|
192 |
+
|
193 |
+
return gradients
|
194 |
+
[warning] Target nvidia-mqpu: This target is deprecating. Please use the 'nvidia' target with option 'mqpu,fp32' or 'mqpu' (fp32 is the default precision option) by adding the command line option '--target-option mqpu,fp32' or passing it as cudaq.set_target('nvidia', option='mqpu,fp32') in Python. Please refer to CUDA-Q ]8;;https://nvidia.github.io/cuda-quantum/latest/using/backends/platform\documentation]8;;\ for more information.
|
195 |
+
The cost function needs to be slightly updated to make use of the gradient in the optimization procedure and allow for a gradient based optimizer like L-BFGS-B to be used.
|
196 |
+
|
197 |
+
gradient = batched_gradient_function(kernel, x0, spin_ham, epsilon)
|
198 |
+
|
199 |
+
exp_vals = []
|
200 |
+
|
201 |
+
|
202 |
+
def objective_function(parameter_vector: list[float], \
|
203 |
+
gradient=gradient, hamiltonian=spin_ham, kernel=kernel):
|
204 |
+
|
205 |
+
get_result = lambda parameter_vector: cudaq.observe\
|
206 |
+
(kernel, hamiltonian, qubit_count, electron_count, parameter_vector).expectation()
|
207 |
+
|
208 |
+
cost = get_result(parameter_vector)
|
209 |
+
exp_vals.append(cost)
|
210 |
+
gradient_vector = batched_gradient_function(kernel, parameter_vector,
|
211 |
+
spin_ham, epsilon)
|
212 |
+
|
213 |
+
return cost, gradient_vector
|
214 |
+
Run the code below. Notice how the result is converged to a lower energy using only 10% of the steps as optimization above without a gradient.
|
215 |
+
|
216 |
+
np.random.seed(42)
|
217 |
+
init_params = np.random.normal(0, 1, parameter_count)
|
218 |
+
|
219 |
+
start_time = timeit.default_timer()
|
220 |
+
result_vqe = minimize(objective_function,
|
221 |
+
init_params,
|
222 |
+
method='L-BFGS-B',
|
223 |
+
jac=True,
|
224 |
+
tol=1e-8,
|
225 |
+
options={'maxiter': 5})
|
226 |
+
end_time = timeit.default_timer()
|
227 |
+
|
228 |
+
print('VQE-UCCSD energy= ', result_vqe.fun)
|
229 |
+
print('Total elapsed time (s) = ', end_time - start_time)
|
230 |
+
|
231 |
+
plt.plot(exp_vals)
|
232 |
+
plt.xlabel('Epochs')
|
233 |
+
plt.ylabel('Energy')
|
234 |
+
plt.title('VQE')
|
235 |
+
plt.show()
|
236 |
+
VQE-UCCSD energy= -73.19471262288755
|
237 |
+
Total elapsed time (s) = 57.27010986900132
|
238 |
+
../../_images/applications_python_vqe_advanced_30_1.png
|
239 |
+
Now, run the code again (the three previous cells) and specify num_qpus to be more than one if you have access to multiple GPUs and notice resulting speedup. Thanks to CUDA-Q, this code could be used without modification in a setting where multiple physical QPUs were availible.
|
240 |
+
|
241 |
+
Using an Active Space¶
|
242 |
+
Performing electronic structure computations with all electrons and orbitals is often prohibitively expensive and unnecessary. Most of the interesting chemistry can be modeled by restricting simulations to the highest energy occupied molecular orbitals and lowest energy unoccupied molecular orbitals. This is known as the active space approximation.
|
243 |
+
|
244 |
+
Below is an example of STO-3G water modeled with a 4 electron 3 orbital active space simulated with UCCSD-VQE. Using an active space means you can run VQE for the same molecule using fewer qubits and a more shallow circuit.
|
245 |
+
|
246 |
+
cas.png
|
247 |
+
|
248 |
+
The molecule is defined the same way, expect for you now include variables nele_cas and norb_cas to define the active space. The ncore
|
249 |
+
|
250 |
+
geometry = [('O', (0.1173, 0.0, 0.0)), ('H', (-0.4691, 0.7570, 0.0)),
|
251 |
+
('H', (-0.4691, -0.7570, 0.0))]
|
252 |
+
basis = 'sto3g'
|
253 |
+
multiplicity = 1
|
254 |
+
charge = 0
|
255 |
+
ncore = 3
|
256 |
+
nele_cas, norb_cas = (4, 3)
|
257 |
+
|
258 |
+
molecule = openfermionpyscf.run_pyscf(
|
259 |
+
openfermion.MolecularData(geometry, basis, multiplicity, charge))
|
260 |
+
The Hamiltonian is now constrcuted with the same steps, but only models the active space.
|
261 |
+
|
262 |
+
molecular_hamiltonian = molecule.get_molecular_hamiltonian(
|
263 |
+
occupied_indices=range(ncore),
|
264 |
+
active_indices=range(ncore, ncore + norb_cas))
|
265 |
+
|
266 |
+
fermion_hamiltonian = get_fermion_operator(molecular_hamiltonian)
|
267 |
+
|
268 |
+
qubit_hamiltonian = jordan_wigner(fermion_hamiltonian)
|
269 |
+
|
270 |
+
spin_ham = cudaq.SpinOperator(qubit_hamiltonian)
|
271 |
+
Similarly, the kernel is defined only by the orbitals and electrons in the active space. Notice how this means you only need to optimize 8 parameters now.
|
272 |
+
|
273 |
+
electron_count = nele_cas
|
274 |
+
qubit_count = 2 * norb_cas
|
275 |
+
|
276 |
+
|
277 |
+
@cudaq.kernel
|
278 |
+
def kernel(qubit_num: int, electron_num: int, thetas: list[float]):
|
279 |
+
qubits = cudaq.qvector(qubit_num)
|
280 |
+
|
281 |
+
for i in range(electron_num):
|
282 |
+
x(qubits[i])
|
283 |
+
|
284 |
+
cudaq.kernels.uccsd(qubits, thetas, electron_num, qubit_num)
|
285 |
+
|
286 |
+
|
287 |
+
parameter_count = cudaq.kernels.uccsd_num_parameters(electron_count,
|
288 |
+
qubit_count)
|
289 |
+
|
290 |
+
print(parameter_count)
|
291 |
+
8
|
292 |
+
def cost(theta):
|
293 |
+
|
294 |
+
exp_val = cudaq.observe(kernel, spin_ham, qubit_count, electron_count,
|
295 |
+
theta).expectation()
|
296 |
+
thetas = theta
|
297 |
+
return exp_val
|
298 |
+
|
299 |
+
|
300 |
+
exp_vals = []
|
301 |
+
|
302 |
+
|
303 |
+
def callback(xk):
|
304 |
+
exp_vals.append(cost(xk))
|
305 |
+
|
306 |
+
|
307 |
+
# Initial variational parameters.
|
308 |
+
np.random.seed(42)
|
309 |
+
x0 = np.random.normal(0, 1, parameter_count)
|
310 |
+
The VQE procedure below is much faster using an active space compared to inclusion of all orbitals and electrons.
|
311 |
+
|
312 |
+
cudaq.set_target("nvidia")
|
313 |
+
|
314 |
+
start_time = timeit.default_timer()
|
315 |
+
result = minimize(cost,
|
316 |
+
x0,
|
317 |
+
method='COBYLA',
|
318 |
+
callback=callback,
|
319 |
+
options={'maxiter': 500})
|
320 |
+
end_time = timeit.default_timer()
|
321 |
+
|
322 |
+
print('UCCSD-VQE energy = ', result.fun)
|
323 |
+
print('Total number of qubits = ', qubit_count)
|
324 |
+
print('Total number of parameters = ', parameter_count)
|
325 |
+
print('Total number of terms in the spin hamiltonian = ',
|
326 |
+
spin_ham.get_term_count())
|
327 |
+
print('Total elapsed time (s) = ', end_time - start_time)
|
328 |
+
|
329 |
+
plt.plot(exp_vals)
|
330 |
+
plt.xlabel('Epochs')
|
331 |
+
plt.ylabel('Energy')
|
332 |
+
plt.title('VQE')
|
333 |
+
plt.show()
|
334 |
+
UCCSD-VQE energy = -74.96341992791962
|
335 |
+
Total number of qubits = 6
|
336 |
+
Total number of parameters = 8
|
337 |
+
Total number of terms in the spin hamiltonian = 62
|
338 |
+
Total elapsed time (s) = 1.754178541001238
|
339 |
+
../../_images/applications_python_vqe_advanced_43_1.png
|
340 |
+
Gate Fusion for Larger Circuits¶
|
341 |
+
CUDA-Q simulations take advantage of a technique called gate fusion. Gate fusion is an optimization technique where consecutive gates are combined into a single gate operation to improve the efficiency of the simulation (See figure below). By targeting the nvidia-mgpu backend and setting the CUDAQ_MGPU_FUSE environment variable, you can select the degree of fusion that takes place.
|
342 |
+
|
343 |
+
gate-fuse.png
|
344 |
+
|
345 |
+
This is particularly important for larger circuits and can have a significant impact on the performance of the simulation. Each system is different, so you should test different gate fusion levels to find out what is best for your system. You can find more information here.
|
docref/molgallery_demo.md
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
The gradio_molgallery3d package is a custom component for Gradio, designed specifically for displaying an interactive gallery of 3D molecular structures. Here are some key features and details:
|
2 |
+
Installation: You can install this component using pip install gradio_molgallery3d.
|
3 |
+
Input: It accepts inputs in the form of RDKit objects, which are commonly used for cheminformatics and molecular modeling.
|
4 |
+
Features:
|
5 |
+
Automatic Rotation: When the automatic_rotation parameter is set to True, the molecular structures rotate automatically, providing a dynamic view of the molecule.
|
6 |
+
Download Capability: Users can download the 3D structure as a .png file by clicking the right mouse button on the structure.
|
7 |
+
Known Issues: Web browsers have limitations on the number of canvas objects that can be displayed on a single page, which might result in some structures not being drawn. This limitation varies by browser.
|
8 |
+
Example Usage for Visualizing H2O:
|
9 |
+
Here's a code snippet to show how you can use gradio_molgallery3d to visualize the H2O molecule:
|
10 |
+
|
11 |
+
|
12 |
+
import gradio as gr
|
13 |
+
from rdkit import Chem
|
14 |
+
from gradio_molgallery import MolGallery3D
|
15 |
+
|
16 |
+
# Create an RDKit molecule object for H2O
|
17 |
+
h2o_mol = Chem.MolFromSmiles('O')
|
18 |
+
|
19 |
+
# Set up the gallery
|
20 |
+
gallery3d = MolGallery3D(
|
21 |
+
columns=1, # Since we're showing only one molecule
|
22 |
+
height=400, # Adjust the height as needed
|
23 |
+
label="3D Structure of H2O",
|
24 |
+
automatic_rotation=True
|
25 |
+
)
|
26 |
+
|
27 |
+
# Define a function to return the molecule
|
28 |
+
def show_h2o():
|
29 |
+
return [h2o_mol]
|
30 |
+
|
31 |
+
# Create the Gradio interface
|
32 |
+
with gr.Blocks() as demo:
|
33 |
+
gr.Markdown("# 3D Visualization of H2O")
|
34 |
+
gallery = gallery3d
|
35 |
+
btn = gr.Button("Show H2O")
|
36 |
+
btn.click(fn=show_h2o, outputs=gallery)
|
37 |
+
|
38 |
+
# Launch the demo
|
39 |
+
demo.launch()
|
docref/py3dmol_demo.md
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Here's a sample code to show an H2 molecule and a water molecule in a py3Dmol flow inside a Gradio display:
|
2 |
+
|
3 |
+
Explanation:
|
4 |
+
Molecule Creation: The molecule_viewer function takes a molecule_type parameter to determine which molecule to visualize. It uses RDKit to create the molecule from SMILES strings, add hydrogens, and embed the molecule in 3D space.
|
5 |
+
Visualization: The molecule is then converted to a MolBlock format, which py3Dmol can read. The view object is created with the molecule data, and styles are applied to show the molecule in stick and sphere representations.
|
6 |
+
Gradio Interface: The Gradio interface allows users to select between H2 and H2O molecules using a radio button. The selected molecule is then visualized in the HTML output.
|
7 |
+
Launch: The Gradio app is launched, providing an interactive web interface where users can view the 3D models of the selected molecules.
|
8 |
+
This code provides a simple yet effective way to visualize H2 and H2O molecules in 3D within a Gradio interface. Users can rotate, zoom, and interact with the molecules to explore their structures.
|
9 |
+
|
10 |
+
|
11 |
+
import gradio as gr
|
12 |
+
from rdkit import Chem
|
13 |
+
from rdkit.Chem import AllChem
|
14 |
+
import py3Dmol
|
15 |
+
|
16 |
+
def molecule_viewer(molecule_type):
|
17 |
+
if molecule_type == "H2":
|
18 |
+
# Create an H2 molecule
|
19 |
+
h2 = Chem.MolFromSmiles("H[H]")
|
20 |
+
h2 = AllChem.AddHs(h2)
|
21 |
+
AllChem.EmbedMolecule(h2)
|
22 |
+
mol_block = Chem.MolToMolBlock(h2)
|
23 |
+
elif molecule_type == "H2O":
|
24 |
+
# Create a water molecule
|
25 |
+
water = Chem.MolFromSmiles("O")
|
26 |
+
water = AllChem.AddHs(water)
|
27 |
+
AllChem.EmbedMolecule(water)
|
28 |
+
mol_block = Chem.MolToMolBlock(water)
|
29 |
+
else:
|
30 |
+
return "Invalid molecule type"
|
31 |
+
|
32 |
+
# Create the 3Dmol view
|
33 |
+
view = py3Dmol.view(data=mol_block, style={"stick": {}, "sphere": {"scale": 0.3}})
|
34 |
+
view.zoomTo()
|
35 |
+
return view
|
36 |
+
|
37 |
+
# Define the Gradio interface
|
38 |
+
demo = gr.Interface(
|
39 |
+
fn=molecule_viewer,
|
40 |
+
inputs=gr.Radio(choices=["H2", "H2O"], label="Select Molecule"),
|
41 |
+
outputs=gr.HTML(label="3D Molecule Viewer")
|
42 |
+
)
|
43 |
+
|
44 |
+
# Launch the Gradio app
|
45 |
+
demo.launch()
|
docref/quantum-demo-blueprint.md
ADDED
@@ -0,0 +1,254 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# NVIDIA Quantum Library Gradio Demo - Project Blueprint
|
2 |
+
|
3 |
+
## Overview
|
4 |
+
This project creates an interactive web demo showcasing NVIDIA's CUDA Quantum Library (CUDA-Q) for quantum computations. The demo focuses on the Variational Quantum Eigensolver (VQE) algorithm for molecular energy estimation, providing users with an educational interface for quantum control and simulation.
|
5 |
+
|
6 |
+
## Project Objectives
|
7 |
+
- Create an intuitive, interactive demo of quantum computations using CUDA-Q
|
8 |
+
- Visualize molecular energy estimation using the VQE algorithm
|
9 |
+
- Provide real-time feedback on quantum simulation parameters
|
10 |
+
- Educate users about quantum chemistry concepts
|
11 |
+
|
12 |
+
## Core Features
|
13 |
+
|
14 |
+
### Molecule Selection
|
15 |
+
- Simple molecule options: H₂, LiH, and HeH⁺
|
16 |
+
- Intuitive dropdown or button-based selection interface
|
17 |
+
- Expandable framework for additional molecules
|
18 |
+
|
19 |
+
### Parameter Controls
|
20 |
+
- **Bond Length Control**
|
21 |
+
- Range: 0.5 Å to 2.5 Å
|
22 |
+
- Real-time updates of molecular geometry
|
23 |
+
- Continuous slider interface
|
24 |
+
|
25 |
+
- **Bond Angle Adjustment**
|
26 |
+
- Range: 90° to 180° (for multi-atom molecules)
|
27 |
+
- Dynamic updates of molecular structure
|
28 |
+
- Visual feedback on angle changes
|
29 |
+
|
30 |
+
### Visualization Components
|
31 |
+
- **Energy Convergence Display**
|
32 |
+
- Real-time line plot showing VQE optimization
|
33 |
+
- Interactive zoom and pan capabilities
|
34 |
+
- Clear indication of convergence points
|
35 |
+
|
36 |
+
- **3D Molecular Visualization**
|
37 |
+
- Dynamic molecular structure rendering
|
38 |
+
- Rotation and zoom capabilities
|
39 |
+
- Real-time updates with parameter changes
|
40 |
+
|
41 |
+
### Backend Features
|
42 |
+
- CUDA-Q integration for quantum calculations
|
43 |
+
- VQE algorithm implementation
|
44 |
+
- GPU-accelerated computations
|
45 |
+
- Real-time parameter optimization
|
46 |
+
|
47 |
+
## Technology Stack
|
48 |
+
|
49 |
+
### Frontend Framework
|
50 |
+
- **Gradio**
|
51 |
+
- Version: Latest stable
|
52 |
+
- Purpose: Web interface construction
|
53 |
+
- Key components: Sliders, buttons, plots
|
54 |
+
|
55 |
+
- **Bokeh**
|
56 |
+
- Version: Latest stable
|
57 |
+
- Purpose: Interactive plotting
|
58 |
+
- Features: Real-time updates, zoom, pan
|
59 |
+
|
60 |
+
### Quantum Computing Stack
|
61 |
+
- **NVIDIA CUDA Quantum Library**
|
62 |
+
- Core quantum simulation engine
|
63 |
+
- GPU acceleration support
|
64 |
+
- Hybrid quantum-classical optimization
|
65 |
+
|
66 |
+
- **Supporting Libraries**
|
67 |
+
- PySCF (optional): Molecular Hamiltonian preparation
|
68 |
+
- Qiskit (optional): Additional quantum tools
|
69 |
+
|
70 |
+
### Visualization Tools
|
71 |
+
- **ASE (Atomic Simulation Environment)**
|
72 |
+
- Molecular geometry handling
|
73 |
+
- Structure visualization
|
74 |
+
- Real-time updates
|
75 |
+
|
76 |
+
- **Matplotlib**
|
77 |
+
- Backup plotting capability
|
78 |
+
- Static visualizations
|
79 |
+
- Export functionality
|
80 |
+
|
81 |
+
### Core Dependencies
|
82 |
+
- NumPy (≥1.20.0)
|
83 |
+
- SciPy (≥1.7.0)
|
84 |
+
- CUDA Toolkit (≥11.0)
|
85 |
+
- Python (≥3.8)
|
86 |
+
|
87 |
+
## Implementation Guide
|
88 |
+
|
89 |
+
### 1. Environment Setup
|
90 |
+
```bash
|
91 |
+
# Core dependencies
|
92 |
+
pip install gradio>=3.50.0 bokeh>=3.0.0 matplotlib>=3.5.0
|
93 |
+
pip install ase>=3.22.0 numpy>=1.20.0 scipy>=1.7.0
|
94 |
+
|
95 |
+
# NVIDIA components
|
96 |
+
# Follow NVIDIA documentation for CUDA Toolkit installation
|
97 |
+
# Install CUDA-Q as per NVIDIA guidelines
|
98 |
+
```
|
99 |
+
|
100 |
+
### 2. Molecular System Implementation
|
101 |
+
```python
|
102 |
+
# Example molecular system setup
|
103 |
+
def setup_molecule(name: str, bond_length: float):
|
104 |
+
molecules = {
|
105 |
+
'H2': create_h2_molecule,
|
106 |
+
'LiH': create_lih_molecule,
|
107 |
+
'HeH+': create_heh_molecule
|
108 |
+
}
|
109 |
+
return molecules[name](bond_length)
|
110 |
+
```
|
111 |
+
|
112 |
+
### 3. VQE Algorithm Setup
|
113 |
+
```python
|
114 |
+
# Basic VQE implementation structure
|
115 |
+
class VQESimulation:
|
116 |
+
def __init__(self, molecule, ansatz):
|
117 |
+
self.molecule = molecule
|
118 |
+
self.ansatz = ansatz
|
119 |
+
|
120 |
+
# Implementation using CUDA-Q
|
121 |
+
result = optimizer.minimize(self.cost_function, initial_parameters, callback=self.callback)
|
122 |
+
return result
|
123 |
+
def cost_function(self, parameters):
|
124 |
+
# Compute expectation value
|
125 |
+
exp_val = cudaq.observe(self.ansatz, self.molecule.hamiltonian, parameters).expectation()
|
126 |
+
return exp_val
|
127 |
+
|
128 |
+
def callback(self, parameters):
|
129 |
+
# Optionally store or display intermediate results
|
130 |
+
pass
|
131 |
+
```
|
132 |
+
|
133 |
+
|
134 |
+
|
135 |
+
### 4. Ansatz Implementation
|
136 |
+
```python
|
137 |
+
# Example UCCSD Ansatz implementation
|
138 |
+
def create_uccsd_ansatz(qubit_num: int, electron_num: int):
|
139 |
+
@cudaq.kernel
|
140 |
+
def kernel(qubit_num: int, electron_num: int, thetas: list[float]):
|
141 |
+
qubits = cudaq.qvector(qubit_num)
|
142 |
+
for i in range(electron_num):
|
143 |
+
x(qubits[i])
|
144 |
+
cudaq.kernels.uccsd(qubits, thetas, electron_num, qubit_num)
|
145 |
+
return kernel
|
146 |
+
Cost function using CUDA-Q observe
|
147 |
+
def cost(theta, kernel, hamiltonian, qubit_count, electron_count):
|
148 |
+
exp_val = cudaq.observe(kernel, hamiltonian, qubit_count, electron_count, theta).expectation()
|
149 |
+
return exp_val
|
150 |
+
Optimization procedure
|
151 |
+
def run_optimization(initial_params, kernel, hamiltonian, qubit_count, electron_count):
|
152 |
+
from scipy.optimize import minimize
|
153 |
+
exp_vals = []
|
154 |
+
def callback(xk):
|
155 |
+
exp_vals.append(cost(xk, kernel, hamiltonian, qubit_count, electron_count))
|
156 |
+
result = minimize(
|
157 |
+
cost,
|
158 |
+
initial_params,
|
159 |
+
args=(kernel, hamiltonian, qubit_count, electron_count),
|
160 |
+
method='COBYLA',
|
161 |
+
callback=callback,
|
162 |
+
options={'maxiter': 500}
|
163 |
+
)
|
164 |
+
return result, exp_vals
|
165 |
+
UI
|
166 |
+
python:src/ui.py
|
167 |
+
Gradio interface setup with VQE integration
|
168 |
+
def create_interface(vqe_simulation):
|
169 |
+
with gr.Blocks() as demo:
|
170 |
+
gr.Markdown("# Quantum Chemistry Simulator")
|
171 |
+
# Molecule Selection
|
172 |
+
molecule = gr.Dropdown(choices=['H2', 'LiH', 'HeH+'], label="Select Molecule")
|
173 |
+
# Parameter Controls
|
174 |
+
bond_length = gr.Slider(0.5, 2.5, step=0.1, label="Bond Length (Å)")
|
175 |
+
bond_angle = gr.Slider(90, 180, step=1, label="Bond Angle (°)")
|
176 |
+
electric_field = gr.Slider(0, 10, step=0.1, label="Electric Field (V/nm)")
|
177 |
+
# Run VQE Button
|
178 |
+
run_button = gr.Button("Run VQE")
|
179 |
+
# Output Display
|
180 |
+
energy_output = gr.Textbox(label="Computed Energy")
|
181 |
+
convergence_plot = gr.Plot(label="Energy Convergence")
|
182 |
+
def run_vqe(selected_molecule, bond_len, bond_ang, e_field):
|
183 |
+
molecule = setup_molecule(selected_molecule, bond_len)
|
184 |
+
# Update molecular system parameters based on UI inputs
|
185 |
+
vqe_simulation = VQESimulation(molecule, ansatz)
|
186 |
+
initial_params = np.random.normal(0, 1, vqe_simulation.ansatz.parameter_count)
|
187 |
+
result, exp_vals = run_optimization(initial_params, vqe_simulation.ansatz, molecule.hamiltonian, molecule.qubit_count, molecule.electron_count)
|
188 |
+
energy = result.fun
|
189 |
+
# Plot convergence
|
190 |
+
plt.plot(exp_vals)
|
191 |
+
plt.xlabel('Iterations')
|
192 |
+
plt.ylabel('Energy')
|
193 |
+
plt.title('VQE Energy Convergence')
|
194 |
+
return energy, plt
|
195 |
+
run_button.click(run_vqe, inputs=[molecule, bond_length, bond_angle, electric_field], outputs=[energy_output, convergence_plot])
|
196 |
+
return demo
|
197 |
+
integration
|
198 |
+
|
199 |
+
### 7. Advanced VQE Features
|
200 |
+
- **Parallel Gradients:** Implement parameter shift rule with parallel computations using multiple QPUs for gradient calculations to accelerate optimization.
|
201 |
+
- **Active Space Reduction:** Allow users to select active space parameters, reducing the number of qubits and parameters for larger molecules, enhancing performance.
|
202 |
+
- **Gate Fusion:** Optimize larger circuits by implementing gate fusion techniques to improve simulation efficiency on GPU backends.
|
203 |
+
|
204 |
+
|
205 |
+
## Deployment Workflow
|
206 |
+
|
207 |
+
### Local Development
|
208 |
+
1. Clone repository
|
209 |
+
2. Install dependencies
|
210 |
+
3. Configure CUDA environment
|
211 |
+
4. Run development server
|
212 |
+
|
213 |
+
### Production Deployment
|
214 |
+
1. Package application
|
215 |
+
2. Verify CUDA compatibility
|
216 |
+
3. Deploy to target platform
|
217 |
+
4. Configure monitoring
|
218 |
+
|
219 |
+
## Testing Strategy
|
220 |
+
|
221 |
+
### Unit Tests
|
222 |
+
- Molecular system creation
|
223 |
+
- Parameter validation
|
224 |
+
- Energy calculation accuracy
|
225 |
+
|
226 |
+
### Integration Tests
|
227 |
+
- UI component interaction
|
228 |
+
- Data flow verification
|
229 |
+
- GPU utilization
|
230 |
+
|
231 |
+
### Performance Tests
|
232 |
+
- Load testing
|
233 |
+
- GPU memory usage
|
234 |
+
- Calculation speed
|
235 |
+
|
236 |
+
## Future Enhancements
|
237 |
+
- Support for larger molecular systems
|
238 |
+
- Advanced visualization features
|
239 |
+
- Additional quantum algorithms
|
240 |
+
- Export functionality for results
|
241 |
+
- Multi-GPU support
|
242 |
+
|
243 |
+
## Documentation
|
244 |
+
- User guide
|
245 |
+
- API documentation
|
246 |
+
- Installation instructions
|
247 |
+
- Troubleshooting guide
|
248 |
+
|
249 |
+
## Maintenance
|
250 |
+
- Regular dependency updates
|
251 |
+
- Performance optimization
|
252 |
+
- Bug fixes and improvements
|
253 |
+
- User feedback integration
|
254 |
+
|
gitpush.sh
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/bin/bash
|
2 |
+
|
3 |
+
# Check if a commit message was provided
|
4 |
+
if [ $# -eq 0 ]; then
|
5 |
+
echo "Error: Please provide a commit message"
|
6 |
+
echo "Usage: ./git_push.sh \"Your commit message\""
|
7 |
+
exit 1
|
8 |
+
fi
|
9 |
+
|
10 |
+
# Store the commit message
|
11 |
+
commit_message="$1"
|
12 |
+
|
13 |
+
# Echo and execute git add
|
14 |
+
echo "➡️ Adding all files..."
|
15 |
+
git add .
|
16 |
+
|
17 |
+
# Echo and show status
|
18 |
+
echo -e "\n📋 Current git status:"
|
19 |
+
git status
|
20 |
+
|
21 |
+
# Echo and commit with provided message
|
22 |
+
echo -e "\n💾 Committing changes with message: '$commit_message'"
|
23 |
+
git commit -m "$commit_message"
|
24 |
+
|
25 |
+
# Echo and push to main
|
26 |
+
echo -e "\n⬆️ Pushing to main branch..."
|
27 |
+
git push origin main
|
28 |
+
|
29 |
+
echo -e "\n✅ Done!"
|
history.md
ADDED
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Development History
|
2 |
+
|
3 |
+
## 2024-01-06 (Update 9)
|
4 |
+
- Fixed scale factor slider behavior:
|
5 |
+
* Added initial placeholder text for visualization and results
|
6 |
+
* Removed all automatic updates except molecule description
|
7 |
+
* Made scale factor slider explicitly interactive
|
8 |
+
* Added state management for molecule data
|
9 |
+
* Improved user feedback by showing "Click Run" messages
|
10 |
+
|
11 |
+
## 2024-01-06 (Update 8)
|
12 |
+
- Improved interface stability for multi-molecule support:
|
13 |
+
* Removed automatic simulation updates on parameter changes
|
14 |
+
* Combined molecule info updates into single atomic operation
|
15 |
+
* Fixed component creation order and initialization
|
16 |
+
* Added proper error handling for molecule selection
|
17 |
+
* Addressed "Casting complex values to real" warning (normal for quantum chemistry)
|
18 |
+
|
19 |
+
## 2024-01-06 (Update 7)
|
20 |
+
- Major update to handle molecular geometry scaling:
|
21 |
+
* Replaced bond length with universal scale factor for all molecules
|
22 |
+
* Added expand_geometry function to scale molecules from their center
|
23 |
+
* Updated molecules.json to use scale ranges (0.1 to 3.0) instead of bond lengths
|
24 |
+
* Fixed molecules.json structure by removing extra nesting level
|
25 |
+
* Added dynamic scale factor range updates in UI
|
26 |
+
* Updated all molecule descriptions to reflect scaling approach
|
27 |
+
|
28 |
+
## 2024-01-06 (Update 6)
|
29 |
+
- Major restructuring to support multiple molecules:
|
30 |
+
* Created molecules.json to store all molecule metadata
|
31 |
+
* Updated app.py to load molecule data and create dynamic interface
|
32 |
+
* Modified quantum_utils.py to use molecule metadata instead of hardcoded values
|
33 |
+
* Updated visualization.py to use molecule data from JSON
|
34 |
+
* Added molecule descriptions and dynamic bond length ranges
|
35 |
+
* Improved error handling across all components
|
36 |
+
|
37 |
+
## 2024-01-06 (Update 5)
|
38 |
+
- Fixed 3D molecule viewer to properly render in Gradio
|
39 |
+
- Modified create_molecule_viewer to return HTML string instead of view object
|
40 |
+
- Added explicit width and height to py3Dmol view
|
41 |
+
- Added more detailed logging for visualization debugging
|
42 |
+
|
43 |
+
## 2024-01-06 (Update 4)
|
44 |
+
- Simplified molecule visualization to match py3dmol_demo.md exactly
|
45 |
+
- Fixed SMILES format for H2 molecule to "[H][H]"
|
46 |
+
- Removed overcomplicated bond length adjustment code
|
47 |
+
- Fixed py3Dmol view creation parameters
|
48 |
+
|
49 |
+
## 2024-01-06 (Update 3)
|
50 |
+
- Completely revamped molecule visualization to use RDKit for proper molecular structure generation
|
51 |
+
- Added RDKit dependency to requirements.txt
|
52 |
+
- Updated create_molecule_viewer to handle proper 3D coordinates and bond length adjustments
|
53 |
+
- Improved molecule viewer to return py3Dmol view object directly instead of HTML
|
54 |
+
|
55 |
+
## 2024-01-06 (Update 2)
|
56 |
+
- Fixed py3Dmol viewer in Gradio by using _repr_html_() instead of _make_html()
|
57 |
+
- This resolved the JupyterLab extension error message in the 3D viewer
|
58 |
+
|
59 |
+
## 2024-01-06
|
60 |
+
- Fixed import error by updating app.py to import create_molecule_viewer instead of draw_molecule_2d
|
61 |
+
- This resolved the application crash that occurred after the 3D visualization update
|
62 |
+
|
63 |
+
## 2024-01-04 (Update 9)
|
64 |
+
- Successfully integrated HuggingFace ZeroGPU functionality with CUDA-Q
|
65 |
+
- First known working CUDA-Q demo on HuggingFace Spaces platform
|
66 |
+
- Implemented @spaces.GPU decorator for GPU-dependent functions
|
67 |
+
- Added appropriate duration parameter (120s) for VQE simulation
|
68 |
+
- Historic milestone: First functional CUDA-Q HuggingFace Space demo by Alex Krause and... possibly ever, he found no other examples of this.
|
69 |
+
- Current Status: Fully functional GPU-accelerated quantum simulation with dynamic GPU allocation
|
70 |
+
|
71 |
+
## 2024-01-04 (Update 8)
|
72 |
+
- Completely refactored visualization system to use Plotly instead of Matplotlib
|
73 |
+
- Removed complex base64 encoding/file handling for plots
|
74 |
+
- Switched to direct Plotly integration with Gradio's Plot component
|
75 |
+
- Added interactive features to plots (zoom, pan, hover tooltips)
|
76 |
+
- Fixed font debug message spam by adjusting logging levels
|
77 |
+
- Added plotly==5.18.0 to requirements.txt
|
78 |
+
- Current Status: Clean, interactive plotting with proper Gradio integration
|
79 |
+
|
80 |
+
## 2024-01-02 (Update 7)
|
81 |
+
- Modified quantum_utils.py to store iterations as integers instead of strings
|
82 |
+
- Simplified visualization.py to handle native integer iterations
|
83 |
+
- Improved type consistency across the codebase
|
84 |
+
- Current Status: Cleaner data handling with proper numeric types
|
85 |
+
|
86 |
+
## 2024-01-02 (Update 6)
|
87 |
+
- Enhanced visualization.py to handle string iteration numbers robustly
|
88 |
+
- Added error handling for iteration and energy value conversions
|
89 |
+
- Implemented safe type conversion with try-except blocks
|
90 |
+
- Current Status: Visualization properly handles all data types from results dictionary
|
91 |
+
|
92 |
+
## 2024-01-02 (Update 5)
|
93 |
+
- Fixed type conversion in visualization.py for iteration numbers
|
94 |
+
- Modified energy extraction to use actual iteration energy values
|
95 |
+
- Resolved "list indices must be integers or slices, not str" error
|
96 |
+
- Current Status: Visualization properly handles string iteration numbers from results
|
97 |
+
|
98 |
+
## 2024-01-02 (Update 4)
|
99 |
+
- Modified visualization.py to handle full results dictionary
|
100 |
+
- Updated plot_convergence to extract iteration data from results history
|
101 |
+
- Added final energy reference line to convergence plot
|
102 |
+
- Maintained rich data structure between modules for future extensibility
|
103 |
+
- Current Status: Improved integration between quantum_utils.py and visualization.py while preserving data
|
104 |
+
|
105 |
+
## 2024-01-02 (Update 2)
|
106 |
+
- Fixed "unhashable type: 'dict'" error in results processing
|
107 |
+
- Modified results dictionary structure to ensure all keys are strings
|
108 |
+
- Added energy values to iteration history entries
|
109 |
+
- Improved type handling in results dictionary to prevent hashing issues
|
110 |
+
- Current Status: VQE optimization and results processing working correctly
|
111 |
+
|
112 |
+
## 2024-01-02 (Update 1)
|
113 |
+
- Fixed iteration history processing in VQE simulation
|
114 |
+
- Added more robust data extraction from solver results
|
115 |
+
- Implemented fallback mechanism for iteration history
|
116 |
+
- Current Status: VQE optimization successfully completes with final energy: -1.8513513513513509
|
117 |
+
- Added better error handling and logging for iteration data processing
|
118 |
+
- Previous warning about missing 'energy' attribute has been addressed with more flexible data extraction
|
119 |
+
|
120 |
+
## Current Status (2024-01-02)
|
121 |
+
- VQE optimization now successfully completes with final energy: -1.8513513513513509
|
122 |
+
- Fixed several major issues:
|
123 |
+
1. Replaced `@cudaq.kernel` decorator with `cudaq.make_kernel()` to properly handle variational parameters
|
124 |
+
2. Modified parameter handling to use list type instead of float
|
125 |
+
3. Successfully implemented proper logging to both file and console
|
126 |
+
- Current Issue:
|
127 |
+
Unable to process iteration history data from VQE solver. Error indicates the solver's iteration objects
|
128 |
+
don't have expected 'energy' attribute. Need to investigate CUDA-Q solver's iteration data structure.
|
129 |
+
Error: "'cudaq_solvers._pycudaqx_solvers_the_suffix_matters' object has no attribute 'energy'"
|
130 |
+
|
131 |
+
## Bug Fixes (2024-01-02)
|
132 |
+
- Fixed VQE kernel initialization error by replacing `@cudaq.kernel` decorator with `cudaq.make_kernel()`.
|
133 |
+
This resolved the "local variable 'id' referenced before assignment" error by properly creating a parameterized kernel
|
134 |
+
that can handle variational parameters in the VQE optimization process.
|
135 |
+
The `make_kernel()` approach matches CUDA-Q's expected way of handling parameterized quantum circuits.
|
136 |
+
|
137 |
+
## Current Investigation (2024-12-30)
|
138 |
+
Investigating error: "capi_return is NULL Call-back cb_calcfc_in__cobyla__user__routines failed"
|
139 |
+
|
140 |
+
Steps tried so far:
|
141 |
+
1. Initially used direct CUDA-Q optimizer (cudaq.optimizers.COBYLA)
|
142 |
+
2. Switched to cudaq_solvers.vqe with scipy.minimize
|
143 |
+
3. Added args=() tuple based on StackOverflow solution for similar COBYLA callback error
|
144 |
+
4. Added explicit measurement operations to quantum kernel
|
145 |
+
|
146 |
+
## Bug Fixes
|
147 |
+
|
148 |
+
### 2024-12-30
|
149 |
+
- Fixed CUDA-Q kernel return type issue: Modified `h2_ansatz` kernel to have void return type (removed `return qubits` statement) to comply with CUDA-Q's requirement that kernels passed to `observe` must have void return type.
|
150 |
+
- Fixed Hamiltonian construction to use proper CUDA-Q spin operators (`cudaq.spin.x/y/z`) instead of string-based operator construction.
|
151 |
+
- Fixed missing operation error: Added registration of `meas_id` operation using `cudaq.register_operation("meas_id", np.identity(2))` for noise modeling.
|
152 |
+
- Fixed optimization error: Added empty args tuple to VQE solver call to properly handle callback arguments in scipy.optimize.minimize.
|
153 |
+
- Fixed measurement error: Added explicit measurement operation (`mz`) to the quantum kernel to ensure proper state measurement.
|
154 |
+
|
155 |
+
## 2024-03-19
|
156 |
+
- Enhanced molecular visualization by replacing 2D Plotly plots with interactive 3D visualization using py3Dmol
|
157 |
+
- Added py3Dmol dependency to requirements.txt
|
158 |
+
- Updated app interface to use HTML component for 3D molecule viewer
|
159 |
+
- Improved H2 molecule representation with proper 3D coordinates and styling
|
install.py
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import subprocess
|
2 |
+
import sys
|
3 |
+
|
4 |
+
def install_requirements():
|
5 |
+
subprocess.check_call([sys.executable, "-m", "pip", "install", "-r", "requirements.txt"])
|
6 |
+
|
7 |
+
if __name__ == "__main__":
|
8 |
+
install_requirements()
|
molecules.json
ADDED
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"H2": {
|
3 |
+
"name": "Hydrogen molecule",
|
4 |
+
"formula": "H₂",
|
5 |
+
"smiles": "[H][H]",
|
6 |
+
"basis": "sto-3g",
|
7 |
+
"multiplicity": 1,
|
8 |
+
"charge": 0,
|
9 |
+
"spatial_orbitals": 2,
|
10 |
+
"electron_count": 2,
|
11 |
+
"default_scale": 1.0,
|
12 |
+
"scale_range": {
|
13 |
+
"min": 0.1,
|
14 |
+
"max": 3.0,
|
15 |
+
"step": 0.05
|
16 |
+
},
|
17 |
+
"geometry_template": [
|
18 |
+
["H", [0.0, 0.0, 0.0]],
|
19 |
+
["H", [0.74, 0.0, 0.0]]
|
20 |
+
],
|
21 |
+
"description": "Diatomic hydrogen molecule, the simplest neutral molecule. Scale factor affects the overall size of the molecule."
|
22 |
+
},
|
23 |
+
"H2O": {
|
24 |
+
"name": "Water",
|
25 |
+
"formula": "H₂O",
|
26 |
+
"smiles": "O",
|
27 |
+
"basis": "sto-3g",
|
28 |
+
"multiplicity": 1,
|
29 |
+
"charge": 0,
|
30 |
+
"spatial_orbitals": 7,
|
31 |
+
"electron_count": 10,
|
32 |
+
"default_scale": 1.0,
|
33 |
+
"scale_range": {
|
34 |
+
"min": 0.1,
|
35 |
+
"max": 3.0,
|
36 |
+
"step": 0.05
|
37 |
+
},
|
38 |
+
"geometry_template": [
|
39 |
+
["O", [0.1173, 0.0, 0.0]],
|
40 |
+
["H", [-0.4691, 0.7570, 0.0]],
|
41 |
+
["H", [-0.4691, -0.7570, 0.0]]
|
42 |
+
],
|
43 |
+
"description": "Triatomic water molecule with bent geometry. Scale factor affects the overall size of the molecule."
|
44 |
+
},
|
45 |
+
"CH4": {
|
46 |
+
"name": "Methane",
|
47 |
+
"formula": "CH₄",
|
48 |
+
"smiles": "C",
|
49 |
+
"basis": "sto-3g",
|
50 |
+
"multiplicity": 1,
|
51 |
+
"charge": 0,
|
52 |
+
"spatial_orbitals": 9,
|
53 |
+
"electron_count": 10,
|
54 |
+
"default_scale": 1.0,
|
55 |
+
"scale_range": {
|
56 |
+
"min": 0.1,
|
57 |
+
"max": 3.0,
|
58 |
+
"step": 0.05
|
59 |
+
},
|
60 |
+
"geometry_template": [
|
61 |
+
["C", [0.0, 0.0, 0.0]],
|
62 |
+
["H", [1.09, 0.0, 0.0]],
|
63 |
+
["H", [-0.363, 1.03, 0.0]],
|
64 |
+
["H", [-0.363, -0.515, 0.891]],
|
65 |
+
["H", [-0.363, -0.515, -0.891]]
|
66 |
+
],
|
67 |
+
"description": "Tetrahedral methane molecule with single bonds. Scale factor affects the overall size of the molecule."
|
68 |
+
},
|
69 |
+
"BeH2": {
|
70 |
+
"name": "Beryllium hydride",
|
71 |
+
"formula": "BeH₂",
|
72 |
+
"smiles": "[BeH2]",
|
73 |
+
"basis": "sto-3g",
|
74 |
+
"multiplicity": 1,
|
75 |
+
"charge": 0,
|
76 |
+
"spatial_orbitals": 7,
|
77 |
+
"electron_count": 4,
|
78 |
+
"default_scale": 1.0,
|
79 |
+
"scale_range": {
|
80 |
+
"min": 0.1,
|
81 |
+
"max": 3.0,
|
82 |
+
"step": 0.05
|
83 |
+
},
|
84 |
+
"geometry_template": [
|
85 |
+
["Be", [0.0, 0.0, 0.0]],
|
86 |
+
["H", [1.33, 0.0, 0.0]],
|
87 |
+
["H", [-1.33, 0.0, 0.0]]
|
88 |
+
],
|
89 |
+
"description": "Linear beryllium hydride molecule. Scale factor affects the overall size of the molecule."
|
90 |
+
},
|
91 |
+
"CO": {
|
92 |
+
"name": "Carbon monoxide",
|
93 |
+
"formula": "CO",
|
94 |
+
"smiles": "[C-]#[O+]",
|
95 |
+
"basis": "sto-3g",
|
96 |
+
"multiplicity": 1,
|
97 |
+
"charge": 0,
|
98 |
+
"spatial_orbitals": 10,
|
99 |
+
"electron_count": 14,
|
100 |
+
"default_scale": 1.0,
|
101 |
+
"scale_range": {
|
102 |
+
"min": 0.1,
|
103 |
+
"max": 3.0,
|
104 |
+
"step": 0.05
|
105 |
+
},
|
106 |
+
"geometry_template": [
|
107 |
+
["C", [0.0, 0.0, 0.0]],
|
108 |
+
["O", [1.13, 0.0, 0.0]]
|
109 |
+
],
|
110 |
+
"description": "Diatomic carbon monoxide molecule with a triple bond. Scale factor affects the overall size of the molecule."
|
111 |
+
},
|
112 |
+
"N2": {
|
113 |
+
"name": "Nitrogen molecule",
|
114 |
+
"formula": "N₂",
|
115 |
+
"smiles": "N#N",
|
116 |
+
"basis": "sto-3g",
|
117 |
+
"multiplicity": 1,
|
118 |
+
"charge": 0,
|
119 |
+
"spatial_orbitals": 10,
|
120 |
+
"electron_count": 14,
|
121 |
+
"default_scale": 1.0,
|
122 |
+
"scale_range": {
|
123 |
+
"min": 0.1,
|
124 |
+
"max": 3.0,
|
125 |
+
"step": 0.05
|
126 |
+
},
|
127 |
+
"geometry_template": [
|
128 |
+
["N", [0.0, 0.0, 0.0]],
|
129 |
+
["N", [1.1, 0.0, 0.0]]
|
130 |
+
],
|
131 |
+
"description": "Diatomic nitrogen molecule with a triple bond. Scale factor affects the overall size of the molecule."
|
132 |
+
},
|
133 |
+
"NH3": {
|
134 |
+
"name": "Ammonia",
|
135 |
+
"formula": "NH₃",
|
136 |
+
"smiles": "N",
|
137 |
+
"basis": "sto-3g",
|
138 |
+
"multiplicity": 1,
|
139 |
+
"charge": 0,
|
140 |
+
"spatial_orbitals": 9,
|
141 |
+
"electron_count": 10,
|
142 |
+
"default_scale": 1.0,
|
143 |
+
"scale_range": {
|
144 |
+
"min": 0.1,
|
145 |
+
"max": 3.0,
|
146 |
+
"step": 0.05
|
147 |
+
},
|
148 |
+
"geometry_template": [
|
149 |
+
["N", [0.0, 0.0, 0.0]],
|
150 |
+
["H", [1.01, 0.0, 0.0]],
|
151 |
+
["H", [-0.505, 0.875, 0.0]],
|
152 |
+
["H", [-0.505, -0.875, 0.0]]
|
153 |
+
],
|
154 |
+
"description": "Trigonal pyramidal ammonia molecule with lone pair on nitrogen. Scale factor affects the overall size of the molecule."
|
155 |
+
}
|
156 |
+
}
|
157 |
+
|
packages.txt
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
python3-dev
|
2 |
+
python3-pip
|
3 |
+
build-essential
|
quantum_utils.py
ADDED
@@ -0,0 +1,332 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
quantum_utils.py
|
3 |
+
Helper library for running a simplified VQE or quantum simulation
|
4 |
+
that references CUDA-Q for computations.
|
5 |
+
|
6 |
+
References:
|
7 |
+
- @Cuda-Q_install.md for installation details
|
8 |
+
- @VQE_Example.md for example VQE code using nvidia or nvidia-mqpu backend
|
9 |
+
"""
|
10 |
+
|
11 |
+
import cudaq
|
12 |
+
import cudaq_solvers as solvers # Import solvers from cudaq_solvers package
|
13 |
+
import numpy as np
|
14 |
+
from scipy.optimize import minimize
|
15 |
+
import spaces
|
16 |
+
from typing import Dict, List, Union, Any, Tuple
|
17 |
+
import sys
|
18 |
+
import logging
|
19 |
+
import os
|
20 |
+
from logging.handlers import RotatingFileHandler
|
21 |
+
import openfermion
|
22 |
+
import openfermionpyscf
|
23 |
+
from openfermion.transforms import jordan_wigner, get_fermion_operator
|
24 |
+
|
25 |
+
# Create logs directory if it doesn't exist
|
26 |
+
os.makedirs('logs', exist_ok=True)
|
27 |
+
|
28 |
+
# Configure logging to write to both file and stderr
|
29 |
+
# Create a formatter
|
30 |
+
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
31 |
+
|
32 |
+
# Set up file handler with rotation
|
33 |
+
file_handler = RotatingFileHandler(
|
34 |
+
'logs/vqe_simulation.log',
|
35 |
+
maxBytes=10*1024*1024, # 10MB
|
36 |
+
backupCount=5
|
37 |
+
)
|
38 |
+
file_handler.setFormatter(formatter)
|
39 |
+
|
40 |
+
# Set up console handler
|
41 |
+
console_handler = logging.StreamHandler()
|
42 |
+
console_handler.setFormatter(formatter)
|
43 |
+
|
44 |
+
# Configure root logger
|
45 |
+
logger = logging.getLogger()
|
46 |
+
logger.setLevel(logging.DEBUG)
|
47 |
+
logger.addHandler(file_handler)
|
48 |
+
logger.addHandler(console_handler)
|
49 |
+
|
50 |
+
# Log startup message
|
51 |
+
logging.info("VQE Simulation logging initialized")
|
52 |
+
|
53 |
+
print("quantum_utils imported", file=sys.stderr, flush=True)
|
54 |
+
|
55 |
+
@spaces.GPU
|
56 |
+
def setup_target():
|
57 |
+
"""Set up CUDA-Q target based on available hardware."""
|
58 |
+
try:
|
59 |
+
print("Setting up target...", file=sys.stderr, flush=True)
|
60 |
+
gpu_count = cudaq.num_available_gpus()
|
61 |
+
print(f"Number of available GPUs: {gpu_count}", file=sys.stderr, flush=True)
|
62 |
+
|
63 |
+
if gpu_count > 0:
|
64 |
+
print("Attempting to set NVIDIA GPU target...", file=sys.stderr, flush=True)
|
65 |
+
cudaq.set_target("nvidia")
|
66 |
+
print("Successfully set NVIDIA GPU target", file=sys.stderr, flush=True)
|
67 |
+
else:
|
68 |
+
print("No GPU found, attempting to set CPU target...", file=sys.stderr, flush=True)
|
69 |
+
cudaq.set_target("qpp-cpu")
|
70 |
+
print("Successfully set CPU target", file=sys.stderr, flush=True)
|
71 |
+
except Exception as e:
|
72 |
+
print(f"Error setting up quantum target: {str(e)}", file=sys.stderr, flush=True)
|
73 |
+
import traceback
|
74 |
+
print(f"Traceback:\n{traceback.format_exc()}", file=sys.stderr, flush=True)
|
75 |
+
raise
|
76 |
+
|
77 |
+
def vqe_callback(xk):
|
78 |
+
"""Callback function for VQE optimization to track progress."""
|
79 |
+
try:
|
80 |
+
logging.info(f"VQE iteration - Current parameters: {xk}")
|
81 |
+
return True
|
82 |
+
except Exception as e:
|
83 |
+
logging.error(f"Error in VQE callback: {str(e)}")
|
84 |
+
return False
|
85 |
+
|
86 |
+
@spaces.GPU
|
87 |
+
def validate_ansatz(kernel_generator, init_params, qubit_num, electron_num):
|
88 |
+
"""Validate the ansatz kernel before VQE."""
|
89 |
+
try:
|
90 |
+
logging.debug("Validating ansatz kernel...")
|
91 |
+
# Set up target before validation
|
92 |
+
setup_target()
|
93 |
+
|
94 |
+
# Generate the kernel with the correct number of qubits and electrons
|
95 |
+
kernel = kernel_generator(qubit_num, electron_num)
|
96 |
+
|
97 |
+
# Log the types and values of parameters and qubits
|
98 |
+
logging.debug(f"Kernel parameters type: {type(init_params)}, values: {init_params}")
|
99 |
+
logging.debug(f"Kernel qubits type: {type(qubit_num)}, value: {qubit_num}")
|
100 |
+
logging.debug(f"Kernel electrons type: {type(electron_num)}, value: {electron_num}")
|
101 |
+
|
102 |
+
# Try running the kernel with initial parameters
|
103 |
+
result = cudaq.sample(kernel, init_params.tolist()) # Convert to list for validation
|
104 |
+
logging.debug(f"Ansatz validation successful. Sample result: {result}")
|
105 |
+
return True
|
106 |
+
except Exception as e:
|
107 |
+
logging.error(f"Ansatz validation failed: {str(e)}")
|
108 |
+
return False
|
109 |
+
|
110 |
+
# ----------------------------------------------------------
|
111 |
+
# New or updated utility function for generating a Hamiltonian
|
112 |
+
# ----------------------------------------------------------
|
113 |
+
def create_molecular_hamiltonian(geometry: list[tuple[str, tuple[float, float, float]]],
|
114 |
+
basis: str,
|
115 |
+
multiplicity: int,
|
116 |
+
charge: int) -> cudaq.SpinOperator:
|
117 |
+
"""
|
118 |
+
Create a SpinOperator for a given molecule using PySCF + OpenFermion + CUDA-Q.
|
119 |
+
|
120 |
+
Parameters
|
121 |
+
----------
|
122 |
+
geometry : list of (str, (float, float, float)))
|
123 |
+
List describing each atom, e.g. [('H', (0.,0.,0.)), ('H',(0.,0.,bond_length))]
|
124 |
+
basis : str
|
125 |
+
Basis set, e.g. "sto-3g"
|
126 |
+
multiplicity : int
|
127 |
+
Spin multiplicity
|
128 |
+
charge : int
|
129 |
+
Net charge of the molecule
|
130 |
+
|
131 |
+
Returns
|
132 |
+
-------
|
133 |
+
cudaq.SpinOperator
|
134 |
+
The qubit Hamiltonian in CUDA-Q spin-operator form.
|
135 |
+
"""
|
136 |
+
try:
|
137 |
+
# Use PySCF + OpenFermion to build a molecular object
|
138 |
+
molecule = openfermionpyscf.run_pyscf(
|
139 |
+
openfermion.MolecularData(geometry, basis, multiplicity, charge)
|
140 |
+
)
|
141 |
+
|
142 |
+
# Construct the fermionic Hamiltonian
|
143 |
+
molecular_hamiltonian = molecule.get_molecular_hamiltonian()
|
144 |
+
fermion_hamiltonian = get_fermion_operator(molecular_hamiltonian)
|
145 |
+
qubit_hamiltonian = jordan_wigner(fermion_hamiltonian)
|
146 |
+
|
147 |
+
# Convert to CUDA-Q SpinOperator
|
148 |
+
spin_op = cudaq.SpinOperator(qubit_hamiltonian)
|
149 |
+
return spin_op
|
150 |
+
except Exception as e:
|
151 |
+
logging.error(f"Failed to create molecular Hamiltonian: {str(e)}")
|
152 |
+
raise
|
153 |
+
|
154 |
+
# ----------------------------------------------------------
|
155 |
+
# New or updated function for generating a UCCSD ansatz kernel
|
156 |
+
# ----------------------------------------------------------
|
157 |
+
@cudaq.kernel
|
158 |
+
def kernel(qubit_num: int, electron_num: int, thetas: list[float]):
|
159 |
+
"""
|
160 |
+
Generate a UCCSD ansatz kernel given the number of qubits and electrons.
|
161 |
+
|
162 |
+
Parameters
|
163 |
+
----------
|
164 |
+
qubit_num : int
|
165 |
+
Number of qubits in the system
|
166 |
+
electron_num : int
|
167 |
+
Number of electrons (which is the same as number of X gates for HF ref.)
|
168 |
+
|
169 |
+
Returns
|
170 |
+
-------
|
171 |
+
A CUDA-Q kernel that accepts parameters for the UCCSD ansatz.
|
172 |
+
"""
|
173 |
+
qubits = cudaq.qvector(qubit_num)
|
174 |
+
# Prepare the Hartree-Fock reference: X on each occupied orbital
|
175 |
+
for i in range(electron_num):
|
176 |
+
x(qubits[i]) # Apply X gate to each occupied orbital it is defined in the cuda kernel decorator space
|
177 |
+
|
178 |
+
# Add the UCCSD terms
|
179 |
+
cudaq.kernels.uccsd(qubits, thetas, electron_num, qubit_num)
|
180 |
+
|
181 |
+
# define the cost function as the observation of the spin_hamiltonian w.r.t. the ansatz
|
182 |
+
def cost_function(kernel, spin_hamiltonian, qubit_count, electron_count, thetas):
|
183 |
+
# The "cost" is the expectation of the spin_operator w.r.t. the ansatz, for these params
|
184 |
+
exp_val = cudaq.observe(kernel, spin_hamiltonian, qubit_count, electron_count, thetas).expectation()
|
185 |
+
|
186 |
+
logging.info(f"Cost function evaluation: {exp_val:.6f}")
|
187 |
+
return exp_val
|
188 |
+
|
189 |
+
def expand_geometry(geometry_template: List[List[Any]], scale_factor: float) -> List[Tuple[str, Tuple[float, float, float]]]:
|
190 |
+
"""
|
191 |
+
Expand or contract a molecule's geometry by a scale factor.
|
192 |
+
|
193 |
+
Args:
|
194 |
+
geometry_template: List of [atom_symbol, [x, y, z]] coordinates
|
195 |
+
scale_factor: Factor to scale the geometry by (1.0 = no change)
|
196 |
+
|
197 |
+
Returns:
|
198 |
+
List of (atom_symbol, (x, y, z)) tuples with scaled coordinates
|
199 |
+
"""
|
200 |
+
scaled_geometry = []
|
201 |
+
# Find the center of mass (assuming equal weights for simplicity)
|
202 |
+
coords = np.array([coord for _, coord in geometry_template])
|
203 |
+
center = np.mean(coords, axis=0)
|
204 |
+
|
205 |
+
for atom_symbol, coord in geometry_template:
|
206 |
+
# Convert coord to numpy array for vector operations
|
207 |
+
coord = np.array(coord)
|
208 |
+
# Calculate vector from center
|
209 |
+
vec = coord - center
|
210 |
+
# Scale the vector and add back to center
|
211 |
+
scaled_coord = center + vec * scale_factor
|
212 |
+
# Convert back to tuple
|
213 |
+
scaled_geometry.append((atom_symbol, tuple(scaled_coord)))
|
214 |
+
|
215 |
+
return scaled_geometry
|
216 |
+
|
217 |
+
# ----------------------------------------------------------
|
218 |
+
# Edits in run_vqe_simulation to use the new approach
|
219 |
+
# and a CUDA-Q optimizer
|
220 |
+
# ----------------------------------------------------------
|
221 |
+
@spaces.GPU(duration=20)
|
222 |
+
def run_vqe_simulation(molecule_data: Dict[str, Any],
|
223 |
+
scale_factor: float) -> Dict[str, Any]:
|
224 |
+
"""
|
225 |
+
Run a VQE simulation using CUDA-Q.
|
226 |
+
|
227 |
+
Parameters
|
228 |
+
----------
|
229 |
+
molecule_data : Dict[str, Any]
|
230 |
+
Dictionary containing all molecule metadata and parameters
|
231 |
+
scale_factor : float
|
232 |
+
Factor to scale the molecule geometry by (1.0 = original size)
|
233 |
+
|
234 |
+
Returns
|
235 |
+
-------
|
236 |
+
Dict[str, Any]
|
237 |
+
The dictionary has the following keys:
|
238 |
+
- 'final_energy': (float) the final VQE energy
|
239 |
+
- 'parameters': (list) the optimized ansatz parameters
|
240 |
+
- 'success': (bool) indicates if optimization converged
|
241 |
+
- 'iterations': (int) the maximum number of optimizer iterations
|
242 |
+
- 'history': (list) energy values from each iteration
|
243 |
+
- 'message': (str) status or error message
|
244 |
+
"""
|
245 |
+
logging.info(f"Starting VQE simulation for {molecule_data['name']} with scale factor {scale_factor}")
|
246 |
+
|
247 |
+
# Ensure the correct GPU/CPU target is set
|
248 |
+
setup_target()
|
249 |
+
|
250 |
+
# Create scaled geometry
|
251 |
+
geometry = expand_geometry(molecule_data['geometry_template'], scale_factor)
|
252 |
+
logging.info(f"Created scaled geometry with factor = {scale_factor}")
|
253 |
+
|
254 |
+
spin_hamiltonian = create_molecular_hamiltonian(
|
255 |
+
geometry=geometry,
|
256 |
+
basis=molecule_data['basis'],
|
257 |
+
multiplicity=molecule_data['multiplicity'],
|
258 |
+
charge=molecule_data['charge']
|
259 |
+
)
|
260 |
+
|
261 |
+
# Log debugging info about the Hamiltonian
|
262 |
+
term_count = spin_hamiltonian.get_term_count()
|
263 |
+
logging.debug(f"Hamiltonian has {term_count} terms.")
|
264 |
+
logging.debug(f"Hamiltonian details:\n{spin_hamiltonian}")
|
265 |
+
|
266 |
+
# Create the UCCSD ansatz kernel
|
267 |
+
qubit_count = 2 * molecule_data['spatial_orbitals']
|
268 |
+
electron_count = molecule_data['electron_count']
|
269 |
+
optimizer_max_iterations = 10
|
270 |
+
|
271 |
+
# Compute how many UCCSD parameters we need to optimize
|
272 |
+
parameter_count = cudaq.kernels.uccsd_num_parameters(electron_count, qubit_count)
|
273 |
+
logging.info(f"Number of UCCSD parameters to optimize = {parameter_count}")
|
274 |
+
|
275 |
+
# Initialize the parameters for the UCCSD ansatz
|
276 |
+
thetas0 = np.random.normal(0, 1, parameter_count)
|
277 |
+
logging.info(f"Initial parameters: {thetas0}")
|
278 |
+
|
279 |
+
# Create a wrapper function that only takes the parameters to optimize
|
280 |
+
def objective_wrapper(thetas):
|
281 |
+
return cost_function(kernel, spin_hamiltonian, qubit_count, electron_count, thetas)
|
282 |
+
|
283 |
+
# Define the callback function that uses the wrapper
|
284 |
+
exp_vals = []
|
285 |
+
def callback(xk):
|
286 |
+
val = objective_wrapper(xk)
|
287 |
+
exp_vals.append(val)
|
288 |
+
return True
|
289 |
+
|
290 |
+
try:
|
291 |
+
# Test the wrapper function before optimization
|
292 |
+
test_val = objective_wrapper(thetas0)
|
293 |
+
logging.info(f"Debug cost check (initial params): {test_val:.6f}")
|
294 |
+
|
295 |
+
if not np.isfinite(test_val):
|
296 |
+
logging.warning("Debug cost check returned non-finite value. The optimizer may fail.")
|
297 |
+
|
298 |
+
# Perform the VQE optimization using the wrapper function
|
299 |
+
result = minimize(objective_wrapper,
|
300 |
+
thetas0,
|
301 |
+
method='COBYLA',
|
302 |
+
callback=callback,
|
303 |
+
options={'maxiter': optimizer_max_iterations})
|
304 |
+
|
305 |
+
energy = result.fun
|
306 |
+
parameters = result.x
|
307 |
+
success = result.success
|
308 |
+
message = "VQE optimization completed successfully."
|
309 |
+
|
310 |
+
except Exception as e:
|
311 |
+
logging.error(f"VQE optimization failed: {str(e)}")
|
312 |
+
energy = float("inf")
|
313 |
+
parameters = []
|
314 |
+
success = False
|
315 |
+
message = f"VQE optimization failed: {str(e)}"
|
316 |
+
|
317 |
+
logging.info(message)
|
318 |
+
logging.debug(f"Final energy: {energy}")
|
319 |
+
logging.debug(f"Optimized parameters: {parameters}")
|
320 |
+
|
321 |
+
# Build the results dictionary
|
322 |
+
results = {
|
323 |
+
"final_energy": float(energy),
|
324 |
+
"parameters": list(parameters),
|
325 |
+
"success": success,
|
326 |
+
"iterations": optimizer_max_iterations,
|
327 |
+
"history": exp_vals,
|
328 |
+
"message": message,
|
329 |
+
"parameter_count": parameter_count,
|
330 |
+
"hamiltonian_terms": term_count
|
331 |
+
}
|
332 |
+
return results
|
requirements.txt
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# requirements.txt
|
2 |
+
# Lists the essential Python dependencies for the CUDA-Q Gradio VQE demo.
|
3 |
+
|
4 |
+
# Core packages with specific versions
|
5 |
+
numpy==1.24.3
|
6 |
+
scipy==1.10.1
|
7 |
+
|
8 |
+
# Gradio and visualization components
|
9 |
+
gradio==4.44.1
|
10 |
+
gradio_molgallery3d>=0.0.4
|
11 |
+
|
12 |
+
# Plotting and visualization libraries
|
13 |
+
plotly==5.18.0
|
14 |
+
bokeh==3.2.2
|
15 |
+
matplotlib==3.7.1
|
16 |
+
rdkit==2023.09.1
|
17 |
+
|
18 |
+
# ASE for molecular geometry handling
|
19 |
+
ase==3.22.1
|
20 |
+
|
21 |
+
# CUDA-Q libraries for quantum simulation
|
22 |
+
cudaq
|
23 |
+
cudaq_solvers
|
24 |
+
|
25 |
+
# Optional extras for quantum chemistry
|
26 |
+
pyscf==2.6.2
|
27 |
+
openfermion==1.6.1
|
28 |
+
openfermionpyscf==0.5
|
29 |
+
|
30 |
+
# (Optional) Qiskit if desired
|
31 |
+
# qiskit>=0.42.0
|
visualization.py
ADDED
@@ -0,0 +1,189 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
visualization.py
|
3 |
+
Routines for plotting VQE results and molecular visualization using Plotly and MolGallery3D
|
4 |
+
"""
|
5 |
+
|
6 |
+
import plotly.graph_objects as go
|
7 |
+
import numpy as np
|
8 |
+
import logging
|
9 |
+
from rdkit import Chem
|
10 |
+
from rdkit.Chem import AllChem
|
11 |
+
from gradio_molgallery import MolGallery3D
|
12 |
+
import json
|
13 |
+
|
14 |
+
# Configure logging
|
15 |
+
logger = logging.getLogger(__name__)
|
16 |
+
logger.setLevel(logging.INFO)
|
17 |
+
|
18 |
+
def format_molecule_params(molecule_data: dict) -> str:
|
19 |
+
"""
|
20 |
+
Format molecule parameters into a readable HTML string.
|
21 |
+
|
22 |
+
Args:
|
23 |
+
molecule_data: Dictionary containing molecule parameters
|
24 |
+
Returns:
|
25 |
+
HTML formatted string of parameters
|
26 |
+
"""
|
27 |
+
try:
|
28 |
+
# Create a formatted HTML string with molecule parameters
|
29 |
+
params_html = f"""
|
30 |
+
<div style='font-family: monospace; padding: 10px;'>
|
31 |
+
<h3>Molecule Parameters:</h3>
|
32 |
+
<ul style='list-style-type: none; padding-left: 0;'>
|
33 |
+
<li><b>Name:</b> {molecule_data.get('name', 'N/A')} - {molecule_data.get('formula', 'N/A')}</li>
|
34 |
+
<li><b>Electrons:</b> {molecule_data.get('electron_count', 'N/A')}</li>
|
35 |
+
<li><b>Basis:</b> {molecule_data.get('basis', 'N/A')}, <b>Spatial Orbitals:</b> {molecule_data.get('spatial_orbitals', 'N/A')}</li>
|
36 |
+
<li><b>Charge:</b> {molecule_data.get('charge', 'N/A')}</li>
|
37 |
+
</ul>
|
38 |
+
</div>
|
39 |
+
"""
|
40 |
+
return params_html
|
41 |
+
except Exception as e:
|
42 |
+
logger.error(f"Error formatting molecule parameters: {e}")
|
43 |
+
return "<div>Error: Could not format molecule parameters</div>"
|
44 |
+
|
45 |
+
def plot_convergence(results):
|
46 |
+
"""
|
47 |
+
Create a convergence plot from VQE iteration history using Plotly.
|
48 |
+
Returns Plotly figure
|
49 |
+
|
50 |
+
Args:
|
51 |
+
results: Dictionary containing VQE results including history of energy values,
|
52 |
+
or list of energy values directly
|
53 |
+
"""
|
54 |
+
logger.info(f"Plotting convergence with results type: {type(results)}")
|
55 |
+
|
56 |
+
# Extract iteration numbers and energies
|
57 |
+
iterations = []
|
58 |
+
energies = []
|
59 |
+
|
60 |
+
# Handle different input formats
|
61 |
+
if isinstance(results, dict):
|
62 |
+
history = results.get('history', [])
|
63 |
+
else:
|
64 |
+
history = results
|
65 |
+
|
66 |
+
logger.info(f"History type: {type(history)}, length: {len(history)}")
|
67 |
+
|
68 |
+
# Handle both dictionary entries and direct energy values
|
69 |
+
for i, entry in enumerate(history):
|
70 |
+
if isinstance(entry, dict):
|
71 |
+
# Handle dictionary format
|
72 |
+
try:
|
73 |
+
iterations.append(entry.get('iteration', i))
|
74 |
+
energies.append(entry['energy'])
|
75 |
+
except Exception as e:
|
76 |
+
logger.warning(f"Skipping invalid entry {i}: {str(e)}")
|
77 |
+
continue
|
78 |
+
else:
|
79 |
+
# Handle direct energy values
|
80 |
+
try:
|
81 |
+
iterations.append(i)
|
82 |
+
energies.append(float(entry))
|
83 |
+
except (ValueError, TypeError) as e:
|
84 |
+
logger.warning(f"Skipping invalid entry {i}: {str(e)}")
|
85 |
+
continue
|
86 |
+
|
87 |
+
if not iterations or not energies:
|
88 |
+
raise ValueError("No valid iteration data found in results")
|
89 |
+
|
90 |
+
# Create Plotly figure
|
91 |
+
fig = go.Figure()
|
92 |
+
|
93 |
+
# Add energy convergence line
|
94 |
+
fig.add_trace(go.Scatter(
|
95 |
+
x=iterations,
|
96 |
+
y=energies,
|
97 |
+
mode='lines+markers',
|
98 |
+
name='Energy',
|
99 |
+
line=dict(color='blue')
|
100 |
+
))
|
101 |
+
|
102 |
+
# Add final energy line if available
|
103 |
+
final_energy = None
|
104 |
+
if isinstance(results, dict) and 'final_energy' in results:
|
105 |
+
try:
|
106 |
+
final_energy = float(results['final_energy'])
|
107 |
+
except (ValueError, TypeError):
|
108 |
+
pass
|
109 |
+
|
110 |
+
if final_energy is None and energies:
|
111 |
+
final_energy = energies[-1]
|
112 |
+
|
113 |
+
if final_energy is not None:
|
114 |
+
fig.add_hline(
|
115 |
+
y=final_energy,
|
116 |
+
line_dash="dash",
|
117 |
+
line_color="red",
|
118 |
+
annotation_text=f"Final Energy: {final_energy:.6f}",
|
119 |
+
annotation_position="bottom right"
|
120 |
+
)
|
121 |
+
|
122 |
+
# Update layout
|
123 |
+
fig.update_layout(
|
124 |
+
title='VQE Convergence',
|
125 |
+
xaxis_title='Iteration',
|
126 |
+
yaxis_title='Energy (Hartree)',
|
127 |
+
showlegend=True,
|
128 |
+
hovermode='x',
|
129 |
+
width=800,
|
130 |
+
height=600
|
131 |
+
)
|
132 |
+
|
133 |
+
return fig
|
134 |
+
|
135 |
+
def create_molecule_viewer(molecule_id: str, scale_factor: float) -> MolGallery3D:
|
136 |
+
"""
|
137 |
+
Create a 3D visualization of the molecule using MolGallery3D and RDKit.
|
138 |
+
Returns MolGallery3D component for Gradio display
|
139 |
+
|
140 |
+
Args:
|
141 |
+
molecule_id: Molecule identifier (e.g., "H2")
|
142 |
+
scale_factor: Bond length scaling factor (1.0 = original size)
|
143 |
+
"""
|
144 |
+
logger.info(f"Creating 3D view for {molecule_id} with scale_factor={scale_factor}")
|
145 |
+
|
146 |
+
try:
|
147 |
+
# Load molecule data
|
148 |
+
with open('molecules.json', 'r') as f:
|
149 |
+
molecules = json.load(f)
|
150 |
+
|
151 |
+
if molecule_id not in molecules:
|
152 |
+
logger.error(f"Unknown molecule {molecule_id}")
|
153 |
+
return None
|
154 |
+
|
155 |
+
molecule_data = molecules[molecule_id]
|
156 |
+
|
157 |
+
# Create molecule using SMILES
|
158 |
+
mol = Chem.MolFromSmiles(molecule_data['smiles'])
|
159 |
+
if mol is None:
|
160 |
+
logger.error(f"Could not create molecule from SMILES: {molecule_data['smiles']}")
|
161 |
+
return None
|
162 |
+
|
163 |
+
mol = AllChem.AddHs(mol)
|
164 |
+
AllChem.EmbedMolecule(mol, randomSeed=42) # Use fixed seed for consistency
|
165 |
+
|
166 |
+
# Scale the molecule coordinates
|
167 |
+
conf = mol.GetConformer()
|
168 |
+
positions = conf.GetPositions()
|
169 |
+
centroid = np.mean(positions, axis=0)
|
170 |
+
|
171 |
+
for i in range(mol.GetNumAtoms()):
|
172 |
+
pos = positions[i]
|
173 |
+
scaled_pos = centroid + (pos - centroid) * scale_factor
|
174 |
+
conf.SetAtomPosition(i, scaled_pos)
|
175 |
+
|
176 |
+
# Create the MolGallery3D component
|
177 |
+
gallery = MolGallery3D(
|
178 |
+
columns=1,
|
179 |
+
height=400,
|
180 |
+
label=f"{molecule_data.get('name', molecule_id)} (Scale: {scale_factor:.2f})",
|
181 |
+
automatic_rotation=True
|
182 |
+
)
|
183 |
+
|
184 |
+
logger.info("Created MolGallery3D component")
|
185 |
+
return [mol] # Return the molecule as a list as expected by MolGallery3D
|
186 |
+
|
187 |
+
except Exception as e:
|
188 |
+
logger.error(f"Failed to create molecule viewer: {e}")
|
189 |
+
return None
|