microsoft/qdk
Publicmirrored from https://github.com/microsoft/qdkAvailable
source/npm/qsharp/ux/estimatesOverview.tsx
317lines · modecode
| 1 | // Copyright (c) Microsoft Corporation. |
| 2 | // Licensed under the MIT License. |
| 3 | |
| 4 | // A component including the results table and the scatter chart together. |
| 5 | // The results table is also a legend for the scatter chart. |
| 6 | |
| 7 | import { useState } from "preact/hooks"; |
| 8 | import { ColorMap } from "./colormap.js"; |
| 9 | import { |
| 10 | CreateSingleEstimateResult, |
| 11 | FrontierEntry, |
| 12 | ReData, |
| 13 | SingleEstimateResult, |
| 14 | } from "./data.js"; |
| 15 | import { ResultsTable, Row } from "./resultsTable.js"; |
| 16 | import { Axis, PlotItem, ScatterChart, ScatterSeries } from "./scatterChart.js"; |
| 17 | |
| 18 | const columnNames = [ |
| 19 | "Run name", |
| 20 | "Estimate type", |
| 21 | "Qubit type", |
| 22 | "QEC scheme", |
| 23 | "Error budget", |
| 24 | "Logical qubits", |
| 25 | "Logical depth", |
| 26 | "Code distance", |
| 27 | "T states", |
| 28 | "T factories", |
| 29 | "T factory fraction", |
| 30 | "Runtime", |
| 31 | "rQOPS", |
| 32 | "Physical qubits", |
| 33 | ]; |
| 34 | |
| 35 | const initialColumns = [0, 10, 13, 11, 12]; |
| 36 | |
| 37 | const xAxis: Axis = { |
| 38 | isTime: true, |
| 39 | label: "Runtime", |
| 40 | }; |
| 41 | |
| 42 | const yAxis: Axis = { |
| 43 | isTime: false, |
| 44 | label: "Physical qubits", |
| 45 | }; |
| 46 | |
| 47 | function reDataToRow(input: ReData, color: string): Row { |
| 48 | const data = CreateSingleEstimateResult(input, 0); |
| 49 | const estimateType = |
| 50 | input.frontierEntries == null |
| 51 | ? "Single" |
| 52 | : "Frontier (" + input.frontierEntries.length + " items)"; |
| 53 | |
| 54 | return { |
| 55 | cells: [ |
| 56 | data.jobParams.runName, |
| 57 | estimateType, |
| 58 | data.jobParams.qubitParams.name, |
| 59 | data.jobParams.qecScheme.name, |
| 60 | data.jobParams.errorBudget, |
| 61 | data.physicalCounts.breakdown.algorithmicLogicalQubits, |
| 62 | data.physicalCounts.breakdown.algorithmicLogicalDepth, |
| 63 | data.logicalQubit.codeDistance, |
| 64 | data.physicalCounts.breakdown.numTstates, |
| 65 | data.physicalCounts.breakdown.numTfactories, |
| 66 | data.physicalCountsFormatted.physicalQubitsForTfactoriesPercentage, |
| 67 | { |
| 68 | value: data.physicalCountsFormatted.runtime, |
| 69 | sortBy: data.physicalCounts.runtime, |
| 70 | }, |
| 71 | data.physicalCounts.rqops, |
| 72 | data.physicalCounts.physicalQubits, |
| 73 | ], |
| 74 | color: color, |
| 75 | }; |
| 76 | } |
| 77 | |
| 78 | function frontierEntryToPlotEntry(entry: FrontierEntry): PlotItem { |
| 79 | return { |
| 80 | x: entry.physicalCounts.runtime, |
| 81 | y: entry.physicalCounts.physicalQubits, |
| 82 | label: |
| 83 | entry.physicalCountsFormatted.runtime + |
| 84 | ", physical qubits: " + |
| 85 | entry.physicalCountsFormatted.physicalQubits + |
| 86 | ", code distance: " + |
| 87 | entry.logicalQubit.codeDistance, |
| 88 | }; |
| 89 | } |
| 90 | |
| 91 | function reDataToRowScatter(data: ReData, color: string): ScatterSeries { |
| 92 | if (data.frontierEntries == null || data.frontierEntries.length === 0) { |
| 93 | return { |
| 94 | color: color, |
| 95 | items: [ |
| 96 | { |
| 97 | x: data.physicalCounts.runtime, |
| 98 | y: data.physicalCounts.physicalQubits, |
| 99 | label: |
| 100 | data.physicalCountsFormatted.runtime + |
| 101 | ", physical qubits: " + |
| 102 | data.physicalCountsFormatted.physicalQubits + |
| 103 | ", code distance: " + |
| 104 | data.logicalQubit.codeDistance, |
| 105 | }, |
| 106 | ], |
| 107 | }; |
| 108 | } |
| 109 | |
| 110 | return { |
| 111 | color: color, |
| 112 | items: data.frontierEntries.map(frontierEntryToPlotEntry), |
| 113 | }; |
| 114 | } |
| 115 | |
| 116 | function createRunNames(estimatesData: ReData[]): string[] { |
| 117 | // If there's only 1 entry, use the shared run name |
| 118 | if (estimatesData.length === 1) { |
| 119 | const name = |
| 120 | estimatesData[0].jobParams.sharedRunName ?? |
| 121 | estimatesData[0].jobParams.qubitParams.name ?? |
| 122 | estimatesData[0].jobParams.qecScheme.name ?? |
| 123 | "estimate"; |
| 124 | |
| 125 | return [name]; |
| 126 | } |
| 127 | |
| 128 | const fields: string[][] = []; |
| 129 | |
| 130 | estimatesData.forEach(() => { |
| 131 | fields.push([]); |
| 132 | }); |
| 133 | |
| 134 | // Could be multiple runs, e.g. against different algorithms. |
| 135 | addIfDifferent(fields, estimatesData, (data) => data.jobParams.sharedRunName); |
| 136 | |
| 137 | addIfDifferent( |
| 138 | fields, |
| 139 | estimatesData, |
| 140 | (data) => data.jobParams.qubitParams.name, |
| 141 | ); |
| 142 | |
| 143 | addIfDifferent( |
| 144 | fields, |
| 145 | estimatesData, |
| 146 | (data) => data.jobParams.qecScheme.name, |
| 147 | ); |
| 148 | |
| 149 | addIfDifferent(fields, estimatesData, (data) => |
| 150 | String(data.jobParams.errorBudget), |
| 151 | ); |
| 152 | |
| 153 | const proposedRunNames = fields.map((field) => field.join(", ")); |
| 154 | if (new Set(proposedRunNames).size != proposedRunNames.length) { |
| 155 | // If there are duplicates, add the run index to the name. |
| 156 | return proposedRunNames.map( |
| 157 | (runName, index) => runName + " (" + index + ")", |
| 158 | ); |
| 159 | } |
| 160 | |
| 161 | return proposedRunNames; |
| 162 | } |
| 163 | |
| 164 | function addIfDifferent( |
| 165 | fields: string[][], |
| 166 | estimatesData: ReData[], |
| 167 | fieldMethod: (data: ReData) => string, |
| 168 | ): void { |
| 169 | const arr = estimatesData.map(fieldMethod); |
| 170 | |
| 171 | const set = new Set(arr); |
| 172 | if (set.size > 1) { |
| 173 | arr.forEach((field, index) => { |
| 174 | fields[index].push(field); |
| 175 | }); |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | export function EstimatesOverview(props: { |
| 180 | estimatesData: ReData[]; |
| 181 | colors: string[] | null; |
| 182 | runNames: string[] | null; |
| 183 | isSimplifiedView: boolean; |
| 184 | onRowDeleted: (rowId: string) => void; |
| 185 | setEstimate: (estimate: SingleEstimateResult | null) => void; |
| 186 | allowSaveImage: boolean; |
| 187 | }) { |
| 188 | const [selectedRow, setSelectedRow] = useState<string | null>(null); |
| 189 | const [selectedPoint, setSelectedPoint] = useState<[number, number]>(); |
| 190 | |
| 191 | const runNameRenderingError = |
| 192 | props.runNames != null && |
| 193 | props.runNames.length > 0 && |
| 194 | props.runNames.length != props.estimatesData.length |
| 195 | ? "Warning: The number of runNames does not match the number of estimates. Ignoring provided runNames." |
| 196 | : ""; |
| 197 | |
| 198 | const runNames = |
| 199 | props.runNames != null && |
| 200 | props.runNames.length == props.estimatesData.length |
| 201 | ? props.runNames |
| 202 | : createRunNames(props.estimatesData); |
| 203 | |
| 204 | props.estimatesData.forEach((item, idx) => { |
| 205 | // Start indexing with 0 to match with the original object indexing. |
| 206 | item.jobParams.runName = runNames[idx]; |
| 207 | }); |
| 208 | |
| 209 | function onPointSelected(seriesIndex: number, pointIndex: number): void { |
| 210 | if (seriesIndex < 0) { |
| 211 | // Point was deselected |
| 212 | onRowSelected(""); |
| 213 | return; |
| 214 | } |
| 215 | |
| 216 | const data = props.estimatesData[seriesIndex]; |
| 217 | props.setEstimate(CreateSingleEstimateResult(data, pointIndex)); |
| 218 | const rowId = props.estimatesData[seriesIndex].jobParams.runName; |
| 219 | setSelectedRow(rowId); |
| 220 | setSelectedPoint([seriesIndex, pointIndex]); |
| 221 | } |
| 222 | |
| 223 | function onRowSelected(rowId: string) { |
| 224 | setSelectedRow(rowId); |
| 225 | if (!rowId) { |
| 226 | props.setEstimate(null); |
| 227 | setSelectedPoint(undefined); |
| 228 | } else { |
| 229 | const index = props.estimatesData.findIndex( |
| 230 | (data) => data.jobParams.runName === rowId, |
| 231 | ); |
| 232 | |
| 233 | if (index == -1) { |
| 234 | props.setEstimate(null); |
| 235 | setSelectedPoint(undefined); |
| 236 | } else { |
| 237 | const estimateFound = props.estimatesData[index]; |
| 238 | setSelectedPoint([index, 0]); |
| 239 | props.setEstimate(CreateSingleEstimateResult(estimateFound, 0)); |
| 240 | } |
| 241 | } |
| 242 | } |
| 243 | |
| 244 | const colorRenderingError = |
| 245 | props.colors != null && |
| 246 | props.colors.length > 0 && |
| 247 | props.colors.length != props.estimatesData.length |
| 248 | ? "Warning: The number of colors does not match the number of estimates. Ignoring provided colors." |
| 249 | : ""; |
| 250 | |
| 251 | const colormap = |
| 252 | props.colors != null && props.colors.length == props.estimatesData.length |
| 253 | ? props.colors |
| 254 | : ColorMap(props.estimatesData.length); |
| 255 | |
| 256 | function getResultTable() { |
| 257 | return ( |
| 258 | <ResultsTable |
| 259 | columnNames={columnNames} |
| 260 | rows={props.estimatesData.map((dataItem, index) => |
| 261 | reDataToRow(dataItem, colormap[index]), |
| 262 | )} |
| 263 | initialColumns={initialColumns} |
| 264 | selectedRow={selectedRow} |
| 265 | onRowSelected={onRowSelected} |
| 266 | onRowDeleted={props.onRowDeleted} |
| 267 | /> |
| 268 | ); |
| 269 | } |
| 270 | |
| 271 | function getScatterChart() { |
| 272 | return ( |
| 273 | <ScatterChart |
| 274 | xAxis={xAxis} |
| 275 | yAxis={yAxis} |
| 276 | data={props.estimatesData.map((dataItem, index) => |
| 277 | reDataToRowScatter(dataItem, colormap[index]), |
| 278 | )} |
| 279 | onPointSelected={onPointSelected} |
| 280 | selectedPoint={selectedPoint} |
| 281 | allowSaveImage={props.allowSaveImage} |
| 282 | /> |
| 283 | ); |
| 284 | } |
| 285 | |
| 286 | return ( |
| 287 | <div className="qs-estimatesOverview"> |
| 288 | {runNameRenderingError != "" && ( |
| 289 | <div class="qs-estimatesOverview-error">{runNameRenderingError}</div> |
| 290 | )} |
| 291 | {colorRenderingError != "" && ( |
| 292 | <div class="qs-estimatesOverview-error">{colorRenderingError}</div> |
| 293 | )} |
| 294 | {!props.isSimplifiedView ? ( |
| 295 | <> |
| 296 | <details open> |
| 297 | <summary style="font-size: 1.5em; font-weight: bold; margin: 24px 8px;"> |
| 298 | Results |
| 299 | </summary> |
| 300 | {getResultTable()} |
| 301 | </details> |
| 302 | <details open> |
| 303 | <summary style="font-size: 1.5em; font-weight: bold; margin: 24px 8px;"> |
| 304 | Space-time diagram |
| 305 | </summary> |
| 306 | {getScatterChart()} |
| 307 | </details> |
| 308 | </> |
| 309 | ) : ( |
| 310 | <> |
| 311 | {getResultTable()} |
| 312 | {getScatterChart()} |
| 313 | </> |
| 314 | )} |
| 315 | </div> |
| 316 | ); |
| 317 | } |
| 318 | |