Changes:
- added rabbitmq hearbeat, consumer and publisher
- fixed vqe to run with rabbitmq
- added frontend via fasthtml
- added proper .env configuration
This commit is contained in:
2026-05-14 13:27:33 +03:00
parent 290c4fada2
commit 96548e60ea
17 changed files with 1488 additions and 228 deletions

View File

@@ -0,0 +1,505 @@
import logging
import os
from datetime import datetime, timedelta
import psutil
from connections.keycloak import get_valid_access_token, keycloak_openid
from connections.local_files import (
delete_client_info,
delete_token,
delete_verifier,
load_client_info,
load_token,
load_verifier,
save_client_info,
save_token,
save_verifier,
)
from connections.quantum_backend import get_device_by_id, get_or_create_device_by_name
from connections.rabbitmq import (
rabbitmq_manager,
)
from fasthtml.common import (
H1,
H2,
H3,
A,
Button,
Div,
FastHTML,
Form,
Input,
Meta,
P,
RedirectResponse,
Script,
Span,
Strong,
Titled,
)
from fasthtml.pico import Card, Container, picolink
from fasthtml.xtend import Style
from keycloak.pkce_utils import generate_code_challenge, generate_code_verifier
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
REDIRECT_URI = f"http://localhost:{os.environ['PORT']}/callback"
css = Style(":root {--pico-font-size:90%,--pico-font-family: Pacifico, cursive;}")
app = FastHTML(hdrs=(picolink, css))
@app.route("/", methods="get")
async def main_page():
# Проверяем наличие валидного токена
token_data = load_token()
client_info = load_client_info()
if token_data:
expires_at = datetime.fromisoformat(token_data.get("expires_at", "2000-01-01"))
if expires_at > datetime.now():
# Если есть токен, но нет информации о клиенте, перенаправляем на регистрацию
if not client_info:
return RedirectResponse("/register-client", status_code=303)
return RedirectResponse("/dashboard", status_code=303)
else:
delete_token()
delete_client_info()
return Titled(
"Главная",
H1("Добро пожаловать в Квантовый Симулятор"),
P("Пожалуйста, войдите для продолжения"),
A("Войти через Keycloak", href="/login", cls="button"),
)
@app.route("/login", methods="get")
async def login():
# Генерация PKCE кода верификатора и запроса
code_verifier = generate_code_verifier()
code_challenge, code_challenge_method = generate_code_challenge(
code_verifier, method="S256"
)
# Сохраняем верификатор локально
save_verifier(code_verifier)
# Формирование URL авторизации
auth_url = keycloak_openid.auth_url(
redirect_uri=REDIRECT_URI,
scope="openid profile email",
code_challenge=code_challenge,
code_challenge_method=code_challenge_method,
)
return RedirectResponse(auth_url, status_code=303)
@app.route("/callback", methods="get")
async def callback(request):
code = request.query_params.get("code")
if not code:
return Titled(
"Ошибка", P("Код не найден"), A("Попробовать снова", href="/login")
)
# Загружаем верификатор
code_verifier = load_verifier()
if not code_verifier:
return Titled(
"Ошибка",
P("PKCE верификатор не найден"),
A("Попробовать снова", href="/login"),
)
# Очищаем верификатор сразу после использования
delete_verifier()
try:
# Обмен кода на токен
token = keycloak_openid.token(
grant_type="authorization_code",
code=code,
redirect_uri=REDIRECT_URI,
code_verifier=code_verifier,
)
# Расчет времени истечения
expires_in = token.get("expires_in", 3600)
expires_at = datetime.now() + timedelta(seconds=expires_in)
# Получение информации о пользователе
access_token = token.get("access_token")
if not access_token:
raise Exception
user_info = keycloak_openid.userinfo(access_token)
# Сохраняем токен
token_data_to_save = {
"access_token": access_token,
"refresh_token": token.get("refresh_token"),
"expires_at": expires_at.isoformat(),
"id_token": token.get("id_token"),
"user_info": user_info,
}
save_token(token_data_to_save)
# Перенаправление на регистрацию клиента (проверит, зарегистрирован ли уже)
return RedirectResponse("/register-client", status_code=303)
except Exception as e:
logger.error(f"Ошибка при обмене токена: {e}")
return Titled("Ошибка", P(str(e)), A("Попробовать снова", href="/login"))
@app.route("/register-client", methods="get")
async def register_client(request):
"""Страница регистрации клиента в центральном микросервисе"""
token_data = load_token()
if not token_data:
return RedirectResponse("/login", status_code=303)
# Проверяем, не зарегистрирован ли уже
client_info = load_client_info()
if client_info:
return RedirectResponse("/dashboard", status_code=303)
# Проверяем истечение токена
expires_at = datetime.fromisoformat(str(token_data.get("expires_at")))
if expires_at <= datetime.now():
delete_token()
return RedirectResponse("/login", status_code=303)
user_info = token_data.get("user_info", {})
username = user_info.get(
"preferred_username", user_info.get("name", "Пользователь")
)
return Titled(
"Регистрация клиента",
Container(
Card(
H2("Добро пожаловать в Квантовый Симулятор!"),
P(f"Здравствуйте, {username}!"),
P("Это устройство должно быть зарегистрировано в центральном сервисе."),
P(
"Пожалуйста, укажите уникальное имя и количество кубит для этого клиента:"
),
Form(
Input(
type="text",
name="client_name",
id="client_name",
placeholder="например: рабочий-ноутбук, домашний-пк, raspberry-pi-01",
required=True,
style="width: 100%; padding: 10px; margin: 10px 0;",
),
Input(
type="number",
name="number_qubits",
id="number_qubits",
placeholder=0,
required=True,
style="width: 100%; padding: 10px; margin: 10px 0;",
),
Div(
Button(
"Зарегистрировать клиент",
type="submit",
cls="button",
style="background-color: #4CAF50;",
),
style="margin-top: 10px;",
),
hx_post="/register-client",
hx_target="#registration-result",
hx_swap="innerHTML",
style="margin-top: 20px;",
),
Div(id="registration-result"),
),
Div(
Button(
"Выйти",
onclick="window.location.href='/logout'",
cls="button",
style="background-color: #f44336; margin-top: 20px;",
),
style="text-align: center;",
),
),
)
@app.route("/register-client", methods="post")
async def register_client_post(request):
"""Обработка POST запроса регистрации клиента"""
form_data = await request.form()
client_name = form_data.get("client_name")
number_qubits = form_data.get("number_qubits")
if not number_qubits:
return Div(
P("❌ Обязательно необходимо указать колчество кубит", style="color: red;"),
)
if not client_name:
return Div(
P("❌ Имя клиента обязательно", style="color: red;"),
)
# Проверка формата имени клиента
import re
if not re.match(r"^[a-zA-Z0-9_-]+$", client_name):
return Div(
P(
"❌ Имя клиента может содержать только буквы, цифры, дефисы и подчеркивания",
style="color: red;",
),
Button("Попробовать снова", onclick="location.reload()", cls="button"),
)
token_data = load_token()
if not token_data:
return Div(
P("❌ Сессия истекла. Пожалуйста, войдите снова.", style="color: red;"),
A("Войти", href="/login", cls="button"),
)
access_token = token_data.get("access_token")
# Регистрация в центральном микросервисе
try:
response = get_or_create_device_by_name(
client_name, number_qubits, access_token
)
save_client_info(
{
"system_id": response["system_id"],
}
)
return Div(
P(
"✅ Клиент успешно зарегистрирован!",
style="color: green; font-weight: bold;",
),
P(f"Имя клиента: {client_name}"),
P("Перенаправление на панель управления..."),
Meta(http_equiv="refresh", content="2;url=/dashboard"),
Script("setTimeout(() => { window.location.href = '/dashboard'; }, 2000);"),
)
except Exception:
return Div(
P("❌ Ошибка регистрации", style="color: red;"),
Button("Попробовать снова", onclick="location.reload()", cls="button"),
P(
"Если проблема повторяется, обратитесь к администратору.",
style="font-size: 12px; margin-top: 10px;",
),
)
@app.route("/dashboard", methods="get")
async def dashboard():
access_token = get_valid_access_token()
if rabbitmq_manager._connection and access_token:
await rabbitmq_manager._connection.update_secret(
access_token, reason="Token expired"
)
if not access_token:
# Token refresh failed, redirect to login
return RedirectResponse("/", status_code=303)
token_data = load_token()
client_info = load_client_info()
if not token_data or not client_info:
return RedirectResponse("/", status_code=303)
user_info = token_data.get("user_info", {})
username = user_info.get(
"preferred_username", user_info.get("name", "Пользователь")
)
system_id = client_info.get("system_id", None)
if not token_data:
return Div(
P("❌ Сессия истекла. Пожалуйста, войдите снова.", style="color: red;"),
A("Войти", href="/login", cls="button"),
)
try:
if system_id:
device_data = get_device_by_id(system_id, access_token)
device_name = device_data["system"]["system_name"]
max_qubits = device_data["system"]["max_qubits"]
created_at = device_data["system"]["created_at"]
else:
device_name = "error"
max_qubits = "error"
created_at = "error"
except Exception as a:
device_name = "error"
max_qubits = "error"
created_at = "error"
return Titled(
"Панель управления",
Container(
Card(
P(Strong("Имя пользователя: "), username),
P(Strong("Email: "), user_info.get("email", "Не указан")),
P(Strong("Устройство зарегестрировано: "), created_at),
Div(style="border: 1px solid black"),
P(Strong("Имя вычислительной системы: "), device_name),
P(Strong("Макс. количество кубит: "), max_qubits),
Div(Button("Изменить", style="background-color: blue;")),
Div(
id="client-status",
hx_get="/client-status",
hx_trigger="load, every 30s",
),
),
Div(id="memory-stats", hx_get="/memory-stats", hx_trigger="load, every 5s"),
A(
Button(
"Выйти",
style="background-color: #f44336;",
),
href="/logout",
style="margin: 0px;color: inherit;text-decoration: inherit;",
),
),
)
@app.route("/client-status", methods="get")
async def status():
"""Получить статус клиента из центрального микросервиса"""
token_data = load_token()
client_info = load_client_info()
if not token_data or not client_info:
return P("Клиент не зарегистрирован", style="color: orange;")
if rabbitmq_manager._connection:
# TODO: FIX
status = (
rabbitmq_manager._connection.connected
) # get_client_status(client_name, access_token)
if status:
return Div(
P(
Strong("Статус клиента: "),
Span("✅ Активен", style="color: green;"),
),
style="padding: 10px; border-radius: 5px; margin: 10px 0;",
)
else:
return Div(
P(
Strong("Статус клиента: "),
Span("Не подключен", style="color: orange;"),
),
P(
"Не удалось связаться с центральным сервисом или данному клиенту отказано в подключении"
),
style="padding: 10px; border-radius: 5px; margin: 10px 0;",
)
@app.route("/memory-stats", methods="get")
async def memory_stats():
"""Endpoint, возвращающий карточку использования памяти (для обновления через HTMX)"""
process = psutil.Process(os.getpid())
memory_info = process.memory_info()
current_time = datetime.now()
timestamp = current_time.strftime("%Y-%m-%d %H:%M:%S")
def format_bytes(bytes):
for unit in ["Б", "КБ", "МБ", "ГБ"]:
if bytes < 1024.0:
return f"{bytes:.1f} {unit}"
bytes /= 1024.0
return f"{bytes:.1f} ГБ"
mem = 0
if os.path.isfile("/sys/fs/cgroup/memory.max"):
with open("/sys/fs/cgroup/memory.max") as limit:
try:
mem = int(limit.read())
except Exception:
mem = 0
# resource.setrlimit(resource.RLIMIT_AS, (mem, mem))
rss_memory = format_bytes(memory_info.rss)
if mem > 0:
memory_percent = (memory_info.rss / mem) * 100
else:
memory_percent = process.memory_percent()
bar_color = (
"#4CAF50"
if memory_percent < 1
else "#FFC107"
if memory_percent < 5
else "#F44336"
)
return Div(
H2("Использование памяти"),
Card(
Div(
H3(rss_memory),
style="width: 100%;display:flex;flex-direction:column;align-items:center",
),
Div(
Div(
style=f"width: {min(memory_percent, 100)}%; background-color: {bar_color}; height: 20px; border-radius: 10px;"
),
style="width: 100%; background-color: #e0e0e0; border-radius: 10px; overflow: hidden; margin: 10px 0;",
),
P(
f"{memory_percent:.2f}% от системной памяти",
style="text-align: center; font-size: 12px;",
),
P(
Strong("Обновлено: "),
timestamp,
style="text-align: center; font-size: 11px; color: #666;",
),
),
)
@app.route("/logout", methods="get")
async def logout():
token_data = load_token()
if token_data and token_data.get("refresh_token"):
keycloak_openid.logout(token_data["refresh_token"])
# Очищаем локальные файлы
delete_token()
delete_client_info()
delete_verifier()
return Titled(
"Выход выполнен",
H2("До свидания!"),
P("Вы успешно вышли из системы."),
P("Ваш клиент был отменен в центральном сервисе."),
A("Войти снова", href="/", cls="button"),
)
# Опционально: endpoint для проверки работоспособности для центрального сервиса
@app.route("/health", methods="get")
async def health():
return {"status": "healthy", "timestamp": datetime.now().isoformat()}

View File

@@ -0,0 +1,326 @@
import asyncio
import json
from multiprocessing import Pipe, Process
from multiprocessing.connection import Connection
from time import sleep
import aio_pika
import pennylane
from aio_pika.abc import AbstractIncomingMessage
from connections.keycloak import (
get_valid_access_token,
)
from connections.local_files import load_client_info
from connections.quantum_backend import (
get_device_by_id,
)
from connections.rabbitmq import rabbitmq_manager
from fastcore.xtras import datetime
from modules.vqe import prepare_data, run_vqe
from pennylane.devices import Device
from uvicorn.main import logger
HEARTBEAT_EXCHANGE = "heartbeat"
HEARTBEAT_INTERVAL = 5
EXCHANGE_NAME = "progress_report"
# Global flag to track if VQE is busy
vqe_busy = False
vqe_busy_lock = asyncio.Lock()
# --- RabbitMQ Consumer Logic ---
async def process_message(message: AbstractIncomingMessage):
"""Your business logic for handling a message."""
global vqe_busy
async with message.process():
body = message.body.decode()
print(f"Received and processing: {body}")
# Parse the JSON message
data = json.loads(body)
task_id = data.get("task_id")
qubits_needed = data.get("qubits_needed")
message_data = data.get("data", {})
loop = asyncio.get_running_loop()
# Extract molecular information from the text
dev = pennylane.device("lightning.qubit", wires=qubits_needed)
sleep(1)
await publish_message(task_id, "", "IN SYSTEM")
data = prepare_data(message_data)
parent_conn, child_conn = Pipe()
# Set busy flag before starting VQE
async with vqe_busy_lock:
vqe_busy = True
try:
# Run VQE in a separate process - will exit automatically when done
vqe_process = Process(
target=run_vqe_process,
args=(child_conn, dev, data),
)
vqe_process.start()
# Monitor pipe in a separate thread to avoid blocking the event loop
def monitor_pipe_thread():
while vqe_process.is_alive():
if parent_conn.poll():
logger.info("polling success")
result = parent_conn.recv()
if isinstance(result, dict) and result.get("status") == "ERROR":
asyncio.run_coroutine_threadsafe(
publish_message(task_id, json.dumps(result), "ERROR"),
loop,
)
break
# Check for sentinel
if result is None:
asyncio.run_coroutine_threadsafe(
publish_message(task_id, "", "COMPLETE"), loop
)
break
# Drain to get latest result
while parent_conn.poll():
result = parent_conn.recv()
if result is None:
asyncio.run_coroutine_threadsafe(
publish_message(task_id, "", "COMPLETE"), loop
)
break
if result is not None:
logger.info(f"publishing {result}")
# Run async publish in the event loop
asyncio.run_coroutine_threadsafe(
publish_message(task_id, json.dumps(result)), loop
)
else:
import time
time.sleep(5)
else:
if vqe_process.exitcode == 0:
asyncio.run_coroutine_threadsafe(
publish_message(task_id, "", "COMPLETE"), loop
)
else:
if parent_conn.poll():
logger.info("polling success")
result = parent_conn.recv()
if (
isinstance(result, dict)
and result.get("status") == "ERROR"
):
print(f"ERROR ERROR ERROR ERROR ERROR ERROR")
asyncio.run_coroutine_threadsafe(
publish_message(
task_id, json.dumps(result), "ERROR"
),
loop,
)
# Run the monitor in a thread
import threading
monitor_thread = threading.Thread(target=monitor_pipe_thread, daemon=True)
monitor_thread.start()
# Wait for the process to finish
await asyncio.to_thread(vqe_process.join)
# Wait for monitor thread to finish
monitor_thread.join(timeout=1)
print(f"Completed task {task_id}")
finally:
# Clear busy flag after VQE completes (success or failure)
async with vqe_busy_lock:
vqe_busy = False
def run_vqe_process(conn: Connection, dev: Device, data: dict):
"""Run VQE in a separate process - process exits when this function returns."""
try:
# Run VQE
run_vqe(conn=conn, dev1=dev, data=data)
# Send sentinel to indicate completion
conn.send(None) # Signal completion
conn.close()
except Exception as e:
logger.info(f"Error in VQE: {e}")
import traceback
# Send error information through the pipe BEFORE sending None
error_msg = {
"error": str(e),
"traceback": traceback.format_exc(),
"status": "ERROR",
}
conn.send(error_msg) # Send error details
conn.send(None) # Signal completion
import time
time.sleep(5)
conn.close()
async def consume_messages_topic():
"""
Subscribe to team-specific qubit queues.
Multiple systems subscribe to the SAME queue: team_{team_id}.qubits_{N}
where N is the number of qubits this system can handle (1 to max_qubits).
This enables round-robin task distribution among systems with sufficient qubits.
"""
channel = await rabbitmq_manager.get_consumer_channel()
if not channel:
raise Exception("Failed to get consumer channel")
client_info = load_client_info()
if not client_info:
raise Exception("Failed to load client info")
system_id = client_info["system_id"]
access_token = get_valid_access_token()
device_data = get_device_by_id(system_id, access_token)
teams = device_data["teams"]
if not teams:
print(f"System {system_id} is not part of any team. No queues to subscribe.")
# Subscribe to qubit-specific queues for each team
subscription_count = 0
for team in teams:
for qubits in range(1, team["num_qubits"] + 1):
# Queue name format: team_{team_id}.qubits_{qubits}
queue_name = f"team_{team['team']['team_id']}.qubits_{qubits}"
# Declare the queue (durable, shared among multiple consumers)
queue = await channel.declare_queue(
queue_name,
durable=True,
arguments={
"x-max-priority": 100, # Allow priorities 0-10
},
)
# Start consuming from this queue
await queue.consume(
process_message,
arguments={"x-priority": qubits, "x-priority-max": 10},
)
subscription_count += 1
print(f"System {system_id} subscribed to queue: {queue_name}")
print(f"System {system_id} subscribed to {subscription_count} queues")
await asyncio.Future() # Keep running
# --- Heartbeat Publisher Logic ---
async def publish_heartbeat():
"""Continuously publish 'alive' or 'busy' messages using the shared connection."""
global vqe_busy
channel = await rabbitmq_manager.get_heartbeat_channel()
if not channel:
raise Exception("Failed to get heartbeat channel")
exchange = await channel.declare_exchange(
HEARTBEAT_EXCHANGE, type=aio_pika.ExchangeType.FANOUT, durable=True
)
print(
f"Heartbeat publisher started, sending status every {HEARTBEAT_INTERVAL} seconds..."
)
while True:
try:
client_info = load_client_info()
if not client_info:
raise Exception("Failed to load client info")
system_id = client_info["system_id"]
# Check current busy status
async with vqe_busy_lock:
current_status = "BUSY" if vqe_busy else "ONLINE"
# Add timestamp for better monitoring
heartbeat_message = {
"device_id": system_id,
"status": current_status,
"timestamp": datetime.now().isoformat(),
}
message_body = json.dumps(heartbeat_message)
await exchange.publish(
aio_pika.Message(
body=message_body.encode(),
delivery_mode=aio_pika.DeliveryMode.PERSISTENT,
),
routing_key="",
)
print(
f"Heartbeat sent: {current_status} at {asyncio.get_event_loop().time():.2f}"
)
await asyncio.sleep(HEARTBEAT_INTERVAL)
except Exception as e:
print(f"Error publishing heartbeat: {e}")
await asyncio.sleep(1)
async def publish_message(task_id: int, message: str, status: str = "PROCESSING"):
"""Publish a message to the exchange using shared connection."""
try:
channel = await rabbitmq_manager.get_publisher_channel()
if not channel:
raise Exception("Failed to get publisher channel")
# Declare exchange instead of queue
exchange = await channel.declare_exchange(
EXCHANGE_NAME, # Using QUEUE_NAME as exchange name
type=aio_pika.ExchangeType.DIRECT, # or TOPIC/FANOUT based on your needs
durable=True,
)
client_info = load_client_info()
if not client_info:
raise Exception("Failed to load client info")
system_id = client_info["system_id"]
# Publish directly to the exchange
await exchange.publish(
aio_pika.Message(
body=message.encode(),
headers={
"task_id": str(task_id),
"status": status, # "PROCESSING" or "COMPLETE"
"system_id": str(system_id),
},
content_type="application/json",
delivery_mode=aio_pika.DeliveryMode.PERSISTENT,
),
routing_key="", # Empty routing key for direct exchange, or use task_id as routing key
)
print(f"Message published to exchange {EXCHANGE_NAME}: {message}")
except Exception as e:
print(f"Error publishing message: {e}")
import traceback
traceback.print_exc()

View File

@@ -0,0 +1,168 @@
import os
from multiprocessing.connection import Connection
import jax
import pennylane as qml
import pennylane.numpy as np
from jax import numpy as jnp
from pennylane import qchem
from pennylane.devices import Device
from pennylane.optimize import GradientDescentOptimizer
jax.config.update("jax_enable_x64", True)
os.environ["OMP_NUM_THREADS"] = "16"
def parse_xyz_from_text(text: str):
"""Parse XYZ format from text content."""
lines = text.strip().split("\n")
# First line: number of atoms
num_atoms = int(lines[0].strip())
# Second line: Charge/Multiplicity/Electrons/Orbitals (optional)
# Skip or parse as needed
symbols = []
coordinates = []
# Parse atom lines (after the second line)
for line in lines[2 : 2 + num_atoms]:
parts = line.strip().split()
if len(parts) >= 4:
symbol = parts[0]
x, y, z = float(parts[1]), float(parts[2]), float(parts[3])
symbols.append(symbol)
coordinates.append([x, y, z])
return symbols, coordinates
def extract_electron_info(text: str):
"""Extract electron and orbital counts from the second line."""
lines = text.strip().split("\n")
if len(lines) >= 2:
second_line = lines[1]
# Parse "Charge=0 Multiplicity=1 Electrons=3 Orbitals=3"
electrons = 3 # default
orbitals = 3 # default
charge = 0 # default
multiplicity = 1 # default
for part in second_line.split():
if "Electrons=" in part:
electrons = int(part.split("=")[1])
elif "Orbitals=" in part:
orbitals = int(part.split("=")[1])
elif "Charge=" in part:
charge = int(part.split("=")[1])
elif "Multiplicity=" in part:
multiplicity = int(part.split("=")[1])
return electrons, orbitals, charge, multiplicity
return 3, 3, 0, 1 # fallback defaults
def prepare_data(data):
text_content = data.get("text", "")
# Parse the molecular data (assuming it's in XYZ format)
symbols, coordinates = parse_xyz_from_text(text_content)
# Extract electron/orbital info (from the Charge/Multiplicity line)
# "Charge=0 Multiplicity=1 Electrons=3 Orbitals=3"
electrons, orbitals, charge, multiplicity = extract_electron_info(text_content)
return {
"symbols": symbols,
"coordinates": coordinates,
"charge": charge,
"multiplicity": multiplicity,
"active_electrons": electrons,
"active_orbitals": orbitals,
"max_iterations": data.get("max_iterations", 200),
"conv_tol": data.get("conv_tol", 1e-6),
"step_size": data.get("step_size", 0.05),
}
def run_vqe(conn: Connection, dev1: Device, data: dict):
coordinates = jnp.array(data.get("coordinates"))
charge = int(data.get("charge"))
multiplicity = int(data.get("multiplicity"))
molecule = qchem.Molecule(
data.get("symbols"),
coordinates,
charge=charge,
mult=multiplicity,
)
active_electrons = int(data.get("active_electrons"))
active_orbitals = int(data.get("active_orbitals"))
max_iterations = int(data.get("max_iterations", 200))
step_size = float(data.get("step_size", 0.05))
conv_tol = float(data.get("conv_tol", 1e-6))
H, qubits = qchem.molecular_hamiltonian(
molecule,
active_electrons=active_electrons,
active_orbitals=active_orbitals,
method="openfermion",
) # type: ignore
singles, doubles = qml.qchem.excitations(active_electrons, qubits)
params = np.array(np.zeros(len(singles) + len(doubles)), requires_grad=True)
conn.send(
{
"iter_num": 0,
"energy": None,
"conv": None,
"params": params.tolist() if hasattr(params, "tolist") else list(params),
}
)
@qml.qnode(dev1)
def circuit(param, wires):
# Map excitations to the wires the UCCSD circuit will act on
s_wires, d_wires = qml.qchem.excitations_to_wires(singles, doubles)
qml.UCCSD(
param,
wires,
s_wires=s_wires,
d_wires=d_wires,
init_state=qml.qchem.hf_state(active_electrons, qubits),
)
return qml.expval(H)
def cost_fn(param):
return circuit(param, wires=range(qubits))
opt = GradientDescentOptimizer(stepsize=step_size)
for n in range(max_iterations):
# Take step
params, prev_energy = opt.step_and_cost(cost_fn, params)
energy = cost_fn(params)
# Calculate difference between new and old energies
conv = np.abs(energy - prev_energy)
conn.send(
{
"iter_num": n,
"energy": float(energy),
"conv": float(conv),
"params": params.tolist()
if hasattr(params, "tolist")
else list(params),
}
)
if conv <= conv_tol:
break