microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
copilot/add-link-to-qsharp-application

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/npm/qsharp/src/data-structures/legacyCircuitUpdate.ts

442lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT license.
3
4import {
5 Circuit,
6 CircuitGroup,
7 ComponentGrid,
8 CURRENT_VERSION,
9 isCircuit,
10 isCircuitGroup,
11 isOperation,
12 Operation,
13 Qubit,
14} from "./circuit.js";
15import { Register } from "./register.js";
16
17export type ToCircuitGroupResult =
18 | { ok: true; circuitGroup: CircuitGroup }
19 | { ok: false; error: string };
20
21/**
22 * Ensures that the given circuit object is a CircuitGroup, doing any
23 * necessary conversions from Circuit or legacy formats.
24 *
25 * @param circuit The circuit to convert.
26 * @returns The result of the conversion.
27 */
28export function toCircuitGroup(circuit: any): ToCircuitGroupResult {
29 const emptyCircuit: Circuit = {
30 qubits: [],
31 componentGrid: [],
32 };
33
34 const emptyCircuitGroup: CircuitGroup = {
35 version: CURRENT_VERSION,
36 circuits: [emptyCircuit],
37 };
38
39 if (circuit && Object.keys(circuit).length === 0) {
40 return { ok: true, circuitGroup: emptyCircuitGroup };
41 }
42
43 if (circuit?.version) {
44 const version = circuit.version;
45 if (isCircuitGroup(circuit)) {
46 return { ok: true, circuitGroup: circuit };
47 } else if (isCircuit(circuit)) {
48 return { ok: true, circuitGroup: { version, circuits: [circuit] } };
49 } else {
50 return {
51 ok: false,
52 error: "Unknown schema: file is neither a CircuitGroup nor a Circuit.",
53 };
54 }
55 } else if (isCircuit(circuit)) {
56 return {
57 ok: true,
58 circuitGroup: { version: CURRENT_VERSION, circuits: [circuit] },
59 };
60 } else if (
61 circuit?.operations &&
62 Array.isArray(circuit.operations) &&
63 circuit?.qubits &&
64 Array.isArray(circuit.qubits)
65 ) {
66 // If it has "operations" and "qubits", it is a legacy schema
67 return tryConvertLegacySchema(circuit);
68 } else {
69 return {
70 ok: false,
71 error: "Unknown schema: file does not match any known format.",
72 };
73 }
74}
75
76/**
77 * Attempts to convert a legacy circuit schema to a CircuitGroup.
78 *
79 * @param circuit The legacy circuit object to convert.
80 * @returns A ToCircuitGroupResult containing the converted CircuitGroup on success,
81 * or an error message on failure.
82 */
83function tryConvertLegacySchema(circuit: any): ToCircuitGroupResult {
84 try {
85 const qubits: Qubit[] = circuit.qubits.map((qubit: any, idx: number) => {
86 if (
87 typeof qubit !== "object" ||
88 qubit === null ||
89 typeof qubit.id !== "number"
90 ) {
91 throw new Error(`Invalid qubit at index ${idx}.`);
92 }
93 return {
94 id: qubit.id,
95 numResults: qubit.numChildren || 0,
96 };
97 });
98
99 const operationList = circuit.operations.map((op: any, idx: number) => {
100 try {
101 return toOperation(op);
102 } catch (e) {
103 throw new Error(
104 `Failed to convert operation at index ${idx}: ${(e as Error).message}`,
105 { cause: e },
106 );
107 }
108 });
109
110 if (!operationList.every(isOperation)) {
111 return {
112 ok: false,
113 error: "Unknown schema: file contains invalid operations.",
114 };
115 }
116
117 const componentGrid = operationListToGrid(operationList, qubits.length);
118
119 return {
120 ok: true,
121 circuitGroup: {
122 version: CURRENT_VERSION,
123 circuits: [
124 {
125 qubits,
126 componentGrid,
127 },
128 ],
129 },
130 };
131 } catch (e) {
132 return {
133 ok: false,
134 error: `Legacy schema: ${e instanceof Error ? e.message : String(e)}`,
135 };
136 }
137}
138
139/**
140 * Converts a legacy operation object to the new Operation format.
141 *
142 * @param op The operation to convert.
143 * @returns The converted Operation.
144 */
145function toOperation(op: any): Operation {
146 let targets = [];
147 if (op.targets) {
148 targets = op.targets.map((t: any) => {
149 return {
150 qubit: t.qId,
151 result: t.cId,
152 };
153 });
154 }
155 let controls = undefined;
156 if (op.controls) {
157 controls = op.controls.map((c: any) => {
158 return {
159 qubit: c.qId,
160 result: c.cId,
161 };
162 });
163 }
164
165 if (op.isMeasurement) {
166 return {
167 ...op,
168 kind: "measurement",
169 qubits: controls || [],
170 results: targets,
171 } as Operation;
172 } else {
173 const ket = op.gate === undefined ? "" : getKetLabel(op.gate);
174 if (ket.length > 0) {
175 return {
176 ...op,
177 kind: "ket",
178 gate: ket,
179 targets,
180 };
181 } else {
182 const convertedOp: Operation = {
183 ...op,
184 kind: "unitary",
185 targets,
186 controls,
187 };
188 if (op.displayArgs) {
189 convertedOp.args = [op.displayArgs];
190 // Assume the parameter is always "theta" for now
191 convertedOp.params = [{ name: "theta", type: "Double" }];
192 }
193 if (op.children) {
194 convertedOp.children = [
195 {
196 components: op.children.map((child: any) => toOperation(child)),
197 },
198 ];
199 }
200 return convertedOp;
201 }
202 }
203}
204
205/**
206 * Get the label from a ket string.
207 *
208 * @param ket The ket string to extract the label from.
209 * @returns The label extracted from the ket string.
210 */
211function getKetLabel(ket: string): string {
212 // Check that the ket conforms to the format |{label}> or |{label}⟩
213 // Be overly permissive with the ket format, allowing for various closing characters
214 const ketRegex = /^\|([^\s〉⟩〉>]+)(?:[〉⟩〉>])$/;
215
216 // Match the ket string against the regex
217 const match = ket.match(ketRegex);
218
219 // If valid, return the inner label (captured group 1), otherwise return an empty string
220 return match ? match[1] : "";
221}
222
223/**
224 * Converts a list of operations into a 2D grid of operations in col-row format.
225 * Operations will be left-justified as much as possible in the resulting grid.
226 * Children operations are recursively converted into a grid.
227 *
228 * @param operations Array of operations.
229 * @param numQubits Number of qubits in the circuit.
230 *
231 * @returns A 2D array of operations.
232 */
233function operationListToGrid(
234 operations: Operation[],
235 numQubits: number,
236): ComponentGrid {
237 operations.forEach((op) => {
238 // The children data structure is a grid, so checking if it is
239 // length 1 is actually checking if it has a single column,
240 // or in other words, we are checking if its children are in a single list.
241 // If the operation has children in a single list, it needs to be converted to a grid.
242 // If it was already converted to a grid, but the grid was still a single list,
243 // then doing it again won't effect anything.
244 if (op.children && op.children.length == 1) {
245 op.children = operationListToGrid(op.children[0].components, numQubits);
246 }
247 });
248
249 return removePadding(operationListToPaddedArray(operations, numQubits)).map(
250 (col) => ({
251 components: col,
252 }),
253 );
254}
255
256/**
257 * Converts a list of operations into a padded 2D array of operations.
258 *
259 * @param operations Array of operations.
260 * @param numQubits Number of qubits in the circuit.
261 *
262 * @returns A 2D array of operations padded with `null`s.
263 */
264function operationListToPaddedArray(
265 operations: Operation[],
266 numQubits: number,
267): (Operation | null)[][] {
268 if (operations.length === 0) return [];
269
270 // Group operations based on registers
271 const groupedOps: number[][] = groupOperations(operations, numQubits);
272
273 // Align operations on multiple registers
274 const alignedOps: (number | null)[][] = transformToColRow(
275 alignOps(groupedOps),
276 );
277
278 const operationArray: (Operation | null)[][] = alignedOps.map((col) =>
279 col.map((opIdx) => {
280 if (opIdx == null) return null;
281 return operations[opIdx];
282 }),
283 );
284
285 return operationArray;
286}
287
288/**
289 * Removes padding (`null` values) from a 2D array of operations.
290 *
291 * @param operations 2D array of operations padded with `null`s.
292 *
293 * @returns A 2D array of operations without `null` values.
294 */
295function removePadding(operations: (Operation | null)[][]): Operation[][] {
296 return operations.map((col) => col.filter((op) => op != null));
297}
298
299/**
300 * Transforms a row-col 2D array into an equivalent col-row 2D array.
301 *
302 * @param alignedOps 2D array of operations in row-col format.
303 *
304 * @returns 2D array of operations in col-row format.
305 */
306function transformToColRow(
307 alignedOps: (number | null)[][],
308): (number | null)[][] {
309 if (alignedOps.length === 0) return [];
310
311 const numRows = alignedOps.length;
312 const numCols = Math.max(...alignedOps.map((row) => row.length));
313
314 const colRowArray: (number | null)[][] = Array.from({ length: numCols }, () =>
315 Array(numRows).fill(null),
316 );
317
318 for (let row = 0; row < numRows; row++) {
319 for (let col = 0; col < alignedOps[row].length; col++) {
320 colRowArray[col][row] = alignedOps[row][col];
321 }
322 }
323
324 return colRowArray;
325}
326
327/**
328 * Group gates provided by operations into their respective registers.
329 *
330 * @param operations Array of operations.
331 * @param numQubits Number of qubits in the circuit.
332 *
333 * @returns 2D array of indices where `groupedOps[i][j]` is the index of the operations
334 * at register `i` and column `j` (not yet aligned/padded).
335 */
336function groupOperations(
337 operations: Operation[],
338 numQubits: number,
339): number[][] {
340 const groupedOps: number[][] = Array.from(
341 Array(numQubits),
342 () => new Array(0),
343 );
344 operations.forEach((operation, instrIdx) => {
345 const [minRegIdx, maxRegIdx] = getMinMaxRegIdx(operation, numQubits);
346 if (minRegIdx > -1 && maxRegIdx > -1) {
347 // Add operation also to registers that are in-between target registers
348 // so that other gates won't render in the middle.
349 for (let i = minRegIdx; i <= maxRegIdx; i++) {
350 groupedOps[i].push(instrIdx);
351 }
352 }
353 });
354 return groupedOps;
355}
356
357/**
358 * Aligns operations by padding registers with `null`s to make sure that multiqubit
359 * gates are in the same column.
360 * e.g. ---[x]---[x]--
361 * ----------|---
362 *
363 * @param ops 2D array of operations. Each row represents a register
364 * and the operations acting on it (in-order).
365 *
366 * @returns 2D array of aligned operations padded with `null`s.
367 */
368function alignOps(ops: number[][]): (number | null)[][] {
369 let maxNumOps: number = Math.max(0, ...ops.map((regOps) => regOps.length));
370 let col = 0;
371 // Deep copy ops to be returned as paddedOps
372 const paddedOps: (number | null)[][] = ops.map((regOps) => [...regOps]);
373 while (col < maxNumOps) {
374 for (let regIdx = 0; regIdx < paddedOps.length; regIdx++) {
375 const reg: (number | null)[] = paddedOps[regIdx];
376 if (reg.length <= col) continue;
377
378 // Should never be null (nulls are only padded to previous columns)
379 const opIdx: number | null = reg[col];
380
381 // Get position of gate
382 const targetsPos: number[] = paddedOps.map((regOps) =>
383 regOps.indexOf(opIdx),
384 );
385 const gatePos: number = Math.max(-1, ...targetsPos);
386
387 // If current column is not desired gate position, pad with null
388 if (col < gatePos) {
389 paddedOps[regIdx].splice(col, 0, null);
390 maxNumOps = Math.max(maxNumOps, paddedOps[regIdx].length);
391 }
392 }
393 col++;
394 }
395 return paddedOps;
396}
397
398/**
399 * Get the minimum and maximum register indices for a given operation.
400 *
401 * @param operation The operation for which to get the register indices.
402 * @param numQubits The number of qubits in the circuit.
403 * @returns A tuple containing the minimum and maximum register indices.
404 */
405function getMinMaxRegIdx(
406 operation: Operation,
407 numQubits: number,
408): [number, number] {
409 let targets: Register[];
410 let controls: Register[];
411 switch (operation.kind) {
412 case "measurement":
413 targets = operation.results;
414 controls = operation.qubits;
415 break;
416 case "unitary":
417 targets = operation.targets;
418 controls = operation.controls || [];
419 break;
420 case "ket":
421 targets = operation.targets;
422 controls = [];
423 break;
424 }
425
426 const qRegs = [...controls, ...targets]
427 .filter(({ result }) => result === undefined)
428 .map(({ qubit }) => qubit);
429 const clsControls: Register[] = controls.filter(
430 ({ result }) => result !== undefined,
431 );
432 const isClassicallyControlled: boolean = clsControls.length > 0;
433 if (!isClassicallyControlled && qRegs.length === 0) return [-1, -1];
434 // If operation is classically-controlled, pad all qubit registers. Otherwise, only pad
435 // the contiguous range of registers that it covers.
436 const minRegIdx: number = isClassicallyControlled ? 0 : Math.min(...qRegs);
437 const maxRegIdx: number = isClassicallyControlled
438 ? numQubits - 1
439 : Math.max(...qRegs);
440
441 return [minRegIdx, maxRegIdx];
442}
443