diff --git a/bqskit/compiler/compile.py b/bqskit/compiler/compile.py index 87db9db93..4824c42ad 100644 --- a/bqskit/compiler/compile.py +++ b/bqskit/compiler/compile.py @@ -52,7 +52,7 @@ from bqskit.passes.mapping.routing.pam import PAMRoutingPass from bqskit.passes.mapping.routing.sabre import GeneralizedSabreRoutingPass from bqskit.passes.mapping.setmodel import ExtractModelConnectivityPass -from bqskit.passes.mapping.setmodel import RestoreModelConnevtivityPass +from bqskit.passes.mapping.setmodel import RestoreModelConnectivityPass from bqskit.passes.mapping.setmodel import SetModelPass from bqskit.passes.mapping.topology import SubtopologySelectionPass from bqskit.passes.mapping.verify import PAMVerificationSequence @@ -1012,7 +1012,7 @@ def build_multi_qudit_retarget_workflow( optimization_level, synthesis_epsilon, ), - RestoreModelConnevtivityPass(), + RestoreModelConnectivityPass(), ], AutoRebase2QuditGatePass(3, 5, synthesis_epsilon), ), @@ -1257,7 +1257,7 @@ def build_seqpam_mapping_optimization_workflow( PAMRoutingPass(), post_pam_seq, UnfoldPass(), - RestoreModelConnevtivityPass(), + RestoreModelConnectivityPass(), LogPass('Recaching permutation-aware synthesis results.'), SubtopologySelectionPass(block_size), diff --git a/bqskit/ir/gates/__init__.py b/bqskit/ir/gates/__init__.py index 7505d0d88..1c9a83cbb 100644 --- a/bqskit/ir/gates/__init__.py +++ b/bqskit/ir/gates/__init__.py @@ -23,6 +23,7 @@ CXGate CYGate CZGate + ECRGate HGate IdentityGate ISwapGate @@ -37,6 +38,7 @@ ShiftGate SqrtCNOTGate SqrtISwapGate + SqrtTGate SubSwapGate SwapGate SqrtXGate diff --git a/bqskit/ir/gates/constant/__init__.py b/bqskit/ir/gates/constant/__init__.py index 5492fa12a..b9c99b1ac 100644 --- a/bqskit/ir/gates/constant/__init__.py +++ b/bqskit/ir/gates/constant/__init__.py @@ -14,6 +14,7 @@ from bqskit.ir.gates.constant.cx import CXGate from bqskit.ir.gates.constant.cy import CYGate from bqskit.ir.gates.constant.cz import CZGate +from bqskit.ir.gates.constant.ecr import ECRGate from bqskit.ir.gates.constant.h import HGate from bqskit.ir.gates.constant.identity import IdentityGate from bqskit.ir.gates.constant.iswap import ISwapGate @@ -28,6 +29,7 @@ from bqskit.ir.gates.constant.shift import ShiftGate from bqskit.ir.gates.constant.sqrtcnot import SqrtCNOTGate from bqskit.ir.gates.constant.sqrtiswap import SqrtISwapGate +from bqskit.ir.gates.constant.sqrtt import SqrtTGate from bqskit.ir.gates.constant.subswap import SubSwapGate from bqskit.ir.gates.constant.swap import SwapGate from bqskit.ir.gates.constant.sx import SqrtXGate @@ -57,6 +59,7 @@ 'CXGate', 'CYGate', 'CZGate', + 'ECRGate', 'HGate', 'IdentityGate', 'ISwapGate', @@ -71,6 +74,7 @@ 'ShiftGate', 'SqrtCNOTGate', 'SqrtISwapGate', + 'SqrtTGate', 'SubSwapGate', 'SwapGate', 'SqrtXGate', diff --git a/bqskit/ir/gates/constant/ecr.py b/bqskit/ir/gates/constant/ecr.py new file mode 100644 index 000000000..9ad42b56d --- /dev/null +++ b/bqskit/ir/gates/constant/ecr.py @@ -0,0 +1,50 @@ +"""This module implements the ECRGate.""" +from __future__ import annotations + +import math + +from bqskit.ir.gates.constantgate import ConstantGate +from bqskit.ir.gates.qubitgate import QubitGate +from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix + + +class ECRGate(ConstantGate, QubitGate): + """ + The echoed cross-resonance gate (ECR). + + The ECR gate is given by the following unitary: + + .. math:: + + \\frac{1}{\\sqrt{2}} + \\begin{pmatrix} + 0 & 1 & 0 & i \\\\ + 1 & 0 & -i & 0 \\\\ + 0 & i & 0 & 1 \\\\ + -i & 0 & 1 & 0 + \\end{pmatrix} + """ + _name = 'ecr' + _num_qudits = 2 + _qasm_name = 'ecr' + _utry = UnitaryMatrix([ + [0, 0, 1 * 1 / math.sqrt(2), 1j * 1 / math.sqrt(2)], + [0, 0, 1j * 1 / math.sqrt(2), 1 * 1 / math.sqrt(2)], + [1 * 1 / math.sqrt(2), -1j * 1 / math.sqrt(2), 0, 0], + [-1j * 1 / math.sqrt(2), 1 * 1 / math.sqrt(2), 0, 0], + ]) + + def __init__(self) -> None: + pass + + def get_qasm_gate_def(self) -> str: + qasm_rzx = ( + 'gate rzx(param0) q0,q1 {' + 'h q1;' + 'cx q0,q1;' + 'rz(param0) q1;' + 'cx q0,q1;' + 'h q1; }' + ) + qasm_ecr = 'gate ecr q0,q1 { rzx(pi/4) q0,q1; x q0; rzx(-pi/4) q0,q1; }' + return qasm_rzx + '\n' + qasm_ecr diff --git a/bqskit/ir/gates/constant/sqrtt.py b/bqskit/ir/gates/constant/sqrtt.py new file mode 100644 index 000000000..93b2b23c7 --- /dev/null +++ b/bqskit/ir/gates/constant/sqrtt.py @@ -0,0 +1,30 @@ +"""This module implements the SqrtTGate.""" +from __future__ import annotations + +import cmath + +from bqskit.ir.gates.constantgate import ConstantGate +from bqskit.ir.gates.qubitgate import QubitGate +from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix + + +class SqrtTGate(ConstantGate, QubitGate): + """ + The single-qubit square root T gate. + + .. math:: + + \\begin{pmatrix} + 1 & 0 \\\\ + 0 & e^{i\\frac{\\pi}{8}} \\\\ + \\end{pmatrix} + """ + + _num_qudits = 1 + _qasm_name = 'st' + _utry = UnitaryMatrix( + [ + [1, 0], + [0, cmath.exp(1j * cmath.pi / 8)], + ], + ) diff --git a/bqskit/ir/gates/parameterized/u8.py b/bqskit/ir/gates/parameterized/u8.py index 85808f713..814ff6aab 100644 --- a/bqskit/ir/gates/parameterized/u8.py +++ b/bqskit/ir/gates/parameterized/u8.py @@ -91,12 +91,12 @@ def get_grad(self, params: RealVector = []) -> npt.NDArray[np.complex128]: [ # wrt params[0] [-s1 * c2 * p1, c1 * p3, -s1 * s2 * p4], [ - -c1 * c2 * c3 * p1 * p2 * m3, -s1 * \ - c3 * p2, -c1 * s2 * c3 * p2 * m3 * p4, + -c1 * c2 * c3 * p1 * p2 * m3, -s1 * c3 * p2, + -c1 * s2 * c3 * p2 * m3 * p4, ], [ - -c1 * c2 * s3 * p1 * m3 * p5, -s1 * \ - s3 * p5, -c1 * s2 * s3 * m3 * p4 * p5, + -c1 * c2 * s3 * p1 * m3 * p5, -s1 * s3 * p5, + -c1 * s2 * s3 * m3 * p4 * p5, ], ], @@ -142,8 +142,8 @@ def get_grad(self, params: RealVector = []) -> npt.NDArray[np.complex128]: [ # wrt params[4] [0, 0, 0], [ - -1j * s1 * c2 * c3 * p1 * p2 * m3, 1j * c1 * \ - c3 * p2, -1j * s1 * s2 * c3 * p2 * m3 * p4, + -1j * s1 * c2 * c3 * p1 * p2 * m3, 1j * c1 * c3 * p2, + -1j * s1 * s2 * c3 * p2 * m3 * p4, ], [1j * s2 * c3 * m2 * m4, 0, -1j * c2 * c3 * m1 * m2], ], @@ -178,8 +178,8 @@ def get_grad(self, params: RealVector = []) -> npt.NDArray[np.complex128]: [0, 0, 0], [-1j * s2 * s3 * m4 * m5, 0, 1j * c2 * s3 * m1 * m5], [ - -1j * s1 * c2 * s3 * p1 * m3 * p5, 1j * c1 * \ - s3 * p5, -1j * s1 * s2 * s3 * m3 * p4 * p5, + -1j * s1 * c2 * s3 * p1 * m3 * p5, 1j * c1 * s3 * p5, + -1j * s1 * s2 * s3 * m3 * p4 * p5, ], ], ], diff --git a/bqskit/ir/lang/qasm2/visitor.py b/bqskit/ir/lang/qasm2/visitor.py index 0adada3a0..771e011c6 100644 --- a/bqskit/ir/lang/qasm2/visitor.py +++ b/bqskit/ir/lang/qasm2/visitor.py @@ -28,6 +28,7 @@ from bqskit.ir.gates.constant.cx import CXGate from bqskit.ir.gates.constant.cy import CYGate from bqskit.ir.gates.constant.cz import CZGate +from bqskit.ir.gates.constant.ecr import ECRGate from bqskit.ir.gates.constant.h import HGate from bqskit.ir.gates.constant.identity import IdentityGate from bqskit.ir.gates.constant.iswap import ISwapGate @@ -221,6 +222,7 @@ def fill_gate_defs(self) -> None: self.gate_defs['CX'] = GateDef('CX', 0, 2, CXGate()) self.gate_defs['cy'] = GateDef('cy', 0, 2, CYGate()) self.gate_defs['cz'] = GateDef('cz', 0, 2, CZGate()) + self.gate_defs['ecr'] = GateDef('ecr', 0, 2, ECRGate()) self.gate_defs['h'] = GateDef('h', 0, 1, HGate()) self.gate_defs['id'] = GateDef('id', 0, 1, IdentityGate(1)) self.gate_defs['u0'] = GateDef('u0', 0, 1, IdentityGate(1)) diff --git a/bqskit/passes/__init__.py b/bqskit/passes/__init__.py index eb8e41607..c7d85a504 100644 --- a/bqskit/passes/__init__.py +++ b/bqskit/passes/__init__.py @@ -129,7 +129,7 @@ PAMRoutingPass EmbedAllPermutationsPass ExtractModelConnectivityPass - RestoreModelConnevtivityPass + RestoreModelConnectivityPass .. rubric:: PAM Verification Passes @@ -234,11 +234,12 @@ from bqskit.passes.mapping.layout.pam import PAMLayoutPass from bqskit.passes.mapping.layout.sabre import GeneralizedSabreLayoutPass from bqskit.passes.mapping.placement.greedy import GreedyPlacementPass +from bqskit.passes.mapping.placement.static import StaticPlacementPass from bqskit.passes.mapping.placement.trivial import TrivialPlacementPass from bqskit.passes.mapping.routing.pam import PAMRoutingPass from bqskit.passes.mapping.routing.sabre import GeneralizedSabreRoutingPass from bqskit.passes.mapping.setmodel import ExtractModelConnectivityPass -from bqskit.passes.mapping.setmodel import RestoreModelConnevtivityPass +from bqskit.passes.mapping.setmodel import RestoreModelConnectivityPass from bqskit.passes.mapping.setmodel import SetModelPass from bqskit.passes.mapping.topology import SubtopologySelectionPass from bqskit.passes.mapping.verify import CalculatePAMErrorsPass @@ -364,6 +365,7 @@ 'GeneralizedSabreLayoutPass', 'GreedyPlacementPass', 'TrivialPlacementPass', + 'StaticPlacementPass', 'GeneralizedSabreRoutingPass', 'SetModelPass', 'U3Decomposition', @@ -398,7 +400,7 @@ 'GeneralSQDecomposition', 'StructureAnalysisPass', 'ExtractModelConnectivityPass', - 'RestoreModelConnevtivityPass', + 'RestoreModelConnectivityPass', 'TagPAMBlockDataPass', 'CalculatePAMErrorsPass', 'UnTagPAMBlockDataPass', diff --git a/bqskit/passes/mapping/__init__.py b/bqskit/passes/mapping/__init__.py index 5ffc9dd2f..83265ea13 100644 --- a/bqskit/passes/mapping/__init__.py +++ b/bqskit/passes/mapping/__init__.py @@ -6,11 +6,12 @@ from bqskit.passes.mapping.layout.pam import PAMLayoutPass from bqskit.passes.mapping.layout.sabre import GeneralizedSabreLayoutPass from bqskit.passes.mapping.placement.greedy import GreedyPlacementPass +from bqskit.passes.mapping.placement.static import StaticPlacementPass from bqskit.passes.mapping.placement.trivial import TrivialPlacementPass from bqskit.passes.mapping.routing.pam import PAMRoutingPass from bqskit.passes.mapping.routing.sabre import GeneralizedSabreRoutingPass from bqskit.passes.mapping.setmodel import ExtractModelConnectivityPass -from bqskit.passes.mapping.setmodel import RestoreModelConnevtivityPass +from bqskit.passes.mapping.setmodel import RestoreModelConnectivityPass from bqskit.passes.mapping.setmodel import SetModelPass from bqskit.passes.mapping.topology import SubtopologySelectionPass from bqskit.passes.mapping.verify import CalculatePAMErrorsPass @@ -22,6 +23,7 @@ 'GeneralizedSabreLayoutPass', 'GreedyPlacementPass', 'TrivialPlacementPass', + 'StaticPlacementPass', 'GeneralizedSabreRoutingPass', 'SetModelPass', 'ApplyPlacement', @@ -30,7 +32,7 @@ 'EmbedAllPermutationsPass', 'SubtopologySelectionPass', 'ExtractModelConnectivityPass', - 'RestoreModelConnevtivityPass', + 'RestoreModelConnectivityPass', 'TagPAMBlockDataPass', 'CalculatePAMErrorsPass', 'UnTagPAMBlockDataPass', diff --git a/bqskit/passes/mapping/placement/__init__.py b/bqskit/passes/mapping/placement/__init__.py index bad954a86..8281c7c4d 100644 --- a/bqskit/passes/mapping/placement/__init__.py +++ b/bqskit/passes/mapping/placement/__init__.py @@ -7,6 +7,7 @@ from __future__ import annotations from bqskit.passes.mapping.placement.greedy import GreedyPlacementPass +from bqskit.passes.mapping.placement.static import StaticPlacementPass from bqskit.passes.mapping.placement.trivial import TrivialPlacementPass -__all__ = ['GreedyPlacementPass', 'TrivialPlacementPass'] +__all__ = ['GreedyPlacementPass', 'TrivialPlacementPass', 'StaticPlacementPass'] diff --git a/bqskit/passes/mapping/placement/static.py b/bqskit/passes/mapping/placement/static.py new file mode 100644 index 000000000..187eeb044 --- /dev/null +++ b/bqskit/passes/mapping/placement/static.py @@ -0,0 +1,135 @@ +"""This module implements the StaticPlacementPass class.""" +from __future__ import annotations + +import logging +import time + +from bqskit.compiler.basepass import BasePass +from bqskit.compiler.passdata import PassData +from bqskit.ir.circuit import Circuit +from bqskit.qis.graph import CouplingGraph + +_logger = logging.getLogger(__name__) + + +class StaticPlacementPass(BasePass): + """Find a subgraph monomorphic to the coupling graph so that no SWAPs are + needed.""" + + def __init__(self, timeout_sec: float = 10) -> None: + self.timeout_sec = timeout_sec + + def _find_monomorphic_subgraph( + self, + time_limit: float, + physical_graph: CouplingGraph, + num_logical_qudits: int, + minimal_degrees: list[int], + connected_indices: list[list[int]], + current_placement: list[int] = [], + current_index: int = 0, + ) -> list[int]: + """Recursively find a monomorphic subgraph.""" + if current_index == num_logical_qudits: + return current_placement + + if time.time() > time_limit: + return [] + + # Find all possible placements for the current logical qudit + candidate_indices = set() + + # Filter out occupied qudits and qudits with insufficient degrees + physical_degrees = physical_graph.get_qudit_degrees() + for x in range(physical_graph.num_qudits): + if ( + physical_degrees[x] >= minimal_degrees[current_index] + and x not in current_placement + ): + candidate_indices.add(x) + + # Filter out qudits that are not connected to previous logical qudits + for i in connected_indices[current_index]: + candidate_indices &= set( + physical_graph.get_neighbors_of(current_placement[i]), + ) + + # Try all possible placements for the current logical qudit + for x in candidate_indices: + new_placement = current_placement + [x] + result = self._find_monomorphic_subgraph( + time_limit, + physical_graph, + num_logical_qudits, + minimal_degrees, + connected_indices, + new_placement, + current_index + 1, + ) + if len(result) == num_logical_qudits: + return result + + # If no valid placement is found, return an empty list + return [] + + def find_monomorphic_subgraph( + self, + physical_graph: CouplingGraph, + logical_graph: CouplingGraph, + ) -> list[int]: + """Try all possible placements.""" + + # To be optimized later + logical_qubit_order = list(range(logical_graph.num_qudits)) + + minimum_degrees = [ + logical_graph.get_qudit_degrees()[i] for i in logical_qubit_order + ] + connected_indices: list[list[int]] = [ + [] for _ in range(logical_graph.num_qudits) + ] + for i in range(logical_graph.num_qudits): + for j in range(i): + if logical_qubit_order[j] in logical_graph.get_neighbors_of( + logical_qubit_order[i], + ): + connected_indices[i].append(j) + + # Find a monomorphic subgraph + start_time = time.time() + index_to_physical = self._find_monomorphic_subgraph( + start_time + self.timeout_sec, + physical_graph, + logical_graph.num_qudits, + minimum_degrees, + connected_indices, + ) + _logger.info(f'elapsed time: {time.time() - start_time}') + if len(index_to_physical) == 0: + return [] + + # Convert the result to a placement + placement = [-1] * logical_graph.num_qudits + for i, x in enumerate(logical_qubit_order): + placement[x] = index_to_physical[i] + return placement + + async def run(self, circuit: Circuit, data: PassData) -> None: + """Perform the pass's operation, see :class:`BasePass` for more.""" + physical_graph = data.model.coupling_graph + logical_graph = circuit.coupling_graph + + # Find an monomorphic subgraph + placement = self.find_monomorphic_subgraph( + physical_graph, logical_graph, + ) + + # Set the placement if it is valid + if len(placement) == logical_graph.num_qudits and all( + placement[e[1]] in physical_graph.get_neighbors_of(placement[e[0]]) + for e in logical_graph + ): + data.placement = placement + _logger.info(f'Placed qudits on {data.placement}') + else: + _logger.info('No valid placement found') diff --git a/bqskit/passes/mapping/setmodel.py b/bqskit/passes/mapping/setmodel.py index 2e26a1e0e..a484fc41d 100644 --- a/bqskit/passes/mapping/setmodel.py +++ b/bqskit/passes/mapping/setmodel.py @@ -42,7 +42,7 @@ class ExtractModelConnectivityPass(BasePass): Extracts and saves the current target machine model's connectivity. The model will remain unchanged except that it will be fully connected until - the RestoreModelConnevtivityPass is executed. + the RestoreModelConnectivityPass is executed. """ key = '_ExtractModelConnectivityPass_connectivity' @@ -55,7 +55,7 @@ async def run(self, circuit: Circuit, data: PassData) -> None: ) -class RestoreModelConnevtivityPass(BasePass): +class RestoreModelConnectivityPass(BasePass): """Restores the connectivity of the target machine model.""" async def run(self, circuit: Circuit, data: PassData) -> None: diff --git a/tests/ir/gates/constant/test_ecr.py b/tests/ir/gates/constant/test_ecr.py new file mode 100644 index 000000000..5e0fa0d28 --- /dev/null +++ b/tests/ir/gates/constant/test_ecr.py @@ -0,0 +1,40 @@ +from __future__ import annotations + +import numpy as np + +from bqskit.ir.circuit import Circuit +from bqskit.ir.gates import CXGate +from bqskit.ir.gates import ECRGate +from bqskit.ir.gates import HGate +from bqskit.ir.gates import RZGate +from bqskit.ir.gates import XGate +from bqskit.ir.lang.qasm2 import OPENQASM2Language + + +def test_ecr() -> None: + c = Circuit(2) + c.append_gate(HGate(), 1) + c.append_gate(CXGate(), (0, 1)) + c.append_gate(RZGate(), 1, [np.pi / 4.0]) + c.append_gate(CXGate(), (0, 1)) + c.append_gate(HGate(), 1) + + c.append_gate(XGate(), 0) + + c.append_gate(HGate(), 1) + c.append_gate(CXGate(), (0, 1)) + c.append_gate(RZGate(), 1, [-np.pi / 4.0]) + c.append_gate(CXGate(), (0, 1)) + c.append_gate(HGate(), 1) + + assert c.get_unitary().get_distance_from(ECRGate().get_unitary()) < 3e-8 + + +def test_ecr_encode_decode() -> None: + c = Circuit(2) + c.append_gate(ECRGate(), (0, 1)) + + output_qasm = c.to('qasm') + + resulting_circuit = OPENQASM2Language().decode(output_qasm) + assert resulting_circuit[0, 0].gate == ECRGate() diff --git a/tests/ir/lang/test_qasm_decode.py b/tests/ir/lang/test_qasm_decode.py index 16bc29151..d637aedc6 100644 --- a/tests/ir/lang/test_qasm_decode.py +++ b/tests/ir/lang/test_qasm_decode.py @@ -10,6 +10,7 @@ from bqskit.ir.gates.barrier import BarrierPlaceholder from bqskit.ir.gates.circuitgate import CircuitGate from bqskit.ir.gates.constant.cx import CNOTGate +from bqskit.ir.gates.constant.ecr import ECRGate from bqskit.ir.gates.measure import MeasurementPlaceholder from bqskit.ir.gates.parameterized.u1 import U1Gate from bqskit.ir.gates.parameterized.u1q import U1qGate @@ -523,3 +524,14 @@ def test_U1Q_gate() -> None: assert circuit.num_operations == 2 assert circuit[0, 0].gate == U1qGate() assert circuit[1, 0].gate == U1qGate() + + +def test_ECR_gate() -> None: + input = """ + OPENQASM 2.0; + qreg q[2]; + ecr q[0],q[1]; + """ + circuit = OPENQASM2Language().decode(input) + assert circuit.num_operations == 1 + assert circuit[0, 0].gate == ECRGate() diff --git a/tests/passes/mapping/test_static.py b/tests/passes/mapping/test_static.py new file mode 100644 index 000000000..759d97935 --- /dev/null +++ b/tests/passes/mapping/test_static.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +import pytest + +from bqskit.compiler import Compiler +from bqskit.compiler import MachineModel +from bqskit.ir.circuit import Circuit +from bqskit.ir.gates import CNOTGate +from bqskit.passes import ApplyPlacement +from bqskit.passes import GreedyPlacementPass +from bqskit.passes import IfThenElsePass +from bqskit.passes import LogPass +from bqskit.passes import SetModelPass +from bqskit.passes import StaticPlacementPass +from bqskit.passes.control.predicates import PhysicalPredicate +from bqskit.qis import CouplingGraph + + +def circular_circuit(n: int) -> Circuit: + circuit = Circuit(n) + for i in range(n): + circuit.append_gate(CNOTGate(), [i, (i + 1) % n]) + return circuit + + +@pytest.mark.parametrize( + ['grid_size', 'logical_qudits'], + sum([[(n, i) for i in range(2, n**2, 2)] for n in range(2, 8)], []) + + sum([[(n, i) for i in range(3, n**2, 2)] for n in range(2, 6)], []), +) +def test_circular_to_grid( + grid_size: int, logical_qudits: int, compiler: Compiler, +) -> None: + circuit = circular_circuit(logical_qudits) + cg = CouplingGraph.grid(grid_size, grid_size) + model = MachineModel(grid_size**2, cg) + workflow = [ + SetModelPass(model), + StaticPlacementPass(timeout_sec=1.0), + IfThenElsePass( + PhysicalPredicate(), + [LogPass('Static Placement Found')], + [LogPass('Greedy Placement Required'), GreedyPlacementPass()], + ), + ApplyPlacement(), + ] + compiler.compile(circuit, workflow)