microsoft/qdk

Public

mirrored fromhttps://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
9831093db0098b3a3e55cbadf3929222d7dd4805

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

source/playground/src/results.tsx

260lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4import { QscEventTarget, ShotResult, VSDiagnostic } from "qsharp-lang";
5import { useEffect, useState } from "preact/hooks";
6
7import { Histogram, Markdown } from "qsharp-lang/ux";
8import { StateTable } from "./state.js";
9import { ActiveTab } from "./main.js";
10
11function resultToLabel(result: string | VSDiagnostic): string {
12 if (typeof result !== "string") return "ERROR";
13 return result;
14}
15
16type ResultsState = {
17 shotCount: number; // How many shots have started
18 resultCount: number; // How many have completed (may be one less than above)
19 currIndex: number; // Which is currently being displayed
20 currResult: ShotResult | undefined; // The shot data to display
21 buckets: Map<string, number>; // Histogram buckets
22 filterValue: string; // Any filter that is in effect (or "")
23 filterIndex: number; // The index into the filtered set
24 currArray: ShotResult[]; // Used to detect a new run
25};
26
27function newRunState() {
28 return {
29 shotCount: 0,
30 resultCount: 0,
31 currIndex: 0,
32 currResult: undefined,
33 buckets: new Map(),
34 filterValue: "",
35 filterIndex: 0,
36 currArray: [],
37 };
38}
39
40function resultIsSame(a: ShotResult, b: ShotResult): boolean {
41 // If the length changed, any entries are different objects, or the final result has changed.
42 if (
43 a.success !== b.success ||
44 a.result !== b.result ||
45 a.events.length !== b.events.length
46 )
47 return false;
48
49 for (let i = 0; i < a.events.length; ++i) {
50 if (a.events[i] !== b.events[i]) return false;
51 }
52
53 return true;
54}
55
56export function ResultsTab(props: {
57 evtTarget: QscEventTarget;
58 onShotError?: (err?: VSDiagnostic) => void;
59 kataMode?: boolean;
60 activeTab: ActiveTab;
61}) {
62 const [resultState, setResultState] = useState<ResultsState>(newRunState());
63
64 // This is more complex than ideal for performance reasons. During a run, results may be getting
65 // updated thousands of times a second, but there is no point trying to render at more than 60fps.
66 // Therefore this subscribes to an event that happens once a frame if changes to results occur.
67 // As the results are mutated array, they don't make good props or state, so need to manually
68 // check for changes that would impact rendering and update state by creating new objects.
69 const evtTarget = props.evtTarget;
70 useEffect(() => {
71 const resultUpdateHandler = () => {
72 const results = evtTarget.getResults();
73
74 // If it's a new run, the entire results array will be a new object
75 const isNewResults = results !== resultState.currArray;
76
77 // If the results object has changed then reset the current index and filter.
78 const newIndex = isNewResults ? 0 : resultState.currIndex;
79 const newFilterValue = isNewResults ? "" : resultState.filterValue;
80 const newFilterIndex = isNewResults ? 0 : resultState.filterIndex;
81
82 const currentResult = resultState.currResult;
83 const updatedResult =
84 newIndex < results.length ? results[newIndex] : undefined;
85
86 const replaceResult =
87 isNewResults ||
88 // One is defined but the other isn't
89 !currentResult !== !updatedResult ||
90 // Or they both exist but are different (e.g. may have new events of have completed)
91 (currentResult &&
92 updatedResult &&
93 !resultIsSame(currentResult, updatedResult));
94
95 // Keep the old object if no need to replace it, else construct a new one
96 const newResult = !replaceResult
97 ? currentResult
98 : !updatedResult
99 ? undefined
100 : {
101 success: updatedResult.success,
102 result: updatedResult.result,
103 events: [...updatedResult.events],
104 };
105
106 // Update the histogram if new results have come in.
107 // For now, just completely recreate the bucket map
108 const resultCount = evtTarget.resultCount();
109 let buckets = resultState.buckets;
110 // If there are entirely new results, or if new results have been added, recalculate.
111 if (isNewResults || resultState.resultCount !== resultCount) {
112 buckets = new Map();
113 for (let i = 0; i < resultCount; ++i) {
114 const key = results[i].result;
115 const strKey = resultToLabel(key);
116 const newValue = (buckets.get(strKey) || 0) + 1;
117 buckets.set(strKey, newValue);
118 }
119 }
120
121 // If anything needs updating, construct the new state object and store
122 if (
123 replaceResult ||
124 resultState.shotCount !== results.length ||
125 resultState.resultCount !== resultCount ||
126 resultState.currIndex !== newIndex
127 ) {
128 setResultState({
129 shotCount: results.length,
130 resultCount: resultCount,
131 currIndex: newIndex,
132 currResult: newResult,
133 filterValue: newFilterValue,
134 filterIndex: newFilterIndex,
135 buckets,
136 currArray: results,
137 });
138 updateEditorError(newResult);
139 }
140 };
141
142 evtTarget.addEventListener("uiResultsRefresh", resultUpdateHandler);
143
144 // Remove the event listener when this component is destroyed
145 return () =>
146 evtTarget.removeEventListener("uiResultsRefresh", resultUpdateHandler);
147 }, [evtTarget]);
148
149 // If there's a filter set, there must have been at least one item for that result.
150 // If there's no filter set, may well be no results at all yet.
151
152 const filterValue = resultState.filterValue;
153 const countForFilter = filterValue
154 ? resultState.buckets.get(filterValue) || 0
155 : resultState.shotCount;
156 const currIndex = filterValue
157 ? resultState.filterIndex
158 : resultState.currIndex;
159 const resultLabel =
160 typeof resultState.currResult?.result === "string"
161 ? resultToLabel(resultState.currResult?.result || "")
162 : `ERROR: ${resultState.currResult?.result.message}`;
163
164 function moveToIndex(idx: number, filter: string) {
165 const results = evtTarget.getResults();
166
167 // The non-filtered default case
168 let currIndex = idx;
169 let currResult = results[idx];
170
171 // If a filter is in effect, need to find the filtered index
172 if (filter !== "") {
173 let found = 0;
174 for (let i = 0; i < results.length; ++i) {
175 // The buckets to filter on have been converted to kets where possible
176 if (resultToLabel(results[i].result) !== filter) continue;
177 if (found === idx) {
178 currIndex = i;
179 currResult = results[i];
180 break;
181 }
182 ++found;
183 }
184 }
185 setResultState({
186 ...resultState,
187 filterValue: filter,
188 filterIndex: idx,
189 currIndex,
190 currResult,
191 });
192 updateEditorError(currResult);
193 }
194
195 function updateEditorError(result?: ShotResult) {
196 if (!props.onShotError) return;
197 if (!result || result.success || typeof result.result === "string") {
198 props.onShotError();
199 } else {
200 props.onShotError(result.result);
201 }
202 }
203
204 function onPrev() {
205 if (currIndex > 0) moveToIndex(currIndex - 1, filterValue);
206 }
207
208 function onNext() {
209 if (currIndex < countForFilter - 1) moveToIndex(currIndex + 1, filterValue);
210 }
211
212 return props.activeTab === "results-tab" ? (
213 <div>
214 {!resultState.shotCount ? null : (
215 <>
216 {resultState.buckets.size > 1 ? (
217 <Histogram
218 shotCount={resultState.shotCount}
219 data={resultState.buckets}
220 filter={filterValue}
221 onFilter={(val: string) => moveToIndex(0, val)}
222 shotsHeader={false}
223 ></Histogram>
224 ) : null}
225 {props.kataMode ? null : (
226 <>
227 <div class="output-header">
228 <div>
229 Shot {currIndex + 1} of {countForFilter}
230 </div>
231 <div class="prev-next">
232 <span onClick={onPrev}>Prev</span> |{" "}
233 <span onClick={onNext}>Next</span>
234 </div>
235 </div>
236 <div class="result-label">Result: {resultLabel}</div>
237 </>
238 )}
239 <div>
240 {resultState.currResult?.events.map((evt) => {
241 return evt.type === "Message" ? (
242 <div class="message-output">&gt; {evt.message}</div>
243 ) : evt.type === "DumpMachine" ? (
244 <div>
245 <StateTable
246 dump={evt.state}
247 latexDump={evt.stateLatex}
248 count={evt.qubitCount}
249 ></StateTable>
250 </div>
251 ) : (
252 <Markdown markdown={evt.matrixLatex} />
253 );
254 })}
255 </div>
256 </>
257 )}
258 </div>
259 ) : null;
260}
261