Skip to content
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

Rebase refactor with simpler RebaseCustom, exposed CircPool and auto_rebase_pass #214

Merged
merged 6 commits into from
Feb 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pytket/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ pybind11_add_module(circuit
binders/circuit/unitid.cpp
binders/circuit/boxes.cpp
binders/circuit/classical.cpp
binders/circuit/library.cpp
binders/circuit/Circuit/main.cpp
binders/circuit/Circuit/add_op.cpp
binders/circuit/Circuit/add_classical_op.cpp)
Expand Down
198 changes: 198 additions & 0 deletions pytket/binders/circuit/library.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
// Copyright 2019-2022 Cambridge Quantum Computing
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <pybind11/pybind11.h>

#include "Circuit/CircPool.hpp"
#include "binder_utils.hpp"
#include "typecast.hpp"

namespace py = pybind11;

namespace tket {

void init_library(py::module &m) {
/* Circuit library */
py::module_ library_m = m.def_submodule(
"_library",
"Library of reusable circuits and circuit generator functions.");
library_m.def(
"_BRIDGE_using_CX_0", &CircPool::BRIDGE_using_CX_0,
"Equivalent to BRIDGE, using four CX, first CX has control on qubit 0");
library_m.def(
"_BRIDGE_using_CX_1", &CircPool::BRIDGE_using_CX_1,
"Equivalent to BRIDGE, using four CX, first CX has control on qubit 1");
library_m.def(
"_CX_using_flipped_CX", &CircPool::CX_using_flipped_CX,
"Equivalent to CX[0,1], using a CX[1,0] and four H gates");
library_m.def(
"_CX_using_ECR", &CircPool::CX_using_ECR,
"Equivalent to CX, using only ECR, Rx and U3 gates");
library_m.def(
"_CX_using_ZZMax", &CircPool::CX_using_ZZMax,
"Equivalent to CX, using only ZZMax, Rx and Rz gates");
library_m.def(
"_CX_using_XXPhase_0", &CircPool::CX_using_XXPhase_0,
"Equivalent to CX, using only XXPhase, Rx, Ry and Rz gates");

library_m.def(
"_CX_using_XXPhase_1", &CircPool::CX_using_XXPhase_1,
"Equivalent to CX, using only XXPhase, Rx, Ry and Rz gates");
library_m.def(
"_CX_VS_CX_reduced", &CircPool::CX_VS_CX_reduced,
"CX-reduced form of CX/V,S/CX");
library_m.def(
"_CX_V_CX_reduced", &CircPool::CX_V_CX_reduced,
"CX-reduced form of CX/V,-/CX");
library_m.def(
"_CX_S_CX_reduced", &CircPool::CX_S_CX_reduced,
"CX-reduced form of CX/-,S/CX (= ZZMax)");
library_m.def(
"_CX_V_S_XC_reduced", &CircPool::CX_V_S_XC_reduced,
"CX-reduced form of CX/V,-/S,-/XC");
library_m.def(
"_CX_S_V_XC_reduced", &CircPool::CX_S_V_XC_reduced,
"CX-reduced form of CX/-,S/-,V/XC");
library_m.def(
"_CX_XC_reduced", &CircPool::CX_XC_reduced, "CX-reduced form of CX/XC");
library_m.def(
"_SWAP_using_CX_0", &CircPool::SWAP_using_CX_0,
"Equivalent to SWAP, using three CX, outer CX have control on qubit 0");
library_m.def(
"_SWAP_using_CX_1", &CircPool::SWAP_using_CX_1,
"Equivalent to SWAP, using three CX, outer CX have control on qubit 1");
library_m.def(
"_two_Rz1", &CircPool::two_Rz1,
"A two-qubit circuit with an Rz(1) on each qubit");
library_m.def("_X1_CX", &CircPool::X1_CX, "X[1]; CX[0,1]");
library_m.def("_Z0_CX", &CircPool::Z0_CX, "Z[0]; CX[0,1] ");

library_m.def(
"_CCX_modulo_phase_shift", &CircPool::CCX_modulo_phase_shift,
"Equivalent to CCX up to phase shift, using three CX. Warning: this is "
"not equivalent to CCX up to global phase so cannot be used as a direct "
"substitution except when the phase reversal can be cancelled. Its "
"unitary representation is like CCX but with a -1 at the (5,5) "
"position.");
library_m.def(
"_CCX_normal_decomp", &CircPool::CCX_normal_decomp,
"Equivalent to CCX, using five CX");
library_m.def(
"_C3X_normal_decomp", &CircPool::C3X_normal_decomp,
"Equivalent to CCCX, using 14 CX");
library_m.def(
"_C4X_normal_decomp", &CircPool::C4X_normal_decomp,
"Equivalent to CCCCX, using 36 CX ");
library_m.def(
"_ladder_down", &CircPool::ladder_down, "CX[0,1]; CX[2,0]; CCX[0,1,2]");
library_m.def(
"_ladder_down_2", &CircPool::ladder_down_2,
"CX[0,1]; X[0]; X[2]; CCX[0,1,2]");
library_m.def(
"_ladder_up", &CircPool::ladder_up, "CCX[0,1,2]; CX[2,0]; CX[2,1]");
library_m.def("_X", &CircPool::X, "Just an X gate");
library_m.def("_CX", &CircPool::CX, "Just a CX[0,1] gate");
library_m.def("_CCX", &CircPool::CCX, "Just a CCX[0,1,2] gate");
library_m.def("_BRIDGE", &CircPool::BRIDGE, "Just a BRIDGE[0,1,2] gate");
library_m.def("_H_CZ_H", &CircPool::H_CZ_H, "H[1]; CZ[0,1]; H[1] ");
library_m.def(
"_CZ_using_CX", &CircPool::CZ_using_CX,
"Equivalent to CZ, using CX and single-qubit gates");
library_m.def(
"_CY_using_CX", &CircPool::CY_using_CX,
"Equivalent to CY, using CX and single-qubit gates");
library_m.def(
"_CH_using_CX", &CircPool::CH_using_CX,
"Equivalent to CH, using CX and single-qubit gates");
library_m.def(
"_CV_using_CX", &CircPool::CV_using_CX,
"Equivalent to CV, using CX and single-qubit gates ");
library_m.def(
"_CVdg_using_CX", &CircPool::CVdg_using_CX,
"Equivalent to CVdg, using CX and single-qubit gates");
library_m.def(
"_CSX_using_CX", &CircPool::CSX_using_CX,
"Equivalent to CSX, using CX and single-qubit gates");
library_m.def(
"_CSXdg_using_CX", &CircPool::CSXdg_using_CX,
"Equivalent to CSXdg, using CX and single-qubit gates");
library_m.def(
"_CSWAP_using_CX", &CircPool::CSWAP_using_CX,
"Equivalent to CSWAP, using CX and single-qubit gates ");
library_m.def(
"_ECR_using_CX", &CircPool::ECR_using_CX,
"Equivalent to ECR, using CX, Rx and U3 gates ");
library_m.def(
"_ZZMax_using_CX", &CircPool::ZZMax_using_CX,
"Equivalent to ZZMax, using CX, Rz and U3 gates ");
library_m.def(
"_CRz_using_CX", &CircPool::CRz_using_CX,
"Equivalent to CRz, using CX and Rz gates");
library_m.def(
"_CRx_using_CX", &CircPool::CRx_using_CX,
"Equivalent to CRx, using CX, H and Rx gates");
library_m.def(
"_CRy_using_CX", &CircPool::CRy_using_CX,
"Equivalent to CRy, using CX and Ry gates");
library_m.def(
"_CU1_using_CX", &CircPool::CU1_using_CX,
"Equivalent to CU1, using CX and U1 gates");
library_m.def(
"_CU3_using_CX", &CircPool::CU3_using_CX,
"Equivalent to CU1, using CX, U1 and U3 gates");
library_m.def(
"_ISWAP_using_CX", &CircPool::ISWAP_using_CX,
"Equivalent to ISWAP, using CX, U3 and Rz gates");
library_m.def(
"_XXPhase_using_CX", &CircPool::XXPhase_using_CX,
"Equivalent to XXPhase, using CX and U3 gates ");
library_m.def(
"_YYPhase_using_CX", &CircPool::YYPhase_using_CX,
"Equivalent to YYPhase, using CX, Rz and U3 gates");
library_m.def(
"_ZZPhase_using_CX", &CircPool::ZZPhase_using_CX,
"Equivalent to ZZPhase, using CX and Rz gates");
library_m.def(
"_XXPhase3_using_CX", &CircPool::XXPhase3_using_CX,
"Equivalent to 3-qubit MS interaction, using CX and U3 gates");
library_m.def(
"_ESWAP_using_CX", &CircPool::XXPhase3_using_CX,
"Equivalent to ESWAP, using CX, X, S, Ry and U1 gates");
library_m.def(
"_FSim_using_CX", &CircPool::FSim_using_CX,
"Equivalent to Fsim, using CX, X, S, U1 and U3 gates ");
library_m.def(
"_PhasedISWAP_using_CX", &CircPool::PhasedISWAP_using_CX,
"Equivalent to PhasedISWAP, using CX, U3 and Rz gates");
library_m.def(
"_NPhasedX_using_CX", &CircPool::NPhasedX_using_CX,
"Unwrap NPhasedX, into number_of_qubits PhasedX gates");

library_m.def(
"_TK1_to_PhasedXRz", &CircPool::tk1_to_PhasedXRz,
"A tk1 equivalent circuit given tk1 parameters in terms of PhasedX, Rz");
library_m.def(
"_TK1_to_RzRx", &CircPool::tk1_to_rzrx,
"A tk1 equivalent circuit given tk1 parameters in terms of Rz, Rx");
library_m.def(
"_TK1_to_RzH", &CircPool::tk1_to_rzh,
"A tk1 equivalent circuit given tk1 parameters in terms of Rz, H");
library_m.def(
"_TK1_to_RzSX", &CircPool::tk1_to_rzsx,
"A tk1 equivalent circuit given tk1 parameters in terms of Rz, Sx");
library_m.def(
"_TK1_to_TK1", &CircPool::tk1_to_tk1,
"A circuit of a single tk1 gate with given parameters");
}
} // namespace tket
2 changes: 2 additions & 0 deletions pytket/binders/circuit/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ void init_unitid(py::module &m);
void init_circuit(py::module &m);
void init_classical(py::module &m);
void init_boxes(py::module &m);
void init_library(py::module &m);

PYBIND11_MODULE(circuit, m) {
init_unitid(m);
Expand Down Expand Up @@ -500,6 +501,7 @@ PYBIND11_MODULE(circuit, m) {
":return: set of symbolic parameters for the command");

init_circuit(m);
init_library(m);
init_boxes(m);
init_classical(m);

Expand Down
15 changes: 7 additions & 8 deletions pytket/binders/passes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -459,25 +459,24 @@ PYBIND11_MODULE(passes, m) {
m.def(
"RebaseCustom", &gen_rebase_pass,
"Construct a custom rebase pass. This pass:\n(1) decomposes "
"multi-qubit gates not in the set of gate types `multiqs` to CX "
"gates;\n(2) if CX is not in `multiqs`, replaces CX gates with "
"multi-qubit gates not in the set of gate types `gateset` to CX "
"gates;\n(2) if CX is not in `gateset`, replaces CX gates with "
"`cx_replacement`;\n(3) converts any single-qubit gates not in the "
"gate type set `singleqs` to the form "
"gate type set to the form "
":math:`\\mathrm{Rz}(a)\\mathrm{Rx}(b)\\mathrm{Rz}(c)` (in "
"matrix-multiplication order, i.e. reverse order in the "
"circuit);\n(4) applies the `tk1_replacement` function to each of "
"these triples :math:`(a,b,c)` to generate replacement circuits."
"\n\n:param multiqs: The allowed multi-qubit operations in the "
"\n\n:param gateset: The allowed multi-qubit operations in the "
"rebased circuit."
"\n:param cx_replacement: The equivalent circuit to replace a CX "
"gate in the desired basis."
"\n:param singleqs: The allowed single-qubit operations in the "
"rebased circuit."
"gate using two qubit gates from the desired basis (can use any single "
"qubit OpTypes)."
"\n:param tk1_replacement: A function which, given the parameters of "
"an Rz(a)Rx(b)Rz(c) triple, returns an equivalent circuit in the "
"desired basis."
"\n:return: a pass that rebases to the given gate set",
py::arg("multiqs"), py::arg("cx_replacement"), py::arg("singleqs"),
py::arg("gateset"), py::arg("cx_replacement"),
lmondada marked this conversation as resolved.
Show resolved Hide resolved
py::arg("tk1_replacement"));

m.def(
Expand Down
2 changes: 2 additions & 0 deletions pytket/docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ API changes:
``map`` property instead.)
* The deprecated ``Backend.compile_circuit`` method is removed. (Use
``get_compiled_circuit`` instead.)
* ``RebaseCustom`` takes one allowed gateset parameter rather than separate single qubit and multiqubit gatesets.

Minor new features:

* Add ``delay_measures`` option to ``DefaultMappingPass``.
* New ``pytket.passes.auto_rebase_pass`` which attempts to construct a rebase pass given a target gate set from known decompositions.

0.19.0 (February 2022)
----------------------
Expand Down
1 change: 1 addition & 0 deletions pytket/pytket/passes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@
from pytket._tket.passes import * # type: ignore

from .script import compilation_pass_from_script, compilation_pass_grammar
from .auto_rebase import auto_rebase_pass
97 changes: 97 additions & 0 deletions pytket/pytket/passes/auto_rebase.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Copyright 2019-2022 Cambridge Quantum Computing
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Set, Union, Callable, Dict, FrozenSet, TYPE_CHECKING
from pytket.circuit import Circuit, OpType # type: ignore
from pytket._tket.circuit import _library # type: ignore
from pytket.passes import RebaseCustom # type: ignore

if TYPE_CHECKING:
from sympy import Expr # type: ignore


class NoAutoRebase(Exception):
"""Automatic rebase could not be found."""


_CX_CIRCS: Dict[OpType, Callable[[], "Circuit"]] = {
OpType.CX: _library._CX,
OpType.ZZMax: _library._CX_using_ZZMax,
OpType.XXPhase: _library._CX_using_XXPhase_0,
OpType.ECR: _library._CX_using_ECR,
OpType.CZ: lambda: Circuit(2).H(1).CZ(0, 1).H(1),
}


def get_cx_decomposition(gateset: Set[OpType]) -> Circuit:
"""Return a Circuit expressing a CX in terms of a two qubit gate in the
gateset if one is available, raise an error otherwise.

:param gateset: Target gate set.
:type gateset: Set[OpType]
:raises NoAutoRebase: No suitable CX decomposition found.
:return: Decomposuition circuit.
:rtype: Circuit
"""
if any((matching := k) in gateset for k in _CX_CIRCS):
return _CX_CIRCS[matching]()
raise NoAutoRebase("No known decomposition from CX to available gateset.")


Param = Union[str, "Expr"]

_TK1_circs: Dict[FrozenSet[OpType], Callable[[Param, Param, Param], "Circuit"]] = {
frozenset({OpType.TK1}): _library._TK1_to_TK1,
frozenset({OpType.PhasedX, OpType.Rz}): _library._TK1_to_PhasedXRz,
frozenset({OpType.Rx, OpType.Rz}): _library._TK1_to_RzRx,
frozenset({OpType.Rz, OpType.H}): _library._TK1_to_RzH,
frozenset({OpType.Rz, OpType.SX}): _library._TK1_to_RzSX,
}


def get_TK1_decomposition_function(
gateset: Set[OpType],
) -> Callable[[Param, Param, Param], "Circuit"]:
"""Return a function for generating TK1 equivalent circuits, which take the
three TK1 parameters as arguments and return a TK1 equivalent single qubit
circuit. If no such function is available, raise an error.

:raises NoAutoRebase: No suitable TK1 decomposition found.
:return: TK1 decomposition function.
:rtype: Callable[[Param, Param, Param], "Circuit"]
"""
if any((matching := k).issubset(gateset) for k in _TK1_circs):
return _TK1_circs[matching]
raise NoAutoRebase("No known decomposition from TK1 to available gateset.")


def auto_rebase_pass(gateset: Set[OpType]) -> RebaseCustom:
"""Attempt to generate a rebase pass automatically for the given target
gateset.

Checks if there are known existing decompositions from CX
to target gateset and TK1 to target gateset and uses those to construct a
custom rebase.
Raises an error if no known decompositions can be found, in which case try
using RebaseCustom with your own decompositions.

:param gateset: Set of supported OpTypes, target gate set.
:type gateset: FrozenSet[OpType]
:raises NoAutoRebase: No suitable CX or TK1 decomposition found.
:return: Rebase pass.
:rtype: RebaseCustom
"""
return RebaseCustom(
gateset, get_cx_decomposition(gateset), get_TK1_decomposition_function(gateset)
)
9 changes: 5 additions & 4 deletions pytket/tests/predicates_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ def test_rebase_pass_generation() -> None:
cx = Circuit(2)
cx.CX(0, 1)
pz_rebase = RebaseCustom(
{OpType.CX}, cx, {OpType.PhasedX, OpType.Rz}, tk1_to_phasedxrz
{OpType.CX, OpType.PhasedX, OpType.Rz}, cx, tk1_to_phasedxrz
)
circ = Circuit(2)
circ.X(0).Y(1)
Expand Down Expand Up @@ -622,14 +622,15 @@ def sq(a: float, b: float, c: float) -> Circuit:
cx = Circuit(2)
cx.CX(0, 1)
pz_rebase = RebaseCustom(
{OpType.CX}, cx, {OpType.PhasedX, OpType.Rz}, tk1_to_phasedxrz
{OpType.CX, OpType.PhasedX, OpType.Rz}, cx, tk1_to_phasedxrz
)
assert pz_rebase.to_dict()["StandardPass"]["name"] == "RebaseCustom"
assert pz_rebase.to_dict()["StandardPass"]["basis_multiqs"] == ["CX"]
assert set(pz_rebase.to_dict()["StandardPass"]["basis_singleqs"]) == {
assert set(pz_rebase.to_dict()["StandardPass"]["basis_allowed"]) == {
"CX",
"PhasedX",
"Rz",
}

assert cx.to_dict() == pz_rebase.to_dict()["StandardPass"]["basis_cx_replacement"]
# EulerAngleReduction
euler_pass = EulerAngleReduction(OpType.Ry, OpType.Rx)
Expand Down
Loading