v1.0
-- full integration with mol-edit backend -- added readme -- added vqe local_computational
This commit is contained in:
198
README.md
Normal file
198
README.md
Normal file
@@ -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
|
||||||
|
```
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
from collections.abc import AsyncIterator
|
from collections.abc import AsyncIterator
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI, Request
|
||||||
from fastapi.exceptions import HTTPException
|
from fastapi.exceptions import HTTPException
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
@@ -14,7 +14,13 @@ from logging_config import logger
|
|||||||
from openbabel import pybel
|
from openbabel import pybel
|
||||||
from pyscf import gto # pyright: ignore
|
from pyscf import gto # pyright: ignore
|
||||||
from redis import asyncio as aioredis
|
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
|
@asynccontextmanager
|
||||||
@@ -28,17 +34,39 @@ async def lifespan(_: FastAPI) -> AsyncIterator[None]:
|
|||||||
app = FastAPI(lifespan=lifespan)
|
app = FastAPI(lifespan=lifespan)
|
||||||
|
|
||||||
|
|
||||||
origins = [
|
class ForceCORSHeadersMiddleware(BaseHTTPMiddleware):
|
||||||
"http://localhost",
|
async def dispatch(self, request: Request, call_next):
|
||||||
"http://localhost:8001",
|
# Handle OPTIONS preflight requests
|
||||||
]
|
if request.method == "OPTIONS":
|
||||||
|
response = JSONResponse(status_code=200, content={})
|
||||||
|
else:
|
||||||
|
response = await call_next(request)
|
||||||
|
|
||||||
app.add_middleware(
|
# Always add CORS headers
|
||||||
CORSMiddleware,
|
response.headers["Access-Control-Allow-Origin"] = "*"
|
||||||
allow_origins=origins,
|
response.headers["Access-Control-Allow-Methods"] = (
|
||||||
allow_credentials=True,
|
"GET, POST, PUT, DELETE, OPTIONS"
|
||||||
allow_methods=["*"],
|
)
|
||||||
allow_headers=["*"],
|
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": "*",
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -46,7 +74,7 @@ app.add_middleware(
|
|||||||
"/convert",
|
"/convert",
|
||||||
responses={
|
responses={
|
||||||
200: {"description": "Coversion Successful", "model": MolFileModel},
|
200: {"description": "Coversion Successful", "model": MolFileModel},
|
||||||
400: {"description": "Item created", "model": BasicError},
|
400: {"description": "Error Converting", "model": BasicError},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
def convert_molecule(req: ConvertRequest):
|
def convert_molecule(req: ConvertRequest):
|
||||||
@@ -84,6 +112,22 @@ def convert_molecule(req: ConvertRequest):
|
|||||||
raise HTTPException(status_code=400, detail={"error": str(e)})
|
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")
|
@app.get("/informats")
|
||||||
@cache()
|
@cache()
|
||||||
async def get_informats():
|
async def get_informats():
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ class ConvertRequest(BaseModel):
|
|||||||
optimize_geometry: bool = False
|
optimize_geometry: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
class SmilesRequest(BaseModel):
|
||||||
|
text: str
|
||||||
|
|
||||||
|
|
||||||
class MolFileModel(BaseModel):
|
class MolFileModel(BaseModel):
|
||||||
molfile: str
|
molfile: str
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<!-- index.html -->
|
<!-- index.html -->
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html style="height: 100%">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
@@ -9,8 +9,6 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background: none;
|
|
||||||
background-color: var(--mantine-color-secondary);
|
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
font-family:
|
font-family:
|
||||||
@@ -19,7 +17,7 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body style="background-color: transparent !important; height: 100%">
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/src/index.tsx"></script>
|
<script type="module" src="/src/index.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useEffect, useRef } from "react";
|
|||||||
interface TextAreaProps {
|
interface TextAreaProps {
|
||||||
text: string;
|
text: string;
|
||||||
onTextChange: (text: string) => void;
|
onTextChange: (text: string) => void;
|
||||||
|
enabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CodeTextArea(props: TextAreaProps) {
|
export function CodeTextArea(props: TextAreaProps) {
|
||||||
@@ -52,6 +53,7 @@ export function CodeTextArea(props: TextAreaProps) {
|
|||||||
className="MoleculeEdit"
|
className="MoleculeEdit"
|
||||||
value={props.text}
|
value={props.text}
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
|
disabled={!props.enabled}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
props.onTextChange(e.currentTarget.value);
|
props.onTextChange(e.currentTarget.value);
|
||||||
}}
|
}}
|
||||||
|
|||||||
62
frontend-plugin/src/EditorPage/ConvertBackendCalls.tsx
Normal file
62
frontend-plugin/src/EditorPage/ConvertBackendCalls.tsx
Normal file
@@ -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<string> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,35 +1,92 @@
|
|||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
Card,
|
||||||
Center,
|
Center,
|
||||||
Divider,
|
Divider,
|
||||||
Tabs,
|
Tabs,
|
||||||
|
Title,
|
||||||
Text,
|
Text,
|
||||||
Textarea,
|
|
||||||
useMantineTheme,
|
useMantineTheme,
|
||||||
|
Paper,
|
||||||
|
Badge,
|
||||||
|
SimpleGrid,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { Group, Panel, Separator } from "react-resizable-panels";
|
import { Group, Panel, Separator } from "react-resizable-panels";
|
||||||
import "./MoleculePage.css";
|
import "./MoleculePage.css";
|
||||||
import {
|
import {
|
||||||
IconCheck,
|
IconAlertCircle,
|
||||||
|
IconChartBar,
|
||||||
IconCode,
|
IconCode,
|
||||||
|
IconGripHorizontal,
|
||||||
IconGripVertical,
|
IconGripVertical,
|
||||||
IconList,
|
IconList,
|
||||||
IconX,
|
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
import { CodeTextArea } from "./CodeTextArea/CodeTextArea";
|
import { CodeTextArea } from "./CodeTextArea/CodeTextArea";
|
||||||
import MoleculeViewer from "./MoleculeViewer/MoleculeViewer";
|
import MoleculeViewer from "./MoleculeViewer/MoleculeViewer";
|
||||||
import { MoleculeData, SelectedAtom, TaskEditorProps } from "../Types/plugin";
|
import { SelectedAtom, TaskEditorProps } from "../Types/plugin";
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { NewMoleculeModal } from "./NewMolecule/NewMolecule";
|
||||||
|
|
||||||
function MoleculeEditorPage(props: TaskEditorProps<MoleculeData>) {
|
function MoleculeEditorPage(props: TaskEditorProps) {
|
||||||
const [text, setText] = useState(props.data?.data.text);
|
const [text, setText] = useState(props.data?.text);
|
||||||
const [selectedAtom, setSelectedAtom] = useState<SelectedAtom | null>(null);
|
const [selectedAtom, setSelectedAtom] = useState<SelectedAtom | null>(null);
|
||||||
const theme = useMantineTheme();
|
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<string, number>,
|
||||||
|
);
|
||||||
|
return variables.Orbitals * 2;
|
||||||
|
} catch {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div style={{ display: "flex", flexDirection: "column", height: "90vh" }}>
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
height: props.simProgress ? "100%" : "90vh",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<NewMoleculeModal
|
||||||
|
isOpened={isOpen}
|
||||||
|
setIsOpened={setIsOpen}
|
||||||
|
setMolecule={(data) => {
|
||||||
|
props.setData(data, get_qubits(data.text));
|
||||||
|
setText(data.text);
|
||||||
|
setTimeout(() => set_counter_new((old) => old + 2), 100);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{!props.simProgress && (
|
||||||
|
<Button onClick={() => setIsOpen(true)}>Импротировать</Button>
|
||||||
|
)}
|
||||||
<Divider style={{ margin: "10px" }} />
|
<Divider style={{ margin: "10px" }} />
|
||||||
|
<Group orientation="vertical" style={{ flex: 1, gap: "5px" }}>
|
||||||
|
<Panel>
|
||||||
<Group style={{ flex: 1, gap: "5px" }}>
|
<Group style={{ flex: 1, gap: "5px" }}>
|
||||||
<Panel minSize={200} defaultSize={500}>
|
<Panel minSize={200} defaultSize={500}>
|
||||||
<Tabs
|
<Tabs
|
||||||
@@ -54,10 +111,11 @@ function MoleculeEditorPage(props: TaskEditorProps<MoleculeData>) {
|
|||||||
|
|
||||||
<Tabs.Panel value="code">
|
<Tabs.Panel value="code">
|
||||||
<CodeTextArea
|
<CodeTextArea
|
||||||
|
enabled={props.simProgress == undefined}
|
||||||
text={text || ""}
|
text={text || ""}
|
||||||
onTextChange={(text) => {
|
onTextChange={(text) => {
|
||||||
setText(text);
|
setText(text);
|
||||||
props.setData({ text: text });
|
props.setData({ text: text }, get_qubits(text));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Tabs.Panel>
|
</Tabs.Panel>
|
||||||
@@ -80,12 +138,155 @@ function MoleculeEditorPage(props: TaskEditorProps<MoleculeData>) {
|
|||||||
</Separator>
|
</Separator>
|
||||||
<Panel style={{ flexShrink: 0 }} minSize={200}>
|
<Panel style={{ flexShrink: 0 }} minSize={200}>
|
||||||
<MoleculeViewer
|
<MoleculeViewer
|
||||||
moleculeData={props.data ? props.data.data.text : ""}
|
moleculeData={props.data ? props.data.text : ""}
|
||||||
selectedAtom={selectedAtom}
|
selectedAtom={selectedAtom}
|
||||||
setSelectedAtom={setSelectedAtom}
|
setSelectedAtom={setSelectedAtom}
|
||||||
|
reload_counter={props.counter + counter_new}
|
||||||
/>
|
/>
|
||||||
</Panel>
|
</Panel>
|
||||||
</Group>
|
</Group>
|
||||||
|
</Panel>
|
||||||
|
{props.simProgress && (
|
||||||
|
<>
|
||||||
|
<Separator>
|
||||||
|
<Center
|
||||||
|
style={{
|
||||||
|
height: "100%",
|
||||||
|
background: theme.colors.dark[4],
|
||||||
|
borderRadius: "5px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconGripHorizontal size={12} />
|
||||||
|
</Center>
|
||||||
|
</Separator>
|
||||||
|
<Panel>
|
||||||
|
{props.simProgress.error ? (
|
||||||
|
<Card withBorder style={{ height: "100%" }}>
|
||||||
|
<Group gap="xs" mb="md" orientation="vertical">
|
||||||
|
<IconAlertCircle size={20} color="#fa5252" />
|
||||||
|
<Title order={4} c="red" style={{ margin: 0 }}>
|
||||||
|
Ошибка выполнения
|
||||||
|
</Title>
|
||||||
|
<Text size="sm" c="red" fw={500} mb="xs">
|
||||||
|
{props.simProgress.error}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{props.simProgress.traceback && (
|
||||||
|
<>
|
||||||
|
<Text size="xs" c="dimmed" mb="xs" fw={500}>
|
||||||
|
Подробности:
|
||||||
|
</Text>
|
||||||
|
<Paper
|
||||||
|
p="xs"
|
||||||
|
withBorder
|
||||||
|
bg="dark.8"
|
||||||
|
style={{
|
||||||
|
maxHeight: "200px",
|
||||||
|
overflow: "auto",
|
||||||
|
fontFamily: "monospace",
|
||||||
|
fontSize: "11px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
size="xs"
|
||||||
|
c="gray.5"
|
||||||
|
style={{
|
||||||
|
whiteSpace: "pre-wrap",
|
||||||
|
wordBreak: "break-all",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.simProgress.traceback}
|
||||||
|
</Text>
|
||||||
|
</Paper>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
) : (
|
||||||
|
<Card withBorder style={{ height: "100%" }}>
|
||||||
|
<Group gap="xs">
|
||||||
|
<IconChartBar size={20} color="#4299e1" />
|
||||||
|
<Title order={4} style={{ margin: 0 }}>
|
||||||
|
Прогресс VQE
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<SimpleGrid cols={3} mb="lg">
|
||||||
|
<Text size="xs" c="dimmed" tt="uppercase" fw={700} mb={4}>
|
||||||
|
Итерация
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text size="xs" c="dimmed" tt="uppercase" fw={700} mb={4}>
|
||||||
|
Энергия (Hartree)
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text size="xs" c="dimmed" tt="uppercase" fw={700} mb={4}>
|
||||||
|
Сходимость
|
||||||
|
</Text>
|
||||||
|
<Text size="xl" fw={700} c="blue">
|
||||||
|
{props.simProgress["iter_num"]}
|
||||||
|
</Text>
|
||||||
|
<Text size="xl" fw={700} c="teal">
|
||||||
|
{props.simProgress["energy"]?.toFixed(8)}
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
size="xl"
|
||||||
|
fw={700}
|
||||||
|
c={
|
||||||
|
props.simProgress["conv"] < 1e-6 ? "green" : "orange"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{props.simProgress["conv"]}
|
||||||
|
</Text>
|
||||||
|
</SimpleGrid>
|
||||||
|
|
||||||
|
<details style={{ marginTop: "16px" }}>
|
||||||
|
<summary
|
||||||
|
style={{
|
||||||
|
cursor: "pointer",
|
||||||
|
fontSize: "13px",
|
||||||
|
color: "#868e96",
|
||||||
|
marginBottom: "8px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Показать итоговые веса (
|
||||||
|
{props.simProgress["params"]?.length})
|
||||||
|
</summary>
|
||||||
|
<Paper
|
||||||
|
p="sm"
|
||||||
|
withBorder
|
||||||
|
bg="dark.8"
|
||||||
|
style={{ maxHeight: "150px", overflow: "auto" }}
|
||||||
|
>
|
||||||
|
<Text size="xs" fw={500} mb="xs" c="dimmed">
|
||||||
|
Параметры оптимизации:
|
||||||
|
</Text>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
gap: "8px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.simProgress["params"]?.map((param, idx) => (
|
||||||
|
<Badge
|
||||||
|
key={idx}
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
color="blue"
|
||||||
|
>
|
||||||
|
p{idx}: {param.toFixed(6)}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Paper>
|
||||||
|
</details>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
</Panel>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import Viewer from "miew-react";
|
import Viewer from "miew-react";
|
||||||
import Miew from "miew";
|
import Miew from "miew";
|
||||||
import {
|
import {
|
||||||
|
LoadingOverlay,
|
||||||
Paper,
|
Paper,
|
||||||
UnstyledButton,
|
UnstyledButton,
|
||||||
useMantineColorScheme,
|
useMantineColorScheme,
|
||||||
@@ -45,6 +46,7 @@ interface MoleculeViewerProps {
|
|||||||
moleculeData: string;
|
moleculeData: string;
|
||||||
selectedAtom: SelectedAtom | null;
|
selectedAtom: SelectedAtom | null;
|
||||||
setSelectedAtom: (selectedAtom: SelectedAtom | null) => void;
|
setSelectedAtom: (selectedAtom: SelectedAtom | null) => void;
|
||||||
|
reload_counter: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function MoleculeViewer(props: MoleculeViewerProps) {
|
function MoleculeViewer(props: MoleculeViewerProps) {
|
||||||
@@ -57,10 +59,16 @@ function MoleculeViewer(props: MoleculeViewerProps) {
|
|||||||
const [isResizing, setIsResizing] = useState<boolean>(false);
|
const [isResizing, setIsResizing] = useState<boolean>(false);
|
||||||
|
|
||||||
const [viewingData, setViewingData] = useState<string>(props.moleculeData);
|
const [viewingData, setViewingData] = useState<string>(props.moleculeData);
|
||||||
|
const [isError, setIsError] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setViewingData(props.moleculeData);
|
||||||
|
}, [props.reload_counter]);
|
||||||
|
|
||||||
//при загрузке сохраняем объект miew
|
//при загрузке сохраняем объект miew
|
||||||
const onInitMiew = (miew: Miew) => {
|
const onInitMiew = (miew: Miew) => {
|
||||||
setMiew(miew);
|
setMiew(miew);
|
||||||
|
setIsError(false);
|
||||||
if (miew && viewingData) {
|
if (miew && viewingData) {
|
||||||
//прогружаем молекулу
|
//прогружаем молекулу
|
||||||
miew
|
miew
|
||||||
@@ -77,15 +85,7 @@ function MoleculeViewer(props: MoleculeViewerProps) {
|
|||||||
if (error.message == "Operation cancelled") {
|
if (error.message == "Operation cancelled") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
notifications.show({
|
setIsError(true);
|
||||||
color: "orange",
|
|
||||||
radius: "md",
|
|
||||||
title: "Ошибка при отображении молекулы",
|
|
||||||
message:
|
|
||||||
"Проверте правильность написания кода молекулы, в нем содержатся ошибки",
|
|
||||||
icon: <IconAlertHexagon />,
|
|
||||||
style: { paddingLeft: "5px" },
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
miew.setOptions({
|
miew.setOptions({
|
||||||
@@ -212,10 +212,16 @@ function MoleculeViewer(props: MoleculeViewerProps) {
|
|||||||
backgroundColor: theme.colors.secondaryDark[7],
|
backgroundColor: theme.colors.secondaryDark[7],
|
||||||
borderRadius: "5px",
|
borderRadius: "5px",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
|
position: "relative",
|
||||||
}}
|
}}
|
||||||
onMouseEnter={handleMouseEnter}
|
onMouseEnter={handleMouseEnter}
|
||||||
onMouseLeave={handleMouseLeave}
|
onMouseLeave={handleMouseLeave}
|
||||||
>
|
>
|
||||||
|
<LoadingOverlay
|
||||||
|
visible={isError}
|
||||||
|
loaderProps={{ children: "Ошибка отображения молекулы" }}
|
||||||
|
zIndex={8}
|
||||||
|
/>
|
||||||
{MemoViewer}
|
{MemoViewer}
|
||||||
{isResizing && viewingData && (
|
{isResizing && viewingData && (
|
||||||
<div
|
<div
|
||||||
@@ -254,7 +260,7 @@ function MoleculeViewer(props: MoleculeViewerProps) {
|
|||||||
left: "10px",
|
left: "10px",
|
||||||
top: "10px",
|
top: "10px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
zIndex: 5,
|
zIndex: 9,
|
||||||
width: "auto",
|
width: "auto",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
.StepperRoot {
|
||||||
|
flex-direction: row-reverse !important;
|
||||||
|
}
|
||||||
119
frontend-plugin/src/EditorPage/NewMolecule/NewMolecule.tsx
Normal file
119
frontend-plugin/src/EditorPage/NewMolecule/NewMolecule.tsx
Normal file
@@ -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<string | null>();
|
||||||
|
|
||||||
|
const [inOptions, setInOptions] = useState<string[] | undefined>();
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Modal
|
||||||
|
opened={props.isOpened}
|
||||||
|
onClose={handleClose}
|
||||||
|
title=<Title size="xl">Новая молекула</Title>
|
||||||
|
centered
|
||||||
|
size="75%"
|
||||||
|
styles={{
|
||||||
|
content: { paddingLeft: "10px" },
|
||||||
|
title: { width: "100%" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Textarea
|
||||||
|
className="moleculeInput"
|
||||||
|
value={inMolecule}
|
||||||
|
onChange={(e) => setInMolecule(e.currentTarget.value)}
|
||||||
|
placeholder="Введите текст молекулы"
|
||||||
|
classNames={{
|
||||||
|
wrapper: "moleculeInputWrapper",
|
||||||
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
// Stop arrow keys from reaching react-resizable-panels
|
||||||
|
if (
|
||||||
|
["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key)
|
||||||
|
) {
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Space h="md" />
|
||||||
|
<Select
|
||||||
|
value={inFormat}
|
||||||
|
onChange={(value: string | null) => setInFormat(value)}
|
||||||
|
searchable
|
||||||
|
data={inOptions}
|
||||||
|
placeholder="Выберите формат"
|
||||||
|
classNames={{
|
||||||
|
input: "selectInFormat",
|
||||||
|
dropdown: "selectDropDown",
|
||||||
|
option: "selectDropDownOption",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Space h="md" />
|
||||||
|
<Button color="accent" onClick={handleConvert}>
|
||||||
|
Преобразовать
|
||||||
|
</Button>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
7
frontend-plugin/src/ListItem/ListItem.css
Normal file
7
frontend-plugin/src/ListItem/ListItem.css
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.truncatedString {
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
@@ -1,22 +1,158 @@
|
|||||||
// ListItem/ListItem.tsx
|
// ListItem/ListItem.tsx
|
||||||
import React from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { MoleculeData, TaskData } from "../Types/plugin";
|
import { MoleculeData } from "../Types/plugin";
|
||||||
|
import { Card, Text, Title } from "@mantine/core";
|
||||||
|
import "./ListItem.css";
|
||||||
|
|
||||||
const ListItem: React.FunctionComponent<{
|
const ListItem: React.FunctionComponent<{
|
||||||
data: TaskData<MoleculeData> | undefined;
|
data: MoleculeData | undefined;
|
||||||
|
simProgress: any;
|
||||||
}> = (props) => {
|
}> = (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<string, number>,
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
variables = {
|
||||||
|
Multiplicity: "-",
|
||||||
|
Charge: "-",
|
||||||
|
Electrons: "-",
|
||||||
|
Orbitals: "-",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const [smiles, setSmiles] = useState<string>("");
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<div
|
<Card orientation="horizontal" style={{ height: "100%" }}>
|
||||||
style={{ padding: "1rem", border: "1px solid #ccc", borderRadius: "4px" }}
|
<Card.Section
|
||||||
|
style={{
|
||||||
|
minWidth: "100px",
|
||||||
|
maxWidth: "200px",
|
||||||
|
justifyContent: "space-evenly",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
flexGrow: 1,
|
||||||
|
}}
|
||||||
|
px="sm"
|
||||||
>
|
>
|
||||||
<h3 style={{ margin: "0 0 0.5rem 0" }}>{props.data?.name}</h3>
|
<Text size="md">
|
||||||
<p style={{ margin: "0 0 0.5rem 0", color: "#666" }}>
|
Заряд: {variables.Charge != undefined ? variables.Charge : "-"}
|
||||||
{props.data?.description}
|
</Text>
|
||||||
</p>
|
<Text size="md">
|
||||||
<div style={{ fontSize: "0.9rem", color: "#999" }}>
|
Мультиплексивность:{" "}
|
||||||
Value: {props.data?.data.text || "Not set"}
|
{variables.Multiplicity != undefined ? variables.Multiplicity : "-"}
|
||||||
</div>
|
</Text>
|
||||||
</div>
|
</Card.Section>
|
||||||
|
<Card.Section
|
||||||
|
style={{
|
||||||
|
minWidth: "100px",
|
||||||
|
maxWidth: "200px",
|
||||||
|
justifyContent: "space-evenly",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
flexGrow: 1,
|
||||||
|
}}
|
||||||
|
px="sm"
|
||||||
|
withBorder
|
||||||
|
>
|
||||||
|
<Text size="md">
|
||||||
|
Акт. эклектроны:{" "}
|
||||||
|
{variables.Electrons != undefined ? variables.Electrons : "-"}
|
||||||
|
</Text>
|
||||||
|
<Text size="md">
|
||||||
|
Акт. орбитали:{" "}
|
||||||
|
{variables.Orbitals != undefined ? variables.Orbitals : "-"}
|
||||||
|
</Text>
|
||||||
|
</Card.Section>
|
||||||
|
<Card.Section
|
||||||
|
style={{
|
||||||
|
minWidth: "150px",
|
||||||
|
maxWidth: "250px",
|
||||||
|
justifyContent: "space-evenly",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
flexGrow: 1,
|
||||||
|
}}
|
||||||
|
px="sm"
|
||||||
|
withBorder
|
||||||
|
>
|
||||||
|
<Text size="md" className="truncatedString">
|
||||||
|
Smiles: {smiles}
|
||||||
|
</Text>
|
||||||
|
<Text size="md">
|
||||||
|
Необходимо кубит: {Number(variables.Orbitals) * 2 || "-"}
|
||||||
|
</Text>
|
||||||
|
</Card.Section>
|
||||||
|
{props.simProgress && !props.simProgress["error"] && (
|
||||||
|
<Card.Section
|
||||||
|
style={{
|
||||||
|
width: "250px",
|
||||||
|
justifyContent: "space-evenly",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
}}
|
||||||
|
px="sm"
|
||||||
|
withBorder
|
||||||
|
>
|
||||||
|
<Title size={"md"}>Прогресс выполнения:</Title>
|
||||||
|
<Text size="md" className="truncatedString">
|
||||||
|
Итерация: {props.simProgress["iter_num"]}
|
||||||
|
</Text>
|
||||||
|
<Text size="md" className="truncatedString">
|
||||||
|
Энергия: {Math.fround(props.simProgress["energy"])}
|
||||||
|
</Text>
|
||||||
|
</Card.Section>
|
||||||
|
)}
|
||||||
|
{props.simProgress && props.simProgress["error"] && (
|
||||||
|
<Card.Section
|
||||||
|
style={{
|
||||||
|
width: "250px",
|
||||||
|
justifyContent: "space-evenly",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
}}
|
||||||
|
px="sm"
|
||||||
|
withBorder
|
||||||
|
>
|
||||||
|
<Title size={"md"}>Ошибка:</Title>
|
||||||
|
<Text size="md" className="truncatedString">
|
||||||
|
{props.simProgress["error"]}
|
||||||
|
</Text>
|
||||||
|
</Card.Section>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,14 @@
|
|||||||
// Types
|
|
||||||
export interface TaskData<TData = any> {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
data: TData;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TaskEditorProps<TData = any> {
|
|
||||||
data: TaskData<TData> | undefined;
|
|
||||||
setData: (data: TData) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TaskTypePlugin<TData = any> {
|
|
||||||
type: string;
|
|
||||||
ListItem: React.ComponentType<TaskData<TData> | undefined>;
|
|
||||||
Editor: React.ComponentType<TaskEditorProps<TData>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MoleculeData {
|
export interface MoleculeData {
|
||||||
text: string;
|
text: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TaskEditorProps {
|
||||||
|
data: MoleculeData | undefined;
|
||||||
|
setData: (data: MoleculeData, qubits_needed: number) => void;
|
||||||
|
counter: number;
|
||||||
|
simProgress: any;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SelectedAtom {
|
export interface SelectedAtom {
|
||||||
serial: number;
|
serial: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -4,19 +4,13 @@ import "./index.css";
|
|||||||
import "@mantine/core/styles.css";
|
import "@mantine/core/styles.css";
|
||||||
import Wrapper from "./wrapper";
|
import Wrapper from "./wrapper";
|
||||||
// Parse URL parameters
|
// Parse URL parameters
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
|
||||||
const mode = urlParams.get("mode") == "List" ? "List" : "Editor";
|
|
||||||
|
|
||||||
// Render directly
|
// Render directly
|
||||||
const rootElement = document.getElementById("root");
|
const rootElement = document.getElementById("root");
|
||||||
|
|
||||||
if (rootElement) {
|
if (rootElement) {
|
||||||
|
rootElement.style = "height: 100%";
|
||||||
const root = ReactDOM.createRoot(rootElement);
|
const root = ReactDOM.createRoot(rootElement);
|
||||||
|
|
||||||
root.render(<Wrapper mode={mode} />);
|
root.render(<Wrapper />);
|
||||||
}
|
|
||||||
|
|
||||||
// Notify parent that plugin is ready
|
|
||||||
if (window.parent !== window) {
|
|
||||||
window.parent.postMessage({ type: "plugin-ready" }, "*");
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,18 +11,15 @@ import {
|
|||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
import "@mantine/core/styles.css";
|
import "@mantine/core/styles.css";
|
||||||
import { MoleculeData, TaskData } from "./Types/plugin";
|
import { MoleculeData } from "./Types/plugin";
|
||||||
|
|
||||||
interface WrapperProps {
|
|
||||||
mode: "List" | "Editor";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create setData function that communicates with parent
|
// Create setData function that communicates with parent
|
||||||
const setData = (newData: MoleculeData) => {
|
const setData = (newData: string, qubits_needed: number) => {
|
||||||
window.parent.postMessage(
|
window.parent.postMessage(
|
||||||
{
|
{
|
||||||
type: "plugin-update",
|
type: "plugin-update",
|
||||||
data: newData,
|
data: newData,
|
||||||
|
qubits_needed: qubits_needed,
|
||||||
},
|
},
|
||||||
"*",
|
"*",
|
||||||
);
|
);
|
||||||
@@ -69,41 +66,80 @@ const theme = createTheme({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const Wrapper: React.FunctionComponent<WrapperProps> = (props) => {
|
const Wrapper: React.FunctionComponent = () => {
|
||||||
const [currentTaskData, setTaskData] = useState<TaskData<MoleculeData>>();
|
const [currentTaskData, setTaskData] = useState<string>();
|
||||||
|
const [currentSimProgress, setCurrentSimProgress] = useState<string>();
|
||||||
const [currentTheme, setTheme] = useState<"light" | "dark">("dark");
|
const [currentTheme, setTheme] = useState<"light" | "dark">("dark");
|
||||||
|
|
||||||
|
const [mode, setMode] = useState("List");
|
||||||
|
const [counter, setCounter] = useState(0);
|
||||||
|
|
||||||
// Handle messages from parent
|
// Handle messages from parent
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.addEventListener("message", (event) => {
|
window.addEventListener("message", (event) => {
|
||||||
const { type, data } = event.data;
|
const { type, data } = event.data;
|
||||||
console.log(event.data);
|
if (type == "plugin-data") {
|
||||||
|
|
||||||
setTaskData(data.taskData);
|
setTaskData(data.taskData);
|
||||||
setTheme(data.theme);
|
setTheme(data.theme);
|
||||||
});
|
setMode(data.mode);
|
||||||
}, [props.mode]);
|
try {
|
||||||
|
JSON.parse(data.simProgress);
|
||||||
if (props.mode === "List") {
|
setCurrentSimProgress(data.simProgress);
|
||||||
return (
|
} catch {
|
||||||
<MantineProvider
|
setCurrentSimProgress(undefined);
|
||||||
colorSchemeManager={colorSchemeManager}
|
|
||||||
theme={theme}
|
|
||||||
forceColorScheme={currentTheme}
|
|
||||||
>
|
|
||||||
<ListItem data={currentTaskData} />
|
|
||||||
</MantineProvider>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<MantineProvider
|
|
||||||
colorSchemeManager={colorSchemeManager}
|
|
||||||
theme={theme}
|
|
||||||
forceColorScheme={currentTheme}
|
|
||||||
>
|
|
||||||
<MoleculeEditorPage data={currentTaskData} setData={setData} />
|
|
||||||
</MantineProvider>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (type == "update") {
|
||||||
|
console.log("Updating");
|
||||||
|
setCounter((prevData) => {
|
||||||
|
return prevData + 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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" ? (
|
||||||
|
<MantineProvider
|
||||||
|
colorSchemeManager={colorSchemeManager}
|
||||||
|
theme={theme}
|
||||||
|
forceColorScheme={currentTheme}
|
||||||
|
>
|
||||||
|
{currentTaskData && (
|
||||||
|
<ListItem
|
||||||
|
data={JSON.parse(currentTaskData) as MoleculeData}
|
||||||
|
simProgress={
|
||||||
|
currentSimProgress ? JSON.parse(currentSimProgress) : undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</MantineProvider>
|
||||||
|
) : (
|
||||||
|
<MantineProvider
|
||||||
|
colorSchemeManager={colorSchemeManager}
|
||||||
|
theme={theme}
|
||||||
|
forceColorScheme={currentTheme}
|
||||||
|
>
|
||||||
|
{currentTaskData && (
|
||||||
|
<MoleculeEditorPage
|
||||||
|
data={JSON.parse(currentTaskData) as MoleculeData}
|
||||||
|
setData={(data, qubits_needed) =>
|
||||||
|
setData(JSON.stringify(data), qubits_needed)
|
||||||
|
}
|
||||||
|
counter={counter}
|
||||||
|
simProgress={
|
||||||
|
currentSimProgress ? JSON.parse(currentSimProgress) : undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</MantineProvider>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Wrapper;
|
export default Wrapper;
|
||||||
|
|||||||
173
local_quantum/vqe.py
Normal file
173
local_quantum/vqe.py
Normal file
@@ -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
|
||||||
Reference in New Issue
Block a user