From 5c9af2322d497094a0ed84fcace2aa77551b6c91 Mon Sep 17 00:00:00 2001 From: Scott Carda Date: Tue, 27 May 2025 13:21:35 -0700 Subject: [PATCH 1/4] Display error message when unrenderable --- npm/qsharp/src/shared/circuit.ts | 134 ++++++++++++++++++- npm/qsharp/src/shared/legacyCircuitUpdate.ts | 83 +++++------- npm/qsharp/src/shared/register.ts | 13 ++ npm/qsharp/ux/circuit.tsx | 81 +++++++---- 4 files changed, 239 insertions(+), 72 deletions(-) diff --git a/npm/qsharp/src/shared/circuit.ts b/npm/qsharp/src/shared/circuit.ts index e88b484b05..b8e737c602 100644 --- a/npm/qsharp/src/shared/circuit.ts +++ b/npm/qsharp/src/shared/circuit.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { Register } from "./register.js"; +import { isRegister, Register } from "./register.js"; /** * Current format version. @@ -13,6 +13,20 @@ export interface CircuitGroup { version: number; } +/** + * Runtime check: is this a valid CircuitGroup? + */ +export function isCircuitGroup(obj: any): obj is CircuitGroup { + return ( + obj && + typeof obj === "object" && + typeof obj.version === "number" && + Array.isArray(obj.circuits) && + obj.circuits.length > 0 && + obj.circuits.every(isCircuit) + ); +} + /** * Circuit to be visualized. */ @@ -22,12 +36,38 @@ export interface Circuit { componentGrid: ComponentGrid; } +/** + * Runtime check: is this a valid Circuit? + */ +export function isCircuit(obj: any): obj is Circuit { + return ( + obj && + typeof obj === "object" && + Array.isArray(obj.qubits) && + obj.qubits.every(isQubit) && + Array.isArray(obj.componentGrid) && + obj.componentGrid.every(isColumn) + ); +} + export type ComponentGrid = Column[]; export interface Column { components: Component[]; } +/** + * Runtime check: is this a valid Column? + */ +export function isColumn(obj: any): obj is Column { + return ( + obj && + typeof obj === "object" && + Array.isArray(obj.components) && + obj.components.every(isOperation) + ); +} + /** * Represents a component of a circuit. Currently, the only component is an operation. * In the future, this may be extended to include other components. @@ -44,6 +84,19 @@ export interface Qubit { numResults?: number; } +/** + * Runtime check: is this a valid Qubit? + */ +export function isQubit(obj: any): obj is Qubit { + return ( + obj && + typeof obj === "object" && + typeof obj.id === "number" && + // numResults is optional, but if present must be a number + (obj.numResults === undefined || typeof obj.numResults === "number") + ); +} + /** * Base type for operations. */ @@ -67,6 +120,40 @@ export interface BaseOperation { conditionalRender?: ConditionalRender; } +/** + * Runtime check: is this a valid BaseOperation? + */ +function isBaseOperation(obj: any): obj is BaseOperation { + return ( + obj && + typeof obj === "object" && + typeof obj.gate === "string" && + // args is optional, but if present must be an array of strings + (obj.args === undefined || + (Array.isArray(obj.args) && + obj.args.every((arg: any) => typeof arg === "string"))) && + // params is optional, but if present must be an array of Parameter + (obj.params === undefined || + (Array.isArray(obj.params) && obj.params.every(isParameter))) && + // children is optional, but if present must be a ComponentGrid + (obj.children === undefined || + (Array.isArray(obj.children) && obj.children.every(isColumn))) && + // dataAttributes is optional, but if present must be an object with string values + (obj.dataAttributes === undefined || + (typeof obj.dataAttributes === "object" && + obj.dataAttributes !== null && + Object.values(obj.dataAttributes).every( + (val) => typeof val === "string", + ))) && + // isConditional is optional, but if present must be boolean + (obj.isConditional === undefined || + typeof obj.isConditional === "boolean") && + // conditionalRender is optional, but if present must be a valid enum value + (obj.conditionalRender === undefined || + Object.values(ConditionalRender).includes(obj.conditionalRender)) + ); +} + /** * Represents a measurement operation and the registers it acts on. */ @@ -108,6 +195,39 @@ export interface Ket extends BaseOperation { */ export type Operation = Unitary | Measurement | Ket; +/** + * Runtime check: is this a valid Operation? + */ +export function isOperation(obj: any): obj is Operation { + if (!isBaseOperation(obj)) return false; + // Re-cast to any so we can check discriminated fields without narrowing + const op: any = obj; + if (typeof op.kind !== "string") return false; + switch (op.kind) { + case "unitary": + return ( + Array.isArray(op.targets) && + op.targets.every(isRegister) && + // controls is optional + (op.controls === undefined || + (Array.isArray(op.controls) && op.controls.every(isRegister))) && + // isAdjoint is optional + (op.isAdjoint === undefined || typeof op.isAdjoint === "boolean") + ); + case "measurement": + return ( + Array.isArray(op.qubits) && + op.qubits.every(isRegister) && + Array.isArray(op.results) && + op.results.every(isRegister) + ); + case "ket": + return Array.isArray(op.targets) && op.targets.every(isRegister); + default: + return false; + } +} + /** * A parameter for an operation. */ @@ -118,6 +238,18 @@ export interface Parameter { type: string; } +/** + * Runtime check: is this a valid Parameter? + */ +export function isParameter(obj: any): obj is Parameter { + return ( + obj && + typeof obj === "object" && + typeof obj.name === "string" && + typeof obj.type === "string" + ); +} + /** * Conditions on when to render the given operation. */ diff --git a/npm/qsharp/src/shared/legacyCircuitUpdate.ts b/npm/qsharp/src/shared/legacyCircuitUpdate.ts index 7da03de3fd..fb83d0c058 100644 --- a/npm/qsharp/src/shared/legacyCircuitUpdate.ts +++ b/npm/qsharp/src/shared/legacyCircuitUpdate.ts @@ -7,18 +7,24 @@ import { CircuitGroup, ComponentGrid, CURRENT_VERSION, + isCircuit, + isCircuitGroup, Operation, Qubit, } from "./circuit.js"; +export type ToCircuitGroupResult = + | { ok: true; circuitGroup: CircuitGroup } + | { ok: false; error: string }; + /** * Ensures that the given circuit object is a CircuitGroup, doing any * necessary conversions from Circuit or legacy formats. * * @param circuit The circuit to convert. - * @returns The converted CircuitGroup. + * @returns The result of the conversion. */ -export function toCircuitGroup(circuit: any): CircuitGroup { +export function toCircuitGroup(circuit: any): ToCircuitGroupResult { const emptyCircuit: Circuit = { qubits: [], componentGrid: [], @@ -30,7 +36,7 @@ export function toCircuitGroup(circuit: any): CircuitGroup { }; if (circuit && Object.keys(circuit).length === 0) { - return emptyCircuitGroup; + return { ok: true, circuitGroup: emptyCircuitGroup }; } if (circuit?.version) { @@ -38,30 +44,30 @@ export function toCircuitGroup(circuit: any): CircuitGroup { // If it has a "version" field, it is up-to-date if (isCircuitGroup(circuit)) { // If it's already a CircuitGroup, return it as is - return circuit; + return { ok: true, circuitGroup: circuit }; } else if (isCircuit(circuit)) { // If it's a Circuit, wrap it in a CircuitGroup + return { ok: true, circuitGroup: { version, circuits: [circuit] } }; + } else { return { - version, - circuits: [circuit], + ok: false, + error: + "Unknown schema: circuit is neither a CircuitGroup nor a Circuit.", }; - } else { - console.error( - "Unknown schema: circuit is neither a CircuitGroup nor a Circuit.", - ); - return emptyCircuitGroup; } } else if (isCircuit(circuit)) { // If it's a Circuit without a version, wrap it in a CircuitGroup return { - version: CURRENT_VERSION, - circuits: [circuit], + ok: true, + circuitGroup: { version: CURRENT_VERSION, circuits: [circuit] }, }; } else if (circuit?.operations) { // Legacy schema: convert to CircuitGroup if (circuit.qubits === undefined || !Array.isArray(circuit.qubits)) { - console.error("Unknown schema: circuit is missing qubit information."); - return emptyCircuitGroup; + return { + ok: false, + error: "Unknown schema: circuit is missing qubit information.", + }; } const qubits: Qubit[] = circuit.qubits.map((qubit: any) => { @@ -77,17 +83,22 @@ export function toCircuitGroup(circuit: any): CircuitGroup { ); return { - version: CURRENT_VERSION, - circuits: [ - { - qubits, - componentGrid, - }, - ], + ok: true, + circuitGroup: { + version: CURRENT_VERSION, + circuits: [ + { + qubits, + componentGrid, + }, + ], + }, }; } else { - console.error("Unknown schema: circuit does not match any known format."); - return emptyCircuitGroup; + return { + ok: false, + error: "Unknown schema: circuit does not match any known format.", + }; } } @@ -157,30 +168,6 @@ function toOperation(op: any): Operation { } } -/** - * Checks if the given object is a CircuitGroup. - * - * @param circuit The object to check. - * @returns True if the object is a CircuitGroup, false otherwise. - */ -function isCircuitGroup(circuit: any): circuit is CircuitGroup { - return circuit && Array.isArray(circuit.circuits); -} - -/** - * Checks if the given object is a Circuit. - * - * @param circuit The object to check. - * @returns True if the object is a Circuit, false otherwise. - */ -function isCircuit(circuit: any): circuit is Circuit { - return ( - circuit && - Array.isArray(circuit.qubits) && - Array.isArray(circuit.componentGrid) - ); -} - /** * Get the label from a ket string. * diff --git a/npm/qsharp/src/shared/register.ts b/npm/qsharp/src/shared/register.ts index 74b086d0e7..e5c19c6381 100644 --- a/npm/qsharp/src/shared/register.ts +++ b/npm/qsharp/src/shared/register.ts @@ -19,6 +19,19 @@ export interface Register { result?: number; } +/** + * Runtime check: is this a valid Register? + */ +export function isRegister(obj: any): obj is Register { + return ( + obj && + typeof obj === "object" && + typeof obj.qubit === "number" && + // result is optional, but if present must be a number + (obj.result === undefined || typeof obj.result === "number") + ); +} + /** * Rendering data for qubit register. */ diff --git a/npm/qsharp/ux/circuit.tsx b/npm/qsharp/ux/circuit.tsx index 21d6f2b36c..5bcdc42437 100644 --- a/npm/qsharp/ux/circuit.tsx +++ b/npm/qsharp/ux/circuit.tsx @@ -23,30 +23,45 @@ export function Circuit(props: { isEditable: boolean; editCallback?: (fileData: qviz.CircuitGroup) => void; }) { - const circuitGroup = toCircuitGroup(props.circuit); - const circuit = circuitGroup.circuits[0]; + let unrenderable = false; + let qubits = 0; + let operations = 0; + let errorMsg: string | undefined = undefined; - if (circuit.componentGrid === undefined) circuit.componentGrid = []; - if (circuit.qubits === undefined) circuit.qubits = []; + console.log("About to parse circuit file"); + const result = toCircuitGroup(props.circuit); - if (circuit.componentGrid === undefined) circuit.componentGrid = []; - if (circuit.qubits === undefined) circuit.qubits = []; + if (result.ok) { + console.log("Successfully parsed circuit file"); + const circuit = result.circuitGroup.circuits[0]; + if (circuit.componentGrid === undefined) circuit.componentGrid = []; + if (circuit.qubits === undefined) circuit.qubits = []; + qubits = circuit.qubits.length; + operations = circuit.componentGrid.length; - const unrenderable = - circuitGroup.circuits.length > MAX_CIRCUITS || - (!props.isEditable && circuit.qubits.length === 0) || - circuit.componentGrid.length > MAX_OPERATIONS || - circuit.qubits.length > MAX_QUBITS; + unrenderable = + unrenderable || + result.circuitGroup.circuits.length > MAX_CIRCUITS || + (!props.isEditable && qubits === 0) || + operations > MAX_OPERATIONS || + qubits > MAX_QUBITS; + } else { + errorMsg = result.error; + console.log("Failed to parse circuit file: ", errorMsg); + } + + console.log("Is Unrenderable after: ", unrenderable); return (
- {unrenderable ? ( + {!result.ok || unrenderable ? ( ) : ( - + )}
); @@ -192,29 +207,49 @@ function ZoomableCircuit(props: { } } -function Unrenderable(props: { qubits: number; operations: number }) { - const errorDiv = - props.qubits === 0 ? ( +function Unrenderable(props: { + qubits: number; + operations: number; + error?: string; +}) { + let errorDiv = null; + + if (props.error) { + errorDiv = ( +
+

+ Unable to render circuit: +

+
{props.error}
+
+ ); + } else if (props.qubits === 0) { + errorDiv = (

No circuit to display. No qubits have been allocated.

- ) : props.operations > MAX_OPERATIONS ? ( - // Don't show the real number of operations here, as that number is - // *already* truncated by the underlying circuit builder. + ); + } else if (props.operations > MAX_OPERATIONS) { + // Don't show the real number of operations here, as that number is + // *already* truncated by the underlying circuit builder. + errorDiv = (

This circuit has too many gates to display. The maximum supported number of gates is {MAX_OPERATIONS}.

- ) : props.qubits > MAX_QUBITS ? ( + ); + } else if (props.qubits > MAX_QUBITS) { + errorDiv = (

This circuit has too many qubits to display. It has {props.qubits}{" "} qubits, but the maximum supported is {MAX_QUBITS}.

- ) : undefined; + ); + } return
{errorDiv}
; } From 82c3aa3e465ebe7cca34298a7cbc2ab40c8b763c Mon Sep 17 00:00:00 2001 From: Scott Carda Date: Wed, 28 May 2025 11:19:04 -0700 Subject: [PATCH 2/4] enhanced the legacy conversion logic --- npm/qsharp/src/shared/circuit.ts | 2 +- npm/qsharp/src/shared/legacyCircuitUpdate.ts | 86 ++++++++++++++------ npm/qsharp/ux/circuit.tsx | 6 -- 3 files changed, 60 insertions(+), 34 deletions(-) diff --git a/npm/qsharp/src/shared/circuit.ts b/npm/qsharp/src/shared/circuit.ts index b8e737c602..3fd1311ff7 100644 --- a/npm/qsharp/src/shared/circuit.ts +++ b/npm/qsharp/src/shared/circuit.ts @@ -202,7 +202,7 @@ export function isOperation(obj: any): obj is Operation { if (!isBaseOperation(obj)) return false; // Re-cast to any so we can check discriminated fields without narrowing const op: any = obj; - if (typeof op.kind !== "string") return false; + if (op.kind === undefined || typeof op.kind !== "string") return false; switch (op.kind) { case "unitary": return ( diff --git a/npm/qsharp/src/shared/legacyCircuitUpdate.ts b/npm/qsharp/src/shared/legacyCircuitUpdate.ts index fb83d0c058..50d5442670 100644 --- a/npm/qsharp/src/shared/legacyCircuitUpdate.ts +++ b/npm/qsharp/src/shared/legacyCircuitUpdate.ts @@ -9,6 +9,7 @@ import { CURRENT_VERSION, isCircuit, isCircuitGroup, + isOperation, Operation, Qubit, } from "./circuit.js"; @@ -41,46 +42,75 @@ export function toCircuitGroup(circuit: any): ToCircuitGroupResult { if (circuit?.version) { const version = circuit.version; - // If it has a "version" field, it is up-to-date if (isCircuitGroup(circuit)) { - // If it's already a CircuitGroup, return it as is return { ok: true, circuitGroup: circuit }; } else if (isCircuit(circuit)) { - // If it's a Circuit, wrap it in a CircuitGroup return { ok: true, circuitGroup: { version, circuits: [circuit] } }; } else { return { ok: false, - error: - "Unknown schema: circuit is neither a CircuitGroup nor a Circuit.", + error: "Unknown schema: file is neither a CircuitGroup nor a Circuit.", }; } } else if (isCircuit(circuit)) { - // If it's a Circuit without a version, wrap it in a CircuitGroup return { ok: true, circuitGroup: { version: CURRENT_VERSION, circuits: [circuit] }, }; - } else if (circuit?.operations) { - // Legacy schema: convert to CircuitGroup - if (circuit.qubits === undefined || !Array.isArray(circuit.qubits)) { - return { - ok: false, - error: "Unknown schema: circuit is missing qubit information.", - }; - } + } else if ( + circuit?.operations && + Array.isArray(circuit.operations) && + circuit?.qubits && + Array.isArray(circuit.qubits) + ) { + // If it has "operations" and "qubits", it is a legacy schema + return tryConvertLegacySchema(circuit); + } else { + return { + ok: false, + error: "Unknown schema: file does not match any known format.", + }; + } +} - const qubits: Qubit[] = circuit.qubits.map((qubit: any) => { +/** + * Attempts to convert a legacy circuit schema to a CircuitGroup. + * Returns a ToCircuitGroupResult with detailed error messages on failure. + */ +function tryConvertLegacySchema(circuit: any): ToCircuitGroupResult { + try { + const qubits: Qubit[] = circuit.qubits.map((qubit: any, idx: number) => { + if ( + typeof qubit !== "object" || + qubit === null || + typeof qubit.id !== "number" + ) { + throw new Error(`Invalid qubit at index ${idx}.`); + } return { id: qubit.id, - numResults: qubit.numChildren || 0, // Rename "numChildren" to "numResults" + numResults: qubit.numChildren || 0, }; }); - const componentGrid = operationListToGrid( - circuit.operations.map(toOperation), - qubits.length, - ); + const operationList = circuit.operations.map((op: any, idx: number) => { + try { + return toOperation(op); + } catch (e) { + throw new Error( + `Failed to convert operation at index ${idx}: ${(e as Error).message}`, + ); + } + }); + + if (!operationList.every(isOperation)) { + return { + ok: false, + error: "Unknown schema: file contains invalid operations.", + }; + } + + const componentGrid = operationListToGrid(operationList, qubits.length); return { ok: true, @@ -94,10 +124,10 @@ export function toCircuitGroup(circuit: any): ToCircuitGroupResult { ], }, }; - } else { + } catch (e) { return { ok: false, - error: "Unknown schema: circuit does not match any known format.", + error: `Legacy schema: ${e instanceof Error ? e.message : String(e)}`, }; } } @@ -136,7 +166,7 @@ function toOperation(op: any): Operation { results: targets, } as Operation; } else { - const ket = getKetLabel(op.gate); + const ket = op.gate === undefined ? "" : getKetLabel(op.gate); if (ket.length > 0) { return { ...op, @@ -309,10 +339,12 @@ function groupOperations( ); operations.forEach((operation, instrIdx) => { const [minRegIdx, maxRegIdx] = getMinMaxRegIdx(operation, numQubits); - // Add operation also to registers that are in-between target registers - // so that other gates won't render in the middle. - for (let i = minRegIdx; i <= maxRegIdx; i++) { - groupedOps[i].push(instrIdx); + if (minRegIdx > -1 && maxRegIdx > -1) { + // Add operation also to registers that are in-between target registers + // so that other gates won't render in the middle. + for (let i = minRegIdx; i <= maxRegIdx; i++) { + groupedOps[i].push(instrIdx); + } } }); return groupedOps; diff --git a/npm/qsharp/ux/circuit.tsx b/npm/qsharp/ux/circuit.tsx index 5bcdc42437..786763b521 100644 --- a/npm/qsharp/ux/circuit.tsx +++ b/npm/qsharp/ux/circuit.tsx @@ -28,11 +28,8 @@ export function Circuit(props: { let operations = 0; let errorMsg: string | undefined = undefined; - console.log("About to parse circuit file"); const result = toCircuitGroup(props.circuit); - if (result.ok) { - console.log("Successfully parsed circuit file"); const circuit = result.circuitGroup.circuits[0]; if (circuit.componentGrid === undefined) circuit.componentGrid = []; if (circuit.qubits === undefined) circuit.qubits = []; @@ -47,11 +44,8 @@ export function Circuit(props: { qubits > MAX_QUBITS; } else { errorMsg = result.error; - console.log("Failed to parse circuit file: ", errorMsg); } - console.log("Is Unrenderable after: ", unrenderable); - return (
{!result.ok || unrenderable ? ( From dd56b31fdd1d33369a6e2ac7d92b180b1d46d8c0 Mon Sep 17 00:00:00 2001 From: Scott Carda Date: Wed, 28 May 2025 11:28:34 -0700 Subject: [PATCH 3/4] Fixed function header --- npm/qsharp/src/shared/legacyCircuitUpdate.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/npm/qsharp/src/shared/legacyCircuitUpdate.ts b/npm/qsharp/src/shared/legacyCircuitUpdate.ts index 50d5442670..d30143627f 100644 --- a/npm/qsharp/src/shared/legacyCircuitUpdate.ts +++ b/npm/qsharp/src/shared/legacyCircuitUpdate.ts @@ -75,7 +75,10 @@ export function toCircuitGroup(circuit: any): ToCircuitGroupResult { /** * Attempts to convert a legacy circuit schema to a CircuitGroup. - * Returns a ToCircuitGroupResult with detailed error messages on failure. + * + * @param circuit The legacy circuit object to convert. + * @returns A ToCircuitGroupResult containing the converted CircuitGroup on success, + * or an error message on failure. */ function tryConvertLegacySchema(circuit: any): ToCircuitGroupResult { try { From c426111615700983af6a8f1f0d51906883e1bc3b Mon Sep 17 00:00:00 2001 From: Scott Carda Date: Wed, 4 Jun 2025 16:31:59 -0700 Subject: [PATCH 4/4] Move `shared/` to `data-structures/` --- npm/qsharp/src/browser.ts | 2 +- npm/qsharp/src/compiler/compiler.ts | 2 +- npm/qsharp/src/data-structures/README.md | 1 + .../{shared => data-structures}/circuit.ts | 0 .../legacyCircuitUpdate.ts | 48 +++++++++++++++++- .../{shared => data-structures}/register.ts | 0 npm/qsharp/src/debug-service/debug-service.ts | 2 +- npm/qsharp/src/shared/README.md | 1 - npm/qsharp/src/utils.ts | 50 +------------------ npm/qsharp/ux/circuit-vis/circuit.ts | 6 +-- npm/qsharp/ux/circuit-vis/draggable.ts | 2 +- npm/qsharp/ux/circuit-vis/events.ts | 3 +- npm/qsharp/ux/circuit-vis/register.ts | 2 +- npm/qsharp/ux/circuit-vis/utils.ts | 47 +++++++++++++++++ 14 files changed, 107 insertions(+), 59 deletions(-) create mode 100644 npm/qsharp/src/data-structures/README.md rename npm/qsharp/src/{shared => data-structures}/circuit.ts (100%) rename npm/qsharp/src/{shared => data-structures}/legacyCircuitUpdate.ts (87%) rename npm/qsharp/src/{shared => data-structures}/register.ts (100%) delete mode 100644 npm/qsharp/src/shared/README.md diff --git a/npm/qsharp/src/browser.ts b/npm/qsharp/src/browser.ts index 4f85b3f982..38377a6d51 100644 --- a/npm/qsharp/src/browser.ts +++ b/npm/qsharp/src/browser.ts @@ -197,4 +197,4 @@ export type { export * as utils from "./utils.js"; -export type { CircuitGroup as CircuitData } from "./shared/circuit.js"; +export type { CircuitGroup as CircuitData } from "./data-structures/circuit.js"; diff --git a/npm/qsharp/src/compiler/compiler.ts b/npm/qsharp/src/compiler/compiler.ts index 08fea131b0..d7a5d8ab30 100644 --- a/npm/qsharp/src/compiler/compiler.ts +++ b/npm/qsharp/src/compiler/compiler.ts @@ -4,7 +4,7 @@ import { CURRENT_VERSION, type CircuitGroup as CircuitData, -} from "../shared/circuit.js"; +} from "../data-structures/circuit.js"; import { IDocFile, IOperationInfo, diff --git a/npm/qsharp/src/data-structures/README.md b/npm/qsharp/src/data-structures/README.md new file mode 100644 index 0000000000..6d07ad93d1 --- /dev/null +++ b/npm/qsharp/src/data-structures/README.md @@ -0,0 +1 @@ +This directory contains the data structures used in both `qsharp/src/` and `qsharp/ux/`. diff --git a/npm/qsharp/src/shared/circuit.ts b/npm/qsharp/src/data-structures/circuit.ts similarity index 100% rename from npm/qsharp/src/shared/circuit.ts rename to npm/qsharp/src/data-structures/circuit.ts diff --git a/npm/qsharp/src/shared/legacyCircuitUpdate.ts b/npm/qsharp/src/data-structures/legacyCircuitUpdate.ts similarity index 87% rename from npm/qsharp/src/shared/legacyCircuitUpdate.ts rename to npm/qsharp/src/data-structures/legacyCircuitUpdate.ts index d30143627f..22c255d577 100644 --- a/npm/qsharp/src/shared/legacyCircuitUpdate.ts +++ b/npm/qsharp/src/data-structures/legacyCircuitUpdate.ts @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { getMinMaxRegIdx } from "../utils.js"; import { Circuit, CircuitGroup, @@ -13,6 +12,7 @@ import { Operation, Qubit, } from "./circuit.js"; +import { Register } from "./register.js"; export type ToCircuitGroupResult = | { ok: true; circuitGroup: CircuitGroup } @@ -393,3 +393,49 @@ function alignOps(ops: number[][]): (number | null)[][] { } return paddedOps; } + +/** + * Get the minimum and maximum register indices for a given operation. + * + * @param operation The operation for which to get the register indices. + * @param numQubits The number of qubits in the circuit. + * @returns A tuple containing the minimum and maximum register indices. + */ +function getMinMaxRegIdx( + operation: Operation, + numQubits: number, +): [number, number] { + let targets: Register[]; + let controls: Register[]; + switch (operation.kind) { + case "measurement": + targets = operation.results; + controls = operation.qubits; + break; + case "unitary": + targets = operation.targets; + controls = operation.controls || []; + break; + case "ket": + targets = operation.targets; + controls = []; + break; + } + + const qRegs = [...controls, ...targets] + .filter(({ result }) => result === undefined) + .map(({ qubit }) => qubit); + const clsControls: Register[] = controls.filter( + ({ result }) => result !== undefined, + ); + const isClassicallyControlled: boolean = clsControls.length > 0; + if (!isClassicallyControlled && qRegs.length === 0) return [-1, -1]; + // If operation is classically-controlled, pad all qubit registers. Otherwise, only pad + // the contiguous range of registers that it covers. + const minRegIdx: number = isClassicallyControlled ? 0 : Math.min(...qRegs); + const maxRegIdx: number = isClassicallyControlled + ? numQubits - 1 + : Math.max(...qRegs); + + return [minRegIdx, maxRegIdx]; +} diff --git a/npm/qsharp/src/shared/register.ts b/npm/qsharp/src/data-structures/register.ts similarity index 100% rename from npm/qsharp/src/shared/register.ts rename to npm/qsharp/src/data-structures/register.ts diff --git a/npm/qsharp/src/debug-service/debug-service.ts b/npm/qsharp/src/debug-service/debug-service.ts index 916912692b..d87b940f90 100644 --- a/npm/qsharp/src/debug-service/debug-service.ts +++ b/npm/qsharp/src/debug-service/debug-service.ts @@ -4,7 +4,7 @@ import { CURRENT_VERSION, type CircuitGroup as CircuitData, -} from "../shared/circuit.js"; +} from "../data-structures/circuit.js"; import type { DebugService, IBreakpointSpan, diff --git a/npm/qsharp/src/shared/README.md b/npm/qsharp/src/shared/README.md deleted file mode 100644 index 79f7c19f16..0000000000 --- a/npm/qsharp/src/shared/README.md +++ /dev/null @@ -1 +0,0 @@ -This directory contains shared modules to be referenced from both `qsharp/src/` and `qsharp/ux/`. diff --git a/npm/qsharp/src/utils.ts b/npm/qsharp/src/utils.ts index 2f0387c527..c253e90160 100644 --- a/npm/qsharp/src/utils.ts +++ b/npm/qsharp/src/utils.ts @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { Operation } from "./shared/circuit.js"; -import { Register } from "./shared/register.js"; +import { Operation } from "./data-structures/circuit.js"; +import { Register } from "./data-structures/register.js"; export type Tick = { value: number; @@ -219,49 +219,3 @@ export function getOperationRegisters(operation: Operation): Register[] { return [...operation.qubits, ...(operation.results || [])]; } } - -/** - * Get the minimum and maximum register indices for a given operation. - * - * @param operation The operation for which to get the register indices. - * @param numQubits The number of qubits in the circuit. - * @returns A tuple containing the minimum and maximum register indices. - */ -export function getMinMaxRegIdx( - operation: Operation, - numQubits: number, -): [number, number] { - let targets: Register[]; - let controls: Register[]; - switch (operation.kind) { - case "measurement": - targets = operation.results; - controls = operation.qubits; - break; - case "unitary": - targets = operation.targets; - controls = operation.controls || []; - break; - case "ket": - targets = operation.targets; - controls = []; - break; - } - - const qRegs = [...controls, ...targets] - .filter(({ result }) => result === undefined) - .map(({ qubit }) => qubit); - const clsControls: Register[] = controls.filter( - ({ result }) => result !== undefined, - ); - const isClassicallyControlled: boolean = clsControls.length > 0; - if (!isClassicallyControlled && qRegs.length === 0) return [-1, -1]; - // If operation is classically-controlled, pad all qubit registers. Otherwise, only pad - // the contiguous range of registers that it covers. - const minRegIdx: number = isClassicallyControlled ? 0 : Math.min(...qRegs); - const maxRegIdx: number = isClassicallyControlled - ? numQubits - 1 - : Math.max(...qRegs); - - return [minRegIdx, maxRegIdx]; -} diff --git a/npm/qsharp/ux/circuit-vis/circuit.ts b/npm/qsharp/ux/circuit-vis/circuit.ts index 51ba236e4a..22d31eb93d 100644 --- a/npm/qsharp/ux/circuit-vis/circuit.ts +++ b/npm/qsharp/ux/circuit-vis/circuit.ts @@ -15,7 +15,7 @@ export { type Unitary, type Parameter, type Qubit, -} from "../../src/shared/circuit"; +} from "../../src/data-structures/circuit"; -export { CURRENT_VERSION } from "../../src/shared/circuit"; -export { toCircuitGroup } from "../../src/shared/legacyCircuitUpdate"; +export { CURRENT_VERSION } from "../../src/data-structures/circuit"; +export { toCircuitGroup } from "../../src/data-structures/legacyCircuitUpdate"; diff --git a/npm/qsharp/ux/circuit-vis/draggable.ts b/npm/qsharp/ux/circuit-vis/draggable.ts index 5054414cf8..d41a1ad6f4 100644 --- a/npm/qsharp/ux/circuit-vis/draggable.ts +++ b/npm/qsharp/ux/circuit-vis/draggable.ts @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { getMinMaxRegIdx } from "../../src/utils"; import { ComponentGrid, Operation } from "./circuit"; import { gatePadding, @@ -18,6 +17,7 @@ import { Sqore } from "./sqore"; import { findLocation, getHostElems, + getMinMaxRegIdx, getToolboxElems, getWireData, locationStringToIndexes, diff --git a/npm/qsharp/ux/circuit-vis/events.ts b/npm/qsharp/ux/circuit-vis/events.ts index 7ec94aeca1..2e63859cb6 100644 --- a/npm/qsharp/ux/circuit-vis/events.ts +++ b/npm/qsharp/ux/circuit-vis/events.ts @@ -15,6 +15,7 @@ import { findParentArray, deepEqual, getQubitLabelElems, + getMinMaxRegIdx, } from "./utils"; import { addContextMenuToHostElem, promptForArguments } from "./contextMenu"; import { @@ -35,7 +36,7 @@ import { makeDropzoneBox, removeAllWireDropzones, } from "./draggable"; -import { getMinMaxRegIdx, getOperationRegisters } from "../../src/utils"; +import { getOperationRegisters } from "../../src/utils"; let events: CircuitEvents | null = null; diff --git a/npm/qsharp/ux/circuit-vis/register.ts b/npm/qsharp/ux/circuit-vis/register.ts index 8f1360b52f..81d849aa33 100644 --- a/npm/qsharp/ux/circuit-vis/register.ts +++ b/npm/qsharp/ux/circuit-vis/register.ts @@ -6,4 +6,4 @@ export { type Register, type RegisterMap, type RegisterRenderData, -} from "../../src/shared/register"; +} from "../../src/data-structures/register"; diff --git a/npm/qsharp/ux/circuit-vis/utils.ts b/npm/qsharp/ux/circuit-vis/utils.ts index 09825c4edb..85b0b44d81 100644 --- a/npm/qsharp/ux/circuit-vis/utils.ts +++ b/npm/qsharp/ux/circuit-vis/utils.ts @@ -207,6 +207,52 @@ const getGateLocationString = (operation: Operation): string | null => { return operation.dataAttributes["location"]; }; +/** + * Get the minimum and maximum register indices for a given operation. + * + * @param operation The operation for which to get the register indices. + * @param numQubits The number of qubits in the circuit. + * @returns A tuple containing the minimum and maximum register indices. + */ +function getMinMaxRegIdx( + operation: Operation, + numQubits: number, +): [number, number] { + let targets: Register[]; + let controls: Register[]; + switch (operation.kind) { + case "measurement": + targets = operation.results; + controls = operation.qubits; + break; + case "unitary": + targets = operation.targets; + controls = operation.controls || []; + break; + case "ket": + targets = operation.targets; + controls = []; + break; + } + + const qRegs = [...controls, ...targets] + .filter(({ result }) => result === undefined) + .map(({ qubit }) => qubit); + const clsControls: Register[] = controls.filter( + ({ result }) => result !== undefined, + ); + const isClassicallyControlled: boolean = clsControls.length > 0; + if (!isClassicallyControlled && qRegs.length === 0) return [-1, -1]; + // If operation is classically-controlled, pad all qubit registers. Otherwise, only pad + // the contiguous range of registers that it covers. + const minRegIdx: number = isClassicallyControlled ? 0 : Math.min(...qRegs); + const maxRegIdx: number = isClassicallyControlled + ? numQubits - 1 + : Math.max(...qRegs); + + return [minRegIdx, maxRegIdx]; +} + /********************** * Finder Functions * **********************/ @@ -399,6 +445,7 @@ export { getChildTargets, locationStringToIndexes, getGateLocationString, + getMinMaxRegIdx, findGateElem, findLocation, findParentOperation,