diff --git a/qubitverse/visualizer/src/components/QuantumCircuit.jsx b/qubitverse/visualizer/src/components/QuantumCircuit.jsx index 3198f46..24d2ae0 100644 --- a/qubitverse/visualizer/src/components/QuantumCircuit.jsx +++ b/qubitverse/visualizer/src/components/QuantumCircuit.jsx @@ -1,1592 +1,1673 @@ -import React, { useState, useRef, useEffect } from "react"; -import { Stage, Layer, Line, Rect, Text, Group, Circle, Shape } from "react-konva"; -import { MathJax, MathJaxContext } from "better-react-mathjax"; -import SendToBackEnd_Calculate from "./SendToBackEnd"; -import { Button } from "./ui/button"; -import ProbGraph from "./ProbGraph"; -import HilbertSpaceResult from "./HilbertSpaceResult"; -import { DataSet } from "vis-network/standalone"; -import MeasurementChart from "./MeasurementChart"; - -// ======================= -// CONFIG CONSTANTS -// ======================= -const qubitSpacing = 50; // vertical spacing between qubit lines -const gateSize = 45; // width/height for single-qubit gate squares -const canvasMinX = 50; // left bound for gates on the stage -const canvasMaxX = window.innerWidth-300 - gateSize; // right bound so gate stays visible - - -// ======================= -// GATE LIST -// ======================= -const gatesList = [ - "I", - "X", - "Y", - "Z", - "H", - "S", - "T", - "P", - "Rx", - "Ry", - "Rz", - "V", - "V†", - "CNOT", - "CZ", - "SWAP", - "M" -]; - -// ======================= -// GATE TOOLTIP DATA (LaTeX + descriptions) -// ======================= -const gateTooltips = { - I: { - desc: "Identity Gate", - latex: "$$I = \\begin{pmatrix} 1 & 0 \\\\ 0 & 1 \\end{pmatrix}$$", - }, - X: { - desc: "Pauli-X (NOT) Gate", - latex: "$$X = \\begin{pmatrix}0 & 1\\\\ 1 & 0\\end{pmatrix}$$", - }, - Y: { - desc: "Pauli-Y Gate", - latex: "$$Y = \\begin{pmatrix}0 & -i\\\\ i & 0\\end{pmatrix}$$", - }, - Z: { - desc: "Pauli-Z Gate", - latex: "$$Z = \\begin{pmatrix} 1 & 0 \\\\ 0 & -1 \\end{pmatrix}$$", - }, - S: { - desc: "Phase π/2 Shift", - latex: "$$S = \\begin{pmatrix} 1 & 0 \\\\ 0 & i \\end{pmatrix}$$", - }, - T: { - desc: "Phase π/4 Shift", - latex: "$$T = \\begin{pmatrix} 1 & 0 \\\\ 0 & e^{iπ/4} \\end{pmatrix}$$", - }, - H: { - desc: "Hadamard (Superposition) Gate", - latex: - "$$H = \\frac{1}{\\sqrt{2}} \\begin{pmatrix}1 & 1\\\\ 1 & -1\\end{pmatrix}$$", - }, - P: { - desc: "General Phase Shift Gate", - latex: - "$$P(\\theta) = \\begin{pmatrix} 1 & 0 \\\\ 0 & e^{i\\theta} \\end{pmatrix}$$", - }, - Rx: { - desc: "Rotation around X-axis", - latex: - "$$R_x(\\theta) = \\begin{pmatrix} \\cos(\\theta/2) & -i\\sin(\\theta/2) \\\\ -i\\sin(\\theta/2) & \\cos(\\theta/2) \\end{pmatrix}$$", - }, - Ry: { - desc: "Rotation around Y-axis", - latex: - "$$R_y(\\theta) = \\begin{pmatrix} \\cos(\\theta/2) & -\\sin(\\theta/2) \\\\ \\sin(\\theta/2) & \\cos(\\theta/2) \\end{pmatrix}$$", - }, - Rz: { - desc: "Rotation around Z-axis", - latex: - "$$R_z(\\theta) = \\begin{pmatrix} e^{-i\\theta/2} & 0 \\\\ 0 & e^{i\\theta/2} \\end{pmatrix}$$", - }, - V: { - desc: "Square-root of NOT Gate (alias for √X)", - latex: - "$$V = \\frac{1}{2} \\begin{pmatrix} 1 + i & 1 - i \\\\ 1 - i & 1 + i \\end{pmatrix}$$", - }, - "V†": { - desc: "Adjoint of the V gate (alias for (√X)⁻¹)", - latex: - "$$V† = \\frac{1}{2} \\begin{pmatrix} 1 - i & 1 + i \\\\ 1 + i & 1 - i \\end{pmatrix}$$", - }, - CNOT: { - desc: "Controlled-NOT (Entanglement) Gate", - latex: - "$$CNOT = \\begin{pmatrix}1 & 0 & 0 & 0\\\\ 0 & 1 & 0 & 0\\\\ 0 & 0 & 0 & 1\\\\ 0 & 0 & 1 & 0\\end{pmatrix}$$", - }, - CZ: { - desc: "Controlled-Z Gate", - latex: - "$$CZ = \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ 0 & 1 & 0 & 0 \\\\ 0 & 0 & 1 & 0 \\\\ 0 & 0 & 0 & -1 \\end{pmatrix}$$", - }, - SWAP: { - desc: "SWAP Gate", - latex: - "$$SWAP = \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ 0 & 0 & 1 & 0 \\\\ 0 & 1 & 0 & 0 \\\\ 0 & 0 & 0 & 1 \\end{pmatrix}$$", - }, - M: { - desc: "Measure", - latex: - "$$\\text{Measure }n^{\\text{th}}\\text{ Qubit}$$" - } -}; - - -// Helper to clamp a value between min and max -const clamp = (value, min, max) => Math.max(min, Math.min(value, max)); - -// ======================= -// SINGLE-QUBIT GATE COMPONENT -// ======================= -const QuantumGate = ({ - x, - y, - text, - draggable, - onDragEnd, - fixedY, - onRightClick, - order, - params = {}, -}) => { - const isRotationGate = params.theta !== undefined; - let thetalength = 0; - if (params.theta != null) { - thetalength = params.theta.toString().length; - } - return ( - ({ - x: clamp(pos.x, canvasMinX, canvasMaxX), - y: fixedY, - }) - : undefined - } - onDragEnd={onDragEnd} - onContextMenu={(e) => { - e.evt.preventDefault(); - onRightClick(); - }} - > - - 1 ? gateSize / 2 - 10 : gateSize / 2 - 5} - y={gateSize / 2 - 10} - /> - {isRotationGate && ( - - )} - {order !== undefined && ( - - )} - - ); -}; - -// ======================= -// CNOT GATE COMPONENT -// ======================= -const CNOTGate = ({ x, control, target, onDragEnd, onRightClick, order }) => { - const yControl = (control + 1) * qubitSpacing; - const yTarget = (target + 1) * qubitSpacing; - return ( - ({ - x: clamp(pos.x, canvasMinX, canvasMaxX), - y: 0, - })} - onDragEnd={onDragEnd} - onContextMenu={(e) => { - e.evt.preventDefault(); - onRightClick(); - }} - > - - - - - - {order !== undefined && ( - - )} - - ); -}; - -// ======================= -// CZ GATE COMPONENT -// ======================= -const CZGate = ({ - x, - control, - target, - onDragStart, - onDragEnd, - onRightClick, - order, -}) => { - const yControl = (control + 1) * qubitSpacing; - const yTarget = (target + 1) * qubitSpacing; - return ( - ({ - x: clamp(pos.x, canvasMinX, canvasMaxX), - y: 0, - })} - onDragStart={onDragStart} - onDragEnd={onDragEnd} - onContextMenu={(e) => { - e.evt.preventDefault(); - onRightClick(); - }} - > - - - - - {order !== undefined && ( - - )} - - ); -}; - -// ======================= -// SWAP GATE COMPONENT -// ======================= -const SWAPGate = ({ - x, - qubit1, - qubit2, - onDragStart, - onDragEnd, - onRightClick, - order, -}) => { - const y1 = (qubit1 + 1) * qubitSpacing; - const y2 = (qubit2 + 1) * qubitSpacing; - const topY = Math.min(y1, y2); - const bottomY = Math.max(y1, y2); - return ( - ({ - x: clamp(pos.x, canvasMinX, canvasMaxX), - y: 0, - })} - onDragStart={onDragStart} - onDragEnd={onDragEnd} - onContextMenu={(e) => { - e.evt.preventDefault(); - onRightClick(); - }} - > - - {/* 'X' cross on qubit1 */} - - - {/* 'X' cross on qubit2 */} - - - {order !== undefined && ( - - )} - - ); -}; - -// ======================= -// Measure Nth COMPONENT -// ======================= -const MeasureNthComponent = ({ - x, - y, - draggable, - onDragEnd, - fixedY, - onRightClick, - order -}) => { - return ( - ({ - x: clamp(pos.x, canvasMinX, canvasMaxX), - y: fixedY, - }) - : undefined - } - onDragEnd={onDragEnd} - onContextMenu={(e) => { - e.evt.preventDefault(); - onRightClick(); - }} - > - {/* Rectangle Border */} - - - {/* Semicircle */} - { - context.beginPath(); - context.arc(gateSize / 2, gateSize / 2 + 5, gateSize / 2.5, Math.PI, 0, false); - context.strokeShape(shape); - }} - stroke="red" - strokeWidth={2} - /> - - {/* Diagonal Line */} - - {/* Small Circle */} - - {order !== undefined && ( - - )} - - ); -}; - -// ======================= -// MAIN QUANTUM CIRCUIT COMPONENT -// ======================= -const QuantumCircuit = ({ numQubits, setNumQubits }) => { - // Result or Log Data from the Backend - const [resultData, setResultData] = useState(null); - // Probs Data for BarGraph - const [probData, setProbData] = useState([]); - // Result Graph Edges - const [edgesResultGraph, setEdgesResultGraph] = useState(null); - // Result Graph Vertices - const [verticesResultGraph, setVerticesResultGraph] = useState(null); - // Result Measurement - const [measuredValue, setMeasuredValue] = useState(NaN); - // Measurement History - const [measurementHist, setMeasurementHist] = useState([]); - // Single-qubit gates - const [gates, setGates] = useState([]); // { x, y, text, params? } - // CNOT gates - const [cnotGates, setCnotGates] = useState([]); // { x, control, target } - // CZ gates - const [czGates, setCzGates] = useState([]); // { x, control, target } - // SWAP gates - const [swapGates, setSwapGates] = useState([]); // { x, qubit1, qubit2 } - // Measure Nth Qubit - const [measureNthQubit, setMeasureNthQubit] = useState([]); // {x, y} - // Error to show when delete Qubit is clicked and numQubits goes less than 1 - const [delQubError, setDelQubError] = useState(false); - - // For rotation/phase gates - const [rotationModalOpen, setRotationModalOpen] = useState(false); - const [rotationValue, setRotationValue] = useState(45); - const [rotationX, setRotationX] = useState(0); - const [rotationY, setRotationY] = useState(0); - const [rotationType, setRotationType] = useState("P"); - - // For CNOT selection - const [cnotModalOpen, setCnotModalOpen] = useState(false); - const [cnotX, setCnotX] = useState(0); - const [cnotControl, setCnotControl] = useState(0); - const [cnotTarget, setCnotTarget] = useState(1); - - // For CZ selection - const [czModalOpen, setCzModalOpen] = useState(false); - const [czX, setCzX] = useState(0); - const [czControl, setCzControl] = useState(0); - const [czTarget, setCzTarget] = useState(1); - - // For SWAP selection - const [swapModalOpen, setSwapModalOpen] = useState(false); - const [swapX, setSwapX] = useState(0); - const [swapQubit1, setSwapQubit1] = useState(0); - const [swapQubit2, setSwapQubit2] = useState(1); - - // Tooltip state - const [tooltip, setTooltip] = useState({ - visible: false, - x: 0, - y: 0, - desc: "", - latex: "", - }); - const [isDragging, setIsDragging] = useState(false); - - // Tabs state ("Circuit" or "Result") - const [activeTab, setActiveTab] = useState("Circuit"); - - const stageRef = useRef(null); - - // ======================= - // ORDERING: Combined ordering for all gates on a qubit line - // ======================= - const combinedGroups = {}; - // Single-qubit - gates.forEach((g, i) => { - const qid = Math.round((g.y + gateSize / 2) / qubitSpacing) - 1; - if (!combinedGroups[qid]) combinedGroups[qid] = []; - combinedGroups[qid].push({ type: "single", index: i, x: g.x }); - }); - // CNOT - cnotGates.forEach((g, i) => { - const qid = g.control; - if (!combinedGroups[qid]) combinedGroups[qid] = []; - combinedGroups[qid].push({ type: "cnot", index: i, x: g.x }); - }); - // CZ - czGates.forEach((g, i) => { - const qid = g.control; - if (!combinedGroups[qid]) combinedGroups[qid] = []; - combinedGroups[qid].push({ type: "cz", index: i, x: g.x }); - }); - // SWAP - swapGates.forEach((g, i) => { - const qid = Math.min(g.qubit1, g.qubit2); - if (!combinedGroups[qid]) combinedGroups[qid] = []; - combinedGroups[qid].push({ type: "swap", index: i, x: g.x }); - }); - // Measure Nth Qubit - measureNthQubit.forEach((g, i) => { - const qid = Math.round((g.y + gateSize / 2) / qubitSpacing) - 1; - if (!combinedGroups[qid]) combinedGroups[qid] = []; - combinedGroups[qid].push({ type: "measure", index: i, x: g.x }); - }); - - const combinedOrders = { - single: {}, - cnot: {}, - cz: {}, - swap: {}, - measure: {} - }; - Object.keys(combinedGroups).forEach((qid) => { - combinedGroups[qid].sort((a, b) => a.x - b.x); - combinedGroups[qid].forEach((item, orderIndex) => { - if (item.type === "single") { - combinedOrders.single[item.index] = orderIndex + 1; - } else if (item.type === "cnot") { - combinedOrders.cnot[item.index] = orderIndex + 1; - } else if (item.type === "cz") { - combinedOrders.cz[item.index] = orderIndex + 1; - } else if (item.type === "swap") { - combinedOrders.swap[item.index] = orderIndex + 1; - } else if (item.type === "measure") { - combinedOrders.measure[item.index] = orderIndex + 1; - } - }); - }); - - // ======================= - // QUBIT LINE COMPONENT - // ======================= - const QubitLine = ({ y, label }) => ( - - - - - ); - - // ======================= - // Helper: snap single-qubit gates to a line - // ======================= - const snapY = (pointerY) => { - const desiredCenter = Math.round(pointerY / qubitSpacing) * qubitSpacing; - const clampedCenter = Math.max( - qubitSpacing, - Math.min(desiredCenter, numQubits * qubitSpacing) - ); - return clampedCenter - gateSize / 2; - }; - - // ======================= - // DRAG & DROP LOGIC - // ======================= - - useEffect(() => { - if (!stageRef.current) return; - const container = stageRef.current.container(); - - const handleDrop = (e) => { - e.preventDefault(); - const rect = container.getBoundingClientRect(); - const pointerX = e.clientX - rect.left; - const pointerY = e.clientY - rect.top; - const gateType = e.dataTransfer.getData("text/plain"); - if (!gateType) return; - - if (gateType === "CNOT") { - setCnotModalOpen(true); - setCnotX(pointerX); - } else if (gateType === "CZ") { - setCzModalOpen(true); - setCzX(pointerX); - } else if (gateType === "SWAP") { - setSwapModalOpen(true); - setSwapX(pointerX); - } else if (["P", "Rx", "Ry", "Rz"].includes(gateType)) { - const snappedY = snapY(pointerY); - setRotationModalOpen(true); - setRotationX(pointerX); - setRotationY(snappedY); - setRotationType(gateType); - setRotationValue(45); // default value - } else if (gateType === "M") { - const snappedY = snapY(pointerY); - setMeasureNthQubit((prev) => [ - ...prev, - { x: pointerX, y: snappedY, }, - ]); - } else { - // Single-qubit gate - const snappedY = snapY(pointerY); - setGates((prev) => [ - ...prev, - { x: pointerX, y: snappedY, text: gateType }, - ]); - } - }; - - const handleDragOver = (e) => e.preventDefault(); - - container.addEventListener("drop", handleDrop); - container.addEventListener("dragover", handleDragOver); - - return () => { - container.removeEventListener("drop", handleDrop); - container.removeEventListener("dragover", handleDragOver); - }; - }, [stageRef.current, numQubits]); - - // ======================= - // DRAG END HANDLERS - // ======================= - const handleGateDragEnd = (e, index) => { - const { x } = e.target.position(); - setGates((prev) => { - const newGates = [...prev]; - newGates[index] = { - ...newGates[index], - x: clamp(x, canvasMinX, canvasMaxX), - }; - return newGates; - }); - setIsDragging(false); - }; - - const handleMeasureDragEnd = (e, index) => { - const { x } = e.target.position(); - setMeasureNthQubit((prev) => { - const newArr = [...prev]; - newArr[index] = { - ...newArr[index], - x: clamp(x, canvasMinX, canvasMaxX), - }; - return newArr; - }); - setIsDragging(false); - } - - const handleCnotDragEnd = (e, index) => { - const { x } = e.target.position(); - setCnotGates((prev) => { - const newArr = [...prev]; - newArr[index].x = clamp(x, canvasMinX, canvasMaxX); - return newArr; - }); - setIsDragging(false); - }; - - const handleCzDragEnd = (e, index) => { - const { x } = e.target.position(); - setCzGates((prev) => { - const newArr = [...prev]; - newArr[index].x = clamp(x, canvasMinX, canvasMaxX); - return newArr; - }); - setIsDragging(false); - }; - - const handleSwapDragEnd = (e, index) => { - const { x } = e.target.position(); - setSwapGates((prev) => { - const newArr = [...prev]; - newArr[index].x = clamp(x, canvasMinX, canvasMaxX); - return newArr; - }); - setIsDragging(false); - }; - - // ======================= - // DELETE HANDLERS (Right-click) - // ======================= - const handleDeleteGate = (index) => { - setGates((prev) => prev.filter((_, i) => i !== index)); - }; - const handleDeleteCnot = (index) => { - setCnotGates((prev) => prev.filter((_, i) => i !== index)); - }; - const handleDeleteCz = (index) => { - setCzGates((prev) => prev.filter((_, i) => i !== index)); - }; - const handleDeleteSwap = (index) => { - setSwapGates((prev) => prev.filter((_, i) => i !== index)); - }; - const handleDeleteMeasure = (index) => { - setMeasureNthQubit((prev) => prev.filter((_, i) => i !== index)); - } - - // ======================= - // TOOLTIP HANDLERS - // ======================= - const handleTooltipEnter = (e, gate) => { - if (isDragging) return; - const rect = e.target.getBoundingClientRect(); - setTooltip({ - visible: true, - x: rect.left + 120, - y: rect.bottom - 100, - desc: gateTooltips[gate].desc, - latex: gateTooltips[gate].latex, - }); - }; - - const handleTooltipLeave = () => { - setTooltip({ visible: false, x: 0, y: 0, desc: "", latex: "" }); - }; - - const handleDragStart = () => { - setIsDragging(true); - setTooltip((prev) => ({ ...prev, visible: false })); - }; - - // ======================= - // ROTATION/PHASE GATE MODAL HANDLERS - // ======================= - const validateRotationValue = (value) => (value === 0 ? 1 : value); - const handleRotationConfirm = () => { - const validTheta = validateRotationValue(rotationValue); - setGates((prev) => [ - ...prev, - { - x: rotationX, - y: rotationY, - text: rotationType, - params: { theta: validTheta }, - }, - ]); - setRotationModalOpen(false); - }; - const handleRotationCancel = () => { - setRotationModalOpen(false); - }; - const handleRotationInputChange = (e) => { - const val = parseFloat(e.target.value) || 1; - setRotationValue(val === 0 ? 1 : val); - }; - const getRotationModalContent = () => { - switch (rotationType) { - case "Rx": - return { - title: "Rotation around X-axis (θ)", - latex: - "$$R_x(\\theta) = \\begin{pmatrix} \\cos(\\theta/2) & -i\\sin(\\theta/2) \\\\ -i\\sin(\\theta/2) & \\cos(\\theta/2) \\end{pmatrix}$$", - }; - case "Ry": - return { - title: "Rotation around Y-axis (θ)", - latex: - "$$R_y(\\theta) = \\begin{pmatrix} \\cos(\\theta/2) & -\\sin(\\theta/2) \\\\ \\sin(\\theta/2) & \\cos(\\theta/2) \\end{pmatrix}$$", - }; - case "Rz": - return { - title: "Rotation around Z-axis (θ)", - latex: - "$$R_z(\\theta) = \\begin{pmatrix} e^{-i\\theta/2} & 0 \\\\ 0 & e^{i\\theta/2} \\end{pmatrix}$$", - }; - default: - return { - title: "Phase Angle (θ)", - latex: - "$$P(\\theta) = \\begin{pmatrix} 1 & 0 \\\\ 0 & e^{i\\theta} \\end{pmatrix}$$", - }; - } - }; - - // ======================= - // CNOT MODAL HANDLERS - // ======================= - const handleCnotConfirm = () => { - if (cnotControl === cnotTarget) { - alert("Control and target qubits must be different!"); - return; - } - setCnotGates((prev) => [ - ...prev, - { x: cnotX, control: cnotControl, target: cnotTarget }, - ]); - setCnotModalOpen(false); - }; - const handleCnotCancel = () => { - setCnotModalOpen(false); - }; - - // ======================= - // CZ MODAL HANDLERS - // ======================= - const handleCzConfirm = () => { - if (czControl === czTarget) { - alert("Control and target qubits must be different!"); - return; - } - setCzGates((prev) => [ - ...prev, - { x: czX, control: czControl, target: czTarget }, - ]); - setCzModalOpen(false); - }; - const handleCzCancel = () => { - setCzModalOpen(false); - }; - - // ======================= - // SWAP MODAL HANDLERS - // ======================= - const handleSwapConfirm = () => { - if (swapQubit1 === swapQubit2) { - alert("SWAP requires two distinct qubits!"); - return; - } - setSwapGates((prev) => [ - ...prev, - { x: swapX, qubit1: swapQubit1, qubit2: swapQubit2 }, - ]); - setSwapModalOpen(false); - }; - const handleSwapCancel = () => { - setSwapModalOpen(false); - }; - - // ======================= - // Add or Remove Qubits - // ======================= - const handleAddQubit = () => { - let q = numQubits + 1; - setNumQubits(q); - setMeasurementHist([]); - } - - const handleDeleteQubit = () => { - const newNumQubits = numQubits - 1; - - if (newNumQubits < 1) { - setDelQubError(true); - return; - } - - setGates(prevGates => - prevGates.filter(g => { - const gateQubit = Math.round((g.y + gateSize / 2) / qubitSpacing) - 1; - return gateQubit < newNumQubits; - }) - ); - - setMeasureNthQubit(prevMeasures => - prevMeasures.filter(g => { - const gateQubit = Math.round((g.y + gateSize / 2) / qubitSpacing) - 1; - return gateQubit < newNumQubits; - }) - ); - - setCnotGates(prevCnot => - prevCnot.filter(g => g.control < newNumQubits && g.target < newNumQubits) - ); - setCzGates(prevCz => - prevCz.filter(g => g.control < newNumQubits && g.target < newNumQubits) - ); - - setSwapGates(prevSwap => - prevSwap.filter(g => g.qubit1 < newNumQubits && g.qubit2 < newNumQubits) - ); - - setNumQubits(newNumQubits); - - setMeasurementHist([]); - } -const [Dragging, setDragging] = useState(false); - const [position, setPosition] = useState({ x: 100, y: 100 }); - const [offset, setOffset] = useState({ x: 0, y: 0 }); - const boxRef = useRef(null); - - const startDrag = (e) => { - setDragging(true); - setOffset({ x: e.clientX - position.x, y: e.clientY - position.y }); - }; - - const handleMouseMove = (e) => { - if (!Dragging) return; - setPosition({ x: e.clientX - offset.x, y: e.clientY - offset.y }); - }; - - const handleMouseUp = () => setDragging(false); - - useEffect(() => { - if (Dragging) { - window.addEventListener("mousemove", handleMouseMove); - window.addEventListener("mouseup", handleMouseUp); - } - return () => { - window.removeEventListener("mousemove", handleMouseMove); - window.removeEventListener("mouseup", handleMouseUp); - }; - }, [Dragging, offset]); - - return ( - -
- {/* Tab Buttons */} -
- {["Circuit", "Result", "Measurement", "Probability", "Log"].map((tab) => ( - - ))} -
- - {/* Left Menu: single box with gates on top and 3 buttons below */} - -
-
-

Quantum Gates

-
- {/* Gates box */} -
- {gatesList.map((gate, index) => { - // Hide multi-qubit gates if only 1 qubit - if (numQubits < 2 && ["CNOT", "CZ", "SWAP"].includes(gate)) { - return null; - } - - const isActive = activeTab === "Circuit"; - const defaultBackground = isActive ? "white" : "#eee"; - - return ( -
{ - if (isActive) { - e.dataTransfer.setData("text/plain", gate); - } - }} - onMouseEnter={(e) => { - if (isActive) handleTooltipEnter(e, gate); - }} - onMouseMove={(e) => { - if (isActive) { - e.currentTarget.style.background = "oklch(80.9% 0.105 251.813)"; - } - }} - onMouseLeave={(e) => { - e.currentTarget.style.background = defaultBackground; - if (isActive) handleTooltipLeave(e); - }} - > - {gate} -
- ); - })} -
- - {/* Buttons under gates */} - -
- - {/* Right Content: depends on active tab */} -
- {/* TOOLTIP */} - {!isDragging && tooltip.visible && ( -
-
- {tooltip.desc} -
- - {tooltip.latex} - -
- )} - {activeTab === "Circuit" ? ( - <> - {/* STAGE: QUBIT LINES & GATES */} - - - {Array.from({ length: numQubits }).map((_, i) => ( - - ))} - {/* Render single-qubit gates */} - {gates.map((g, i) => ( - handleGateDragEnd(e, i)} - onRightClick={() => handleDeleteGate(i)} - order={combinedOrders.single[i]} - /> - ))} - {/* Render CNOT gates */} - {cnotGates.map((g, i) => ( - handleCnotDragEnd(e, i)} - onRightClick={() => handleDeleteCnot(i)} - order={combinedOrders.cnot[i]} - /> - ))} - {/* Render CZ gates */} - {czGates.map((g, i) => ( - handleCzDragEnd(e, i)} - onRightClick={() => handleDeleteCz(i)} - order={combinedOrders.cz[i]} - /> - ))} - {/* Render SWAP gates */} - {swapGates.map((g, i) => ( - handleSwapDragEnd(e, i)} - onRightClick={() => handleDeleteSwap(i)} - order={combinedOrders.swap[i]} - /> - ))} - {/* Render Measure Nth Component */} - {measureNthQubit.map((g, i) => ( - handleMeasureDragEnd(e, i)} - onRightClick={() => handleDeleteMeasure(i)} - onDragStart={handleDragStart} - order={combinedOrders.measure[i]} - /> - ))} - - - - ) : activeTab === "Result" ? ( - - ) : activeTab === "Measurement" ? ( - - ) : activeTab === "Probability" ? ( - // an interactive graph - - ) : activeTab === "Log" ? ( - - ) : (null)} -
-
- - {/* Error Modal */} - {delQubError && ( -
{ setDelQubError(false); }} - style={{ - position: "fixed", - top: 0, - left: 0, - width: "100vw", - height: "100vh", - background: "rgba(0,0,0,0.5)", - display: "flex", - alignItems: "center", - justifyContent: "center", - userSelect: "none" - }} - > -
e.stopPropagation()} - style={{ - background: "white", - padding: "20px", - borderRadius: "10px", - textAlign: "center", - minWidth: "300px", - }} - > -

- Number of Qubits must be greater than or equal to 1. -

-
- {"$$\\text{numQubits}\\geq1$$"} -
- -
-
- ) - } - - {/* ROTATION/PHASE GATE MODAL */} - { - rotationModalOpen && ( -
-
e.stopPropagation()} - style={{ - background: "white", - padding: "20px", - borderRadius: "10px", - textAlign: "center", - minWidth: "300px", - }} - > -

{getRotationModalContent().title}

-
- {getRotationModalContent().latex} -
-
- - -
-
- -
- Note: Angle must be between 1° and 360° -
-
- - -
-
- ) - } - - {/* CNOT MODAL */} - { - cnotModalOpen && ( -
-
e.stopPropagation()} - style={{ - background: "white", - padding: "20px", - borderRadius: "10px", - textAlign: "center", - minWidth: "300px", - }} - > -

Select Control and Target Qubit

-
- - -
-
- - -
- - -
-
- ) - } - - {/* CZ MODAL */} - { - czModalOpen && ( -
-
e.stopPropagation()} - style={{ - background: "white", - padding: "20px", - borderRadius: "10px", - textAlign: "center", - minWidth: "300px", - }} - > -

Select Control and Target Qubit (CZ)

-
- - -
-
- - -
- - -
-
- ) - } - - {/* SWAP MODAL */} - { - swapModalOpen && ( -
-
e.stopPropagation()} - style={{ - background: "white", - padding: "20px", - borderRadius: "10px", - textAlign: "center", - minWidth: "300px", - }} - > -

Select Qubits to SWAP

-
- - -
-
- - -
- - -
-
- ) - } -
- ); -}; - -export default QuantumCircuit; +import React, { useState, useRef, useEffect } from "react"; +import { Stage, Layer, Line, Rect, Text, Group, Circle, Shape } from "react-konva"; +import { MathJax, MathJaxContext } from "better-react-mathjax"; +import SendToBackEnd_Calculate from "./SendToBackEnd"; +import { Button } from "./ui/button"; +import ProbGraph from "./ProbGraph"; +import HilbertSpaceResult from "./HilbertSpaceResult"; +import { DataSet } from "vis-network/standalone"; +import MeasurementChart from "./MeasurementChart"; +import { evaluate } from "mathjs"; + + +// ======================= +// CONFIG CONSTANTS +// ======================= +const qubitSpacing = 50; // vertical spacing between qubit lines +const gateSize = 45; // width/height for single-qubit gate squares +const canvasMinX = 50; // left bound for gates on the stage +const canvasMaxX = window.innerWidth-300 - gateSize; // right bound so gate stays visible + + +// ======================= +// GATE LIST +// ======================= +const gatesList = [ + "I", + "X", + "Y", + "Z", + "H", + "S", + "T", + "P", + "Rx", + "Ry", + "Rz", + "V", + "V†", + "CNOT", + "CZ", + "SWAP", + "M" +]; + +// ======================= +// GATE TOOLTIP DATA (LaTeX + descriptions) +// ======================= +const gateTooltips = { + I: { + desc: "Identity Gate", + latex: "$$I = \\begin{pmatrix} 1 & 0 \\\\ 0 & 1 \\end{pmatrix}$$", + }, + X: { + desc: "Pauli-X (NOT) Gate", + latex: "$$X = \\begin{pmatrix}0 & 1\\\\ 1 & 0\\end{pmatrix}$$", + }, + Y: { + desc: "Pauli-Y Gate", + latex: "$$Y = \\begin{pmatrix}0 & -i\\\\ i & 0\\end{pmatrix}$$", + }, + Z: { + desc: "Pauli-Z Gate", + latex: "$$Z = \\begin{pmatrix} 1 & 0 \\\\ 0 & -1 \\end{pmatrix}$$", + }, + S: { + desc: "Phase π/2 Shift", + latex: "$$S = \\begin{pmatrix} 1 & 0 \\\\ 0 & i \\end{pmatrix}$$", + }, + T: { + desc: "Phase π/4 Shift", + latex: "$$T = \\begin{pmatrix} 1 & 0 \\\\ 0 & e^{iπ/4} \\end{pmatrix}$$", + }, + H: { + desc: "Hadamard (Superposition) Gate", + latex: + "$$H = \\frac{1}{\\sqrt{2}} \\begin{pmatrix}1 & 1\\\\ 1 & -1\\end{pmatrix}$$", + }, + P: { + desc: "General Phase Shift Gate", + latex: + "$$P(\\theta) = \\begin{pmatrix} 1 & 0 \\\\ 0 & e^{i\\theta} \\end{pmatrix}$$", + }, + Rx: { + desc: "Rotation around X-axis", + latex: + "$$R_x(\\theta) = \\begin{pmatrix} \\cos(\\theta/2) & -i\\sin(\\theta/2) \\\\ -i\\sin(\\theta/2) & \\cos(\\theta/2) \\end{pmatrix}$$", + }, + Ry: { + desc: "Rotation around Y-axis", + latex: + "$$R_y(\\theta) = \\begin{pmatrix} \\cos(\\theta/2) & -\\sin(\\theta/2) \\\\ \\sin(\\theta/2) & \\cos(\\theta/2) \\end{pmatrix}$$", + }, + Rz: { + desc: "Rotation around Z-axis", + latex: + "$$R_z(\\theta) = \\begin{pmatrix} e^{-i\\theta/2} & 0 \\\\ 0 & e^{i\\theta/2} \\end{pmatrix}$$", + }, + V: { + desc: "Square-root of NOT Gate (alias for √X)", + latex: + "$$V = \\frac{1}{2} \\begin{pmatrix} 1 + i & 1 - i \\\\ 1 - i & 1 + i \\end{pmatrix}$$", + }, + "V†": { + desc: "Adjoint of the V gate (alias for (√X)⁻¹)", + latex: + "$$V† = \\frac{1}{2} \\begin{pmatrix} 1 - i & 1 + i \\\\ 1 + i & 1 - i \\end{pmatrix}$$", + }, + CNOT: { + desc: "Controlled-NOT (Entanglement) Gate", + latex: + "$$CNOT = \\begin{pmatrix}1 & 0 & 0 & 0\\\\ 0 & 1 & 0 & 0\\\\ 0 & 0 & 0 & 1\\\\ 0 & 0 & 1 & 0\\end{pmatrix}$$", + }, + CZ: { + desc: "Controlled-Z Gate", + latex: + "$$CZ = \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ 0 & 1 & 0 & 0 \\\\ 0 & 0 & 1 & 0 \\\\ 0 & 0 & 0 & -1 \\end{pmatrix}$$", + }, + SWAP: { + desc: "SWAP Gate", + latex: + "$$SWAP = \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ 0 & 0 & 1 & 0 \\\\ 0 & 1 & 0 & 0 \\\\ 0 & 0 & 0 & 1 \\end{pmatrix}$$", + }, + M: { + desc: "Measure", + latex: + "$$\\text{Measure }n^{\\text{th}}\\text{ Qubit}$$" + } +}; + + +// Helper to clamp a value between min and max +const clamp = (value, min, max) => Math.max(min, Math.min(value, max)); + +// ======================= +// SINGLE-QUBIT GATE COMPONENT +// ======================= +const QuantumGate = ({ + x, + y, + text, + draggable, + onDragEnd, + fixedY, + onRightClick, + order, + params = {}, +}) => { + const isRotationGate = params.theta !== undefined; + let thetalength = 0; + if (params.theta != null) { + thetalength = params.theta.toString().length; + } + return ( + ({ + x: clamp(pos.x, canvasMinX, canvasMaxX), + y: fixedY, + }) + : undefined + } + onDragEnd={onDragEnd} + onContextMenu={(e) => { + e.evt.preventDefault(); + onRightClick(); + }} + > + + 1 ? gateSize / 2 - 10 : gateSize / 2 - 5} + y={gateSize / 2 - 10} + /> + {isRotationGate && ( + + )} + {order !== undefined && ( + + )} + + ); +}; + + + + +// ======================= +// CZ GATE COMPONENT +// ======================= +const CZGate = ({ + x, + control, + target, + onDragStart, + onDragEnd, + onRightClick, + order, +}) => { + const yControl = (control + 1) * qubitSpacing; + const yTarget = (target + 1) * qubitSpacing; + return ( + ({ + x: clamp(pos.x, canvasMinX, canvasMaxX), + y: 0, + })} + onDragStart={onDragStart} + onDragEnd={onDragEnd} + onContextMenu={(e) => { + e.evt.preventDefault(); + onRightClick(); + }} + > + + + + + {order !== undefined && ( + + )} + + ); +}; + +// ======================= +// SWAP GATE COMPONENT +// ======================= +const SWAPGate = ({ + x, + qubit1, + qubit2, + onDragStart, + onDragEnd, + onRightClick, + order, +}) => { + const y1 = (qubit1 + 1) * qubitSpacing; + const y2 = (qubit2 + 1) * qubitSpacing; + const topY = Math.min(y1, y2); + const bottomY = Math.max(y1, y2); + return ( + ({ + x: clamp(pos.x, canvasMinX, canvasMaxX), + y: 0, + })} + onDragStart={onDragStart} + onDragEnd={onDragEnd} + onContextMenu={(e) => { + e.evt.preventDefault(); + onRightClick(); + }} + > + + {/* 'X' cross on qubit1 */} + + + {/* 'X' cross on qubit2 */} + + + {order !== undefined && ( + + )} + + ); +}; + +// ======================= +// Measure Nth COMPONENT +// ======================= +const MeasureNthComponent = ({ + x, + y, + draggable, + onDragEnd, + fixedY, + onRightClick, + order +}) => { + return ( + ({ + x: clamp(pos.x, canvasMinX, canvasMaxX), + y: fixedY, + }) + : undefined + } + onDragEnd={onDragEnd} + onContextMenu={(e) => { + e.evt.preventDefault(); + onRightClick(); + }} + > + {/* Rectangle Border */} + + + {/* Semicircle */} + { + context.beginPath(); + context.arc(gateSize / 2, gateSize / 2 + 5, gateSize / 2.5, Math.PI, 0, false); + context.strokeShape(shape); + }} + stroke="red" + strokeWidth={2} + /> + + {/* Diagonal Line */} + + {/* Small Circle */} + + {order !== undefined && ( + + )} + + ); +}; + +// ======================= +// MAIN QUANTUM CIRCUIT COMPONENT +// ======================= +const QuantumCircuit = ({ numQubits, setNumQubits }) => { + // Result or Log Data from the Backend + const [resultData, setResultData] = useState(null); + // Probs Data for BarGraph + const [probData, setProbData] = useState([]); + // Result Graph Edges + const [edgesResultGraph, setEdgesResultGraph] = useState(null); + // Result Graph Vertices + const [verticesResultGraph, setVerticesResultGraph] = useState(null); + // Result Measurement + const [measuredValue, setMeasuredValue] = useState(NaN); + // Measurement History + const [measurementHist, setMeasurementHist] = useState([]); + // Single-qubit gates + const [gates, setGates] = useState([]); // { x, y, text, params? } + // CNOT gates + const [cnotGates, setCnotGates] = useState([]); // { x, control, target } + // CZ gates + const [czGates, setCzGates] = useState([]); // { x, control, target } + // SWAP gates + const [swapGates, setSwapGates] = useState([]); // { x, qubit1, qubit2 } + // Measure Nth Qubit + const [measureNthQubit, setMeasureNthQubit] = useState([]); // {x, y} + // Error to show when delete Qubit is clicked and numQubits goes less than 1 + const [delQubError, setDelQubError] = useState(false); + + // For rotation/phase gates + const [rotationModalOpen, setRotationModalOpen] = useState(false); + const [rotationValue, setRotationValue] = useState(45); + const [rotationX, setRotationX] = useState(0); + const [rotationY, setRotationY] = useState(0); + const [rotationType, setRotationType] = useState("P"); + + // For CNOT selection + const [cnotModalOpen, setCnotModalOpen] = useState(false); + const [cnotX, setCnotX] = useState(0); + const [cnotControl, setCnotControl] = useState(0); + const [cnotTarget, setCnotTarget] = useState(1); + + // For CZ selection + const [czModalOpen, setCzModalOpen] = useState(false); + const [czX, setCzX] = useState(0); + const [czControl, setCzControl] = useState(0); + const [czTarget, setCzTarget] = useState(1); + + // For SWAP selection + const [swapModalOpen, setSwapModalOpen] = useState(false); + const [swapX, setSwapX] = useState(0); + const [swapQubit1, setSwapQubit1] = useState(0); + const [swapQubit2, setSwapQubit2] = useState(1); + + // Tooltip state + const [tooltip, setTooltip] = useState({ + visible: false, + x: 0, + y: 0, + desc: "", + latex: "", + }); + const [isDragging, setIsDragging] = useState(false); + + // Tabs state ("Circuit" or "Result") + const [activeTab, setActiveTab] = useState("Circuit"); + + const stageRef = useRef(null); + + const [circuit, setCircuit] = useState([]); + + const createGate = (type, id, order) => { + switch (type) { + case "Rx": + case "Ry": + case "Rz": + return { id, type, order, params: { theta: 0 } }; + case "U3": + return { id, type, order, params: { theta: 0, phi: 0, lambda: 0 } }; + default: + return { id, type, order }; + } + }; + +//only for testing + const addTestGates = () => { + setCircuit([ + createGate("Rx", "rx1", 1), + createGate("Ry", "ry1", 2), + createGate("Rz", "rz1", 3), + createGate("U3", "u31", 4), + ]); + }; + + + + const handleParamChange = (gateId, paramName, value) => { + let radianValue = value; + + try { + // Convert user input into radians + radianValue = evaluate(value, { pi: Math.PI }); + } catch (err) { + console.error("Invalid angle expression:", value); + alert("Invalid angle expression! Please enter something like pi/2 or 1.57"); + return; + } + + // Store radians + setCircuit((prev) => + prev.map((g) => + g.id === gateId + ? { ...g, params: { ...g.params, [paramName]: evaluate(value) } } + : g + ) + ); + } + + // ======================= + // ORDERING: Combined ordering for all gates on a qubit line + // ======================= + const combinedGroups = {}; + // Single-qubit + gates.forEach((g, i) => { + const qid = Math.round((g.y + gateSize / 2) / qubitSpacing) - 1; + if (!combinedGroups[qid]) combinedGroups[qid] = []; + combinedGroups[qid].push({ type: "single", index: i, x: g.x }); + }); + // CNOT + cnotGates.forEach((g, i) => { + const qid = g.control; + if (!combinedGroups[qid]) combinedGroups[qid] = []; + combinedGroups[qid].push({ type: "cnot", index: i, x: g.x }); + }); + // CZ + czGates.forEach((g, i) => { + const qid = g.control; + if (!combinedGroups[qid]) combinedGroups[qid] = []; + combinedGroups[qid].push({ type: "cz", index: i, x: g.x }); + }); + // SWAP + swapGates.forEach((g, i) => { + const qid = Math.min(g.qubit1, g.qubit2); + if (!combinedGroups[qid]) combinedGroups[qid] = []; + combinedGroups[qid].push({ type: "swap", index: i, x: g.x }); + }); + // Measure Nth Qubit + measureNthQubit.forEach((g, i) => { + const qid = Math.round((g.y + gateSize / 2) / qubitSpacing) - 1; + if (!combinedGroups[qid]) combinedGroups[qid] = []; + combinedGroups[qid].push({ type: "measure", index: i, x: g.x }); + }); + + const combinedOrders = { + single: {}, + cnot: {}, + cz: {}, + swap: {}, + measure: {} + }; + Object.keys(combinedGroups).forEach((qid) => { + combinedGroups[qid].sort((a, b) => a.x - b.x); + combinedGroups[qid].forEach((item, orderIndex) => { + if (item.type === "single") { + combinedOrders.single[item.index] = orderIndex + 1; + } else if (item.type === "cnot") { + combinedOrders.cnot[item.index] = orderIndex + 1; + } else if (item.type === "cz") { + combinedOrders.cz[item.index] = orderIndex + 1; + } else if (item.type === "swap") { + combinedOrders.swap[item.index] = orderIndex + 1; + } else if (item.type === "measure") { + combinedOrders.measure[item.index] = orderIndex + 1; + } + }); + }); + + // ======================= + // QUBIT LINE COMPONENT + // ======================= + const QubitLine = ({ y, label }) => ( + + + + + ); + + // ======================= + // Helper: snap single-qubit gates to a line + // ======================= + const snapY = (pointerY) => { + const desiredCenter = Math.round(pointerY / qubitSpacing) * qubitSpacing; + const clampedCenter = Math.max( + qubitSpacing, + Math.min(desiredCenter, numQubits * qubitSpacing) + ); + return clampedCenter - gateSize / 2; + }; + + // ======================= + // DRAG & DROP LOGIC + // ======================= + + useEffect(() => { + if (!stageRef.current) return; + const container = stageRef.current.container(); + + const handleDrop = (e) => { + e.preventDefault(); + const rect = container.getBoundingClientRect(); + const pointerX = e.clientX - rect.left; + const pointerY = e.clientY - rect.top; + const gateType = e.dataTransfer.getData("text/plain"); + if (!gateType) return; + + if (gateType === "CNOT") { + setCnotModalOpen(true); + setCnotX(pointerX); + } else if (gateType === "CZ") { + setCzModalOpen(true); + setCzX(pointerX); + } else if (gateType === "SWAP") { + setSwapModalOpen(true); + setSwapX(pointerX); + } else if (["P", "Rx", "Ry", "Rz"].includes(gateType)) { + const snappedY = snapY(pointerY); + setRotationModalOpen(true); + setRotationX(pointerX); + setRotationY(snappedY); + setRotationType(gateType); + setRotationValue(45); // default value + } else if (gateType === "M") { + const snappedY = snapY(pointerY); + setMeasureNthQubit((prev) => [ + ...prev, + { x: pointerX, y: snappedY, }, + ]); + } else { + // Single-qubit gate + const snappedY = snapY(pointerY); + setGates((prev) => [ + ...prev, + { x: pointerX, y: snappedY, text: gateType }, + ]); + } + }; + + const handleDragOver = (e) => e.preventDefault(); + + container.addEventListener("drop", handleDrop); + container.addEventListener("dragover", handleDragOver); + + return () => { + container.removeEventListener("drop", handleDrop); + container.removeEventListener("dragover", handleDragOver); + }; + }, [stageRef.current, numQubits]); + + // ======================= + // DRAG END HANDLERS + // ======================= + const handleGateDragEnd = (e, index) => { + const { x } = e.target.position(); + setGates((prev) => { + const newGates = [...prev]; + newGates[index] = { + ...newGates[index], + x: clamp(x, canvasMinX, canvasMaxX), + }; + return newGates; + }); + setIsDragging(false); + }; + + const handleMeasureDragEnd = (e, index) => { + const { x } = e.target.position(); + setMeasureNthQubit((prev) => { + const newArr = [...prev]; + newArr[index] = { + ...newArr[index], + x: clamp(x, canvasMinX, canvasMaxX), + }; + return newArr; + }); + setIsDragging(false); + } + + const handleCnotDragEnd = (e, index) => { + const { x } = e.target.position(); + setCnotGates((prev) => { + const newArr = [...prev]; + newArr[index].x = clamp(x, canvasMinX, canvasMaxX); + return newArr; + }); + setIsDragging(false); + }; + + const handleCzDragEnd = (e, index) => { + const { x } = e.target.position(); + setCzGates((prev) => { + const newArr = [...prev]; + newArr[index].x = clamp(x, canvasMinX, canvasMaxX); + return newArr; + }); + setIsDragging(false); + }; + + const handleSwapDragEnd = (e, index) => { + const { x } = e.target.position(); + setSwapGates((prev) => { + const newArr = [...prev]; + newArr[index].x = clamp(x, canvasMinX, canvasMaxX); + return newArr; + }); + setIsDragging(false); + }; + + // ======================= + // DELETE HANDLERS (Right-click) + // ======================= + const handleDeleteGate = (index) => { + setGates((prev) => prev.filter((_, i) => i !== index)); + }; + const handleDeleteCnot = (index) => { + setCnotGates((prev) => prev.filter((_, i) => i !== index)); + }; + const handleDeleteCz = (index) => { + setCzGates((prev) => prev.filter((_, i) => i !== index)); + }; + const handleDeleteSwap = (index) => { + setSwapGates((prev) => prev.filter((_, i) => i !== index)); + }; + const handleDeleteMeasure = (index) => { + setMeasureNthQubit((prev) => prev.filter((_, i) => i !== index)); + } + + // ======================= + // TOOLTIP HANDLERS + // ======================= + const handleTooltipEnter = (e, gate) => { + if (isDragging) return; + const rect = e.target.getBoundingClientRect(); + setTooltip({ + visible: true, + x: rect.left + 120, + y: rect.bottom - 100, + desc: gateTooltips[gate].desc, + latex: gateTooltips[gate].latex, + }); + }; + + const handleTooltipLeave = () => { + setTooltip({ visible: false, x: 0, y: 0, desc: "", latex: "" }); + }; + + const handleDragStart = () => { + setIsDragging(true); + setTooltip((prev) => ({ ...prev, visible: false })); + }; + + // ======================= + // ROTATION/PHASE GATE MODAL HANDLERS + // ======================= + const validateRotationValue = (value) => (value === 0 ? 1 : value); + const handleRotationConfirm = () => { + const validTheta = validateRotationValue(rotationValue); + setGates((prev) => [ + ...prev, + { + x: rotationX, + y: rotationY, + text: rotationType, + params: { theta: validTheta }, + }, + ]); + setRotationModalOpen(false); + }; + const handleRotationCancel = () => { + setRotationModalOpen(false); + }; + const handleRotationInputChange = (e) => { + const val = parseFloat(e.target.value) || 1; + setRotationValue(val === 0 ? 1 : val); + }; + const getRotationModalContent = () => { + switch (rotationType) { + case "Rx": + return { + title: "Rotation around X-axis (θ)", + latex: + "$$R_x(\\theta) = \\begin{pmatrix} \\cos(\\theta/2) & -i\\sin(\\theta/2) \\\\ -i\\sin(\\theta/2) & \\cos(\\theta/2) \\end{pmatrix}$$", + }; + case "Ry": + return { + title: "Rotation around Y-axis (θ)", + latex: + "$$R_y(\\theta) = \\begin{pmatrix} \\cos(\\theta/2) & -\\sin(\\theta/2) \\\\ \\sin(\\theta/2) & \\cos(\\theta/2) \\end{pmatrix}$$", + }; + case "Rz": + return { + title: "Rotation around Z-axis (θ)", + latex: + "$$R_z(\\theta) = \\begin{pmatrix} e^{-i\\theta/2} & 0 \\\\ 0 & e^{i\\theta/2} \\end{pmatrix}$$", + }; + default: + return { + title: "Phase Angle (θ)", + latex: + "$$P(\\theta) = \\begin{pmatrix} 1 & 0 \\\\ 0 & e^{i\\theta} \\end{pmatrix}$$", + }; + } + }; + + // ======================= + // CNOT MODAL HANDLERS + // ======================= + const handleCnotConfirm = () => { + if (cnotControl === cnotTarget) { + alert("Control and target qubits must be different!"); + return; + } + setCnotGates((prev) => [ + ...prev, + { x: cnotX, control: cnotControl, target: cnotTarget }, + ]); + setCnotModalOpen(false); + }; + const handleCnotCancel = () => { + setCnotModalOpen(false); + }; + + // ======================= + // CZ MODAL HANDLERS + // ======================= + const handleCzConfirm = () => { + if (czControl === czTarget) { + alert("Control and target qubits must be different!"); + return; + } + setCzGates((prev) => [ + ...prev, + { x: czX, control: czControl, target: czTarget }, + ]); + setCzModalOpen(false); + }; + const handleCzCancel = () => { + setCzModalOpen(false); + }; + + // ======================= + // SWAP MODAL HANDLERS + // ======================= + const handleSwapConfirm = () => { + if (swapQubit1 === swapQubit2) { + alert("SWAP requires two distinct qubits!"); + return; + } + setSwapGates((prev) => [ + ...prev, + { x: swapX, qubit1: swapQubit1, qubit2: swapQubit2 }, + ]); + setSwapModalOpen(false); + }; + const handleSwapCancel = () => { + setSwapModalOpen(false); + }; + + // ======================= + // Add or Remove Qubits + // ======================= + const handleAddQubit = () => { + let q = numQubits + 1; + setNumQubits(q); + setMeasurementHist([]); + } + + const handleDeleteQubit = () => { + const newNumQubits = numQubits - 1; + + if (newNumQubits < 1) { + setDelQubError(true); + return; + } + + setGates(prevGates => + prevGates.filter(g => { + const gateQubit = Math.round((g.y + gateSize / 2) / qubitSpacing) - 1; + return gateQubit < newNumQubits; + }) + ); + + setMeasureNthQubit(prevMeasures => + prevMeasures.filter(g => { + const gateQubit = Math.round((g.y + gateSize / 2) / qubitSpacing) - 1; + return gateQubit < newNumQubits; + }) + ); + + setCnotGates(prevCnot => + prevCnot.filter(g => g.control < newNumQubits && g.target < newNumQubits) + ); + setCzGates(prevCz => + prevCz.filter(g => g.control < newNumQubits && g.target < newNumQubits) + ); + + setSwapGates(prevSwap => + prevSwap.filter(g => g.qubit1 < newNumQubits && g.qubit2 < newNumQubits) + ); + + setNumQubits(newNumQubits); + + setMeasurementHist([]); + } +const [Dragging, setDragging] = useState(false); + const [position, setPosition] = useState({ x: 100, y: 100 }); + const [offset, setOffset] = useState({ x: 0, y: 0 }); + const boxRef = useRef(null); + + const startDrag = (e) => { + setDragging(true); + setOffset({ x: e.clientX - position.x, y: e.clientY - position.y }); + }; + + const handleMouseMove = (e) => { + if (!Dragging) return; + setPosition({ x: e.clientX - offset.x, y: e.clientY - offset.y }); + }; + + const handleMouseUp = () => setDragging(false); + + useEffect(() => { + if (Dragging) { + window.addEventListener("mousemove", handleMouseMove); + window.addEventListener("mouseup", handleMouseUp); + } + return () => { + window.removeEventListener("mousemove", handleMouseMove); + window.removeEventListener("mouseup", handleMouseUp); + }; + }, [Dragging, offset]); + + return ( + +
+ {/* Tab Buttons */} +
+ {["Circuit", "Result", "Measurement", "Probability", "Log"].map((tab) => ( + + ))} +
+ + {/* Left Menu: single box with gates on top and 3 buttons below */} + +
+
+

Quantum Gates

+
+ {/* Gates box */} +
+ {gatesList.map((gate, index) => { + // Hide multi-qubit gates if only 1 qubit + if (numQubits < 2 && ["CNOT", "CZ", "SWAP"].includes(gate)) { + return null; + } + + const isActive = activeTab === "Circuit"; + const defaultBackground = isActive ? "white" : "#eee"; + + return ( + <> + + {circuit.map((gate) => ( + console.log("Right click")} + onDragEnd={() => console.log("Drag end")} + /> + ))} + + {/* ====== Parameters ====== */} +
+ + {circuit.map((gate) => { + if (!gate.params) return null; + + return ( +
+

{gate.type} Gate

+ + {/* input */} + {"theta" in gate.params && ( + + )} + + {/* input (for U3 gate) */} + {"phi" in gate.params && ( + + )} + + {/* lambda input (for U3 gate) */} + {"lambda" in gate.params && ( + + )} +
+ ); + })} +
+
{ + if (isActive) { + e.dataTransfer.setData("text/plain", gate); + } + }} + onMouseEnter={(e) => { + if (isActive) handleTooltipEnter(e, gate); + }} + onMouseMove={(e) => { + if (isActive) { + e.currentTarget.style.background = "oklch(80.9% 0.105 251.813)"; + } + }} + onMouseLeave={(e) => { + e.currentTarget.style.background = defaultBackground; + if (isActive) handleTooltipLeave(e); + }} + > + {gate} +
+ + ); + })} +
+ + {/* Buttons under gates */} + +
+ + {/* Right Content: depends on active tab */} +
+ {/* TOOLTIP */} + {!isDragging && tooltip.visible && ( +
+
+ {tooltip.desc} +
+ + {tooltip.latex} + +
+ )} + {activeTab === "Circuit" ? ( + <> + {/* STAGE: QUBIT LINES & GATES */} + + + {Array.from({ length: numQubits }).map((_, i) => ( + + ))} + {/* Render single-qubit gates */} + {gates.map((g, i) => ( + handleGateDragEnd(e, i)} + onRightClick={() => handleDeleteGate(i)} + order={combinedOrders.single[i]} + /> + ))} + {/* Render CNOT gates */} + {cnotGates.map((g, i) => ( + handleCnotDragEnd(e, i)} + onRightClick={() => handleDeleteCnot(i)} + order={combinedOrders.cnot[i]} + /> + ))} + {/* Render CZ gates */} + {czGates.map((g, i) => ( + handleCzDragEnd(e, i)} + onRightClick={() => handleDeleteCz(i)} + order={combinedOrders.cz[i]} + /> + ))} + {/* Render SWAP gates */} + {swapGates.map((g, i) => ( + handleSwapDragEnd(e, i)} + onRightClick={() => handleDeleteSwap(i)} + order={combinedOrders.swap[i]} + /> + ))} + {/* Render Measure Nth Component */} + {measureNthQubit.map((g, i) => ( + handleMeasureDragEnd(e, i)} + onRightClick={() => handleDeleteMeasure(i)} + onDragStart={handleDragStart} + order={combinedOrders.measure[i]} + /> + ))} + + + + ) : activeTab === "Result" ? ( + + ) : activeTab === "Measurement" ? ( + + ) : activeTab === "Probability" ? ( + // an interactive graph + + ) : activeTab === "Log" ? ( + + ) : (null)} +
+
+ + {/* Error Modal */} + {delQubError && ( +
{ setDelQubError(false); }} + style={{ + position: "fixed", + top: 0, + left: 0, + width: "100vw", + height: "100vh", + background: "rgba(0,0,0,0.5)", + display: "flex", + alignItems: "center", + justifyContent: "center", + userSelect: "none" + }} + > +
e.stopPropagation()} + style={{ + background: "white", + padding: "20px", + borderRadius: "10px", + textAlign: "center", + minWidth: "300px", + }} + > +

+ Number of Qubits must be greater than or equal to 1. +

+
+ {"$$\\text{numQubits}\\geq1$$"} +
+ +
+
+ ) + } + + {/* ROTATION/PHASE GATE MODAL */} + { + rotationModalOpen && ( +
+
e.stopPropagation()} + style={{ + background: "white", + padding: "20px", + borderRadius: "10px", + textAlign: "center", + minWidth: "300px", + }} + > +

{getRotationModalContent().title}

+
+ {getRotationModalContent().latex} +
+
+ + +
+
+ +
+ Note: Angle must be between 1° and 360° +
+
+ + +
+
+ ) + } + + {/* CNOT MODAL */} + { + cnotModalOpen && ( +
+
e.stopPropagation()} + style={{ + background: "white", + padding: "20px", + borderRadius: "10px", + textAlign: "center", + minWidth: "300px", + }} + > +

Select Control and Target Qubit

+
+ + +
+
+ + +
+ + +
+
+ ) + } + + {/* CZ MODAL */} + { + czModalOpen && ( +
+
e.stopPropagation()} + style={{ + background: "white", + padding: "20px", + borderRadius: "10px", + textAlign: "center", + minWidth: "300px", + }} + > +

Select Control and Target Qubit (CZ)

+
+ + +
+
+ + +
+ + +
+
+ ) + } + + {/* SWAP MODAL */} + { + swapModalOpen && ( +
+
e.stopPropagation()} + style={{ + background: "white", + padding: "20px", + borderRadius: "10px", + textAlign: "center", + minWidth: "300px", + }} + > +

Select Qubits to SWAP

+
+ + +
+
+ + +
+ + +
+
+ ) + } +
+ ); +}; + +export default QuantumCircuit; diff --git a/qubitverse/visualizer/src/components/SendToBackEnd.jsx b/qubitverse/visualizer/src/components/SendToBackEnd.jsx index 97e1b03..8721c40 100644 --- a/qubitverse/visualizer/src/components/SendToBackEnd.jsx +++ b/qubitverse/visualizer/src/components/SendToBackEnd.jsx @@ -1,183 +1,196 @@ -import { Button } from "@/components/ui/button"; -import ParseResultData from "./ParseResultData"; - -// This function extracts circuit data from the gates state -function extractCircuitData(gates, cnotGates, czGates, swapGates, measureNthQ, nQ) { - // Process single-qubit gates - const processedGates = gates.map((gate) => { - // Calculate which qubit this gate is on based on y position - const qubitIndex = Math.round((gate.y + 20) / 50) - 1; // 20 is half of gateSize, 50 is qubitSpacing - return { - type: "single", - gateType: (gate.text === "V†" ? "adjV" : gate.text), // X, Y, H, etc. - qubit: qubitIndex, - theta: Object.hasOwn(gate, "params") === false ? -1 : gate.params.theta, - position: gate.x, - }; - }); - - // Process CNOT gates - const processedCnotGates = cnotGates.map((gate) => { - return { - type: "cnot", - control: gate.control, - target: gate.target, - position: gate.x, - }; - }); - - // Process CZ gates - const processedCZGates = czGates.map((gate) => { - return { - type: "cz", - control: gate.control, - target: gate.target, - position: gate.x, - }; - }); - - // Process SWAP gates - const processedSwapGates = swapGates.map((gate) => { - return { - type: "swap", - qubitA: gate.qubit1, - qubitB: gate.qubit2, - position: gate.x, - }; - }); - - // Process Measurement - const measureNthQubits = measureNthQ.map((gate) => { - const qubitIndex = Math.round((gate.y + 20) / 50) - 1; // 20 is half of gateSize, 50 is qubitSpacing - return { - type: "measurenth", - qubit: qubitIndex, - position: gate.x, - } - }); - - // Combine all gates and sort by position (x coordinate) - const allGates = [...processedGates, ...processedCnotGates, ...processedCZGates, ...processedSwapGates, ...measureNthQubits].sort( - (a, b) => a.position - b.position - ); - - // Create the final circuit data object - return { - numQubits: nQ, - gates: allGates, - }; -} - -function quantum_encode(cktData, feature) { - let s = feature + "n:" + cktData.numQubits + "\n"; - for (let i = 0; i < cktData.gates.length; i++) { - for (const key in cktData.gates[i]) { - if (Object.hasOwn(cktData.gates[i], key)) { - s += key + ":" + cktData.gates[i][key] + "\n"; - } - } - s += "@\n"; - } - return s; -} - -export function SendToBackEnd_Calculate({ gates, cnotGates, czGates, swapGates, measureNthQ, numQubits, setLog, setProbData, setEdgesResultGraph, setVerticesResultGraph, setMeasuredValue, setMeasurementHist, funcAddQubits, funcRemoveQubits }) { - const request_backend = async (dat) => { - try { - const response = await fetch('http://localhost:9080/api/endpoint', { - method: 'POST', - headers: { - 'Content-Type': 'text/plain', - }, - body: dat - }); - return await response.text(); - } catch (error) { - console.error('Error:', error); - return null; - } - }; - - const sendCalculate = () => { - request_backend( - quantum_encode(extractCircuitData(gates, cnotGates, czGates, swapGates, measureNthQ, numQubits), "0") - ).then(responseText => { setLog(responseText); ParseResultData({ data: responseText, setProbData, setEdgesResultGraph, setVerticesResultGraph, setMeasuredValue, setMeasurementHist }) }); - - - }; - - const sendProbability = () => { - request_backend( - quantum_encode(extractCircuitData(gates, cnotGates, czGates, swapGates, measureNthQ, numQubits), "1") - ).then(responseText => { setLog(responseText); ParseResultData({ data: responseText, setProbData, setEdgesResultGraph, setVerticesResultGraph, setMeasuredValue, setMeasurementHist }) }); - }; - - const sendMeasure = () => { - request_backend( - quantum_encode(extractCircuitData(gates, cnotGates, czGates, swapGates, measureNthQ, numQubits), "2") - ).then(responseText => { setLog(responseText); ParseResultData({ data: responseText, setProbData, setEdgesResultGraph, setVerticesResultGraph, setMeasuredValue, setMeasurementHist }) }); - }; - - return ( -
- - - - - -
- ); -}; - +import { Button } from "@/components/ui/button"; +import ParseResultData from "./ParseResultData"; + +// This function extracts circuit data from the gates state +function extractCircuitData(gates, cnotGates, czGates, swapGates, measureNthQ, nQ) { + // Process single-qubit gates + const processedGates = gates.map((gate) => { + // Calculate which qubit this gate is on based on y position + const qubitIndex = Math.round((gate.y + 20) / 50) - 1; // 20 is half of gateSize, 50 is qubitSpacing + return { + type: "single", + gateType: (gate.text === "V†" ? "adjV" : gate.text), // X, Y, H, etc. + qubit: qubitIndex, + params: { + theta: gate.params.theta ?? 0, + phi: gate.params.phi ?? 0, + lambda: gate.params.lambda ?? 0, + }, + position: gate.x, + }; + }); + + // Process CNOT gates + const processedCnotGates = cnotGates.map((gate) => { + return { + type: "cnot", + control: gate.control, + target: gate.target, + position: gate.x, + }; + }); + + // Process CZ gates + const processedCZGates = czGates.map((gate) => { + return { + type: "cz", + control: gate.control, + target: gate.target, + position: gate.x, + }; + }); + + // Process SWAP gates + const processedSwapGates = swapGates.map((gate) => { + return { + type: "swap", + qubitA: gate.qubit1, + qubitB: gate.qubit2, + position: gate.x, + }; + }); + + // Process Measurement + const measureNthQubits = measureNthQ.map((gate) => { + const qubitIndex = Math.round((gate.y + 20) / 50) - 1; // 20 is half of gateSize, 50 is qubitSpacing + return { + type: "measurenth", + qubit: qubitIndex, + position: gate.x, + } + }); + + // Combine all gates and sort by position (x coordinate) + const allGates = [...processedGates, ...processedCnotGates, ...processedCZGates, ...processedSwapGates, ...measureNthQubits].sort( + (a, b) => a.position - b.position + ); + + // Create the final circuit data object + return { + numQubits: nQ, + gates: allGates, + }; +} + +function quantum_encode(cktData, feature) { + let s = ""; + for (let i = 0; i < cktData.gates.length; i++) { + const gate = cktData.gates[i]; + for (const key in gate) { + if (Object.hasOwn(gate, key)) { + if (key === "params") { + for (const p in gate.params) { + if (Object.hasOwn(gate.params, p)) { + s += p + ":" + gate.params[p] + "\n"; + } + } + } else { + s += key + ":" + gate[key] + "\n"; + } + } + } + s += "@\n"; + } + return s; +} + +export function SendToBackEnd_Calculate({ gates, cnotGates, czGates, swapGates, measureNthQ, numQubits, setLog, setProbData, setEdgesResultGraph, setVerticesResultGraph, setMeasuredValue, setMeasurementHist, funcAddQubits, funcRemoveQubits }) { + const request_backend = async (dat) => { + try { + const response = await fetch('http://localhost:9080/api/endpoint', { + method: 'POST', + headers: { + 'Content-Type': 'text/plain', + }, + body: dat + }); + return await response.text(); + } catch (error) { + console.error('Error:', error); + return null; + } + }; + + const sendCalculate = () => { + request_backend( + quantum_encode(extractCircuitData(gates, cnotGates, czGates, swapGates, measureNthQ, numQubits), "0") + ).then(responseText => { setLog(responseText); ParseResultData({ data: responseText, setProbData, setEdgesResultGraph, setVerticesResultGraph, setMeasuredValue, setMeasurementHist }) }); + + + }; + + const sendProbability = () => { + request_backend( + quantum_encode(extractCircuitData(gates, cnotGates, czGates, swapGates, measureNthQ, numQubits), "1") + ).then(responseText => { setLog(responseText); ParseResultData({ data: responseText, setProbData, setEdgesResultGraph, setVerticesResultGraph, setMeasuredValue, setMeasurementHist }) }); + }; + + const sendMeasure = () => { + request_backend( + quantum_encode(extractCircuitData(gates, cnotGates, czGates, swapGates, measureNthQ, numQubits), "2") + ).then(responseText => { setLog(responseText); ParseResultData({ data: responseText, setProbData, setEdgesResultGraph, setVerticesResultGraph, setMeasuredValue, setMeasurementHist }) }); + }; + + return ( +
+ + + + + +
+ ); +}; + export default SendToBackEnd_Calculate; \ No newline at end of file