diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f03ac90
--- /dev/null
+++ b/README.md
@@ -0,0 +1,198 @@
+
+# Quantum Compute Modules
+
+Репозиторий содержит набор модулей для распределённой системы квантовых вычислений. Каждый модуль представляет собой тип эксперимента, который может быть выполнен на локальных вычислительных системах.
+
+
+## Архитектура модуля
+
+Каждый модуль состоит из двух независимых компонентов:
+
+| Компонент | Назначение |
+|-----------|------------|
+| **Frontend** | Пользовательский интерфейс. React SPA, компилируется в HTML, встраивается в iframe на центральном сервере. |
+| **Executor** | Python модуль, который скачивается и выполняется на локальной вычислительной системе. |
+
+## Frontend
+
+### Режимы работы
+
+Frontend поддерживает два режима отображения:
+
+| Режим | Когда используется | Что показывает |
+|-------|-------------------|----------------|
+| `List` | В списке задач эксперимента | Краткая информация (карточка) |
+| `Editor` | На странице отдельной задачи | Полный редактор с параметрами |
+
+### API взаимодействия (postMessage)
+
+#### Получение данных от системы
+
+```javascript
+window.addEventListener("message", (event) => {
+ const { type, data } = event.data;
+
+ if (type === "plugin-data") {
+ // data.taskData — JSON строка с данными задачи
+ // data.mode — "List" или "Editor"
+ // data.theme — "light" или "dark"
+ // data.simProgress — прогресс выполнения (если есть)
+ }
+
+ if (type === "update") {
+ // Сигнал обновить UI (после сохранения)
+ }
+});
+```
+
+#### Отправка данных в систему
+
+```javascript
+// При изменении данных задачи
+window.parent.postMessage({
+ type: "plugin-update",
+ data: JSON.stringify(taskData), // Данные задачи
+ qubits_needed: number // Требуемое количество кубит
+}, "*");
+
+// Сигнал готовности (отправляется после загрузки)
+window.parent.postMessage({ type: "plugin-ready" }, "*");
+```
+
+### Обязательные экспорты
+
+React приложение должно экспортировать компоненты с интерфейсом:
+
+```typescript
+interface TaskData {
+ // Определяется для каждого модуля
+}
+
+interface TaskEditorProps {
+ data: TaskData | undefined;
+ setData: (data: TaskData, qubits_needed: number) => void;
+ counter: number; // Инкрементируется при обновлении данных извне
+ simProgress: any; // Прогресс выполнения (conn.send из executor)
+}
+```
+
+### Сборка
+
+```bash
+npm run build
+# Результат: dist/index.html — один файл со встроенными CSS/JS
+```
+
+## Executor
+
+Python модуль, который скачивается с сервера и выполняется на локальной ВС. Должен содержать две функции:
+
+### prepare_data
+
+Валидация и предобработка данных задачи. Вызывается на сервере перед помещением задачи в очередь.
+
+```python
+def prepare_data(data: dict, system_info: dict) -> dict:
+```
+
+Результаты, возвращаемые функцией, передаются затем в run_computation.
+
+В случае ошибки в данных, функция должны поднять ошибку.
+
+### run_computation
+
+Выполнение вычислений на локальной ВС.
+
+```python
+def run_computation(conn, dev, data):
+ """
+ Args:
+ conn: Connection объект для отправки прогресса
+ conn.send(dict) — отправляет сообщение на сервер
+ (сообщения сохраняются в simulation_result.result_data)
+
+ dev: PennyLane устройство
+ qml.device(dev['name'], wires=dev['wires'])
+
+ data: processed_data из prepare_data
+
+ Returns: None
+ """
+```
+
+### Пример executor'а
+
+```python
+def prepare_data(data: dict, system_info: dict) -> dict:
+ # Валидация входных данных
+ if "my_param" not in data:
+ return {
+ "valid": False,
+ "error_message": "Отсутствует параметр my_param"
+ }
+
+ return {
+ "valid": True,
+ "processed_data": data,
+ "qubits_required": data.get("qubits", 10)
+ }
+
+
+def run_computation(conn, dev, data):
+ import numpy as np
+
+ for step in range(data.get("steps", 100)):
+ # ... вычисления ...
+
+ # Отправка прогресса
+ conn.send({
+ "step": step,
+ "current_value": current_value
+ })
+
+ return
+```
+
+## Создание нового модуля
+
+### Шаг 1: Создание Frontend
+
+1. Скопируйте шаблон `new_module/` или создайте React приложение с нуля
+2. Реализуйте компоненты `ListItem` и `EditorPage`
+3. Реализуйте `wrapper.tsx` с обработкой postMessage
+4. Соберите проект: `npm run build`
+
+### Шаг 2: Создание Executor
+
+1. Создайте файл `executor.py`
+2. Реализуйте функцию `prepare_data`
+3. Реализуйте функцию `run_computation`
+
+### Шаг 3: Регистрация модуля
+
+Через API центрального сервера:
+
+```bash
+curl -X POST https://quantum-backend/experiment/types \
+ -F "name=Название модуля" \
+ -F "description=Описание модуля" \
+ -F "file_frontend=@dist/index.html" \
+ -F "file_comp_system=@executor.py"
+```
+
+
+## Список модулей
+
+| Модуль | Описание | Статус |
+|--------|----------|--------|
+| `vqe` | Расчёт энергии основного состояния молекул | ✅ Готов |
+
+## Шаблон нового модуля
+
+```bash
+git clone https://git.deowl.ru/vkrb/vqe_module
+cd quantum-modules
+cp -r new_module my_module
+# Редактируем frontend и executor
+# Регистрируем через API
+```
diff --git a/backend/src/app.py b/backend/src/app.py
index 1d337be..0202f9a 100644
--- a/backend/src/app.py
+++ b/backend/src/app.py
@@ -1,7 +1,7 @@
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
-from fastapi import FastAPI
+from fastapi import FastAPI, Request
from fastapi.exceptions import HTTPException
from fastapi.middleware.cors import CORSMiddleware
@@ -14,7 +14,13 @@ from logging_config import logger
from openbabel import pybel
from pyscf import gto # pyright: ignore
from redis import asyncio as aioredis
-from request_response_models import BasicError, ConvertRequest, MolFileModel
+from request_response_models import (
+ BasicError,
+ ConvertRequest,
+ MolFileModel,
+ SmilesRequest,
+)
+from starlette.middleware.base import BaseHTTPMiddleware
@asynccontextmanager
@@ -28,25 +34,47 @@ async def lifespan(_: FastAPI) -> AsyncIterator[None]:
app = FastAPI(lifespan=lifespan)
-origins = [
- "http://localhost",
- "http://localhost:8001",
-]
+class ForceCORSHeadersMiddleware(BaseHTTPMiddleware):
+ async def dispatch(self, request: Request, call_next):
+ # Handle OPTIONS preflight requests
+ if request.method == "OPTIONS":
+ response = JSONResponse(status_code=200, content={})
+ else:
+ response = await call_next(request)
-app.add_middleware(
- CORSMiddleware,
- allow_origins=origins,
- allow_credentials=True,
- allow_methods=["*"],
- allow_headers=["*"],
-)
+ # Always add CORS headers
+ response.headers["Access-Control-Allow-Origin"] = "*"
+ response.headers["Access-Control-Allow-Methods"] = (
+ "GET, POST, PUT, DELETE, OPTIONS"
+ )
+ response.headers["Access-Control-Allow-Headers"] = "*"
+ response.headers["Access-Control-Allow-Credentials"] = "true"
+
+ return response
+
+
+# Add this middleware FIRST
+app.add_middleware(ForceCORSHeadersMiddleware)
+
+
+@app.options("/{path:path}")
+async def options_handler(request: Request):
+ return JSONResponse(
+ content={},
+ status_code=200,
+ headers={
+ "Access-Control-Allow-Origin": "*", # or specific origins
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
+ "Access-Control-Allow-Headers": "*",
+ },
+ )
@app.post(
"/convert",
responses={
200: {"description": "Coversion Successful", "model": MolFileModel},
- 400: {"description": "Item created", "model": BasicError},
+ 400: {"description": "Error Converting", "model": BasicError},
},
)
def convert_molecule(req: ConvertRequest):
@@ -84,6 +112,22 @@ def convert_molecule(req: ConvertRequest):
raise HTTPException(status_code=400, detail={"error": str(e)})
+@app.post(
+ "/smiles",
+)
+def smiles_mol(req: SmilesRequest):
+ try:
+ # Rad the string and format from request
+ mol = pybel.readstring("xyz", req.text)
+ mol.addh()
+ mol.OBMol.SetTitle("")
+
+ mol2string = mol.write("smi", opt={"h": True, "U": True})
+ return JSONResponse({"molfile": mol2string}, 200)
+ except Exception as e:
+ raise HTTPException(status_code=400, detail={"error": str(e)})
+
+
@app.get("/informats")
@cache()
async def get_informats():
diff --git a/backend/src/request_response_models.py b/backend/src/request_response_models.py
index c8eec84..ce09a64 100644
--- a/backend/src/request_response_models.py
+++ b/backend/src/request_response_models.py
@@ -9,6 +9,10 @@ class ConvertRequest(BaseModel):
optimize_geometry: bool = False
+class SmilesRequest(BaseModel):
+ text: str
+
+
class MolFileModel(BaseModel):
molfile: str
diff --git a/frontend-plugin/index.html b/frontend-plugin/index.html
index 360e96f..10e2d28 100644
--- a/frontend-plugin/index.html
+++ b/frontend-plugin/index.html
@@ -1,6 +1,6 @@
-
+
@@ -9,8 +9,6 @@
margin: 0;
padding: 0;
box-sizing: border-box;
- background: none;
- background-color: var(--mantine-color-secondary);
}
body {
font-family:
@@ -19,7 +17,7 @@
}
-
+
diff --git a/frontend-plugin/src/EditorPage/CodeTextArea/CodeTextArea.tsx b/frontend-plugin/src/EditorPage/CodeTextArea/CodeTextArea.tsx
index 7688d2d..effe891 100644
--- a/frontend-plugin/src/EditorPage/CodeTextArea/CodeTextArea.tsx
+++ b/frontend-plugin/src/EditorPage/CodeTextArea/CodeTextArea.tsx
@@ -4,6 +4,7 @@ import { useEffect, useRef } from "react";
interface TextAreaProps {
text: string;
onTextChange: (text: string) => void;
+ enabled: boolean;
}
export function CodeTextArea(props: TextAreaProps) {
@@ -52,6 +53,7 @@ export function CodeTextArea(props: TextAreaProps) {
className="MoleculeEdit"
value={props.text}
ref={inputRef}
+ disabled={!props.enabled}
onChange={(e) => {
props.onTextChange(e.currentTarget.value);
}}
diff --git a/frontend-plugin/src/EditorPage/ConvertBackendCalls.tsx b/frontend-plugin/src/EditorPage/ConvertBackendCalls.tsx
new file mode 100644
index 0000000..5cff2a9
--- /dev/null
+++ b/frontend-plugin/src/EditorPage/ConvertBackendCalls.tsx
@@ -0,0 +1,62 @@
+export interface ConvertSchema {
+ inputText: string;
+ inputFormat: string;
+ add_h: boolean;
+ make_3d: boolean;
+ optimize: boolean;
+}
+
+export async function ConvertMoleculeToStandart(
+ data: ConvertSchema,
+): Promise {
+ try {
+ const response = await fetch("https://mol-convert.deowl.ru" + "/convert", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ text: data.inputText,
+ format: data.inputFormat,
+ convert_3d: data.make_3d,
+ add_hydrogen: data.add_h,
+ optimize_geometry: data.optimize,
+ }),
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const responseData = await response.json();
+ return responseData.molfile;
+ } catch (error) {
+ // Error handling
+ if (error instanceof Error) {
+ return error;
+ }
+ throw error;
+ }
+}
+
+export async function GetInFormats(): Promise<{ [key: string]: string }> {
+ try {
+ const response = await fetch(
+ "https://mol-convert.deowl.ru" + "/informats",
+ {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ },
+ );
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ return await response.json();
+ } catch (error) {
+ throw error;
+ }
+}
diff --git a/frontend-plugin/src/EditorPage/EditorPage.tsx b/frontend-plugin/src/EditorPage/EditorPage.tsx
index b4e21fe..92b5d3c 100644
--- a/frontend-plugin/src/EditorPage/EditorPage.tsx
+++ b/frontend-plugin/src/EditorPage/EditorPage.tsx
@@ -1,90 +1,291 @@
import {
Button,
+ Card,
Center,
Divider,
Tabs,
+ Title,
Text,
- Textarea,
useMantineTheme,
+ Paper,
+ Badge,
+ SimpleGrid,
} from "@mantine/core";
import { Group, Panel, Separator } from "react-resizable-panels";
import "./MoleculePage.css";
import {
- IconCheck,
+ IconAlertCircle,
+ IconChartBar,
IconCode,
+ IconGripHorizontal,
IconGripVertical,
IconList,
- IconX,
} from "@tabler/icons-react";
import { CodeTextArea } from "./CodeTextArea/CodeTextArea";
import MoleculeViewer from "./MoleculeViewer/MoleculeViewer";
-import { MoleculeData, SelectedAtom, TaskEditorProps } from "../Types/plugin";
-import { useState } from "react";
+import { SelectedAtom, TaskEditorProps } from "../Types/plugin";
+import { useEffect, useState } from "react";
+import { NewMoleculeModal } from "./NewMolecule/NewMolecule";
-function MoleculeEditorPage(props: TaskEditorProps) {
- const [text, setText] = useState(props.data?.data.text);
+function MoleculeEditorPage(props: TaskEditorProps) {
+ const [text, setText] = useState(props.data?.text);
const [selectedAtom, setSelectedAtom] = useState(null);
const theme = useMantineTheme();
+ const [isOpen, setIsOpen] = useState(false);
+ const [counter_new, set_counter_new] = useState(0);
+
+ useEffect(() => {
+ console.log("Resetting text");
+ setText(props.data?.text);
+ setSelectedAtom(null);
+ }, [props.counter]);
+
+ const get_qubits = (text: string) => {
+ try {
+ const lines = text.trim().replace("\\\\", "\\").split("\n") || [
+ "",
+ "",
+ "",
+ ];
+ console.log(lines);
+ const secondLine = lines[1]; // "Charge=0 Multiplicity=1 Electrons=10 Orbitals=9"
+ const variables = secondLine.split(" ").reduce(
+ (acc, pair) => {
+ const [key, value] = pair.split("=");
+ acc[key] = parseInt(value); // or Number(value) if you want to keep as number
+ return acc;
+ },
+ {} as Record,
+ );
+ return variables.Orbitals * 2;
+ } catch {
+ return 0;
+ }
+ };
return (
<>
-
+
+
{
+ props.setData(data, get_qubits(data.text));
+ setText(data.text);
+ setTimeout(() => set_counter_new((old) => old + 2), 100);
+ }}
+ />
+ {!props.simProgress && (
+
+ )}
-
-
-
-
- }>
- Код
-
- }>
- Список
-
-
-
-
- {
- setText(text);
- props.setData({ text: text });
+
+
+
+
+
-
+ radius="lg"
+ defaultValue="code"
+ classNames={{ panel: "tabPanel" }}
+ >
+
+ }>
+ Код
+
+ }>
+ Список
+
+
-
- <>>
-
-
-
-
-
-
-
-
-
-
+
+ {
+ setText(text);
+ props.setData({ text: text }, get_qubits(text));
+ }}
+ />
+
+
+
+ <>>
+
+
+
+
+
+
+
+
+
+
+
+
+ {props.simProgress && (
+ <>
+
+
+
+
+
+
+ {props.simProgress.error ? (
+
+
+
+
+ Ошибка выполнения
+
+
+ {props.simProgress.error}
+
+
+
+ {props.simProgress.traceback && (
+ <>
+
+ Подробности:
+
+
+
+ {props.simProgress.traceback}
+
+
+ >
+ )}
+
+ ) : (
+
+
+
+
+ Прогресс VQE
+
+
+
+
+
+ Итерация
+
+
+
+ Энергия (Hartree)
+
+
+
+ Сходимость
+
+
+ {props.simProgress["iter_num"]}
+
+
+ {props.simProgress["energy"]?.toFixed(8)}
+
+
+ {props.simProgress["conv"]}
+
+
+
+
+
+ Показать итоговые веса (
+ {props.simProgress["params"]?.length})
+
+
+
+ Параметры оптимизации:
+
+
+ {props.simProgress["params"]?.map((param, idx) => (
+
+ p{idx}: {param.toFixed(6)}
+
+ ))}
+
+
+
+
+ )}
+
+ >
+ )}
>
diff --git a/frontend-plugin/src/EditorPage/MoleculeViewer/MoleculeViewer.tsx b/frontend-plugin/src/EditorPage/MoleculeViewer/MoleculeViewer.tsx
index c777370..f82f2f0 100644
--- a/frontend-plugin/src/EditorPage/MoleculeViewer/MoleculeViewer.tsx
+++ b/frontend-plugin/src/EditorPage/MoleculeViewer/MoleculeViewer.tsx
@@ -1,6 +1,7 @@
import Viewer from "miew-react";
import Miew from "miew";
import {
+ LoadingOverlay,
Paper,
UnstyledButton,
useMantineColorScheme,
@@ -45,6 +46,7 @@ interface MoleculeViewerProps {
moleculeData: string;
selectedAtom: SelectedAtom | null;
setSelectedAtom: (selectedAtom: SelectedAtom | null) => void;
+ reload_counter: number;
}
function MoleculeViewer(props: MoleculeViewerProps) {
@@ -57,10 +59,16 @@ function MoleculeViewer(props: MoleculeViewerProps) {
const [isResizing, setIsResizing] = useState
(false);
const [viewingData, setViewingData] = useState(props.moleculeData);
+ const [isError, setIsError] = useState(false);
+
+ useEffect(() => {
+ setViewingData(props.moleculeData);
+ }, [props.reload_counter]);
//при загрузке сохраняем объект miew
const onInitMiew = (miew: Miew) => {
setMiew(miew);
+ setIsError(false);
if (miew && viewingData) {
//прогружаем молекулу
miew
@@ -77,15 +85,7 @@ function MoleculeViewer(props: MoleculeViewerProps) {
if (error.message == "Operation cancelled") {
return;
}
- notifications.show({
- color: "orange",
- radius: "md",
- title: "Ошибка при отображении молекулы",
- message:
- "Проверте правильность написания кода молекулы, в нем содержатся ошибки",
- icon: ,
- style: { paddingLeft: "5px" },
- });
+ setIsError(true);
});
}
miew.setOptions({
@@ -212,10 +212,16 @@ function MoleculeViewer(props: MoleculeViewerProps) {
backgroundColor: theme.colors.secondaryDark[7],
borderRadius: "5px",
overflow: "hidden",
+ position: "relative",
}}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
+
{MemoViewer}
{isResizing && viewingData && (
diff --git a/frontend-plugin/src/EditorPage/NewMolecule/NewMolecule.css b/frontend-plugin/src/EditorPage/NewMolecule/NewMolecule.css
new file mode 100644
index 0000000..7383265
--- /dev/null
+++ b/frontend-plugin/src/EditorPage/NewMolecule/NewMolecule.css
@@ -0,0 +1,3 @@
+.StepperRoot {
+ flex-direction: row-reverse !important;
+}
diff --git a/frontend-plugin/src/EditorPage/NewMolecule/NewMolecule.tsx b/frontend-plugin/src/EditorPage/NewMolecule/NewMolecule.tsx
new file mode 100644
index 0000000..e0f7b16
--- /dev/null
+++ b/frontend-plugin/src/EditorPage/NewMolecule/NewMolecule.tsx
@@ -0,0 +1,119 @@
+import { Button, Modal, Space, Title, Select, Textarea } from "@mantine/core";
+import { useEffect, useState } from "react";
+import "./NewMolecule.css";
+import {
+ ConvertMoleculeToStandart,
+ GetInFormats,
+} from "../ConvertBackendCalls";
+import { MoleculeData } from "../../Types/plugin";
+
+interface NewMoleculeModalProps {
+ isOpened: boolean;
+ setIsOpened: (opened: boolean) => void;
+ setMolecule: (mol: MoleculeData) => void;
+}
+
+export function NewMoleculeModal(props: NewMoleculeModalProps) {
+ const [inMolecule, setInMolecule] = useState("");
+ const [inFormat, setInFormat] = useState
();
+
+ const [inOptions, setInOptions] = useState();
+
+ const getOptions = () => {
+ GetInFormats()
+ .then((value: { [key: string]: string }) => {
+ const values = Object.keys(value);
+ setInOptions(values);
+ })
+ .catch(() => {});
+ };
+
+ useEffect(() => {
+ getOptions();
+ }, []);
+
+ //reset on open dialog
+ useEffect(() => {
+ if (props.isOpened) {
+ setInMolecule("");
+ setInFormat(undefined);
+ if (inOptions == undefined) {
+ getOptions();
+ }
+ }
+ }, [props.isOpened]);
+
+ const handleClose = () => {
+ props.setIsOpened(false);
+ };
+
+ const handleConvert = () => {
+ if (inFormat) {
+ ConvertMoleculeToStandart({
+ inputText: inMolecule,
+ inputFormat: inFormat,
+ make_3d: true,
+ add_h: false,
+ optimize: false,
+ }).then((value: string) => {
+ props.setMolecule({ text: value });
+ props.setIsOpened(false);
+ });
+ } else {
+ //TODO: add no format selected handling
+ }
+ };
+
+ const handleEmptyMolecule = () => {
+ props.setIsOpened(false);
+ };
+
+ return (
+ Новая молекула
+ centered
+ size="75%"
+ styles={{
+ content: { paddingLeft: "10px" },
+ title: { width: "100%" },
+ }}
+ >
+
+ );
+}
diff --git a/frontend-plugin/src/ListItem/ListItem.css b/frontend-plugin/src/ListItem/ListItem.css
new file mode 100644
index 0000000..99382fd
--- /dev/null
+++ b/frontend-plugin/src/ListItem/ListItem.css
@@ -0,0 +1,7 @@
+.truncatedString {
+ -webkit-line-clamp: 2;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
diff --git a/frontend-plugin/src/ListItem/ListItem.tsx b/frontend-plugin/src/ListItem/ListItem.tsx
index b3b2fa5..19c55f9 100644
--- a/frontend-plugin/src/ListItem/ListItem.tsx
+++ b/frontend-plugin/src/ListItem/ListItem.tsx
@@ -1,22 +1,158 @@
// ListItem/ListItem.tsx
-import React from "react";
-import { MoleculeData, TaskData } from "../Types/plugin";
+import React, { useEffect, useState } from "react";
+import { MoleculeData } from "../Types/plugin";
+import { Card, Text, Title } from "@mantine/core";
+import "./ListItem.css";
const ListItem: React.FunctionComponent<{
- data: TaskData | undefined;
+ data: MoleculeData | undefined;
+ simProgress: any;
}> = (props) => {
+ var variables = {
+ Multiplicity: "-",
+ Charge: "-",
+ Electrons: "-",
+ Orbitals: "-",
+ };
+ try {
+ const lines = props.data?.text.trim().split("\n") || ["", "", ""];
+ const secondLine = lines[1]; // "Charge=0 Multiplicity=1 Electrons=10 Orbitals=9"
+ variables = secondLine.split(" ").reduce(
+ (acc, pair) => {
+ const [key, value] = pair.split("=");
+ acc[key] = parseInt(value); // or Number(value) if you want to keep as number
+ return acc;
+ },
+ {} as Record,
+ );
+ } catch {
+ variables = {
+ Multiplicity: "-",
+ Charge: "-",
+ Electrons: "-",
+ Orbitals: "-",
+ };
+ }
+
+ const [smiles, setSmiles] = useState("");
+
+ useEffect(() => {
+ const response = fetch("https://mol-convert.deowl.ru/smiles", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ text: props.data?.text }),
+ }).then((data) => {
+ if (!data.ok) {
+ throw new Error(`HTTP error! status: ${data.status}`);
+ }
+
+ data.json().then((data) => {
+ setSmiles(data.molfile);
+ });
+ });
+ }, []);
+
return (
-
-
{props.data?.name}
-
- {props.data?.description}
-
-
- Value: {props.data?.data.text || "Not set"}
-
-
+
+
+
+ Заряд: {variables.Charge != undefined ? variables.Charge : "-"}
+
+
+ Мультиплексивность:{" "}
+ {variables.Multiplicity != undefined ? variables.Multiplicity : "-"}
+
+
+
+
+ Акт. эклектроны:{" "}
+ {variables.Electrons != undefined ? variables.Electrons : "-"}
+
+
+ Акт. орбитали:{" "}
+ {variables.Orbitals != undefined ? variables.Orbitals : "-"}
+
+
+
+
+ Smiles: {smiles}
+
+
+ Необходимо кубит: {Number(variables.Orbitals) * 2 || "-"}
+
+
+ {props.simProgress && !props.simProgress["error"] && (
+
+ Прогресс выполнения:
+
+ Итерация: {props.simProgress["iter_num"]}
+
+
+ Энергия: {Math.fround(props.simProgress["energy"])}
+
+
+ )}
+ {props.simProgress && props.simProgress["error"] && (
+
+ Ошибка:
+
+ {props.simProgress["error"]}
+
+
+ )}
+
);
};
diff --git a/frontend-plugin/src/Types/plugin.tsx b/frontend-plugin/src/Types/plugin.tsx
index ed802a5..86a4fbf 100644
--- a/frontend-plugin/src/Types/plugin.tsx
+++ b/frontend-plugin/src/Types/plugin.tsx
@@ -1,26 +1,14 @@
-// Types
-export interface TaskData {
- id: number;
- name: string;
- description: string;
- data: TData;
-}
-
-export interface TaskEditorProps {
- data: TaskData | undefined;
- setData: (data: TData) => void;
-}
-
-export interface TaskTypePlugin {
- type: string;
- ListItem: React.ComponentType | undefined>;
- Editor: React.ComponentType>;
-}
-
export interface MoleculeData {
text: string;
}
+export interface TaskEditorProps {
+ data: MoleculeData | undefined;
+ setData: (data: MoleculeData, qubits_needed: number) => void;
+ counter: number;
+ simProgress: any;
+}
+
export interface SelectedAtom {
serial: number;
name: string;
diff --git a/frontend-plugin/src/index.tsx b/frontend-plugin/src/index.tsx
index 01b24b8..db870fe 100644
--- a/frontend-plugin/src/index.tsx
+++ b/frontend-plugin/src/index.tsx
@@ -4,19 +4,13 @@ import "./index.css";
import "@mantine/core/styles.css";
import Wrapper from "./wrapper";
// Parse URL parameters
-const urlParams = new URLSearchParams(window.location.search);
-const mode = urlParams.get("mode") == "List" ? "List" : "Editor";
// Render directly
const rootElement = document.getElementById("root");
if (rootElement) {
+ rootElement.style = "height: 100%";
const root = ReactDOM.createRoot(rootElement);
- root.render();
-}
-
-// Notify parent that plugin is ready
-if (window.parent !== window) {
- window.parent.postMessage({ type: "plugin-ready" }, "*");
+ root.render();
}
diff --git a/frontend-plugin/src/wrapper.tsx b/frontend-plugin/src/wrapper.tsx
index 07454b3..c4fb2fa 100644
--- a/frontend-plugin/src/wrapper.tsx
+++ b/frontend-plugin/src/wrapper.tsx
@@ -11,18 +11,15 @@ import {
} from "@mantine/core";
import "./index.css";
import "@mantine/core/styles.css";
-import { MoleculeData, TaskData } from "./Types/plugin";
-
-interface WrapperProps {
- mode: "List" | "Editor";
-}
+import { MoleculeData } from "./Types/plugin";
// Create setData function that communicates with parent
-const setData = (newData: MoleculeData) => {
+const setData = (newData: string, qubits_needed: number) => {
window.parent.postMessage(
{
type: "plugin-update",
data: newData,
+ qubits_needed: qubits_needed,
},
"*",
);
@@ -69,41 +66,80 @@ const theme = createTheme({
},
});
-const Wrapper: React.FunctionComponent = (props) => {
- const [currentTaskData, setTaskData] = useState>();
+const Wrapper: React.FunctionComponent = () => {
+ const [currentTaskData, setTaskData] = useState();
+ const [currentSimProgress, setCurrentSimProgress] = useState();
const [currentTheme, setTheme] = useState<"light" | "dark">("dark");
+
+ const [mode, setMode] = useState("List");
+ const [counter, setCounter] = useState(0);
+
// Handle messages from parent
useEffect(() => {
window.addEventListener("message", (event) => {
const { type, data } = event.data;
- console.log(event.data);
-
- setTaskData(data.taskData);
- setTheme(data.theme);
+ if (type == "plugin-data") {
+ setTaskData(data.taskData);
+ setTheme(data.theme);
+ setMode(data.mode);
+ try {
+ JSON.parse(data.simProgress);
+ setCurrentSimProgress(data.simProgress);
+ } catch {
+ setCurrentSimProgress(undefined);
+ }
+ }
+ if (type == "update") {
+ console.log("Updating");
+ setCounter((prevData) => {
+ return prevData + 1;
+ });
+ }
});
- }, [props.mode]);
- if (props.mode === "List") {
- return (
-
-
-
- );
- } else {
- return (
-
-
-
- );
- }
+ const sendReadyWithRetry = () => {
+ window.parent.postMessage({ type: "plugin-ready" }, "*");
+ };
+
+ // Also send ready after a short delay to catch any late listeners
+ const timeoutId = setTimeout(sendReadyWithRetry, 100);
+ }, []);
+
+ return mode === "List" ? (
+
+ {currentTaskData && (
+
+ )}
+
+ ) : (
+
+ {currentTaskData && (
+
+ setData(JSON.stringify(data), qubits_needed)
+ }
+ counter={counter}
+ simProgress={
+ currentSimProgress ? JSON.parse(currentSimProgress) : undefined
+ }
+ />
+ )}
+
+ );
};
export default Wrapper;
diff --git a/local_quantum/vqe.py b/local_quantum/vqe.py
new file mode 100644
index 0000000..bb0b8ed
--- /dev/null
+++ b/local_quantum/vqe.py
@@ -0,0 +1,173 @@
+import os
+from multiprocessing.connection import Connection
+from typing import List
+
+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_computation(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)
+
+ if False:
+ params = np.array(last_state, requires_grad=True)
+ else:
+ 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