microsoft/qdk

Public

mirrored from https://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
billti/num2-sim

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/npm/qsharp/ux/circuit.tsx

336lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4import * as qviz from "./circuit-vis";
5import { useEffect, useRef, useState } from "preact/hooks";
6import { CircuitProps } from "./data.js";
7import { Spinner } from "./spinner.js";
8import { toCircuitGroup } from "./circuit-vis/circuit";
9
10// For perf reasons we set a limit on how many gates/qubits
11// we attempt to render. This is still a lot higher than a human would
12// reasonably want to look at, but it takes about a second to
13// render a circuit this big on a mid-grade laptop so we allow it.
14const MAX_OPERATIONS = 10000;
15const MAX_QUBITS = 1000;
16
17// For now we only support one circuit at a time.
18const MAX_CIRCUITS = 1;
19
20// This component is shared by the Python widget and the VS Code panel
21export function Circuit(props: {
22 circuit?: qviz.CircuitGroup | qviz.Circuit;
23 isEditable: boolean;
24 editCallback?: (fileData: qviz.CircuitGroup) => void;
25 runCallback?: () => void;
26}) {
27 let unrenderable = false;
28 let qubits = 0;
29 let operations = 0;
30 let errorMsg: string | undefined = undefined;
31
32 const result = toCircuitGroup(props.circuit);
33 if (result.ok) {
34 const circuit = result.circuitGroup.circuits[0];
35 if (circuit.componentGrid === undefined) circuit.componentGrid = [];
36 if (circuit.qubits === undefined) circuit.qubits = [];
37 qubits = circuit.qubits.length;
38 operations = circuit.componentGrid.length;
39
40 unrenderable =
41 unrenderable ||
42 result.circuitGroup.circuits.length > MAX_CIRCUITS ||
43 (!props.isEditable && qubits === 0) ||
44 operations > MAX_OPERATIONS ||
45 qubits > MAX_QUBITS;
46 } else {
47 errorMsg = result.error;
48 }
49
50 return (
51 <div>
52 {!result.ok || unrenderable ? (
53 <Unrenderable
54 qubits={qubits}
55 operations={operations}
56 error={errorMsg}
57 />
58 ) : (
59 <ZoomableCircuit {...props} circuitGroup={result.circuitGroup} />
60 )}
61 </div>
62 );
63}
64
65function ZoomableCircuit(props: {
66 circuitGroup: qviz.CircuitGroup;
67 isEditable: boolean;
68 editCallback?: (fileData: qviz.CircuitGroup) => void;
69 runCallback?: () => void;
70}) {
71 const circuitDiv = useRef<HTMLDivElement>(null);
72 const [zoomLevel, setZoomLevel] = useState(100);
73 const [rendering, setRendering] = useState(true);
74 const [zoomOnResize, setZoomOnResize] = useState(true);
75
76 useEffect(() => {
77 // Enable "rendering" text while the circuit is being drawn
78 setRendering(true);
79 const container = circuitDiv.current!;
80 container.innerHTML = "";
81 }, [props.circuitGroup]);
82
83 useEffect(() => {
84 if (rendering) {
85 const container = circuitDiv.current!;
86 // Draw the circuits - may take a while for large circuits
87 const svg = renderCircuits(
88 props.circuitGroup,
89 container,
90 props.isEditable,
91 props.editCallback,
92 props.runCallback,
93 );
94
95 if (!props.isEditable) {
96 const initialZoom = calculateZoomToFit(container, svg as SVGElement);
97 // Set the initial zoom level
98 setZoomLevel(initialZoom);
99 // Resize the SVG to fit
100 updateWidth();
101 }
102
103 // Calculate the initial zoom level based on the container width
104 // Disable "rendering" text
105 setRendering(false);
106 } else if (!props.isEditable) {
107 // Initial drawing done, attach window resize handler
108 window.addEventListener("resize", onResize);
109 return () => {
110 window.removeEventListener("resize", onResize);
111 };
112 }
113 }, [rendering, zoomOnResize]);
114
115 useEffect(() => {
116 updateWidth();
117 }, [zoomLevel]);
118
119 return (
120 <div>
121 <div>
122 {props.isEditable || rendering ? null : (
123 <ZoomControl zoom={zoomLevel} onInput={userSetZoomLevel} />
124 )}
125 </div>
126 <div>
127 {rendering
128 ? `Rendering diagram with ${props.circuitGroup.circuits[0].componentGrid.length} gates...`
129 : ""}
130 </div>
131 <div class="qs-circuit" ref={circuitDiv}></div>
132 </div>
133 );
134
135 /**
136 * Window resize handler to recalculate and set the zoom level
137 * based on the new window width.
138 */
139 function onResize() {
140 if (!zoomOnResize) {
141 return;
142 }
143
144 const [container, svg] = [circuitDiv.current, currentSvg()];
145 if (container && svg) {
146 // Recalculate the zoom level based on the container width
147 const initialZoom = calculateZoomToFit(container, svg);
148 // Set the zoom level
149 setZoomLevel(initialZoom);
150 }
151 }
152
153 /**
154 * Update the width of the SVG element based on the current zoom level.
155 */
156 function updateWidth() {
157 const svg = currentSvg();
158 if (svg) {
159 // The width attribute contains the true width, generated by qviz.
160 // We'll leave this attribute untouched, so we can use it again if the
161 // zoom level is ever updated.
162 const width = svg.getAttribute("width")!;
163
164 // We'll set the width in the style attribute to (true width * zoom level).
165 // This value takes precedence over the true width in the width attribute.
166 svg.setAttribute(
167 "style",
168 `max-width: ${width}; width: ${(parseInt(width) * (zoomLevel || 100)) / 100}; height: auto`,
169 );
170 }
171 }
172
173 function renderCircuits(
174 circuitGroup: qviz.CircuitGroup,
175 container: HTMLDivElement,
176 isEditable: boolean,
177 editCallback?: (fileData: qviz.CircuitGroup) => void,
178 runCallback?: () => void,
179 ) {
180 qviz.draw(
181 circuitGroup,
182 container,
183 0,
184 isEditable,
185 editCallback,
186 runCallback,
187 );
188 return container.getElementsByClassName("qviz")[0]!;
189 }
190
191 /**
192 * Calculate the zoom level that will fit the circuit into the current size of the container.
193 */
194 function calculateZoomToFit(container: HTMLDivElement, svg: SVGElement) {
195 const containerWidth = container.clientWidth;
196 // width and height are the true dimensions generated by qviz
197 const width = parseInt(svg.getAttribute("width")!);
198 const height = svg.getAttribute("height")!;
199
200 svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
201 const zoom = Math.min(Math.ceil((containerWidth / width) * 100), 100);
202 return zoom;
203 }
204
205 function currentSvg(): SVGElement | undefined {
206 return circuitDiv.current?.querySelector(".qviz") ?? undefined;
207 }
208
209 function userSetZoomLevel(zoom: number) {
210 setZoomOnResize(false);
211 setZoomLevel(zoom);
212 }
213}
214
215function Unrenderable(props: {
216 qubits: number;
217 operations: number;
218 error?: string;
219}) {
220 let errorDiv = null;
221
222 if (props.error) {
223 errorDiv = (
224 <div>
225 <p>
226 <b>Unable to render circuit:</b>
227 </p>
228 <pre>{props.error}</pre>
229 </div>
230 );
231 } else if (props.qubits === 0) {
232 errorDiv = (
233 <div>
234 <p>No circuit to display. No qubits have been allocated.</p>
235 </div>
236 );
237 } else if (props.operations > MAX_OPERATIONS) {
238 // Don't show the real number of operations here, as that number is
239 // *already* truncated by the underlying circuit builder.
240 errorDiv = (
241 <div>
242 <p>
243 This circuit has too many gates to display. The maximum supported
244 number of gates is {MAX_OPERATIONS}.
245 </p>
246 </div>
247 );
248 } else if (props.qubits > MAX_QUBITS) {
249 errorDiv = (
250 <div>
251 <p>
252 This circuit has too many qubits to display. It has {props.qubits}{" "}
253 qubits, but the maximum supported is {MAX_QUBITS}.
254 </p>
255 </div>
256 );
257 }
258
259 return <div class="qs-circuit-error">{errorDiv}</div>;
260}
261
262function ZoomControl(props: { zoom: number; onInput: (zoom: number) => void }) {
263 return (
264 <p>
265 <label htmlFor="qs-circuit-zoom">Zoom </label>
266 <input
267 id="qs-circuit-zoom"
268 type="number"
269 min="10"
270 max="100"
271 step="10"
272 value={props.zoom}
273 onInput={(e) =>
274 props.onInput(parseInt((e.target as HTMLInputElement).value) || 0)
275 }
276 />
277 %
278 </p>
279 );
280}
281
282// This component is exclusive to the VS Code panel
283export function CircuitPanel(props: CircuitProps) {
284 const error = props.errorHtml ? (
285 <div>
286 <p>
287 {props.circuit
288 ? "The program encountered a failure. See the error(s) below."
289 : "A circuit could not be generated for this program. See the error(s) below."}
290 <br />
291 </p>
292 <div dangerouslySetInnerHTML={{ __html: props.errorHtml }}></div>
293 </div>
294 ) : null;
295
296 return (
297 <div class="qs-circuit-panel">
298 <div>
299 <h1>
300 {props.title} {props.simulated ? "(Trace)" : ""}
301 </h1>
302 </div>
303 {error && <div class="qs-circuit-error">{error}</div>}
304 {props.targetProfile && <p>{props.targetProfile}</p>}
305 {props.simulated && (
306 <p>
307 WARNING: This diagram shows the result of tracing a dynamic circuit,
308 and may change from run to run.
309 </p>
310 )}
311 <p>
312 Learn more at{" "}
313 {props.isEditable ? (
314 <a href="https://aka.ms/qdk.circuit-editor">
315 https://aka.ms/qdk.circuit-editor
316 </a>
317 ) : (
318 <a href="https://aka.ms/qdk.circuits">https://aka.ms/qdk.circuits</a>
319 )}
320 </p>
321 {props.calculating ? (
322 <div>
323 <Spinner />
324 </div>
325 ) : null}
326 {props.circuit ? (
327 <Circuit
328 circuit={props.circuit}
329 isEditable={props.isEditable}
330 editCallback={props.editCallback}
331 runCallback={props.runCallback}
332 ></Circuit>
333 ) : null}
334 </div>
335 );
336}
337