-
Notifications
You must be signed in to change notification settings - Fork 2
JCZ transpiler revision #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
335e4d3
94bea7d
86b3701
1b619c7
70c5c8c
af68b6e
9e092d9
f4b435d
057a3e9
d02b16c
97ab84a
c513c43
248ed5b
c1d1d21
eeddaf9
b01f5f1
3ca5c2c
9a5c69b
4cdb31c
509a2d9
6d0907f
a417506
95aeae0
bcdf5a8
cc7dfbe
9c0c709
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,6 @@ | ||
| *~ | ||
| *.egg-info/ | ||
| __pycache__/ | ||
| .coverage | ||
| .vscode/ | ||
| .mypy_cache/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,15 +12,19 @@ | |
| from math import pi | ||
| from typing import TYPE_CHECKING, ClassVar, Literal | ||
|
|
||
| import networkx as nx | ||
| from graphix import Pattern, command, instruction | ||
| from graphix.fundamentals import Plane | ||
| from graphix.instruction import InstructionKind | ||
| from graphix.measurements import Measurement | ||
| from graphix.opengraph import OpenGraph | ||
| from graphix.transpiler import Circuit, TranspileResult | ||
| from typing_extensions import TypeAlias, assert_never | ||
|
|
||
| if TYPE_CHECKING: | ||
| from collections.abc import Iterable | ||
| from collections.abc import Sequence | ||
|
|
||
| from graphix.parameters import ExpressionOrFloat | ||
| from graphix.parameter import ExpressionOrFloat | ||
|
|
||
|
|
||
| class JCZInstructionKind(Enum): | ||
|
|
@@ -124,15 +128,17 @@ def decompose_rzz(instr: instruction.RZZ) -> list[instruction.CNOT | instruction | |
|
|
||
| """ | ||
| return [ | ||
| instruction.CNOT(control=instr.control, target=instr.target), | ||
| instruction.CNOT(target=instr.target, control=instr.control), | ||
| instruction.RZ(instr.target, instr.angle), | ||
| instruction.CNOT(control=instr.control, target=instr.target), | ||
| instruction.CNOT(target=instr.target, control=instr.control), | ||
| ] | ||
|
|
||
|
|
||
| def decompose_cnot(instr: instruction.CNOT) -> list[instruction.H | CZ]: | ||
| """Return a decomposition of the CNOT gate as H·∧z·H. | ||
|
|
||
| Vincent Danos, Elham Kashefi, Prakash Panangaden, The Measurement Calculus, 2007. | ||
|
|
||
| Args: | ||
| ---- | ||
| instr: the CNOT instruction to decompose. | ||
|
|
@@ -152,6 +158,11 @@ def decompose_cnot(instr: instruction.CNOT) -> list[instruction.H | CZ]: | |
| def decompose_swap(instr: instruction.SWAP) -> list[instruction.CNOT]: | ||
| """Return a decomposition of the SWAP gate as CNOT(0, 1)·CNOT(1, 0)·CNOT(0, 1). | ||
|
|
||
| Michael A. Nielsen and Isaac L. Chuang, | ||
| Quantum Computation and Quantum Information, | ||
| Cambridge University Press, 2000 | ||
| (p. 23 in the 10th Anniversary Edition). | ||
|
|
||
| Args: | ||
| ---- | ||
| instr: the SWAP instruction to decompose. | ||
|
|
@@ -168,7 +179,7 @@ def decompose_swap(instr: instruction.SWAP) -> list[instruction.CNOT]: | |
| ] | ||
|
|
||
|
|
||
| def decompose_y(instr: instruction.Y) -> Iterable[instruction.X | instruction.Z]: | ||
| def decompose_y(instr: instruction.Y) -> list[instruction.X | instruction.Z]: | ||
| """Return a decomposition of the Y gate as X·Z. | ||
|
|
||
| Args: | ||
|
|
@@ -180,7 +191,7 @@ def decompose_y(instr: instruction.Y) -> Iterable[instruction.X | instruction.Z] | |
| the decomposition. | ||
|
|
||
| """ | ||
| return reversed([instruction.X(instr.target), instruction.Z(instr.target)]) | ||
| return list(reversed([instruction.X(instr.target), instruction.Z(instr.target)])) | ||
|
|
||
|
|
||
| def decompose_rx(instr: instruction.RX) -> list[J]: | ||
|
|
@@ -220,7 +231,7 @@ def decompose_ry(instr: instruction.RY) -> list[J]: | |
| return [J(target=instr.target, angle=angle) for angle in reversed((0, pi / 2, instr.angle, -pi / 2))] | ||
|
|
||
|
|
||
| def decompose_rz(instr: instruction.RZ) -> Iterable[J]: | ||
| def decompose_rz(instr: instruction.RZ) -> list[J]: | ||
| """Return a J decomposition of the RZ gate. | ||
|
|
||
| The Rz(α) gate is decomposed into H·J(α) (that is to say, J(0)·J(α)). | ||
|
|
@@ -238,7 +249,7 @@ def decompose_rz(instr: instruction.RZ) -> Iterable[J]: | |
| return [J(target=instr.target, angle=angle) for angle in reversed((0, instr.angle))] | ||
|
|
||
|
|
||
| def instruction_to_jcz(instr: JCZInstruction) -> Iterable[J | CZ]: | ||
| def instruction_to_jcz(instr: JCZInstruction) -> Sequence[J | CZ]: | ||
| """Return a J-∧z decomposition of the instruction. | ||
|
|
||
| Args: | ||
|
|
@@ -282,7 +293,7 @@ def instruction_to_jcz(instr: JCZInstruction) -> Iterable[J | CZ]: | |
| assert_never(instr.kind) | ||
|
|
||
|
|
||
| def instruction_list_to_jcz(instrs: Iterable[JCZInstruction]) -> list[J | CZ]: | ||
| def instruction_list_to_jcz(instrs: Sequence[JCZInstruction]) -> list[J | CZ]: | ||
| """Return a J-∧z decomposition of the sequence of instructions. | ||
|
|
||
| Args: | ||
|
|
@@ -297,14 +308,22 @@ def instruction_list_to_jcz(instrs: Iterable[JCZInstruction]) -> list[J | CZ]: | |
| return [jcz_instr for instr in instrs for jcz_instr in instruction_to_jcz(instr)] | ||
|
|
||
|
|
||
| class IllformedPatternError(Exception): | ||
| class IllformedCircuitError(Exception): | ||
| """Raised if the circuit is ill-formed.""" | ||
|
|
||
| def __init__(self) -> None: | ||
| """Build the exception.""" | ||
| super().__init__("Ill-formed pattern") | ||
|
|
||
|
|
||
| class CircuitWithMeasurementError(Exception): | ||
| """Raised if the circuit contains measurements.""" | ||
|
|
||
| def __init__(self) -> None: | ||
| """Build the exception.""" | ||
| super().__init__("Circuits containing measurements are not supported by the transpiler.") | ||
|
|
||
|
|
||
| class InternalInstructionError(Exception): | ||
| """Raised if the circuit contains internal _XC or _ZC instructions.""" | ||
|
|
||
|
|
@@ -313,6 +332,29 @@ def __init__(self, instr: instruction.Instruction) -> None: | |
| super().__init__(f"Internal instruction: {instr}") | ||
|
|
||
|
|
||
| def j_commands(current_node: int, next_node: int, angle: ExpressionOrFloat) -> list[command.Command]: | ||
| """Return the MBQC pattern commands for a J gate. | ||
|
|
||
| Args: | ||
| ---- | ||
| current_node: the current node. | ||
| next_node: the next node. | ||
| angle: the angle of the J gate. | ||
| domain: the domain the X correction is based on. | ||
|
|
||
| Returns: | ||
| ------- | ||
| the MBQC pattern commands for a J gate as a list | ||
|
|
||
| """ | ||
| return [ | ||
| command.N(node=next_node), | ||
| command.E(nodes=(current_node, next_node)), | ||
| command.M(node=current_node, angle=(angle / pi) + 0.0), # Avoids -0.0 | ||
| command.X(node=next_node, domain={current_node}), | ||
| ] | ||
|
|
||
|
|
||
| def transpile_jcz(circuit: Circuit) -> TranspileResult: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comment for line 374: why do we expect the circuit to contain
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't, but unfortunately the instructions |
||
| """Transpile a circuit via a J-∧z decomposition. | ||
|
|
||
|
|
@@ -326,8 +368,8 @@ def transpile_jcz(circuit: Circuit) -> TranspileResult: | |
|
|
||
| Raises: | ||
| ------ | ||
| IllformedPatternError: if the pattern is ill-formed. | ||
| InternalInstructionError: if the circuit contains internal _XC or _ZC instructions. | ||
| IllformedCircuitError: if the circuit has underdefined instructions. | ||
|
|
||
| """ | ||
| indices: list[int | None] = list(range(circuit.width)) | ||
|
|
@@ -347,26 +389,91 @@ def transpile_jcz(circuit: Circuit) -> TranspileResult: | |
| if instr_jcz.kind == JCZInstructionKind.J: | ||
| target = indices[instr_jcz.target] | ||
| if target is None: | ||
| raise IllformedPatternError | ||
| raise IllformedCircuitError | ||
| ancilla = n_nodes | ||
| n_nodes += 1 | ||
| pattern.extend( | ||
| [ | ||
| command.N(node=ancilla), | ||
| command.E(nodes=(target, ancilla)), | ||
| command.M(node=target, angle=-instr_jcz.angle / pi), | ||
| command.X(node=ancilla, domain={target}), | ||
| ], | ||
| ) | ||
| pattern.extend(j_commands(target, ancilla, -instr_jcz.angle)) | ||
| indices[instr_jcz.target] = ancilla | ||
| continue | ||
| if instr_jcz.kind == JCZInstructionKind.CZ: | ||
| t0, t1 = instr_jcz.targets | ||
| i0, i1 = indices[t0], indices[t1] | ||
| if i0 is None or i1 is None: | ||
| raise IllformedPatternError | ||
| raise IllformedCircuitError | ||
| pattern.extend([command.E(nodes=(i0, i1))]) | ||
| continue | ||
| assert_never(instr_jcz.kind) | ||
| pattern.reorder_output_nodes([i for i in indices if i is not None]) | ||
| return TranspileResult(pattern, tuple(classical_outputs)) | ||
|
|
||
|
|
||
| def circuit_to_open_graph(circuit: Circuit) -> OpenGraph[Measurement]: | ||
| """Transpile a circuit via a J-∧z-like decomposition to an open graph. | ||
|
|
||
| Args: | ||
| ---- | ||
| circuit: the circuit to transpile. | ||
|
|
||
| Returns: | ||
| ------- | ||
| the result of the transpilation: an open graph. | ||
|
|
||
| Raises: | ||
| ------ | ||
| IllformedCircuitError: if the pattern is ill-formed (operation on already measured node) | ||
| InternalInstructionError: if the circuit contains internal _XC or _ZC instructions. | ||
| CircuitWithMeasurementError: if the circuit contains measurements. | ||
|
|
||
| """ | ||
| indices: list[int | None] = list(range(circuit.width)) | ||
| n_nodes = circuit.width | ||
| measurements: dict[int, Measurement] = {} | ||
| inputs = list(range(n_nodes)) | ||
| graph: nx.Graph[int] = nx.Graph() # type: ignore[name-defined,attr-defined] | ||
| graph.add_nodes_from(inputs) | ||
| for instr in circuit.instruction: | ||
| if instr.kind == InstructionKind.M: | ||
| raise CircuitWithMeasurementError | ||
| # Use == for mypy | ||
| if instr.kind == InstructionKind._XC or instr.kind == InstructionKind._ZC: # noqa: PLR1714, SLF001 | ||
| raise InternalInstructionError(instr) | ||
| for instr_jcz in instruction_to_jcz(instr): | ||
| if instr_jcz.kind == JCZInstructionKind.J: | ||
| target = indices[instr_jcz.target] | ||
| if target is None: | ||
| raise IllformedCircuitError | ||
| ancilla = n_nodes | ||
| n_nodes += 1 | ||
| graph.add_node(ancilla) | ||
| graph.add_edge(target, ancilla) | ||
| measurements[target] = Measurement(-instr_jcz.angle / pi, plane=Plane.XY) | ||
| indices[instr_jcz.target] = ancilla | ||
| continue | ||
| if instr_jcz.kind == JCZInstructionKind.CZ: | ||
| t0, t1 = instr_jcz.targets | ||
| i0, i1 = indices[t0], indices[t1] | ||
| if i0 is None or i1 is None: | ||
| raise IllformedCircuitError | ||
| graph.add_edge(i0, i1) | ||
| continue | ||
| assert_never(instr_jcz.kind) | ||
| outputs = [i for i in indices if i is not None] | ||
| return OpenGraph(graph=graph, input_nodes=inputs, output_nodes=outputs, measurements=measurements) | ||
|
|
||
|
|
||
| def transpile_jcz_open_graph(circuit: Circuit) -> TranspileResult: | ||
| """Transpile a circuit via a J-∧z-like decomposition to a pattern. | ||
|
|
||
| Currently fails due to overuse of memory in conversion from open graph to pattern, assumed in the causal flow step. | ||
|
|
||
| Args: | ||
| ---- | ||
| circuit: the circuit to transpile. | ||
|
|
||
| Returns: | ||
| ------- | ||
| the result of the transpilation: a pattern. | ||
|
|
||
| """ | ||
| og = circuit_to_open_graph(circuit) | ||
| return TranspileResult(og.to_pattern(), tuple(og.measurements.keys())) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe remove the
. vscodethat is local?