From 1b4d9c7f8105b85f7ae52b61055c13fdc0953b08 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Thu, 5 Feb 2026 15:36:38 +0200 Subject: [PATCH 01/31] ignore local test files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index acf32d471168..2301d3ac1733 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ dist node_modules _config_webpack.yml +test_local \ No newline at end of file From 5cd12b640e5215a110ccbf8257df9ac3b0303b86 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Thu, 5 Feb 2026 15:36:47 +0200 Subject: [PATCH 02/31] add aalto q20 entry --- content/_data/constants.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/content/_data/constants.yml b/content/_data/constants.yml index 690c29b90097..2776ed2bb123 100644 --- a/content/_data/constants.yml +++ b/content/_data/constants.yml @@ -262,6 +262,12 @@ cookie_consent: basis: "PRX, CZ" topology: "Square grid" device_id: "Q50" + + - name: Aalto Q20 + qubits: 20 + basis: "PRX, CZ" + topology: "Square grid" + device_id: "Q20" "/cookies/": title: Cookie Policy From 3c0c32a3494edbf05a4dd75536122c3bdf1548f2 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Thu, 5 Feb 2026 15:37:15 +0200 Subject: [PATCH 03/31] healthcheck from localhost for testing --- src/components/ServiceStatus.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ServiceStatus.jsx b/src/components/ServiceStatus.jsx index d21af3be8158..a395c1f49119 100644 --- a/src/components/ServiceStatus.jsx +++ b/src/components/ServiceStatus.jsx @@ -40,7 +40,7 @@ const StatusCard = (props) => { } export const ServiceStatus = (props) => { - const { status: statusList } = useStatus("https://fiqci-backend.2.rahtiapp.fi/devices/healthcheck"); + const { status: statusList } = useStatus("http://127.0.0.1:3000/devices/healthcheck"); const { bookingData: bookingData } = useBookings("https://fiqci-backend.2.rahtiapp.fi/bookings") const qcs = props["quantum-computers"] || []; From 2d0937fef350803cbab5ec9ac2f22e6d07e393d1 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Thu, 5 Feb 2026 15:37:39 +0200 Subject: [PATCH 04/31] use sidebarutils get;etricStatistics --- src/components/QcLayouts/Base.jsx | 46 ++----------------------------- 1 file changed, 3 insertions(+), 43 deletions(-) diff --git a/src/components/QcLayouts/Base.jsx b/src/components/QcLayouts/Base.jsx index 3bac0e6c31f0..77fee14a9a6f 100644 --- a/src/components/QcLayouts/Base.jsx +++ b/src/components/QcLayouts/Base.jsx @@ -2,6 +2,7 @@ import React, { useState, useRef } from 'react'; import { motion } from 'framer-motion'; import { getColorForMetricValue } from '../../utils/generateGradient'; import { formatMetricValue } from '../../utils/formatMetricValue'; +import { getMetricStatistics } from '../../utils/sidebarUtils'; export function BaseQcLayout({ rawNodes, edges, spacing, calibrationData, qubitMetric, couplerMetric, qubitMetricFormatted, couplerMetricFormatted, thresholdQubit, thresholdCoupler }) { @@ -12,47 +13,6 @@ export function BaseQcLayout({ rawNodes, edges, spacing, calibrationData, qubitM const [tooltip, setTooltip] = useState(null); const containerRef = useRef(null); - - // Calculate metric statistics for qubit metrics - const getQubitMetricStats = () => { - if (!calibrationData || !qubitMetric || !calibrationData[qubitMetric]) { - return null; - } - - const values = Object.values(calibrationData[qubitMetric]) - .map(data => data?.value) - .filter(value => value !== null && value !== undefined); - - if (values.length === 0) return null; - - const sortedValues = [...values].sort((a, b) => a - b); - const worst = sortedValues[0]; - const best = sortedValues[sortedValues.length - 1]; - const average = calibrationData[qubitMetric].statistics.median; - - return { worst, best, average }; - }; - - // Calculate metric statistics for coupler metrics - const getCouplerMetricStats = () => { - if (!calibrationData || !couplerMetric || !calibrationData[couplerMetric]) { - return null; - } - - const values = Object.values(calibrationData[couplerMetric]) - .map(data => data?.value) - .filter(value => value !== null && value !== undefined); - - if (values.length === 0) return null; - - const sortedValues = [...values].sort((a, b) => a - b); - const worst = sortedValues[0]; - const best = sortedValues[sortedValues.length - 1]; - const average = calibrationData[couplerMetric].statistics.median; - - return { worst, best, average }; - }; - // Get metric value for a specific qubit const getQubitMetricValue = (qubitId) => { if (!calibrationData || !qubitMetric || !calibrationData[qubitMetric]) { @@ -90,7 +50,7 @@ export function BaseQcLayout({ rawNodes, edges, spacing, calibrationData, qubitM return '#888888'; } - const stats = getQubitMetricStats(); + const stats = getMetricStatistics(calibrationData, qubitMetric, 1); if (!stats) { return '#888888'; } @@ -161,7 +121,7 @@ export function BaseQcLayout({ rawNodes, edges, spacing, calibrationData, qubitM return '#888888'; // Grey if no data found } - const stats = getCouplerMetricStats(); + const stats = getMetricStatistics(calibrationData, couplerMetric, 2); if (!stats) { return '#888888'; } From 2d287147c721e9c91b9c308078818ca4a4f1118a Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Thu, 5 Feb 2026 15:37:49 +0200 Subject: [PATCH 05/31] Q20 layout --- src/components/QcLayouts/Q20.jsx | 181 +++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 src/components/QcLayouts/Q20.jsx diff --git a/src/components/QcLayouts/Q20.jsx b/src/components/QcLayouts/Q20.jsx new file mode 100644 index 000000000000..c3185529fd85 --- /dev/null +++ b/src/components/QcLayouts/Q20.jsx @@ -0,0 +1,181 @@ +import React from 'react'; +import { BaseQcLayout } from './Base'; + +const spacing = 100; +const xOrigin = -210; +const yOrigin = 440; + +const rawNodes = [ + // Row 1 (3 qubits) + { id: 'QB20', x: xOrigin + spacing * 0, y: yOrigin + spacing * 0 }, + { id: 'QB19', x: xOrigin + spacing * -1, y: yOrigin + spacing * -1 }, + { id: 'QB18', x: xOrigin + spacing * -2, y: yOrigin + spacing * -2 }, + + // Row 2 (5 qubits) + { id: 'QB17', x: xOrigin + spacing * 2, y: yOrigin + spacing * 0 }, + { id: 'QB16', x: xOrigin + spacing * 1, y: yOrigin + spacing * -1 }, + { id: 'QB15', x: xOrigin + spacing * 0, y: yOrigin + spacing * -2 }, + { id: 'QB14', x: xOrigin + spacing * -1, y: yOrigin + spacing * -3 }, + { id: 'QB13', x: xOrigin + spacing * -2, y: yOrigin + spacing * -4 }, + + // Row 3 (5 qubits) + { id: 'QB12', x: xOrigin + spacing * 3, y: yOrigin + spacing * -1 }, + { id: 'QB11', x: xOrigin + spacing * 2, y: yOrigin + spacing * -2 }, + { id: 'QB10', x: xOrigin + spacing * 1, y: yOrigin + spacing * -3 }, + { id: 'QB9', x: xOrigin + spacing * 0, y: yOrigin + spacing * -4 }, + { id: 'QB8', x: xOrigin + spacing * -1, y: yOrigin + spacing * -5 }, + + + // Row 4 (5 qubits) + { id: 'QB7', x: xOrigin + spacing * 4, y: yOrigin + spacing * -2 }, + { id: 'QB6', x: xOrigin + spacing * 3, y: yOrigin + spacing * -3 }, + { id: 'QB5', x: xOrigin + spacing * 2, y: yOrigin + spacing * -4 }, + { id: 'QB4', x: xOrigin + spacing * 1, y: yOrigin + spacing * -5 }, + { id: 'QB3', x: xOrigin + spacing * 0, y: yOrigin + spacing * -6 }, + // Row 5 (2 qubits) + { id: 'QB2', x: xOrigin + spacing * 3, y: yOrigin + spacing * -5 }, + { id: 'QB1', x: xOrigin + spacing * 2, y: yOrigin + spacing * -6 }, + + +]; + +// Define edges based on nearest neighbor connectivity in the diamond pattern +const edges = [ + [ + "QB1", + "QB2" + ], + [ + "QB1", + "QB4" + ], + [ + "QB3", + "QB4" + ], + [ + "QB3", + "QB8" + ], + [ + "QB4", + "QB5" + ], + [ + "QB5", + "QB2" + ], + [ + "QB5", + "QB6" + ], + [ + "QB5", + "QB10" + ], + [ + "QB7", + "QB6" + ], + [ + "QB7", + "QB12" + ], + [ + "QB9", + "QB4" + ], + [ + "QB9", + "QB8" + ], + [ + "QB9", + "QB10" + ], + [ + "QB9", + "QB14" + ], + [ + "QB11", + "QB6" + ], + [ + "QB11", + "QB10" + ], + [ + "QB11", + "QB12" + ], + [ + "QB11", + "QB16" + ], + [ + "QB13", + "QB8" + ], + [ + "QB13", + "QB14" + ], + [ + "QB15", + "QB10" + ], + [ + "QB15", + "QB14" + ], + [ + "QB15", + "QB16" + ], + [ + "QB15", + "QB19" + ], + [ + "QB17", + "QB12" + ], + [ + "QB17", + "QB16" + ], + [ + "QB18", + "QB14" + ], + [ + "QB18", + "QB19" + ], + [ + "QB20", + "QB16" + ], + [ + "QB20", + "QB19" + ] +]; +export const Q20Layout = ({ calibrationData, qubitMetric, couplerMetric, + qubitMetricFormatted, couplerMetricFormatted, thresholdQubit, thresholdCoupler }) => { + return ( + + ) +} \ No newline at end of file From 8c918043e48b5e4150f9a3f04e26640b6c2e9744 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Thu, 5 Feb 2026 15:38:08 +0200 Subject: [PATCH 06/31] update function arguments --- src/components/StatusModal/MetricSwitcher.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/StatusModal/MetricSwitcher.jsx b/src/components/StatusModal/MetricSwitcher.jsx index 10b4c5da0be0..20a82db77e15 100644 --- a/src/components/StatusModal/MetricSwitcher.jsx +++ b/src/components/StatusModal/MetricSwitcher.jsx @@ -55,7 +55,7 @@ export const MetricSwitcher = (props) => {
{(metricsState.qubitMetric) && (() => { - const qubitStats = metricsState.qubitMetric ? getMetricStatistics(calibrationData, metricsState.qubitMetric) : null; + const qubitStats = metricsState.qubitMetric ? getMetricStatistics(calibrationData, metricsState.qubitMetric, 1) : null; return (
@@ -197,7 +197,7 @@ export const MetricSwitcher = (props) => {
{(metricsState.couplerMetric) && (() => { - const couplerStats = metricsState.couplerMetric ? getMetricStatistics(calibrationData, metricsState.couplerMetric) : null; + const couplerStats = metricsState.couplerMetric ? getMetricStatistics(calibrationData, metricsState.couplerMetric, 2) : null; return (
From 4b9855a70475e4387467633b56dc76adce5ce38a Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Thu, 5 Feb 2026 15:38:51 +0200 Subject: [PATCH 07/31] add argument to specify number of qubits in metric. This is because qx has merged both entries of clifford fidelity under the same key --- src/components/StatusModal/SideBar.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/StatusModal/SideBar.jsx b/src/components/StatusModal/SideBar.jsx index 6a04ac102e4b..8daa3a42af31 100644 --- a/src/components/StatusModal/SideBar.jsx +++ b/src/components/StatusModal/SideBar.jsx @@ -29,7 +29,7 @@ export const SideBar = (props) => { // Calculate threshold values when dependencies change useEffect(() => { if (metricsState.qubitMetric && calibrationData) { - const qubitStats = getMetricStatistics(calibrationData, metricsState.qubitMetric); + const qubitStats = getMetricStatistics(calibrationData, metricsState.qubitMetric, 1); if (qubitStats) { const isLowerBetter = metricsState.qubitMetric.includes("error"); const worst = isLowerBetter ? qubitStats.best : qubitStats.worst; @@ -49,7 +49,7 @@ export const SideBar = (props) => { useEffect(() => { if (metricsState.couplerMetric && calibrationData) { - const couplerStats = getMetricStatistics(calibrationData, metricsState.couplerMetric); + const couplerStats = getMetricStatistics(calibrationData, metricsState.couplerMetric, 2); if (couplerStats) { const isLowerBetter = metricsState.couplerMetric.includes("error"); const worst = isLowerBetter ? couplerStats.best : couplerStats.worst; From 69f55f0d9864dd9517743ad5d99d1dc81602b68a Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Thu, 5 Feb 2026 15:39:21 +0200 Subject: [PATCH 08/31] add q20, fix metrics, change to localhost for testing --- .../StatusModal/StatusModalConent.jsx | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/components/StatusModal/StatusModalConent.jsx b/src/components/StatusModal/StatusModalConent.jsx index 18f9dd192ebf..8de1d91d0d4a 100644 --- a/src/components/StatusModal/StatusModalConent.jsx +++ b/src/components/StatusModal/StatusModalConent.jsx @@ -4,6 +4,7 @@ import { useCalibration } from '../../hooks/useCalibration'; import { useDeviceInfo } from '../../hooks/useDeviceInfo'; import { HelmiLayout } from '../QcLayouts/Helmi'; import { Q50Layout } from '../QcLayouts/Q50'; +import { Q20Layout } from '../QcLayouts/Q20'; import { Overview } from './StatusOverview'; import { CalibrationTable } from './CalibrationTable'; import { SideBar } from './SideBar'; @@ -16,8 +17,8 @@ import { object } from 'framer-motion/client'; export const ModalContent = (props) => { - const { calibrationData: calibrationDataAll, calibrationError } = useCalibration(`https://fiqci-backend.2.rahtiapp.fi/device/${props.device_id.toLowerCase()}/calibration`) - const { deviceInfo: deviceInfoData, infoError } = useDeviceInfo(`https://fiqci-backend.2.rahtiapp.fi/device/${props.device_id.toLowerCase()}`) + const { calibrationData: calibrationDataAll, calibrationError } = useCalibration(`http://127.0.0.1:3000/device/${props.device_id.toLowerCase()}/calibration`) + const { deviceInfo: deviceInfoData, infoError } = useDeviceInfo(`http://127.0.0.1:3000/device/${props.device_id.toLowerCase()}`) const [activeTab, setActiveTab] = useState('overview'); @@ -58,6 +59,7 @@ export const ModalContent = (props) => { //gate fidelity { name: 'PRX Gate Fidelity', value: 'prx_rb_fidelity' }, + { name: 'Clifford Gate Fidelity', value: 'clifford_rb_fidelity' }, //Q50 readout metrics //Helmi: does not exist @@ -73,10 +75,10 @@ export const ModalContent = (props) => { //Q50: destructiveness of measurement metrics //Helmi: does not exist - { name: 'QNDness Fidelity', value: 'measure_qndness_fidelity', title: 'QND = Quantum Non-Demolition'}, - { name: 'QNDness 0 State', value: 'measure_qndness_qndness_0', title: 'QND = Quantum Non-Demolition'}, - { name: 'QNDness 1 State', value: 'measure_qndness_qndness_1', title: 'QND = Quantum Non-Demolition'}, - { name: 'QNDness Repeatability', value: 'measure_qndness_repeatability', title: 'QND = Quantum Non-Demolition'}, + { name: 'QNDness Fidelity', value: 'measure_qndness_fidelity', title: 'QND = Quantum Non-Demolition' }, + { name: 'QNDness 0 State', value: 'measure_qndness_qndness_0', title: 'QND = Quantum Non-Demolition' }, + { name: 'QNDness 1 State', value: 'measure_qndness_qndness_1', title: 'QND = Quantum Non-Demolition' }, + { name: 'QNDness Repeatability', value: 'measure_qndness_repeatability', title: 'QND = Quantum Non-Demolition' }, ] @@ -89,6 +91,7 @@ export const ModalContent = (props) => { const couplerMetricOptions = [ { name: 'CZ Gate Fidelity', value: 'cz_irb_fidelity' }, { name: 'Clifford Gate Fidelity', value: 'clifford_rb_fidelity' }, + ] return ( @@ -144,6 +147,16 @@ export const ModalContent = (props) => { thresholdQubit={metricsState.thresholdQubitValue} thresholdCoupler={metricsState.thresholdCouplerValue} /> + ) : props.device_id.toLowerCase() === 'q20' ? ( + m.value === metricsState.qubitMetric)?.name || metricsState.qubitMetric} + couplerMetricFormatted={couplerMetricOptions.find(m => m.value === metricsState.couplerMetric)?.name || metricsState.couplerMetric} + thresholdQubit={metricsState.thresholdQubitValue} + thresholdCoupler={metricsState.thresholdCouplerValue} + /> ) : ( Date: Thu, 5 Feb 2026 15:39:31 +0200 Subject: [PATCH 09/31] fix to work nicely with qx --- src/utils/sidebarUtils.js | 57 +++++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/src/utils/sidebarUtils.js b/src/utils/sidebarUtils.js index 0d36544dc93d..0464dcfdb3f1 100644 --- a/src/utils/sidebarUtils.js +++ b/src/utils/sidebarUtils.js @@ -26,24 +26,59 @@ export const downloadRawData = (data, deviceData, rawDataType) => { URL.revokeObjectURL(url); }; -export const getMetricStatistics = (calibrationData, metric) => { +export const getMetricStatistics = (calibrationData, metric, qubits = null) => { if (!calibrationData || !metric || !calibrationData[metric]) { return null; } - const values = Object.values(calibrationData[metric]) - .map(item => item?.value) - .filter(value => value !== null && value !== undefined && !isNaN(value)); + var values; + + if (qubits == 1) { + var met = calibrationData[metric]; + var filtered_met = {} + + Object.keys(met).filter(k => !k.includes("__")).map(k => filtered_met[k] = met[k]); + + values = Object.values(filtered_met).map(item => item?.value).filter(value => value !== null && value !== undefined && !isNaN(value)); + } + + else if (qubits == 2) { + var met = calibrationData[metric]; + var filtered_met = {} + + Object.keys(met).filter(k => k.includes("__")).map(k => filtered_met[k] = met[k]); + + values = Object.values(filtered_met).map(item => item?.value).filter(value => value !== null && value !== undefined && !isNaN(value)); + } + + else { + values = Object.values(calibrationData[metric]) + .map(item => item?.value) + .filter(value => value !== null && value !== undefined && !isNaN(value)); + } if (values.length === 0) return null; const sorted = values.sort((a, b) => a - b); - return { - worst: sorted[0], - best: sorted[sorted.length - 1], - average: calibrationData[metric].statistics.average, - median: calibrationData[metric].statistics.median, - unit: Object.values(calibrationData[metric]).find(item => item?.unit)?.unit || '' - }; + + + if (metric === "clifford_rb_fidelity" && qubits == 1) { + return { + worst: sorted[0], + best: sorted[sorted.length - 1], + average: sorted.reduce((sum, val) => sum + val, 0) / sorted.length, + median: sorted.length % 2 === 0 ? (sorted[sorted.length / 2 - 1] + sorted[sorted.length / 2]) / 2 : sorted[Math.floor(sorted.length / 2)], + unit: Object.values(calibrationData[metric]).find(item => item?.unit)?.unit || '' + } + } + else { + return { + worst: sorted[0], + best: sorted[sorted.length - 1], + average: calibrationData[metric].statistics.average, + median: calibrationData[metric].statistics.median, + unit: Object.values(calibrationData[metric]).find(item => item?.unit)?.unit || '' + }; + } }; From 643b6dd16ad19067f7091e91b746290e6f846cab Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 18 Feb 2026 10:13:12 +0200 Subject: [PATCH 10/31] add q20 to device metric --- src/config/deviceMetrics.js | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/config/deviceMetrics.js b/src/config/deviceMetrics.js index 94ecc2e92e72..3027d99dfedc 100644 --- a/src/config/deviceMetrics.js +++ b/src/config/deviceMetrics.js @@ -34,6 +34,42 @@ export const DEVICE_METRICS = { ], }, + Q20: { + overview: { + single: { + singleGateFidelity: ['prx_rb_drag_crf_fidelity', 'prx_rb_fidelity'], + readoutFidelity: ['measure_fidelity_ssro_constant_fidelity', 'measure_ssro_fidelity'], + t1: ['t1_time'], + t2: ['t2_time'], + }, + coupler: { + twoQubitFidelity: ['cz_irb_tgss_crf_fidelity', 'cz_irb_fidelity'], + cliffordFidelity: ['clifford_rb_uz_cz_fidelity', 'clifford_rb_fidelity'], + }, + }, + qubitOptions: [ + { name: 'T1 Time', value: 't1_time' }, + { name: 'T2 Time', value: 't2_time' }, + { name: 'T2 Echo Time', value: 't2_echo_time' }, + { name: 'PRX Gate Fidelity', value: 'prx_rb_drag_crf_fidelity' }, + { name: 'Clifford Gate Fidelity', value: 'clifford_rb_xy_fidelity' }, + { name: '1->0 Readout Error', value: 'measure_fidelity_ssro_constant_error_1_to_0' }, + { name: '0->1 Readout Error', value: 'measure_fidelity_ssro_constant_error_0_to_1' }, + { name: 'Readout Fidelity', value: 'measure_fidelity_ssro_constant_fidelity' }, + { name: '1->0 MCM Error', value: 'measure_ssro_constant_error_1_to_0', title: 'MCM = Mid Circuit Measurement' }, + { name: '0->1 MCM Error', value: 'measure_ssro_constant_error_0_to_1', title: 'MCM = Mid Circuit Measurement' }, + { name: 'MCM Fidelity', value: 'measure_ssro_constant_fidelity', title: 'MCM = Mid Circuit Measurement' }, + { name: 'QNDness Fidelity', value: 'measure_qndness_constant_fidelity', title: 'QND = Quantum Non-Demolition' }, + { name: 'QNDness 0 State', value: 'measure_qndness_constant_qndness_0', title: 'QND = Quantum Non-Demolition' }, + { name: 'QNDness 1 State', value: 'measure_qndness_constant_qndness_1', title: 'QND = Quantum Non-Demolition' }, + { name: 'QNDness Repeatability', value: 'measure_qndness_constant_repeatability', title: 'QND = Quantum Non-Demolition' }, + ], + couplerOptions: [ + { name: 'CZ Gate Fidelity', value: 'cz_irb_tgss_crf_fidelity' }, + { name: 'Clifford Gate Fidelity', value: 'clifford_rb_uz_cz_fidelity' }, + ], + }, + Q50: { overview: { single: { From bb4adc9e55a313dd73aaf0ae05612dfed452b725 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 18 Feb 2026 11:12:16 +0200 Subject: [PATCH 11/31] add sorting for qcs --- src/components/ServiceStatus.jsx | 73 ++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 13 deletions(-) diff --git a/src/components/ServiceStatus.jsx b/src/components/ServiceStatus.jsx index a395c1f49119..2591dc4570fa 100644 --- a/src/components/ServiceStatus.jsx +++ b/src/components/ServiceStatus.jsx @@ -1,9 +1,9 @@ -import React, { useState } from 'react' +import React, { useState, useCallback, useEffect, useMemo } from 'react' import { useStatus } from '../hooks/useStatus' import { useBookings } from '../hooks/useBookings.jsx'; import { mdiInformation, mdiClose, mdiAlert } from '@mdi/js'; -import { CCard, CCardTitle, CCardContent, CIcon, CButton } from '@cscfi/csc-ui-react'; +import { CCard, CCardTitle, CCardContent, CIcon, CButton, CSelect } from '@cscfi/csc-ui-react'; import { StatusModal } from './StatusModal/StatusModal'; import { BookingModal } from './bookingCalendar.jsx'; @@ -44,19 +44,47 @@ export const ServiceStatus = (props) => { const { bookingData: bookingData } = useBookings("https://fiqci-backend.2.rahtiapp.fi/bookings") const qcs = props["quantum-computers"] || []; - const devicesWithStatus = (qcs.length === 0 || !Array.isArray(statusList)) - ? qcs - : qcs.map(device => { + const devicesWithStatus = useMemo(() => { + return (qcs.length === 0 || !Array.isArray(statusList)) + ? qcs + : qcs.map(device => { const deviceStatus = statusList.find(({ name }) => name === device.device_id); return { ...device, health: deviceStatus?.health ?? false, }; }); - + }, [qcs, statusList]); + const [bookingModalOpen, setBookingModalOpen] = useState(false) const [modalOpen, setModalOpen] = useState(false); const [modalProps, setModalProps] = useState({}); + const [sort, setSort] = useState("status") + const [sortedDevices, setSortedDevices] = useState(devicesWithStatus); + + useEffect(() => { + let soted = devicesWithStatus; + if (sort === 'status') { + soted = [...devicesWithStatus].sort((a, b) => (b.health === a.health) ? b.qubits - a.qubits : b.health ? 1 : -1); + } else if (sort === 'least_qubits') { + soted = [...devicesWithStatus].sort((a, b) => a.qubits - b.qubits); + } else if (sort === 'most_qubits') { + soted = [...devicesWithStatus].sort((a, b) => b.qubits - a.qubits); + } else if (sort === 'host') { + soted = [...devicesWithStatus].sort((a, b) => { + const hostAName = a.name.split(" ")[0]; + const hostBName = b.name.split(" ")[0]; + const nameCompare = hostAName.localeCompare(hostBName); + return nameCompare !== 0 ? nameCompare : b.qubits - a.qubits; + }); + } + setSortedDevices(soted); + }, [devicesWithStatus, sort]); + + const handleSortChange = useCallback(selectedSort => { + setSort(selectedSort.detail || 'status'); + }, []); + const handleCardClick = (qc) => { setModalProps({ ...qc, devicesWithStatus }); setModalOpen(true); @@ -94,19 +122,38 @@ export const ServiceStatus = (props) => {

Reservations

- VTT devices can at times be reserved. At these times the queue will be paused. + VTT devices can at times be reserved. At these times the queue will be paused. Reservations can be viewed from this calendar. Note that making reservations through FiQCI is not currently possible.

setBookingModalOpen(true)}>View Reservations
- -

Devices

+ +
+

Devices

+ +
+
- {devicesWithStatus.map((qc, index) => ( + {sortedDevices.map((qc, index) => ( handleCardClick(qc)} /> ))} - - + +
{bookingModalOpen && ( @@ -115,7 +162,7 @@ export const ServiceStatus = (props) => { {modalOpen && ( )} - +
); } From 2f86a87a05cf6d497a011f960ddab4fcbc007efc Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Mon, 23 Feb 2026 18:11:36 +0200 Subject: [PATCH 12/31] test --- src/components/ServiceStatus.jsx | 49 ++++++++++++++++---------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/src/components/ServiceStatus.jsx b/src/components/ServiceStatus.jsx index d4ef1767aaca..92cf3252ccb1 100644 --- a/src/components/ServiceStatus.jsx +++ b/src/components/ServiceStatus.jsx @@ -49,7 +49,7 @@ const StatusCard = (props) => { } export const ServiceStatus = (props) => { - const { status: statusList } = useStatus(`${API_BASE_URL}/devices/healthcheck`); + const { status: statusList } = useStatus(`https://fiqci-backend.2.rahtiapp.fi/devices/healthcheck`); const { bookingData: bookingData } = useBookings(`${API_BASE_URL}/bookings`) const qcs = props["quantum-computers"] || []; @@ -57,35 +57,36 @@ export const ServiceStatus = (props) => { const [devicesWithStatus, setDevicesWithStatus] = useState([]); useEffect(() => { - // Compute devicesWithStatus inside the effect - const devicesWithStatus = (qcs.length === 0 || !Array.isArray(statusList)) - ? qcs - : qcs.map(device => { - const deviceStatus = statusList.find(({ name }) => name === device.device_id); - return { - ...device, - health: deviceStatus?.health ?? false, - }; - }); - - setDevicesWithStatus(devicesWithStatus); - - if (!devicesWithStatus || devicesWithStatus.length === 0) { - setDeviceStatusList([]); + // Only run when both are ready + if (!Array.isArray(statusList) || statusList.length === 0 || !Array.isArray(qcs) || qcs.length === 0) { return; } + // Calculate devicesWithStatus once + const devicesWithStatus = qcs.map(device => { + const deviceStatus = statusList.find(({ name }) => name === device.device_id); + return { + ...device, + health: deviceStatus?.health ?? false, + }; + }); + const deviceStatusList = devicesWithStatus.map(async device => { const url = `${API_BASE_URL}/device/${device.device_id.toLowerCase()}`; - const data = await fetch(url) - .then(resp => resp.json()) - .then(result => result?.data || {}) - .catch(() => ({status: 'offline'})); - return { ...device, device_status: data.status }; + try { + const resp = await fetch(url); + if (!resp.ok) { + return { ...device, device_status: "offline" }; + } + const result = await resp.json(); + const data = result?.data || {}; + return { ...device, device_status: data.status }; + } catch (e) { + return { ...device, device_status: "offline" }; + } }); - + Promise.all(deviceStatusList).then(results => { - console.log(`Device status list results:`, results); setDeviceStatusList(results); }); }, [qcs, statusList]); @@ -122,7 +123,7 @@ export const ServiceStatus = (props) => { }); } setSortedDevices(sorted); - }, [devicesWithStatus, sort]); + }, [device_status_list, sort]); const handleSortChange = useCallback(selectedSort => { setSort(selectedSort.detail || 'status'); From eda24ce9a633fe79b0ca71991217c0c5fa77b744 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 25 Feb 2026 12:58:39 +0200 Subject: [PATCH 13/31] replace hardcoded url --- src/components/StatusModal/StatusModalConent.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/StatusModal/StatusModalConent.jsx b/src/components/StatusModal/StatusModalConent.jsx index 034aa01bb555..386ad51b112d 100644 --- a/src/components/StatusModal/StatusModalConent.jsx +++ b/src/components/StatusModal/StatusModalConent.jsx @@ -19,8 +19,8 @@ import { API_BASE_URL } from '../../config/api'; export const ModalContent = (props) => { - const { calibrationData: calibrationDataAll, calibrationError } = useCalibration(`http://127.0.0.1:3000/device/${props.device_id.toLowerCase()}/calibration`) - const { deviceInfo: deviceInfoData, infoError } = useDeviceInfo(`http://127.0.0.1:3000/device/${props.device_id.toLowerCase()}`) + const { calibrationData: calibrationDataAll, calibrationError } = useCalibration(`${API_BASE_URL}/device/${props.device_id.toLowerCase()}/calibration`) + const { deviceInfo: deviceInfoData, infoError } = useDeviceInfo(`${API_BASE_URL}/device/${props.device_id.toLowerCase()}`) const [activeTab, setActiveTab] = useState('overview'); From 85d297a602aee2437dd337925541c3e43d426dc2 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 27 May 2026 13:27:10 +0300 Subject: [PATCH 14/31] rename --- ...26-01-30-Q50-Call-current.md => 2026-01-30-Q50-Call-1_2026.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename content/_publications/{2026-01-30-Q50-Call-current.md => 2026-01-30-Q50-Call-1_2026.md} (100%) diff --git a/content/_publications/2026-01-30-Q50-Call-current.md b/content/_publications/2026-01-30-Q50-Call-1_2026.md similarity index 100% rename from content/_publications/2026-01-30-Q50-Call-current.md rename to content/_publications/2026-01-30-Q50-Call-1_2026.md From 1828870ffe3423e0d7a301ab237ac80a0a4158d2 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 27 May 2026 13:31:46 +0300 Subject: [PATCH 15/31] rever status check to prod --- src/components/ServiceStatus.jsx | 112 +++++++------------------------ 1 file changed, 23 insertions(+), 89 deletions(-) diff --git a/src/components/ServiceStatus.jsx b/src/components/ServiceStatus.jsx index b692f355d295..8e7b625029cb 100644 --- a/src/components/ServiceStatus.jsx +++ b/src/components/ServiceStatus.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from 'react' +import React, { useState } from 'react' import { useStatus } from '../hooks/useStatus' import { useBookings } from '../hooks/useBookings.jsx'; @@ -6,17 +6,10 @@ import { mdiInformation, mdiClose, mdiAlert } from '@mdi/js'; import { CCard, CCardTitle, CCardContent, CIcon, CButton } from '@cscfi/csc-ui-react'; import { StatusModal } from './StatusModal/StatusModal'; import { BookingModal } from './bookingCalendar.jsx'; -import { set } from 'date-fns'; -import { capitalizeFirstLetter } from '../utils/textUtils.js'; - -import { API_BASE_URL } from '../config/api'; const StatusCard = (props) => { const isOnline = props.health; const { onClick, ...rest } = props - - const status_options = ["offline", "online", "healthy", "unknown"] - return ( @@ -31,20 +24,14 @@ const StatusCard = (props) => {
Service status: - {(props.device_status && !status_options.includes(props.device_status)) ? ( -
-

{capitalizeFirstLetter(props.device_status)}

+ {isOnline ? ( +
+

Online

) : ( - isOnline ? ( -
-

Online

-
- ) : ( -
-

Offline

-
- ) +
+

Offline

+
)}
@@ -53,70 +40,25 @@ const StatusCard = (props) => { } export const ServiceStatus = (props) => { - const { status: statusList } = useStatus(`${API_BASE_URL}/devices/healthcheck`); - const { bookingData: bookingData } = useBookings(`${API_BASE_URL}/bookings`) - const qcs = Array.isArray(props["quantum-computers"]) ? props["quantum-computers"] : []; - const qcsKey = useMemo( - () => qcs.map(d => d?.device_id?.toLowerCase()).filter(Boolean).sort().join('|'), - [qcs] - ); - - const [device_status_list, setDeviceStatusList] = useState([]); + const { status: statusList } = useStatus("https://fiqci-backend.2.rahtiapp.fi/devices/healthcheck"); + const { bookingData: bookingData } = useBookings("https://fiqci-backend.2.rahtiapp.fi/bookings") + const qcs = props["quantum-computers"] || []; const devicesWithStatus = (qcs.length === 0 || !Array.isArray(statusList)) ? qcs : qcs.map(device => { - const deviceStatus = statusList.find(({ name }) => name === device.device_id); - return { - ...device, - health: deviceStatus?.health ?? false, - }; - }); - - useEffect(() => { - let cancelled = false; - - if (!qcs.length) { - setDeviceStatusList({}); - return; - } - - // reset for current device set - setDeviceStatusList({}); - - qcs.forEach((device) => { - const id = device.device_id.toLowerCase(); - fetch(`${API_BASE_URL}/device/${id}`) - .then((resp) => resp.json()) - .then((result) => result?.data?.status) - .then((status) => { - if (cancelled) return; - setDeviceStatusList((prev) => - prev[id] === status ? prev : { ...prev, [id]: status } - ); - }) - .catch(() => { - if (cancelled) return; - setDeviceStatusList((prev) => - prev[id] === 'unknown' ? prev : { ...prev, [id]: 'unknown' } - ); + const deviceStatus = statusList.find(({ name }) => name === device.device_id); + return { + ...device, + health: deviceStatus?.health ?? false, + }; }); - }); - - return () => { - cancelled = true; - }; -}, [qcsKey]); - - + const [bookingModalOpen, setBookingModalOpen] = useState(false) const [modalOpen, setModalOpen] = useState(false); const [modalProps, setModalProps] = useState({}); const handleCardClick = (qc) => { - - const add_device_status = devicesWithStatus.map(d => d.device_id.toLowerCase() === qc.device_id.toLowerCase() ? { ...d, device_status: device_status_list?.[qc.device_id.toLowerCase()] || "unknown" } : d); - - setModalProps({ ...qc, devicesWithStatus: add_device_status.find(d => d.device_id.toLowerCase() === qc.device_id.toLowerCase()) }); + setModalProps({ ...qc, devicesWithStatus }); setModalOpen(true); }; // Determine alert color based on props.alert.type @@ -152,26 +94,19 @@ export const ServiceStatus = (props) => {

Reservations

- VTT devices can at times be reserved. At these times the queue will be paused. + VTT devices can at times be reserved. At these times the queue will be paused. Reservations can be viewed from this calendar. Note that making reservations through FiQCI is not currently possible.

setBookingModalOpen(true)}>View Reservations
- +

Devices

{devicesWithStatus.map((qc, index) => ( - handleCardClick(qc)} - /> + handleCardClick(qc)} /> ))} - - + +
{bookingModalOpen && ( @@ -180,9 +115,8 @@ export const ServiceStatus = (props) => { {modalOpen && ( )} - +
); } - From 4a11404be0ba67d64a6ef45848b27cc4b2308043 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 27 May 2026 13:41:06 +0300 Subject: [PATCH 16/31] add qpu sorting --- src/components/ServiceStatus.jsx | 53 +++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/src/components/ServiceStatus.jsx b/src/components/ServiceStatus.jsx index 8e7b625029cb..b8b887896a33 100644 --- a/src/components/ServiceStatus.jsx +++ b/src/components/ServiceStatus.jsx @@ -1,9 +1,9 @@ -import React, { useState } from 'react' +import React, { useCallback, useMemo, useState } from 'react' import { useStatus } from '../hooks/useStatus' import { useBookings } from '../hooks/useBookings.jsx'; import { mdiInformation, mdiClose, mdiAlert } from '@mdi/js'; -import { CCard, CCardTitle, CCardContent, CIcon, CButton } from '@cscfi/csc-ui-react'; +import { CCard, CCardTitle, CCardContent, CIcon, CButton, CSelect } from '@cscfi/csc-ui-react'; import { StatusModal } from './StatusModal/StatusModal'; import { BookingModal } from './bookingCalendar.jsx'; @@ -57,6 +57,32 @@ export const ServiceStatus = (props) => { const [bookingModalOpen, setBookingModalOpen] = useState(false) const [modalOpen, setModalOpen] = useState(false); const [modalProps, setModalProps] = useState({}); + const [sort, setSort] = useState("status") + + const sortedDevices = useMemo(() => { + if (sort === 'status') { + return [...devicesWithStatus].sort((a, b) => ( + b.health === a.health ? b.qubits - a.qubits : b.health ? 1 : -1 + )); + } else if (sort === 'least_qubits') { + return [...devicesWithStatus].sort((a, b) => a.qubits - b.qubits); + } else if (sort === 'most_qubits') { + return [...devicesWithStatus].sort((a, b) => b.qubits - a.qubits); + } else if (sort === 'host') { + return [...devicesWithStatus].sort((a, b) => { + const hostAName = a.name.split(" ")[0]; + const hostBName = b.name.split(" ")[0]; + const nameCompare = hostAName.localeCompare(hostBName); + return nameCompare !== 0 ? nameCompare : b.qubits - a.qubits; + }); + } + return devicesWithStatus; + }, [devicesWithStatus, sort]); + + const handleSortChange = useCallback(selectedSort => { + setSort(selectedSort.detail || 'status'); + }, []); + const handleCardClick = (qc) => { setModalProps({ ...qc, devicesWithStatus }); setModalOpen(true); @@ -100,9 +126,28 @@ export const ServiceStatus = (props) => { setBookingModalOpen(true)}>View Reservations
-

Devices

+
+

Devices

+ +
+
- {devicesWithStatus.map((qc, index) => ( + {sortedDevices.map((qc, index) => ( handleCardClick(qc)} /> ))} From f263e2720e44ba2ee324d791f902f4d64bb09966 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 27 May 2026 13:42:13 +0300 Subject: [PATCH 17/31] npm audit fix --- package-lock.json | 93 ++++++++++++++++++++++------------------------- 1 file changed, 43 insertions(+), 50 deletions(-) diff --git a/package-lock.json b/package-lock.json index 621aaa1ed69b..ee4ca72b6b1a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -67,13 +67,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -384,21 +383,19 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -429,27 +426,25 @@ } }, "node_modules/@babel/helpers": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", - "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.0" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", - "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -1669,15 +1664,14 @@ } }, "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1703,24 +1697,22 @@ } }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@cscfi/csc-ui": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@cscfi/csc-ui/-/csc-ui-2.3.0.tgz", - "integrity": "sha512-MLr+mF7IPJpSVX0doqHGs07Atmkps3/ofjk3/inG/zUJXWvbQp0WohdCYX6STDFD9BLgOCGfN2Wvg6IWfKhyUw==", - "license": "MIT", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@cscfi/csc-ui/-/csc-ui-2.3.4.tgz", + "integrity": "sha512-J8ynSGFhhYAkgy9Ug9SKYfuTppDqBA8xhhNNmO5NcOchgdv+wvgbAWQktyeoo55kf2ubTJ2LScNF11B41OHhQg==", "dependencies": { "@mdi/js": "^7.4.47", "@stencil/core": "^4.22.1", @@ -1732,10 +1724,9 @@ } }, "node_modules/@cscfi/csc-ui-react": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@cscfi/csc-ui-react/-/csc-ui-react-2.3.0.tgz", - "integrity": "sha512-R/nVguU60yn+jNDJXPaM6UX2QNIDaFlvTokjbCAwXbzkfLiirN6H94hHH9fVUr46fGKQvKdTtX2yR8jsH6FmgA==", - "license": "MIT", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@cscfi/csc-ui-react/-/csc-ui-react-2.3.4.tgz", + "integrity": "sha512-+uJcvtmESM/sczJMHWITSAajpVxryH+2NTmjtiAdK8mXECeVsHSxq0ykD7S6oEVG08EYxONBLh0M4LF269ZSJQ==", "dependencies": { "@cscfi/csc-ui": "*", "@stencil/react-output-target": "^0.7.1" @@ -6328,16 +6319,18 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", - "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", "dev": true, - "license": "ISC", "bin": { "yaml": "bin.mjs" }, "engines": { - "node": ">= 14" + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" } }, "node_modules/yargs": { From 133676351229125dd90caa0c6cddfaed2caf2f52 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 27 May 2026 14:22:51 +0300 Subject: [PATCH 18/31] update device metric keys --- src/config/deviceMetrics.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/deviceMetrics.js b/src/config/deviceMetrics.js index 0d644cbbc329..35f51a956ed6 100644 --- a/src/config/deviceMetrics.js +++ b/src/config/deviceMetrics.js @@ -74,7 +74,7 @@ export const DEVICE_METRICS = { Q20: { overview: { single: { - singleGateFidelity: ['prx_rb_drag_crf_sx_fidelity', 'prx_rb_fidelity'], + singleGateFidelity: ['prx_rb_drag_crf_fidelity', 'prx_rb_fidelity'], readoutFidelity: ['measure_fidelity_ssro_constant_fidelity', 'measure_ssro_fidelity'], t1: ['t1_time'], t2: ['t2_time'], From 386e116dd0183f339b855d447ee4f46d44f28328 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 27 May 2026 14:23:06 +0300 Subject: [PATCH 19/31] use api url from config --- src/components/ServiceStatus.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/ServiceStatus.jsx b/src/components/ServiceStatus.jsx index b8b887896a33..0058902d46a9 100644 --- a/src/components/ServiceStatus.jsx +++ b/src/components/ServiceStatus.jsx @@ -6,6 +6,7 @@ import { mdiInformation, mdiClose, mdiAlert } from '@mdi/js'; import { CCard, CCardTitle, CCardContent, CIcon, CButton, CSelect } from '@cscfi/csc-ui-react'; import { StatusModal } from './StatusModal/StatusModal'; import { BookingModal } from './bookingCalendar.jsx'; +import { API_BASE_URL } from '../config/api'; const StatusCard = (props) => { const isOnline = props.health; @@ -40,8 +41,8 @@ const StatusCard = (props) => { } export const ServiceStatus = (props) => { - const { status: statusList } = useStatus("https://fiqci-backend.2.rahtiapp.fi/devices/healthcheck"); - const { bookingData: bookingData } = useBookings("https://fiqci-backend.2.rahtiapp.fi/bookings") + const { status: statusList } = useStatus(`${API_BASE_URL}/devices/healthcheck`); + const { bookingData: bookingData } = useBookings(`${API_BASE_URL}/bookings`) const qcs = props["quantum-computers"] || []; const devicesWithStatus = (qcs.length === 0 || !Array.isArray(statusList)) From 9304e781107d8347b951644930957dbb2e9add6d Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 27 May 2026 15:07:23 +0300 Subject: [PATCH 20/31] remove helmi --- content/_data/constants.yml | 34 +++------ content/pages/about.md | 7 +- docs/blog_post.md | 2 +- docs/edit_or_add_content.md | 71 ++++++++----------- docs/event.md | 2 +- src/components/QcLayouts/Helmi.jsx | 36 ---------- src/components/ServiceStatus.jsx | 67 ----------------- .../StatusModal/StatusModalConent.jsx | 13 +--- 8 files changed, 46 insertions(+), 186 deletions(-) delete mode 100644 src/components/QcLayouts/Helmi.jsx diff --git a/content/_data/constants.yml b/content/_data/constants.yml index 53916882a988..e59e300f6577 100644 --- a/content/_data/constants.yml +++ b/content/_data/constants.yml @@ -72,7 +72,7 @@ cookie_consent: description: |- The Finnish quantum-computing infrastructure (FiQCI) provides access to quantum computers through the LUMI supercomputer environment. Currently there are two quantum computers - available for use: VTT Q5 (Helmi), and VTT Q50. + available for use: Aalto Q20 and VTT Q50. links: - title: Quantum computer resources href: /access/#quantum @@ -112,21 +112,16 @@ cookie_consent: href: https://fiqci.fi/resource-estimator quantum_resources: - - name: VTT Q5 (Helmi) + - name: Aalto Q20 desc: |- - The VTT Q5 "Helmi" is the first Finnish quantum computer. Helmi is based on superconducting technology and provides 5 qubits - in a star-shape topology. Hybrid HPC+QC access was opened in 2022. Helmi is operated by VTT, - and was co-developed with IQM Quantum Computers. + Aalto Q20 is an IQM superconducting quantum computer with 20 qubits in a square grid topology. It is operated by Aalto University. image: "/assets/images/vtt-images/VTT_lab-2.webp" links: - - link: "/publications/2022-11-01-Helmi_pilot" - teaser: "Apply for access to VTT Q5" + - link: "/publications/2026-05-11-Academic-Call-2026-02" + teaser: "Apply for access to Aalto Q20" - link: "https://docs.csc.fi/computing/quantum-computing/running-quantum-jobs/" - teaser: "How to access VTT Q5, instructions" + teaser: "How to access Aalto Q20, instructions" icon: mdiArrowRight - - link: "https://qx.vtt.fi/docs/devices/q5.html" - teaser: "Read more about VTT Q5 (VTT website)" - icon: mdiOpenInNew - name: VTT Q50 desc: |- @@ -234,11 +229,8 @@ cookie_consent: Additionally, users should also acknowledge using FiQCI quantum computers if applicable: - helmi-link-url: /publications/2022-11-01-Helmi_pilot - helmi-link-text: VTT Q5 (Helmi) - - q50-link-url: /publications/2025-03-04-Q50-Call-2_2025 - q50-link-text: VTT Q50 + acknowledge-link-url: /publications/2026-05-11-Academic-Call-2026-02 + acknowledge-link-text: see here "/status/": info: Here you can find the status of the Quantum Computers connected to LUMI. @@ -249,15 +241,9 @@ cookie_consent: href: https://www.lumi-supercomputer.eu/lumi-service-status/ alert: text: |- - VTT Q5 (Helmi) will be indefinitely offline and will be removed from our services soon. - type: warning # Alert type can be info, warning, or error + Aalto Q20 is now available for use. + type: info # Alert type can be info, warning, or error quantum-computers: - - name: VTT Q5 (Helmi) - qubits: 5 - basis: "PRX, CZ" - topology: "Star" - device_id: "Q5" - - name: VTT Q50 qubits: 53 basis: "PRX, CZ" diff --git a/content/pages/about.md b/content/pages/about.md index 567d28713c92..af441b331940 100644 --- a/content/pages/about.md +++ b/content/pages/about.md @@ -35,11 +35,8 @@ react: true

Acknowledgement

{{ about_data.advisory-group.acknowledgement.desc }} - - {{ about_data.advisory-group.acknowledgement.helmi-link-text }} - , - - {{ about_data.advisory-group.acknowledgement.q50-link-text }} + + {{ about_data.advisory-group.acknowledgement.acknowledge-link-text }}

diff --git a/docs/blog_post.md b/docs/blog_post.md index 27bd8aaed80e..bba94d89235d 100644 --- a/docs/blog_post.md +++ b/docs/blog_post.md @@ -38,7 +38,7 @@ header: #thumbnail image for the post published: true author: author name #name of the author layout: post #don't change -tags: #keywords related to the topic of the blog, e.g Helmi, Quantum, etc +tags: #keywords related to the topic of the blog, e.g Q20, Quantum, etc - tag_1 - tag_2 - etc diff --git a/docs/edit_or_add_content.md b/docs/edit_or_add_content.md index c5cf86fa3755..16b4eab44835 100644 --- a/docs/edit_or_add_content.md +++ b/docs/edit_or_add_content.md @@ -41,11 +41,11 @@ Add the quantum computer to the lists at: ```yml "/status/": quantum-computers: - - name: VTT Q5 (Helmi) - qubits: 5 + - name: Aalto Q20 + qubits: 20 basis: "PRX, CZ" - topology: "Star" - device_id: "Q5" + topology: "Grid" + device_id: "Q20" ... ``` @@ -53,20 +53,17 @@ and ```yml "/access/": - quantum_resources: #list of the available quantum resources - - name: VTT Q5 (Helmi) + quantum_resources: + - name: Aalto Q20 desc: |- - The VTT Q5 "Helmi" is the first Finnish quantum computer. Helmi is based on superconducting technology and provides 5 qubits - in a star-shape topology. Hybrid HPC+QC access was opened in 2022. Helmi is operated by VTT, - and was co-developed with IQM Quantm Computers. + Aalto Q20 is an IQM superconducting quantum computer with 20 qubits in a square grid topology. It is operated by Aalto University. image: "/assets/images/vtt-images/VTT_lab-2.webp" links: - - link: "" - teaser: "How to access VTT Q5, instructions" + - link: "/publications/2026-05-11-Academic-Call-2026-02" + teaser: "Apply for access to Aalto Q20" + - link: "https://docs.csc.fi/computing/quantum-computing/running-quantum-jobs/" + teaser: "How to access Aalto Q20, instructions" icon: mdiArrowRight - - link: "" - teaser: "Read more about VTT Q5 (VTT website)" - icon: mdiOpenInNew ... ``` @@ -281,7 +278,7 @@ Each section contains static text content, link urls, image paths, etc else that description: |- The Finnish quantum-computing infrastructure (FiQCI) provides access to quantum computers through the LUMI supercomputer environment. Currently there are two quantum computers - available for use: VTT Q5 (Helmi), and VTT Q50 + available for use: Aalto Q20, and VTT Q50 links: - title: Quantum computer resources href: /access/#quantum @@ -317,20 +314,18 @@ Each section contains static text content, link urls, image paths, etc else that title: FiQCI QPU resource estimator href: https://fiqci.fi/resource-estimator - quantum_resources: #list of the available quantum resources - - name: VTT Q5 (Helmi) + quantum_resources: + - name: Aalto Q20 desc: |- - The VTT Q5 "Helmi" is the first Finnish quantum computer. Helmi is based on superconducting technology and provides 5 qubits - in a star-shape topology. Hybrid HPC+QC access was opened in 2022. Helmi is operated by VTT, - and was co-developed with IQM Quantm Computers. + Aalto Q20 is an IQM superconducting quantum computer with 20 qubits in a square grid topology. It is operated by Aalto University. image: "/assets/images/vtt-images/VTT_lab-2.webp" links: - - link: "" - teaser: "How to access VTT Q5, instructions" + - link: "/publications/2026-05-11-Academic-Call-2026-02" + teaser: "Apply for access to Aalto Q20" + - link: "https://docs.csc.fi/computing/quantum-computing/running-quantum-jobs/" + teaser: "How to access Aalto Q20, instructions" icon: mdiArrowRight - - link: "" - teaser: "Read more about VTT Q5 (VTT website)" - icon: mdiOpenInNew + - name: VTT Q50 desc: |- @@ -432,8 +427,7 @@ Each section contains static text content, link urls, image paths, etc else that institution: Aalto University title: FiQCI vice-director - acknowledgement: #info for how to acknowledge results - #obtained using FiQCI + acknowledgement: desc: |- When publishing the results that utilise the FiQCI infrastructure, users should acknowledge the use of FiQCI, preferably in the format: "These [results] have been acquired using the @@ -441,11 +435,8 @@ Each section contains static text content, link urls, image paths, etc else that Additionally, users should also acknowledge using FiQCI quantum computers if applicable: - helmi-link-url: /publications/2022-11-01-Helmi_pilot - helmi-link-text: VTT Q5 (Helmi) - - q50-link-url: /publications/2025-07-03-Q50-Call-2_2025 - q50-link-text: VTT Q50 + acknowledge-link-url: /publications/2026-05-11-Academic-Call-2026-02 + acknowledge-link-text: VTT Q50 ``` ## Status @@ -461,18 +452,18 @@ Each section contains static text content, link urls, image paths, etc else that alert: text: Note! Access to VTT Q50 through FiQCI is coming soon. Status of the quantum computer can already be seen below. type: warning # Alert type can be info, warning, or error - quantum-computers: - - name: VTT Q5 (Helmi) - qubits: 5 - basis: "PRX, CZ" - topology: "Star" - device_id: "Q5" - + quantum-computers: - name: VTT Q50 - qubits: 54 + qubits: 53 basis: "PRX, CZ" topology: "Square grid" device_id: "Q50" + + - name: Aalto Q20 + qubits: 20 + basis: "PRX, CZ" + topology: "Square grid" + device_id: "Q20" ``` ## Cookies diff --git a/docs/event.md b/docs/event.md index 84c0a5b31d4e..ba6ba65c460f 100644 --- a/docs/event.md +++ b/docs/event.md @@ -25,7 +25,7 @@ title: 'Title of the Event' #Event name date: yyyy-mm-dd #date when to be published as yyyy-mm-dd published: true #leave as is link: https://the-event.fi/info/signup #link to event page -tags: #keywords related to the event, e.g Helmi, Quantum, etc +tags: #keywords related to the event, e.g Q20, Quantum, etc - tag_1 - tag_2 - etc diff --git a/src/components/QcLayouts/Helmi.jsx b/src/components/QcLayouts/Helmi.jsx deleted file mode 100644 index cfc7abdfd087..000000000000 --- a/src/components/QcLayouts/Helmi.jsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; -import { BaseQcLayout } from './Base'; - -const spacing = 100; - -const rawNodes = [ - { id: 'QB5', x: -200 + 20, y: 0 + 20 }, - { id: 'QB4', x: -360 - 20, y: -160 - 20 }, - { id: 'QB3', x: -280, y: -80 }, // QB2 stays in place - { id: 'QB2', x: -360 - 20, y: 0 + 20 }, - { id: 'QB1', x: -200 + 20, y: -160 - 20 }, -]; -const edges = [ - ['QB3', 'QB5'], - ['QB3', 'QB4'], - ['QB2', 'QB3'], - ['QB1', 'QB3'], -]; - -export const HelmiLayout = ({ calibrationData, qubitMetric, couplerMetric, - qubitMetricFormatted, couplerMetricFormatted, thresholdQubit, thresholdCoupler }) => { - return ( - - ) -} \ No newline at end of file diff --git a/src/components/ServiceStatus.jsx b/src/components/ServiceStatus.jsx index 7f835644b255..4e9c0b7c155c 100644 --- a/src/components/ServiceStatus.jsx +++ b/src/components/ServiceStatus.jsx @@ -1,11 +1,9 @@ import React, { useCallback, useMemo, useState } from 'react' -import React, { useCallback, useMemo, useState } from 'react' import { useStatus } from '../hooks/useStatus' import { useBookings } from '../hooks/useBookings.jsx'; import { mdiInformation, mdiClose, mdiAlert } from '@mdi/js'; import { CCard, CCardTitle, CCardContent, CIcon, CButton, CSelect } from '@cscfi/csc-ui-react'; -import { CCard, CCardTitle, CCardContent, CIcon, CButton, CSelect } from '@cscfi/csc-ui-react'; import { StatusModal } from './StatusModal/StatusModal'; import { BookingModal } from './bookingCalendar.jsx'; import { API_BASE_URL } from '../config/api'; @@ -27,14 +25,6 @@ const StatusCard = (props) => {
Service status: - {isOnline ? ( -
-

Online

-
- ) : ( -
-

Offline

-
{isOnline ? (

Online

@@ -65,13 +55,6 @@ export const ServiceStatus = (props) => { }; }); - const deviceStatus = statusList.find(({ name }) => name === device.device_id); - return { - ...device, - health: deviceStatus?.health ?? false, - }; - }); - const [bookingModalOpen, setBookingModalOpen] = useState(false) const [modalOpen, setModalOpen] = useState(false); const [modalProps, setModalProps] = useState({}); @@ -101,32 +84,6 @@ export const ServiceStatus = (props) => { setSort(selectedSort.detail || 'status'); }, []); - const [sort, setSort] = useState("status") - - const sortedDevices = useMemo(() => { - if (sort === 'status') { - return [...devicesWithStatus].sort((a, b) => ( - b.health === a.health ? b.qubits - a.qubits : b.health ? 1 : -1 - )); - } else if (sort === 'least_qubits') { - return [...devicesWithStatus].sort((a, b) => a.qubits - b.qubits); - } else if (sort === 'most_qubits') { - return [...devicesWithStatus].sort((a, b) => b.qubits - a.qubits); - } else if (sort === 'host') { - return [...devicesWithStatus].sort((a, b) => { - const hostAName = a.name.split(" ")[0]; - const hostBName = b.name.split(" ")[0]; - const nameCompare = hostAName.localeCompare(hostBName); - return nameCompare !== 0 ? nameCompare : b.qubits - a.qubits; - }); - } - return devicesWithStatus; - }, [devicesWithStatus, sort]); - - const handleSortChange = useCallback(selectedSort => { - setSort(selectedSort.detail || 'status'); - }, []); - const handleCardClick = (qc) => { setModalProps({ ...qc, devicesWithStatus }); setModalProps({ ...qc, devicesWithStatus }); @@ -165,34 +122,12 @@ export const ServiceStatus = (props) => {

Reservations

- VTT devices can at times be reserved. At these times the queue will be paused. VTT devices can at times be reserved. At these times the queue will be paused. Reservations can be viewed from this calendar. Note that making reservations through FiQCI is not currently possible.

setBookingModalOpen(true)}>View Reservations
-
-

Devices

- -
- -

Devices

{
- {sortedDevices.map((qc, index) => ( - handleCardClick(qc)} /> {sortedDevices.map((qc, index) => ( handleCardClick(qc)} /> ))} diff --git a/src/components/StatusModal/StatusModalConent.jsx b/src/components/StatusModal/StatusModalConent.jsx index 386ad51b112d..8049436bd542 100644 --- a/src/components/StatusModal/StatusModalConent.jsx +++ b/src/components/StatusModal/StatusModalConent.jsx @@ -2,7 +2,6 @@ import React from 'react' import { useState } from 'react' import { useCalibration } from '../../hooks/useCalibration'; import { useDeviceInfo } from '../../hooks/useDeviceInfo'; -import { HelmiLayout } from '../QcLayouts/Helmi'; import { Q50Layout } from '../QcLayouts/Q50'; import { Q20Layout } from '../QcLayouts/Q20'; import { Overview } from './StatusOverview'; @@ -120,17 +119,7 @@ export const ModalContent = (props) => { thresholdQubit={metricsState.thresholdQubitValue} thresholdCoupler={metricsState.thresholdCouplerValue} /> - ) : ( - m.value === metricsState.qubitMetric)?.name || metricsState.qubitMetric} - couplerMetricFormatted={couplerMetricOptions.find(m => m.value === metricsState.couplerMetric)?.name || metricsState.couplerMetric} - thresholdQubit={metricsState.thresholdQubitValue} - thresholdCoupler={metricsState.thresholdCouplerValue} - /> - )} + ) : null}
From 616caff9cf922c213369590159034dde36bd3753 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 3 Jun 2026 11:50:26 +0300 Subject: [PATCH 21/31] aalto q20 image --- content/assets/images/aalto-qc/aalto-q20.webp | Bin 0 -> 14720 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 content/assets/images/aalto-qc/aalto-q20.webp diff --git a/content/assets/images/aalto-qc/aalto-q20.webp b/content/assets/images/aalto-qc/aalto-q20.webp new file mode 100644 index 0000000000000000000000000000000000000000..281efa6df1f18374e62279f9be15e26ba1ae6827 GIT binary patch literal 14720 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 OC_aWx9H$t_fB*nDoZG_y literal 0 HcmV?d00001 From dcf7b3709ef7e416cbc15a944412bb0948dbee96 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 3 Jun 2026 11:50:35 +0300 Subject: [PATCH 22/31] edit qc cards --- content/_data/constants.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/content/_data/constants.yml b/content/_data/constants.yml index e59e300f6577..b1aaee4d064d 100644 --- a/content/_data/constants.yml +++ b/content/_data/constants.yml @@ -115,7 +115,7 @@ cookie_consent: - name: Aalto Q20 desc: |- Aalto Q20 is an IQM superconducting quantum computer with 20 qubits in a square grid topology. It is operated by Aalto University. - image: "/assets/images/vtt-images/VTT_lab-2.webp" + image: "/assets/images/aalto-qc/aalto-q20.webp" links: - link: "/publications/2026-05-11-Academic-Call-2026-02" teaser: "Apply for access to Aalto Q20" @@ -137,6 +137,18 @@ cookie_consent: - link: "https://qx.vtt.fi/docs/devices/q50.html" teaser: "Read more about VTT Q50 (VTT website)" icon: mdiOpenInNew + + - name: VTT Q5 (Helmi) - End of life + desc: |- + VTT Q5 (Helmi) was a superconducting quantum computer with 5 qubits in a star topology. It was operated by VTT, and was co-developed with IQM Quantum Computers. The device was decommissioned in 2026. + image: "/assets/images/vtt-images/VTT_lab-2.webp" + links: + - link: "https://docs.csc.fi/computing/quantum-computing/running-quantum-jobs/" + teaser: "How to access VTT Q5 (Helmi), instructions" + icon: mdiArrowRight + - link: "https://qx.vtt.fi/docs/devices/q5.html" + teaser: "Read more about VTT Q5 (VTT website)" + icon: mdiOpenInNew supercomputer_resources: - name: LUMI From d422a30ce2ef49bf998d5879c567eb96c1f927da Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 3 Jun 2026 12:15:47 +0300 Subject: [PATCH 23/31] refactor qc layouts --- src/components/QcLayouts/Q20.jsx | 181 ------------- src/components/QcLayouts/Q50.jsx | 176 ------------ .../QcLayouts/{Base.jsx => QcLayout.jsx} | 254 +++++------------- src/components/QcLayouts/layouts.js | 94 +++++++ .../StatusModal/StatusModalConent.jsx | 35 +-- 5 files changed, 180 insertions(+), 560 deletions(-) delete mode 100644 src/components/QcLayouts/Q20.jsx delete mode 100644 src/components/QcLayouts/Q50.jsx rename src/components/QcLayouts/{Base.jsx => QcLayout.jsx} (50%) create mode 100644 src/components/QcLayouts/layouts.js diff --git a/src/components/QcLayouts/Q20.jsx b/src/components/QcLayouts/Q20.jsx deleted file mode 100644 index c3185529fd85..000000000000 --- a/src/components/QcLayouts/Q20.jsx +++ /dev/null @@ -1,181 +0,0 @@ -import React from 'react'; -import { BaseQcLayout } from './Base'; - -const spacing = 100; -const xOrigin = -210; -const yOrigin = 440; - -const rawNodes = [ - // Row 1 (3 qubits) - { id: 'QB20', x: xOrigin + spacing * 0, y: yOrigin + spacing * 0 }, - { id: 'QB19', x: xOrigin + spacing * -1, y: yOrigin + spacing * -1 }, - { id: 'QB18', x: xOrigin + spacing * -2, y: yOrigin + spacing * -2 }, - - // Row 2 (5 qubits) - { id: 'QB17', x: xOrigin + spacing * 2, y: yOrigin + spacing * 0 }, - { id: 'QB16', x: xOrigin + spacing * 1, y: yOrigin + spacing * -1 }, - { id: 'QB15', x: xOrigin + spacing * 0, y: yOrigin + spacing * -2 }, - { id: 'QB14', x: xOrigin + spacing * -1, y: yOrigin + spacing * -3 }, - { id: 'QB13', x: xOrigin + spacing * -2, y: yOrigin + spacing * -4 }, - - // Row 3 (5 qubits) - { id: 'QB12', x: xOrigin + spacing * 3, y: yOrigin + spacing * -1 }, - { id: 'QB11', x: xOrigin + spacing * 2, y: yOrigin + spacing * -2 }, - { id: 'QB10', x: xOrigin + spacing * 1, y: yOrigin + spacing * -3 }, - { id: 'QB9', x: xOrigin + spacing * 0, y: yOrigin + spacing * -4 }, - { id: 'QB8', x: xOrigin + spacing * -1, y: yOrigin + spacing * -5 }, - - - // Row 4 (5 qubits) - { id: 'QB7', x: xOrigin + spacing * 4, y: yOrigin + spacing * -2 }, - { id: 'QB6', x: xOrigin + spacing * 3, y: yOrigin + spacing * -3 }, - { id: 'QB5', x: xOrigin + spacing * 2, y: yOrigin + spacing * -4 }, - { id: 'QB4', x: xOrigin + spacing * 1, y: yOrigin + spacing * -5 }, - { id: 'QB3', x: xOrigin + spacing * 0, y: yOrigin + spacing * -6 }, - // Row 5 (2 qubits) - { id: 'QB2', x: xOrigin + spacing * 3, y: yOrigin + spacing * -5 }, - { id: 'QB1', x: xOrigin + spacing * 2, y: yOrigin + spacing * -6 }, - - -]; - -// Define edges based on nearest neighbor connectivity in the diamond pattern -const edges = [ - [ - "QB1", - "QB2" - ], - [ - "QB1", - "QB4" - ], - [ - "QB3", - "QB4" - ], - [ - "QB3", - "QB8" - ], - [ - "QB4", - "QB5" - ], - [ - "QB5", - "QB2" - ], - [ - "QB5", - "QB6" - ], - [ - "QB5", - "QB10" - ], - [ - "QB7", - "QB6" - ], - [ - "QB7", - "QB12" - ], - [ - "QB9", - "QB4" - ], - [ - "QB9", - "QB8" - ], - [ - "QB9", - "QB10" - ], - [ - "QB9", - "QB14" - ], - [ - "QB11", - "QB6" - ], - [ - "QB11", - "QB10" - ], - [ - "QB11", - "QB12" - ], - [ - "QB11", - "QB16" - ], - [ - "QB13", - "QB8" - ], - [ - "QB13", - "QB14" - ], - [ - "QB15", - "QB10" - ], - [ - "QB15", - "QB14" - ], - [ - "QB15", - "QB16" - ], - [ - "QB15", - "QB19" - ], - [ - "QB17", - "QB12" - ], - [ - "QB17", - "QB16" - ], - [ - "QB18", - "QB14" - ], - [ - "QB18", - "QB19" - ], - [ - "QB20", - "QB16" - ], - [ - "QB20", - "QB19" - ] -]; -export const Q20Layout = ({ calibrationData, qubitMetric, couplerMetric, - qubitMetricFormatted, couplerMetricFormatted, thresholdQubit, thresholdCoupler }) => { - return ( - - ) -} \ No newline at end of file diff --git a/src/components/QcLayouts/Q50.jsx b/src/components/QcLayouts/Q50.jsx deleted file mode 100644 index a6a9492360a1..000000000000 --- a/src/components/QcLayouts/Q50.jsx +++ /dev/null @@ -1,176 +0,0 @@ -import React from 'react'; -import { BaseQcLayout } from './Base'; - -const spacing = 100; -const xOrigin = -210; -const yOrigin = 440; - -const rawNodes = [ - // Top row (3 qubit) - { id: 'QB54', x: xOrigin + spacing * 0, y: yOrigin + spacing * 0 }, - { id: 'QB53', x: xOrigin + spacing * -1, y: yOrigin + spacing * -1 }, - { id: 'QB52', x: xOrigin + spacing * -2, y: yOrigin + spacing * -2 }, - - // Row 2 (5 qubits) - { id: 'QB51', x: xOrigin + spacing * 2, y: yOrigin + spacing * 0 }, - { id: 'QB50', x: xOrigin + spacing * 1, y: yOrigin + spacing * -1 }, - { id: 'QB49', x: xOrigin + spacing * 0, y: yOrigin + spacing * -2 }, - { id: 'QB48', x: xOrigin + spacing * -1, y: yOrigin + spacing * -3 }, - { id: 'QB47', x: xOrigin + spacing * -2, y: yOrigin + spacing * -4 }, - - // Row 3 (7 qubits) - { id: 'QB46', x: xOrigin + spacing * 4, y: yOrigin + spacing * 0 }, - { id: 'QB45', x: xOrigin + spacing * 3, y: yOrigin + spacing * -1 }, - { id: 'QB44', x: xOrigin + spacing * 2, y: yOrigin + spacing * -2 }, - { id: 'QB43', x: xOrigin + spacing * 1, y: yOrigin + spacing * -3 }, - { id: 'QB42', x: xOrigin + spacing * 0, y: yOrigin + spacing * -4 }, - { id: 'QB41', x: xOrigin + spacing * -1, y: yOrigin + spacing * -5 }, - { id: 'QB40', x: xOrigin + spacing * -2, y: yOrigin + spacing * -6 }, - - // Row 4 (9 qubits) - { id: 'QB39', x: xOrigin + spacing * 6, y: yOrigin + spacing * 0 }, - { id: 'QB38', x: xOrigin + spacing * 5, y: yOrigin + spacing * -1 }, - { id: 'QB37', x: xOrigin + spacing * 4, y: yOrigin + spacing * -2 }, - { id: 'QB36', x: xOrigin + spacing * 3, y: yOrigin + spacing * -3 }, - { id: 'QB35', x: xOrigin + spacing * 2, y: yOrigin + spacing * -4 }, - { id: 'QB34', x: xOrigin + spacing * 1, y: yOrigin + spacing * -5 }, - { id: 'QB33', x: xOrigin + spacing * 0, y: yOrigin + spacing * -6 }, - { id: 'QB32', x: xOrigin + spacing * -1, y: yOrigin + spacing * -7 }, - - // Row 5 (9 qubits) - { id: 'QB31', x: xOrigin + spacing * 7, y: yOrigin + spacing * -1 }, - { id: 'QB30', x: xOrigin + spacing * 6, y: yOrigin + spacing * -2 }, - { id: 'QB29', x: xOrigin + spacing * 5, y: yOrigin + spacing * -3 }, - { id: 'QB28', x: xOrigin + spacing * 4, y: yOrigin + spacing * -4 }, - { id: 'QB27', x: xOrigin + spacing * 3, y: yOrigin + spacing * -5 }, - { id: 'QB26', x: xOrigin + spacing * 2, y: yOrigin + spacing * -6 }, - { id: 'QB25', x: xOrigin + spacing * 1, y: yOrigin + spacing * -7 }, - { id: 'QB24', x: xOrigin + spacing * 0, y: yOrigin + spacing * -8 }, - { id: 'QB23', x: xOrigin + spacing * -1, y: yOrigin + spacing * -9 }, - - // Row 6 (8 qubits) - { id: 'QB22', x: xOrigin + spacing * 7, y: yOrigin + spacing * -3 }, - { id: 'QB21', x: xOrigin + spacing * 6, y: yOrigin + spacing * -4 }, - { id: 'QB20', x: xOrigin + spacing * 5, y: yOrigin + spacing * -5 }, - { id: 'QB19', x: xOrigin + spacing * 4, y: yOrigin + spacing * -6 }, - { id: 'QB18', x: xOrigin + spacing * 3, y: yOrigin + spacing * -7 }, - { id: 'QB17', x: xOrigin + spacing * 2, y: yOrigin + spacing * -8 }, - { id: 'QB16', x: xOrigin + spacing * 1, y: yOrigin + spacing * -9 }, - { id: 'QB15', x: xOrigin + spacing * 0, y: yOrigin + spacing * -10 }, - - // Row 7 (7 qubits) - { id: 'QB14', x: xOrigin + spacing * 8, y: yOrigin + spacing * -4 }, - { id: 'QB13', x: xOrigin + spacing * 7, y: yOrigin + spacing * -5 }, - { id: 'QB12', x: xOrigin + spacing * 6, y: yOrigin + spacing * -6 }, - { id: 'QB11', x: xOrigin + spacing * 5, y: yOrigin + spacing * -7 }, - { id: 'QB10', x: xOrigin + spacing * 4, y: yOrigin + spacing * -8 }, - { id: 'QB9', x: xOrigin + spacing * 3, y: yOrigin + spacing * -9 }, - { id: 'QB8', x: xOrigin + spacing * 2, y: yOrigin + spacing * -10 }, - - // Row 8 (6 qubits) - { id: 'QB7', x: xOrigin + spacing * 8, y: yOrigin + spacing * -6 }, - { id: 'QB6', x: xOrigin + spacing * 7, y: yOrigin + spacing * -7 }, - { id: 'QB5', x: xOrigin + spacing * 6, y: yOrigin + spacing * -8 }, - { id: 'QB4', x: xOrigin + spacing * 5, y: yOrigin + spacing * -9 }, - { id: 'QB3', x: xOrigin + spacing * 4, y: yOrigin + spacing * -10 }, - - // Row 9 (2 qubits) - { id: 'QB2', x: xOrigin + spacing * 8, y: yOrigin + spacing * -8 }, - { id: 'QB1', x: xOrigin + spacing * 7, y: yOrigin + spacing * -9 }, - - -]; - -// Define edges based on nearest neighbor connectivity in the diamond pattern -const edges = [ - // Row 1 connections - ['QB54', 'QB53'], ['QB53', 'QB52'], ['QB54', 'QB50'], ['QB53', 'QB49'], ['QB52', 'QB48'], - - // Row 2 connections - ['QB51', 'QB50'], ['QB50', 'QB49'], - ['QB49', 'QB48'], ['QB48', 'QB47'], - - ['QB51', 'QB45'], ['QB50', 'QB44'], - ['QB49', 'QB43'], ['QB48', 'QB42'], ['QB47', 'QB41'], - - // Row 3 connections - ['QB46', 'QB45'], ['QB45', 'QB44'], - ['QB44', 'QB43'], ['QB43', 'QB42'], - ['QB42', 'QB41'], ['QB41', 'QB40'], - - ['QB46', 'QB38'], ['QB45', 'QB37'], - ['QB44', 'QB36'], ['QB43', 'QB35'], - ['QB42', 'QB34'], ['QB41', 'QB33'], ['QB40', 'QB32'], - - - // Row 4 connections - ['QB39', 'QB38'], ['QB38', 'QB37'], - ['QB37', 'QB36'], ['QB36', 'QB35'], - ['QB35', 'QB34'], ['QB34', 'QB33'], - ['QB33', 'QB32'], - - ['QB39', 'QB31'], ['QB38', 'QB30'], - ['QB37', 'QB29'], ['QB36', 'QB28'], - ['QB35', 'QB27'], ['QB34', 'QB26'], - ['QB33', 'QB25'], ['QB32', 'QB24'], - - // Row 5 connections - ['QB31', 'QB30'], ['QB30', 'QB29'], - ['QB29', 'QB28'], ['QB28', 'QB27'], - ['QB27', 'QB26'], ['QB26', 'QB25'], - ['QB25', 'QB24'], ['QB24', 'QB23'], - - ['QB30', 'QB22'], - ['QB29', 'QB21'], ['QB28', 'QB20'], - ['QB27', 'QB19'], ['QB26', 'QB18'], - ['QB25', 'QB17'], ['QB24', 'QB16'], - ['QB23', 'QB15'], - - // Row 6 connections - ['QB22', 'QB21'], ['QB21', 'QB20'], - ['QB20', 'QB19'], ['QB19', 'QB18'], - ['QB18', 'QB17'], ['QB17', 'QB16'], - ['QB16', 'QB15'], - - ['QB22', 'QB14'], ['QB21', 'QB13'], - ['QB20', 'QB12'], ['QB19', 'QB11'], - ['QB18', 'QB10'], ['QB17', 'QB9'], - ['QB16', 'QB8'], - - // Row 7 connections - ['QB14', 'QB13'], ['QB13', 'QB12'], - ['QB12', 'QB11'], ['QB11', 'QB10'], - ['QB10', 'QB9'], ['QB9', 'QB8'], - - ['QB13', 'QB7'], ['QB12', 'QB6'], - ['QB11', 'QB5'], ['QB10', 'QB4'], - ['QB9', 'QB3'], - - - // Row 8 connections - ['QB7', 'QB6'], ['QB6', 'QB5'], ['QB5', 'QB4'], - ['QB4', 'QB3'], - - ['QB6', 'QB2'], ['QB5', 'QB1'], - - // Row 9 connections - ['QB2', 'QB1'] -]; - -export const Q50Layout = ({ calibrationData, qubitMetric, couplerMetric, - qubitMetricFormatted, couplerMetricFormatted, thresholdQubit, thresholdCoupler }) => { - return ( - - ) -} \ No newline at end of file diff --git a/src/components/QcLayouts/Base.jsx b/src/components/QcLayouts/QcLayout.jsx similarity index 50% rename from src/components/QcLayouts/Base.jsx rename to src/components/QcLayouts/QcLayout.jsx index 77fee14a9a6f..ab96ac51a7bc 100644 --- a/src/components/QcLayouts/Base.jsx +++ b/src/components/QcLayouts/QcLayout.jsx @@ -4,8 +4,22 @@ import { getColorForMetricValue } from '../../utils/generateGradient'; import { formatMetricValue } from '../../utils/formatMetricValue'; import { getMetricStatistics } from '../../utils/sidebarUtils'; -export function BaseQcLayout({ rawNodes, edges, spacing, calibrationData, qubitMetric, couplerMetric, - qubitMetricFormatted, couplerMetricFormatted, thresholdQubit, thresholdCoupler }) { +const DEFAULT_NODE_COLOR = '#004E84'; +const GREY = '#888888'; + +// Node rect side length in SVG user units (must match the below). +const NODE_UNITS = 90; +// Largest a qubit node may render on screen, in pixels. The whole layout is +// capped proportionally so nodes/couplers never exceed this, regardless of how +// few qubits a layout has (otherwise small layouts like Q20 scale up huge). +const MAX_NODE_PX = 48; + +export function QcLayout({ layout, metrics }) { + const { spacing, nodes, edges } = layout; + const { + calibrationData, qubitMetric, couplerMetric, + qubitMetricFormatted, couplerMetricFormatted, thresholdQubit, thresholdCoupler, + } = metrics; const [hoveredNode, setHoveredNode] = useState(null); const [hoveredEdge, setHoveredEdge] = useState(null); @@ -13,150 +27,77 @@ export function BaseQcLayout({ rawNodes, edges, spacing, calibrationData, qubitM const [tooltip, setTooltip] = useState(null); const containerRef = useRef(null); - // Get metric value for a specific qubit - const getQubitMetricValue = (qubitId) => { - if (!calibrationData || !qubitMetric || !calibrationData[qubitMetric]) { - return null; - } - - const metricData = calibrationData[qubitMetric][qubitId]; - return metricData?.value ?? null; + // Get the metric value for a given id (qubit id or coupler key) + const getMetricValue = (metric, id) => { + if (!calibrationData || !metric || !calibrationData[metric]) return null; + return calibrationData[metric][id]?.value ?? null; }; - // Get unit for the current metric - const getMetricUnit = () => { - if (!calibrationData || !qubitMetric || !calibrationData[qubitMetric]) { - return ''; - } + // Get the unit for a metric, read from the first entry that has one + const getMetricUnit = (metric) => { + if (!calibrationData || !metric || !calibrationData[metric]) return ''; + const entry = Object.values(calibrationData[metric]).find(data => data?.unit); + return entry?.unit || ''; + }; - // Try to get unit from first available qubit - const firstQubitData = Object.values(calibrationData[qubitMetric]).find(data => data?.unit); - return firstQubitData?.unit || ''; + // Resolve a coupler key from the two endpoints (try both orderings) + const couplerKey = (a, b) => { + if (getMetricValue(couplerMetric, `${a}__${b}`) !== null) return `${a}__${b}`; + return `${b}__${a}`; }; - // Get color for a specific qubit - const getQubitColor = (qubitId) => { - // If no metric is selected, return default blue - if (!qubitMetric || qubitMetric == '') { - return '#004E84'; - } - // If qubit does not exist in calibration data, return grey - if (!calibrationData || !calibrationData[qubitMetric] || !(qubitId in calibrationData[qubitMetric])) { - return '#888888'; - } + // Color for a metric value. dim: 1 = qubit, 2 = coupler. + const getColor = (metric, id, dim, threshold) => { + // No metric selected: qubits get the brand blue, couplers a light grey + if (!metric || metric === '') return dim === 1 ? DEFAULT_NODE_COLOR : '#aaa'; + if (!calibrationData || !calibrationData[metric]) return GREY; - const value = getQubitMetricValue(qubitId); - if (value === null || value === undefined) { - return '#888888'; - } + const value = getMetricValue(metric, id); + if (value === null || value === undefined) return GREY; - const stats = getMetricStatistics(calibrationData, qubitMetric, 1); - if (!stats) { - return '#888888'; - } + const stats = getMetricStatistics(calibrationData, metric, dim); + if (!stats) return GREY; - // For metrics where lower values are better (like error rates, times) - const unit = getMetricUnit(); let worst = stats.worst; let best = stats.best; - // Reverse the scale for certain metrics where lower is better - if ((qubitMetric && qubitMetric.includes("error"))) { + // Reverse the scale for metrics where lower is better (error rates) + if (metric.includes('error')) { worst = stats.best; best = stats.worst; - if (parseFloat(value) > thresholdQubit) { - return '#888888'; // Grey if above threshold (higher error is worse) - } - } - else { - if (parseFloat(value) < thresholdQubit) { - return '#888888'; // Grey if below threshold (lower value is worse) - } + if (parseFloat(value) > threshold) return GREY; // above threshold = worse + } else if (parseFloat(value) < threshold) { + return GREY; // below threshold = worse } return getColorForMetricValue(value, worst, best, stats.average); }; - // Get coupler metric value for a specific coupler - const getCouplerMetricValue = (coupleId) => { - if (!calibrationData || !couplerMetric || !calibrationData[couplerMetric]) { - return null; - } + // Build tooltip content for a hovered qubit or coupler. + // label is the displayed title (e.g. "Qubit: QB1" / "Coupler: QB1__QB2"). + const buildTooltip = (type, metric, id, formatted, label) => { + const value = getMetricValue(metric, id); + const unit = getMetricUnit(metric); + const metricInfo = value !== null && formatted ? `${formatted}: ${formatMetricValue(value, unit)}` : ''; - const metricData = calibrationData[couplerMetric][coupleId]; - return metricData?.value ?? null; - }; + const uncertainty = calibrationData?.[metric]?.[id]?.uncertainty; + const uncertaintyInfo = (uncertainty !== undefined && uncertainty !== null && uncertainty !== 'None') + ? ` (±${formatMetricValue(uncertainty, unit)})` + : ''; - // Get unit for the current coupler metric - const getCouplerMetricUnit = () => { - if (!calibrationData || !couplerMetric || !calibrationData[couplerMetric]) { - return ''; - } + const details = (metric && metric !== '' && value === null) + ? 'Disabled' + : `${metricInfo}${uncertaintyInfo}`; - // Try to get unit from first available coupler - const firstCouplerData = Object.values(calibrationData[couplerMetric]).find(data => data?.unit); - return firstCouplerData?.unit || ''; - }; - - // Get color for a specific coupler/edge - const getCouplerColor = (nodeA, nodeB) => { - // If no metric is selected, return default grey - if (!couplerMetric || couplerMetric == '') { - return '#aaa'; - } - - // Create coupler key - const coupleId1 = `${nodeA}__${nodeB}`; - const coupleId2 = `${nodeB}__${nodeA}`; - - // If coupler does not exist in calibration data, return grey - if (!calibrationData || !calibrationData[couplerMetric]) { - return '#888888'; - } - - let value = getCouplerMetricValue(coupleId1); - if (value === null) { - value = getCouplerMetricValue(coupleId2); - } - if (value === null || value === undefined) { - return '#888888'; // Grey if no data found - } - - const stats = getMetricStatistics(calibrationData, couplerMetric, 2); - if (!stats) { - return '#888888'; - } - - // For metrics where lower values are better - const unit = getCouplerMetricUnit(); - let worst = stats.worst; - let best = stats.best; - - // Reverse the scale for certain metrics where lower is better - if ((couplerMetric && couplerMetric.includes("error"))) { - worst = stats.best; - best = stats.worst; - - if (parseFloat(value) > thresholdCoupler) { - return '#888888'; // Grey if above threshold (higher error is worse) - } - } - else { - if (parseFloat(value) < thresholdCoupler) { - return '#888888'; // Grey if below threshold (lower value is worse) - } - } - - return getColorForMetricValue(value, worst, best, stats.average); + return { type, content: label, details }; }; // Handle mouse move to update tooltip position relative to the container const handleMouseMove = (event) => { if (containerRef.current) { const rect = containerRef.current.getBoundingClientRect(); - setMousePos({ x: event.clientX - rect.left, - y: event.clientY - rect.top + y: event.clientY - rect.top, }); } }; @@ -201,12 +142,12 @@ export function BaseQcLayout({ rawNodes, edges, spacing, calibrationData, qubitM return { left, top }; }; - // Build coordinate map directly from raw nodes - const coordMap = Object.fromEntries(rawNodes.map(n => [n.id, n])); + // Build coordinate map directly from nodes + const coordMap = Object.fromEntries(nodes.map(n => [n.id, n])); // Compute dynamic bounds - const xs = rawNodes.map(n => n.x); - const ys = rawNodes.map(n => n.y); + const xs = nodes.map(n => n.x); + const ys = nodes.map(n => n.y); const minX = Math.min(...xs) - spacing; const maxX = Math.max(...xs) + spacing; const minY = Math.min(...ys) - spacing; @@ -215,8 +156,10 @@ export function BaseQcLayout({ rawNodes, edges, spacing, calibrationData, qubitM const viewBoxHeight = maxY - minY; const viewBox = `${minX} ${-maxY} ${viewBoxWidth} ${viewBoxHeight}`; - // Calculate max width based on number of qubits - const maxWidth = rawNodes.length * 100; + // Cap the rendered size so an SVG unit never maps to more pixels than + // MAX_NODE_PX / NODE_UNITS — i.e. nodes/couplers stay the same on-screen size + // across layouts. Use the larger viewBox dimension so neither axis overshoots. + const maxWidth = (MAX_NODE_PX / NODE_UNITS) * Math.max(viewBoxWidth, viewBoxHeight); return (
{ - const coupleId1 = `${a}__${b}`; - const coupleId2 = `${b}__${a}`; - const metricValue = getCouplerMetricValue(coupleId1) ?? getCouplerMetricValue(coupleId2); - const unit = getCouplerMetricUnit(); - const formattedValue = formatMetricValue(metricValue, unit); - const metricInfo = metricValue !== null && couplerMetricFormatted ? `${couplerMetricFormatted}: ${formattedValue}` : ''; setHoveredEdge(key); - let uncertainty = null; - const coupleId = getCouplerMetricValue(coupleId1) !== null ? coupleId1 : coupleId2; - if ( - calibrationData && - couplerMetric && - calibrationData[couplerMetric] && - calibrationData[couplerMetric][coupleId] && - calibrationData[couplerMetric][coupleId].uncertainty !== undefined - ) { - uncertainty = calibrationData[couplerMetric][coupleId].uncertainty; - } - let uncertaintyInfo = (uncertainty !== null && uncertainty !== "None") ? ` (±${formatMetricValue(uncertainty, unit)})` : ''; - let details; - if (couplerMetric && couplerMetric !== '' && metricValue === null) { - details = `Disabled`; - } else { - details = `${metricInfo}${uncertaintyInfo}`; - } - setTooltip({ - type: 'edge', - content: `Coupler: ${a}__${b}`, - details - }); + setTooltip(buildTooltip('edge', couplerMetric, couplerKey(a, b), couplerMetricFormatted, `Coupler: ${a}__${b}`)); }} onMouseLeave={() => { setHoveredEdge(null); setTooltip(null); }} className={hover ? 'cursor-pointer filter drop-shadow-md' : 'cursor-pointer'} /> ); })} - {rawNodes.map(n => { + {nodes.map(n => { const hover = hoveredNode === n.id; - const nodeColor = getQubitColor(n.id); + const nodeColor = getColor(qubitMetric, n.id, 1, thresholdQubit); return ( { - const metricValue = getQubitMetricValue(n.id); - const unit = getMetricUnit(); - const formattedValue = formatMetricValue(metricValue, unit); - const metricInfo = metricValue !== null ? `${qubitMetricFormatted}: ${formattedValue}` : ''; setHoveredNode(n.id); - let uncertainty = null; - if ( - calibrationData && - qubitMetric && - calibrationData[qubitMetric] && - calibrationData[qubitMetric][n.id] && - calibrationData[qubitMetric][n.id].uncertainty !== undefined - ) { - uncertainty = calibrationData[qubitMetric][n.id].uncertainty; - } - let uncertaintyInfo = (uncertainty !== null && uncertainty !== "None") ? ` (±${formatMetricValue(uncertainty, unit)})` : ''; - let details; - if (qubitMetric && qubitMetric !== '' && metricValue === null) { - details = `Disabled`; - } else { - details = `${metricInfo}${uncertaintyInfo}`; - } - setTooltip({ - type: 'node', - content: `Qubit: ${n.id}`, - details - }); + setTooltip(buildTooltip('node', qubitMetric, n.id, qubitMetricFormatted, `Qubit: ${n.id}`)); }} onMouseLeave={() => { setHoveredNode(null); setTooltip(null); }} className={hover ? 'cursor-pointer filter drop-shadow-lg' : 'cursor-pointer'} diff --git a/src/components/QcLayouts/layouts.js b/src/components/QcLayouts/layouts.js new file mode 100644 index 000000000000..37b8aeb25ddf --- /dev/null +++ b/src/components/QcLayouts/layouts.js @@ -0,0 +1,94 @@ +// Layout data for quantum computer topology maps. +// +// To add a new QC: add one entry to QC_LAYOUTS keyed by the lowercased device_id, +// with { spacing, nodes, edges }. No new component or call-site change is needed. +// +// Nodes are positioned on a diagonal grid. `grid(xOrigin, yOrigin, spacing)` returns +// a helper `(id, col, row) => { id, x, y }` so coordinates read as grid offsets instead +// of repeated `xOrigin + spacing * N` arithmetic. + +const grid = (xOrigin, yOrigin, spacing) => (id, col, row) => ({ + id, + x: xOrigin + spacing * col, + y: yOrigin + spacing * row, +}); + +// --- Q20 --------------------------------------------------------------------- +const q20Spacing = 100; +const q20 = grid(-210, 440, q20Spacing); +const q20Nodes = [ + // Row 1 (3 qubits) + q20('QB20', 0, 0), q20('QB19', -1, -1), q20('QB18', -2, -2), + // Row 2 (5 qubits) + q20('QB17', 2, 0), q20('QB16', 1, -1), q20('QB15', 0, -2), q20('QB14', -1, -3), q20('QB13', -2, -4), + // Row 3 (5 qubits) + q20('QB12', 3, -1), q20('QB11', 2, -2), q20('QB10', 1, -3), q20('QB9', 0, -4), q20('QB8', -1, -5), + // Row 4 (5 qubits) + q20('QB7', 4, -2), q20('QB6', 3, -3), q20('QB5', 2, -4), q20('QB4', 1, -5), q20('QB3', 0, -6), + // Row 5 (2 qubits) + q20('QB2', 3, -5), q20('QB1', 2, -6), +]; +const q20Edges = [ + ['QB1', 'QB2'], ['QB1', 'QB4'], ['QB3', 'QB4'], ['QB3', 'QB8'], ['QB4', 'QB5'], + ['QB5', 'QB2'], ['QB5', 'QB6'], ['QB5', 'QB10'], ['QB7', 'QB6'], ['QB7', 'QB12'], + ['QB9', 'QB4'], ['QB9', 'QB8'], ['QB9', 'QB10'], ['QB9', 'QB14'], ['QB11', 'QB6'], + ['QB11', 'QB10'], ['QB11', 'QB12'], ['QB11', 'QB16'], ['QB13', 'QB8'], ['QB13', 'QB14'], + ['QB15', 'QB10'], ['QB15', 'QB14'], ['QB15', 'QB16'], ['QB15', 'QB19'], ['QB17', 'QB12'], + ['QB17', 'QB16'], ['QB18', 'QB14'], ['QB18', 'QB19'], ['QB20', 'QB16'], ['QB20', 'QB19'], +]; + +// --- Q50 --------------------------------------------------------------------- +const q50Spacing = 100; +const q50 = grid(-210, 440, q50Spacing); +const q50Nodes = [ + // Row 1 (3 qubits) + q50('QB54', 0, 0), q50('QB53', -1, -1), q50('QB52', -2, -2), + // Row 2 (5 qubits) + q50('QB51', 2, 0), q50('QB50', 1, -1), q50('QB49', 0, -2), q50('QB48', -1, -3), q50('QB47', -2, -4), + // Row 3 (7 qubits) + q50('QB46', 4, 0), q50('QB45', 3, -1), q50('QB44', 2, -2), q50('QB43', 1, -3), q50('QB42', 0, -4), q50('QB41', -1, -5), q50('QB40', -2, -6), + // Row 4 (8 qubits) + q50('QB39', 6, 0), q50('QB38', 5, -1), q50('QB37', 4, -2), q50('QB36', 3, -3), q50('QB35', 2, -4), q50('QB34', 1, -5), q50('QB33', 0, -6), q50('QB32', -1, -7), + // Row 5 (9 qubits) + q50('QB31', 7, -1), q50('QB30', 6, -2), q50('QB29', 5, -3), q50('QB28', 4, -4), q50('QB27', 3, -5), q50('QB26', 2, -6), q50('QB25', 1, -7), q50('QB24', 0, -8), q50('QB23', -1, -9), + // Row 6 (8 qubits) + q50('QB22', 7, -3), q50('QB21', 6, -4), q50('QB20', 5, -5), q50('QB19', 4, -6), q50('QB18', 3, -7), q50('QB17', 2, -8), q50('QB16', 1, -9), q50('QB15', 0, -10), + // Row 7 (7 qubits) + q50('QB14', 8, -4), q50('QB13', 7, -5), q50('QB12', 6, -6), q50('QB11', 5, -7), q50('QB10', 4, -8), q50('QB9', 3, -9), q50('QB8', 2, -10), + // Row 8 (5 qubits) + q50('QB7', 8, -6), q50('QB6', 7, -7), q50('QB5', 6, -8), q50('QB4', 5, -9), q50('QB3', 4, -10), + // Row 9 (2 qubits) + q50('QB2', 8, -8), q50('QB1', 7, -9), +]; +const q50Edges = [ + // Row 1 connections + ['QB54', 'QB53'], ['QB53', 'QB52'], ['QB54', 'QB50'], ['QB53', 'QB49'], ['QB52', 'QB48'], + // Row 2 connections + ['QB51', 'QB50'], ['QB50', 'QB49'], ['QB49', 'QB48'], ['QB48', 'QB47'], + ['QB51', 'QB45'], ['QB50', 'QB44'], ['QB49', 'QB43'], ['QB48', 'QB42'], ['QB47', 'QB41'], + // Row 3 connections + ['QB46', 'QB45'], ['QB45', 'QB44'], ['QB44', 'QB43'], ['QB43', 'QB42'], ['QB42', 'QB41'], ['QB41', 'QB40'], + ['QB46', 'QB38'], ['QB45', 'QB37'], ['QB44', 'QB36'], ['QB43', 'QB35'], ['QB42', 'QB34'], ['QB41', 'QB33'], ['QB40', 'QB32'], + // Row 4 connections + ['QB39', 'QB38'], ['QB38', 'QB37'], ['QB37', 'QB36'], ['QB36', 'QB35'], ['QB35', 'QB34'], ['QB34', 'QB33'], ['QB33', 'QB32'], + ['QB39', 'QB31'], ['QB38', 'QB30'], ['QB37', 'QB29'], ['QB36', 'QB28'], ['QB35', 'QB27'], ['QB34', 'QB26'], ['QB33', 'QB25'], ['QB32', 'QB24'], + // Row 5 connections + ['QB31', 'QB30'], ['QB30', 'QB29'], ['QB29', 'QB28'], ['QB28', 'QB27'], ['QB27', 'QB26'], ['QB26', 'QB25'], ['QB25', 'QB24'], ['QB24', 'QB23'], + ['QB30', 'QB22'], ['QB29', 'QB21'], ['QB28', 'QB20'], ['QB27', 'QB19'], ['QB26', 'QB18'], ['QB25', 'QB17'], ['QB24', 'QB16'], ['QB23', 'QB15'], + // Row 6 connections + ['QB22', 'QB21'], ['QB21', 'QB20'], ['QB20', 'QB19'], ['QB19', 'QB18'], ['QB18', 'QB17'], ['QB17', 'QB16'], ['QB16', 'QB15'], + ['QB22', 'QB14'], ['QB21', 'QB13'], ['QB20', 'QB12'], ['QB19', 'QB11'], ['QB18', 'QB10'], ['QB17', 'QB9'], ['QB16', 'QB8'], + // Row 7 connections + ['QB14', 'QB13'], ['QB13', 'QB12'], ['QB12', 'QB11'], ['QB11', 'QB10'], ['QB10', 'QB9'], ['QB9', 'QB8'], + ['QB13', 'QB7'], ['QB12', 'QB6'], ['QB11', 'QB5'], ['QB10', 'QB4'], ['QB9', 'QB3'], + // Row 8 connections + ['QB7', 'QB6'], ['QB6', 'QB5'], ['QB5', 'QB4'], ['QB4', 'QB3'], + ['QB6', 'QB2'], ['QB5', 'QB1'], + // Row 9 connections + ['QB2', 'QB1'], +]; + +export const QC_LAYOUTS = { + q20: { spacing: q20Spacing, nodes: q20Nodes, edges: q20Edges }, + q50: { spacing: q50Spacing, nodes: q50Nodes, edges: q50Edges }, +}; diff --git a/src/components/StatusModal/StatusModalConent.jsx b/src/components/StatusModal/StatusModalConent.jsx index 8049436bd542..dcfdbec1fed2 100644 --- a/src/components/StatusModal/StatusModalConent.jsx +++ b/src/components/StatusModal/StatusModalConent.jsx @@ -2,8 +2,8 @@ import React from 'react' import { useState } from 'react' import { useCalibration } from '../../hooks/useCalibration'; import { useDeviceInfo } from '../../hooks/useDeviceInfo'; -import { Q50Layout } from '../QcLayouts/Q50'; -import { Q20Layout } from '../QcLayouts/Q20'; +import { QcLayout } from '../QcLayouts/QcLayout'; +import { QC_LAYOUTS } from '../QcLayouts/layouts'; import { Overview } from './StatusOverview'; import { CalibrationTable } from './CalibrationTable'; import { SideBar } from './SideBar'; @@ -99,25 +99,18 @@ export const ModalContent = (props) => {
- {props.device_id.toLowerCase() === 'q50' ? ( - m.value === metricsState.qubitMetric)?.name || metricsState.qubitMetric} - couplerMetricFormatted={couplerMetricOptions.find(m => m.value === metricsState.couplerMetric)?.name || metricsState.couplerMetric} - thresholdQubit={metricsState.thresholdQubitValue} - thresholdCoupler={metricsState.thresholdCouplerValue} - /> - ) : props.device_id.toLowerCase() === 'q20' ? ( - m.value === metricsState.qubitMetric)?.name || metricsState.qubitMetric} - couplerMetricFormatted={couplerMetricOptions.find(m => m.value === metricsState.couplerMetric)?.name || metricsState.couplerMetric} - thresholdQubit={metricsState.thresholdQubitValue} - thresholdCoupler={metricsState.thresholdCouplerValue} + {QC_LAYOUTS[props.device_id.toLowerCase()] ? ( + m.value === metricsState.qubitMetric)?.name || metricsState.qubitMetric, + couplerMetricFormatted: couplerMetricOptions.find(m => m.value === metricsState.couplerMetric)?.name || metricsState.couplerMetric, + thresholdQubit: metricsState.thresholdQubitValue, + thresholdCoupler: metricsState.thresholdCouplerValue, + }} /> ) : null}
From 7826c0b31c41d254e4c01abd4bc61d0b4ee98c6c Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 3 Jun 2026 12:29:35 +0300 Subject: [PATCH 24/31] change api --- src/config/api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/api.js b/src/config/api.js index d085c2efa315..a690bbce64dd 100644 --- a/src/config/api.js +++ b/src/config/api.js @@ -1 +1 @@ -export const API_BASE_URL = "http://localhost:3000" //"https://fiqci-backend.2.rahtiapp.fi"; \ No newline at end of file +export const API_BASE_URL = "https://fiqci-backend.2.rahtiapp.fi" //"https://fiqci-backend.2.rahtiapp.fi"; \ No newline at end of file From 21f05f015ce9402ab87f1def0052ba0b66dffb04 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Mon, 8 Jun 2026 16:58:05 +0300 Subject: [PATCH 25/31] localhost for testing --- src/config/api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/api.js b/src/config/api.js index a690bbce64dd..d085c2efa315 100644 --- a/src/config/api.js +++ b/src/config/api.js @@ -1 +1 @@ -export const API_BASE_URL = "https://fiqci-backend.2.rahtiapp.fi" //"https://fiqci-backend.2.rahtiapp.fi"; \ No newline at end of file +export const API_BASE_URL = "http://localhost:3000" //"https://fiqci-backend.2.rahtiapp.fi"; \ No newline at end of file From 553ff35a1bd5d53ec749b440f01ae47fbc9949e4 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Mon, 8 Jun 2026 16:58:14 +0300 Subject: [PATCH 26/31] update metrics --- src/config/deviceMetrics.js | 40 ++----------------------------------- 1 file changed, 2 insertions(+), 38 deletions(-) diff --git a/src/config/deviceMetrics.js b/src/config/deviceMetrics.js index 35f51a956ed6..9a9ca09818b3 100644 --- a/src/config/deviceMetrics.js +++ b/src/config/deviceMetrics.js @@ -35,42 +35,6 @@ export const DEVICE_METRICS = { ], }, - Q20: { - overview: { - single: { - singleGateFidelity: ['prx_rb_drag_crf_fidelity', 'prx_rb_fidelity'], - readoutFidelity: ['measure_fidelity_ssro_constant_fidelity', 'measure_ssro_fidelity'], - t1: ['t1_time'], - t2: ['t2_time'], - }, - coupler: { - twoQubitFidelity: ['cz_irb_tgss_crf_fidelity', 'cz_irb_fidelity'], - cliffordFidelity: ['clifford_rb_uz_cz_fidelity', 'clifford_rb_fidelity'], - }, - }, - qubitOptions: [ - { name: 'T1 Time', value: 't1_time' }, - { name: 'T2 Time', value: 't2_time' }, - { name: 'T2 Echo Time', value: 't2_echo_time' }, - { name: 'PRX Gate Fidelity', value: 'prx_rb_drag_crf_fidelity' }, - { name: 'Clifford Gate Fidelity', value: 'clifford_rb_xy_fidelity' }, - { name: '1->0 Readout Error', value: 'measure_fidelity_ssro_constant_error_1_to_0' }, - { name: '0->1 Readout Error', value: 'measure_fidelity_ssro_constant_error_0_to_1' }, - { name: 'Readout Fidelity', value: 'measure_fidelity_ssro_constant_fidelity' }, - { name: '1->0 MCM Error', value: 'measure_ssro_constant_error_1_to_0', title: 'MCM = Mid Circuit Measurement' }, - { name: '0->1 MCM Error', value: 'measure_ssro_constant_error_0_to_1', title: 'MCM = Mid Circuit Measurement' }, - { name: 'MCM Fidelity', value: 'measure_ssro_constant_fidelity', title: 'MCM = Mid Circuit Measurement' }, - { name: 'QNDness Fidelity', value: 'measure_qndness_constant_fidelity', title: 'QND = Quantum Non-Demolition' }, - { name: 'QNDness 0 State', value: 'measure_qndness_constant_qndness_0', title: 'QND = Quantum Non-Demolition' }, - { name: 'QNDness 1 State', value: 'measure_qndness_constant_qndness_1', title: 'QND = Quantum Non-Demolition' }, - { name: 'QNDness Repeatability', value: 'measure_qndness_constant_repeatability', title: 'QND = Quantum Non-Demolition' }, - ], - couplerOptions: [ - { name: 'CZ Gate Fidelity', value: 'cz_irb_tgss_crf_fidelity' }, - { name: 'Clifford Gate Fidelity', value: 'clifford_rb_uz_cz_fidelity' }, - ], - }, - Q20: { overview: { single: { @@ -88,7 +52,7 @@ export const DEVICE_METRICS = { { name: 'T1 Time', value: 't1_time' }, { name: 'T2 Time', value: 't2_time' }, { name: 'T2 Echo Time', value: 't2_echo_time' }, - { name: 'PRX Gate Fidelity', value: 'prx_rb_drag_crf_sx_fidelity' }, + { name: 'PRX Gate Fidelity', value: 'prx_rb_drag_crf_fidelity' }, { name: 'Clifford Gate Fidelity', value: 'clifford_rb_xy_fidelity' }, { name: '1->0 Readout Error', value: 'measure_fidelity_ssro_constant_error_1_to_0' }, { name: '0->1 Readout Error', value: 'measure_fidelity_ssro_constant_error_0_to_1' }, @@ -125,7 +89,7 @@ export const DEVICE_METRICS = { { name: 'T2 Time', value: 't2_time' }, { name: 'T2 Echo Time', value: 't2_echo_time' }, { name: 'PRX Gate Fidelity', value: 'prx_rb_drag_crf_sx_fidelity' }, - { name: 'Clifford Gate Fidelity', value: 'clifford_rb_xy_fidelity' }, + { name: 'Clifford Gate Fidelity', value: 'clifford_rb_xy_sx_fidelity' }, { name: '1->0 Readout Error', value: 'measure_fidelity_ssro_constant_error_1_to_0' }, { name: '0->1 Readout Error', value: 'measure_fidelity_ssro_constant_error_0_to_1' }, { name: 'Readout Fidelity', value: 'measure_fidelity_ssro_constant_fidelity' }, From d1b8237fd4b8087b17b72e6705572d8b0ff8a79d Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Mon, 8 Jun 2026 17:13:18 +0300 Subject: [PATCH 27/31] use real url --- src/config/api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/api.js b/src/config/api.js index d085c2efa315..a690bbce64dd 100644 --- a/src/config/api.js +++ b/src/config/api.js @@ -1 +1 @@ -export const API_BASE_URL = "http://localhost:3000" //"https://fiqci-backend.2.rahtiapp.fi"; \ No newline at end of file +export const API_BASE_URL = "https://fiqci-backend.2.rahtiapp.fi" //"https://fiqci-backend.2.rahtiapp.fi"; \ No newline at end of file From 6032f84da3868d6d4bbf2570877aa88a50e45d52 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Tue, 9 Jun 2026 15:05:16 +0300 Subject: [PATCH 28/31] zoom/move controls for layout --- package-lock.json | 32 +++++++++++++++- package.json | 3 +- src/components/QcLayouts/QcLayout.jsx | 54 ++++++++++++++++++++++----- 3 files changed, 77 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 215cf5c6e44e..635bf7335246 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,8 @@ "katex": "^0.16.21", "lunr": "^2.3.9", "prismjs": "^1.30.0", - "react-calendar": "^6.0.0" + "react-calendar": "^6.0.0", + "react-zoom-pan-pinch": "^4.0.3" }, "devDependencies": { "@babel/core": "^7.26.0", @@ -3659,6 +3660,21 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -5082,6 +5098,20 @@ "integrity": "sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug==", "license": "MIT" }, + "node_modules/react-zoom-pan-pinch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/react-zoom-pan-pinch/-/react-zoom-pan-pinch-4.0.3.tgz", + "integrity": "sha512-N2Hi6L78fFmhRra+ORpFSW7WST5x6kxpOPplIvtB0b7b+U2anpo1z1wLgaWRPS2kUSqcraRG+JgBCIlDJnqqAg==", + "license": "MIT", + "engines": { + "node": ">=8", + "npm": ">=5" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", diff --git a/package.json b/package.json index 0fe90016a244..5b0045266fc9 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,8 @@ "katex": "^0.16.21", "lunr": "^2.3.9", "prismjs": "^1.30.0", - "react-calendar": "^6.0.0" + "react-calendar": "^6.0.0", + "react-zoom-pan-pinch": "^4.0.3" }, "private": "true" } diff --git a/src/components/QcLayouts/QcLayout.jsx b/src/components/QcLayouts/QcLayout.jsx index ab96ac51a7bc..8e1cb93c56a3 100644 --- a/src/components/QcLayouts/QcLayout.jsx +++ b/src/components/QcLayouts/QcLayout.jsx @@ -1,5 +1,6 @@ import React, { useState, useRef } from 'react'; import { motion } from 'framer-motion'; +import { TransformWrapper, TransformComponent } from 'react-zoom-pan-pinch'; import { getColorForMetricValue } from '../../utils/generateGradient'; import { formatMetricValue } from '../../utils/formatMetricValue'; import { getMetricStatistics } from '../../utils/sidebarUtils'; @@ -169,13 +170,45 @@ export function QcLayout({ layout, metrics }) { onMouseMove={handleMouseMove} onMouseLeave={() => { setHoveredNode(null); setHoveredEdge(null); setTooltip(null); }} > - setTooltip(null)} > - {edges.map(([a, b]) => { + {({ zoomIn, zoomOut, resetTransform }) => ( + <> +
+ + + +
+ + + {edges.map(([a, b]) => { const A = coordMap[a]; const B = coordMap[b]; const key = `${a}-${b}`; @@ -234,7 +267,11 @@ export function QcLayout({ layout, metrics }) {
); })} - + + + + )} + {tooltip && (
- {/* Arrow pointing to the element */} -
- {/* Header with icon */}
From f1191b78feab4e082a336d5a9d14f1fb6b062e85 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Mon, 13 Jul 2026 10:56:49 +0300 Subject: [PATCH 29/31] =?UTF-8?q?u=C3=A5date=20call=20links?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- content/_data/constants.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/content/_data/constants.yml b/content/_data/constants.yml index 35a9cec9892a..52bfcb79f01d 100644 --- a/content/_data/constants.yml +++ b/content/_data/constants.yml @@ -44,10 +44,10 @@ cookie_consent: tagline: Making the power of quantum computing accessible subtitle: FiQCI – Finnish quantum-computing infrastructure announcement: - text: The VTT Q50 Quantum Computer is now available for use through FiQCI! + text: The Aalto Q20 Quantum Computer is now available for use through FiQCI! link: - title: How to get access to the Q50 quantum computer? - href: "/publications/2026-05-11-Academic-Call-2026-02" + title: How to get access to FiQCI quantum computers? + href: "/resource-call" buttons: - title: How to get access href: "/access" @@ -117,7 +117,7 @@ cookie_consent: Aalto Q20 is an IQM superconducting quantum computer with 20 qubits in a square grid topology. It is operated by Aalto University. image: "/assets/images/aalto-qc/aalto-q20.webp" links: - - link: "/publications/2026-05-11-Academic-Call-2026-02" + - link: "/resource-call" teaser: "Apply for access to Aalto Q20" - link: "https://docs.csc.fi/computing/quantum-computing/running-quantum-jobs/" teaser: "How to access Aalto Q20, instructions" @@ -129,7 +129,7 @@ cookie_consent: Hybrid HPC+QC access was opened in 2025. VTT Q50 is operated by VTT, and was co-developed with IQM Quantum Computers. image: "/assets/images/vtt-images/VTT_lab-2.webp" links: - - link: "/publications/2026-05-11-Academic-Call-2026-02" + - link: "/resource-call" teaser: "Apply for access to VTT Q50" - link: "https://docs.csc.fi/computing/quantum-computing/running-quantum-jobs/" teaser: "How to access VTT Q50, instructions" @@ -241,7 +241,7 @@ cookie_consent: Additionally, users should also acknowledge using FiQCI quantum computers if applicable: - acknowledge-link-url: /publications/2026-05-11-Academic-Call-2026-02 + acknowledge-link-url: /resource-call acknowledge-link-text: see here "/status/": From 92441405980f0a8c3a5f8c8a98a527279d6d72cd Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Tue, 21 Jul 2026 14:41:50 +0300 Subject: [PATCH 30/31] npm audit fix --- package-lock.json | 175 ++++++++++++++++++++++++---------------------- 1 file changed, 91 insertions(+), 84 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6ef84e82be11..2cab621f6e71 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,20 +53,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@babel/code-frame": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", @@ -82,9 +68,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.0.tgz", - "integrity": "sha512-qETICbZSLe7uXv9VE8T/RWOdIE5qqyTucOt4zLYMafj2MRO271VGgLd4RACJMeBO37UPWhXiKMBk7YlJ0fOzQA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", "engines": { @@ -92,22 +78,22 @@ } }, "node_modules/@babel/core": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", - "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.26.0", - "@babel/generator": "^7.26.0", - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helpers": "^7.26.0", - "@babel/parser": "^7.26.0", - "@babel/template": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.26.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -123,14 +109,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -167,14 +153,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", - "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.25.9", - "@babel/helper-validator-option": "^7.25.9", + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -241,9 +227,9 @@ } }, "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, "license": "MIT", "engines": { @@ -265,29 +251,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -402,9 +388,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", - "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, "license": "MIT", "engines": { @@ -1679,18 +1665,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", "debug": "^4.3.1" }, "engines": { @@ -1791,6 +1777,17 @@ "@jridgewell/trace-mapping": "^0.3.24" } }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -2542,9 +2539,9 @@ } }, "node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -3145,9 +3142,9 @@ } }, "node_modules/dompurify": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.0.tgz", - "integrity": "sha512-nolgK9JcaUXMSmW+j1yaSvaEaoXYHwWyGJlkoCTghc97KgGDDSnpoU/PlEnw63Ah+TGKFOyY+X5LnxaWbCSfXg==", + "version": "3.4.12", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.12.tgz", + "integrity": "sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -3648,9 +3645,9 @@ } }, "node_modules/front-matter/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.0.tgz", + "integrity": "sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==", "license": "MIT", "dependencies": { "argparse": "^1.0.7", @@ -3791,9 +3788,9 @@ } }, "node_modules/gray-matter/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.0.tgz", + "integrity": "sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==", "license": "MIT", "dependencies": { "argparse": "^1.0.7", @@ -4115,10 +4112,20 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -5409,9 +5416,9 @@ } }, "node_modules/shell-quote": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz", - "integrity": "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.10.0.tgz", + "integrity": "sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==", "dev": true, "license": "MIT", "engines": { From e2773b70f33f591ab9f4e9c7367a6f7b6fabb654 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Tue, 21 Jul 2026 14:47:46 +0300 Subject: [PATCH 31/31] update notifications --- content/_data/constants.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/_data/constants.yml b/content/_data/constants.yml index 52bfcb79f01d..3e967bb528f7 100644 --- a/content/_data/constants.yml +++ b/content/_data/constants.yml @@ -44,7 +44,7 @@ cookie_consent: tagline: Making the power of quantum computing accessible subtitle: FiQCI – Finnish quantum-computing infrastructure announcement: - text: The Aalto Q20 Quantum Computer is now available for use through FiQCI! + text: Access to the Aalto Q20 Quantum Computer is coming soon! link: title: How to get access to FiQCI quantum computers? href: "/resource-call" @@ -253,7 +253,7 @@ cookie_consent: href: https://www.lumi-supercomputer.eu/lumi-service-status/ alerts: # Each alert renders as its own banner. type can be info, warning, or error - text: |- - Aalto Q20 is now available for use. + Aalto Q20 access is coming soon! type: info - text: |- VTT Q50 will be down for maintenance from 18th June to 17th July. During this time, the Q50 will not be available for use. We apologize for any inconvenience this may cause.