diff --git a/pytket/binders/passes.cpp b/pytket/binders/passes.cpp index dfe27683da..3275a395c1 100644 --- a/pytket/binders/passes.cpp +++ b/pytket/binders/passes.cpp @@ -58,7 +58,7 @@ Transforms::TwoQbFidelities get_fidelities(const py::kwargs &kwargs) { } else if (kwargstr == "ZZMax_fidelity") { fid.ZZMax_fidelity = py::cast(kwarg.second); } else if (kwargstr == "ZZPhase_fidelity") { - fid.ZZPhase_fidelity = py::cast(kwarg.second); + fid.ZZPhase_fidelity = py::cast>(kwarg.second); } else { throw py::type_error( "got an unexpected keyword argument '" + kwargstr + "'"); @@ -405,9 +405,9 @@ PYBIND11_MODULE(passes, m) { "`ZZPhase_fidelity`. If provided, the `CX` and `ZZMax` fidelities " "must be given by a single floating point fidelity. The `ZZPhase` " "fidelity is given as a lambda float -> float, mapping a ZZPhase " - "angle parameter to its fidelity. These parameters will be used " - "to return the optimal decomposition of each TK2 gate, taking " - "noise into consideration.\n\n" + "angle parameter to its fidelity, or by a single float. These parameters " + "will be used to return the optimal decomposition of each TK2 gate, " + "taking noise into consideration.\n\n" "If no fidelities are provided, the TK2 gates will be decomposed " "exactly using CX gates. For equal fidelities, ZZPhase will be prefered " "over ZZMax and CX if the decomposition results in fewer two-qubit " diff --git a/pytket/binders/transform.cpp b/pytket/binders/transform.cpp index e376762a37..8ef8027b78 100644 --- a/pytket/binders/transform.cpp +++ b/pytket/binders/transform.cpp @@ -48,7 +48,7 @@ Transforms::TwoQbFidelities get_fidelities(const py::kwargs &kwargs) { } else if (kwargstr == "ZZMax_fidelity") { fid.ZZMax_fidelity = py::cast(kwarg.second); } else if (kwargstr == "ZZPhase_fidelity") { - fid.ZZPhase_fidelity = py::cast(kwarg.second); + fid.ZZPhase_fidelity = py::cast>(kwarg.second); } else { throw py::type_error( "got an unexpected keyword argument '" + kwargstr + "'"); @@ -228,9 +228,9 @@ PYBIND11_MODULE(transform, m) { "`ZZPhase_fidelity`. If provided, the `CX` and `ZZMax` fidelities " "must be given by a single floating point fidelity. The `ZZPhase` " "fidelity is given as a lambda float -> float, mapping a ZZPhase " - "angle parameter to its fidelity. These parameters will be used " - "to return the optimal decomposition of each TK2 gate, taking " - "noise into consideration.\n\n" + "angle parameter to its fidelity, or by a single float. These " + "parameters will be used to return the optimal decomposition of each " + "TK2 gate, taking noise into consideration.\n\n" "Using the `allow_swaps=True` (default) option, qubits will be " "swapped when convenient to reduce the two-qubit gate count of the " "decomposed TK2.\n\n" diff --git a/pytket/conanfile.py b/pytket/conanfile.py index fef1dc8ad3..d40d046736 100644 --- a/pytket/conanfile.py +++ b/pytket/conanfile.py @@ -32,7 +32,7 @@ def package(self): cmake.install() def requirements(self): - self.requires("tket/1.3.21@tket/stable") + self.requires("tket/1.3.22@tket/stable") self.requires("tklog/0.3.3@tket/stable") self.requires("tkrng/0.3.3@tket/stable") self.requires("tkassert/0.3.4@tket/stable") diff --git a/pytket/docs/changelog.rst b/pytket/docs/changelog.rst index aae91b09f4..d8ff307da7 100644 --- a/pytket/docs/changelog.rst +++ b/pytket/docs/changelog.rst @@ -1,6 +1,14 @@ Changelog ========= +Unreleased +---------- + +Features: + +* DecomposeTK2 pass and transform can now accept a float for ZZPhase_fidelity. +* DecomposeTK2 pass now has a json representation when it contains no functions. + 1.32.0 (September 2024) ----------------------- diff --git a/pytket/pytket/_tket/passes.pyi b/pytket/pytket/_tket/passes.pyi index a764716485..20fc8db621 100644 --- a/pytket/pytket/_tket/passes.pyi +++ b/pytket/pytket/_tket/passes.pyi @@ -346,7 +346,7 @@ def DecomposeTK2(allow_swaps: bool = True, **kwargs: Any) -> BasePass: Gate fidelities can be passed as keyword arguments to perform noise-aware decompositions. If the fidelities of several gate types are provided, the best will be chosen. - We currently support `CX_fidelity`, `ZZMax_fidelity` and `ZZPhase_fidelity`. If provided, the `CX` and `ZZMax` fidelities must be given by a single floating point fidelity. The `ZZPhase` fidelity is given as a lambda float -> float, mapping a ZZPhase angle parameter to its fidelity. These parameters will be used to return the optimal decomposition of each TK2 gate, taking noise into consideration. + We currently support `CX_fidelity`, `ZZMax_fidelity` and `ZZPhase_fidelity`. If provided, the `CX` and `ZZMax` fidelities must be given by a single floating point fidelity. The `ZZPhase` fidelity is given as a lambda float -> float, mapping a ZZPhase angle parameter to its fidelity, or by a single float. These parameters will be used to return the optimal decomposition of each TK2 gate, taking noise into consideration. If no fidelities are provided, the TK2 gates will be decomposed exactly using CX gates. For equal fidelities, ZZPhase will be prefered over ZZMax and CX if the decomposition results in fewer two-qubit gates. diff --git a/pytket/pytket/_tket/transform.pyi b/pytket/pytket/_tket/transform.pyi index d033e46221..2a4d720607 100644 --- a/pytket/pytket/_tket/transform.pyi +++ b/pytket/pytket/_tket/transform.pyi @@ -140,7 +140,7 @@ class Transform: All TK2 gate parameters must be normalised, i.e. they must satisfy `NormalisedTK2Predicate`. - Gate fidelities are passed as keyword arguments to perform noise-aware decompositions. We currently support `CX_fidelity`, `ZZMax_fidelity` and `ZZPhase_fidelity`. If provided, the `CX` and `ZZMax` fidelities must be given by a single floating point fidelity. The `ZZPhase` fidelity is given as a lambda float -> float, mapping a ZZPhase angle parameter to its fidelity. These parameters will be used to return the optimal decomposition of each TK2 gate, taking noise into consideration. + Gate fidelities are passed as keyword arguments to perform noise-aware decompositions. We currently support `CX_fidelity`, `ZZMax_fidelity` and `ZZPhase_fidelity`. If provided, the `CX` and `ZZMax` fidelities must be given by a single floating point fidelity. The `ZZPhase` fidelity is given as a lambda float -> float, mapping a ZZPhase angle parameter to its fidelity, or by a single float. These parameters will be used to return the optimal decomposition of each TK2 gate, taking noise into consideration. Using the `allow_swaps=True` (default) option, qubits will be swapped when convenient to reduce the two-qubit gate count of the decomposed TK2. diff --git a/pytket/tests/passes_serialisation_test.py b/pytket/tests/passes_serialisation_test.py index 242f2f33cf..cf7423248c 100644 --- a/pytket/tests/passes_serialisation_test.py +++ b/pytket/tests/passes_serialisation_test.py @@ -299,6 +299,18 @@ def nonparam_predicate_dict(name: str) -> Dict[str, Any]: "AutoRebase": standard_pass_dict( {"name": "AutoRebase", "basis_allowed": ["H", "TK1", "CX"], "allow_swaps": True} ), + # ZZPhase must be a float and not a function. + "DecomposeTK2": standard_pass_dict( + { + "name": "DecomposeTK2", + "fidelities": { + "CX": None, + "ZZMax": 1.0, + "ZZPhase": 0.5, + }, + "allow_swaps": True, + } + ), } # non-parametrized passes that satisfy pass.from_dict(d).to_dict()==d diff --git a/pytket/tests/transform_test.py b/pytket/tests/transform_test.py index e4f99f602b..4edac7b179 100644 --- a/pytket/tests/transform_test.py +++ b/pytket/tests/transform_test.py @@ -209,6 +209,16 @@ def test_DecomposeTK2() -> None: assert c.n_gates_of_type(OpType.CX) == 0 assert c.n_gates_of_type(OpType.ZZMax) == 3 + c = Circuit(2).add_gate(OpType.TK2, [0.5, 0.5, 0.5], [0, 1]) + Transform.DecomposeTK2(False, ZZPhase_fidelity=0.8).apply(c) + assert c.n_gates_of_type(OpType.CX) == 0 + assert c.n_gates_of_type(OpType.ZZPhase) == 3 + + c = Circuit(2).add_gate(OpType.TK2, [0.5, 0.5, 0.5], [0, 1]) + Transform.DecomposeTK2(False, ZZPhase_fidelity=lambda _: 0.8).apply(c) + assert c.n_gates_of_type(OpType.CX) == 0 + assert c.n_gates_of_type(OpType.ZZPhase) == 3 + def test_fidelity_KAK() -> None: c = get_KAK_test_circuit() diff --git a/schemas/compiler_pass_v1.json b/schemas/compiler_pass_v1.json index 3d117a7b1c..e3a807dde3 100644 --- a/schemas/compiler_pass_v1.json +++ b/schemas/compiler_pass_v1.json @@ -320,6 +320,11 @@ "type": ["number", "null"], "minimum": 0, "maximum": 1 + }, + "ZZPhase": { + "type": ["number", "null"], + "minimum": 0, + "maximum": 1 } } }, diff --git a/tket/conanfile.py b/tket/conanfile.py index 558f4756d0..10f613618c 100644 --- a/tket/conanfile.py +++ b/tket/conanfile.py @@ -23,7 +23,7 @@ class TketConan(ConanFile): name = "tket" - version = "1.3.21" + version = "1.3.22" package_type = "library" license = "Apache 2" homepage = "https://github.com/CQCL/tket" diff --git a/tket/include/tket/Transformations/Transform.hpp b/tket/include/tket/Transformations/Transform.hpp index 6b0a93cb89..f19f9560da 100644 --- a/tket/include/tket/Transformations/Transform.hpp +++ b/tket/include/tket/Transformations/Transform.hpp @@ -99,7 +99,8 @@ inline const Transform id = struct TwoQbFidelities { std::optional CX_fidelity; std::optional ZZMax_fidelity; - std::optional> ZZPhase_fidelity; + std::optional>> + ZZPhase_fidelity; }; } // namespace Transforms diff --git a/tket/include/tket/Utils/Expression.hpp b/tket/include/tket/Utils/Expression.hpp index a70ca14add..00efb94f85 100644 --- a/tket/include/tket/Utils/Expression.hpp +++ b/tket/include/tket/Utils/Expression.hpp @@ -28,6 +28,15 @@ #include "Json.hpp" #include "Symbols.hpp" +/** Helper struct for use with std::visit */ +template +struct overloaded : Ts... { + using Ts::operator()...; +}; +// explicit deduction guide (not needed as of C++20) +template +overloaded(Ts...) -> overloaded; + namespace tket { /** Representation of a phase as a multiple of \f$ \pi \f$ */ diff --git a/tket/src/Predicates/CompilerPass.cpp b/tket/src/Predicates/CompilerPass.cpp index e9cb2f52ab..56f7054b54 100644 --- a/tket/src/Predicates/CompilerPass.cpp +++ b/tket/src/Predicates/CompilerPass.cpp @@ -15,6 +15,7 @@ #include "tket/Predicates/CompilerPass.hpp" #include +#include #include #include "tket/Mapping/RoutingMethodJson.hpp" @@ -393,7 +394,9 @@ void from_json(const nlohmann::json& j, PassPtr& pp) { content.at("fidelities").at("CX").get>(); fid.ZZMax_fidelity = content.at("fidelities").at("ZZMax").get>(); - fid.ZZPhase_fidelity = std::nullopt; + fid.ZZPhase_fidelity = + content.at("fidelities") + .value>("ZZPhase", std::nullopt); bool allow_swaps = content.at("allow_swaps").get(); pp = DecomposeTK2(fid, allow_swaps); } else if (passname == "PeepholeOptimise2Q") { diff --git a/tket/src/Predicates/PassGenerators.cpp b/tket/src/Predicates/PassGenerators.cpp index 0a6c3f316a..584cb6b062 100644 --- a/tket/src/Predicates/PassGenerators.cpp +++ b/tket/src/Predicates/PassGenerators.cpp @@ -811,7 +811,18 @@ PassPtr DecomposeTK2(const Transforms::TwoQbFidelities& fid, bool allow_swaps) { j["allow_swaps"] = allow_swaps; nlohmann::json fid_json; fid_json["CX"] = fid.CX_fidelity; - fid_json["ZZPhase"] = "SERIALIZATION OF FUNCTIONS IS NOT SUPPORTED"; + if (fid.ZZPhase_fidelity.has_value()) { + std::visit( + overloaded{ + [&fid_json](double arg) { fid_json["ZZPhase"] = arg; }, + [&fid_json](std::function) { + fid_json["ZZPhase"] = + "SERIALIZATION OF FUNCTIONS IS NOT SUPPORTED"; + }}, + fid.ZZPhase_fidelity.value()); + } else { + fid_json["ZZPhase"] = std::nullptr_t{}; + } fid_json["ZZMax"] = fid.ZZMax_fidelity; j["fidelities"] = fid_json; return std::make_shared(precons, t, postcons, j); diff --git a/tket/src/Transformations/Decomposition.cpp b/tket/src/Transformations/Decomposition.cpp index 56b9fe84dc..e3d36157ff 100644 --- a/tket/src/Transformations/Decomposition.cpp +++ b/tket/src/Transformations/Decomposition.cpp @@ -616,7 +616,14 @@ static double best_noise_aware_decomposition( unsigned max_nzz = fid.ZZMax_fidelity ? 1 : 3; for (unsigned n_zz = 0; n_zz <= max_nzz; ++n_zz) { if (n_zz > 0) { - double gate_fid = (*fid.ZZPhase_fidelity)(angles[n_zz - 1]); + double gate_fid = std::visit( + overloaded{// Constant value + [](double arg) { return arg; }, + // A value depending on the angle + [angles, n_zz](std::function arg) { + return (arg)(angles[n_zz - 1]); + }}, + *fid.ZZPhase_fidelity); if (gate_fid < 0 || gate_fid > 1) { throw std::domain_error( "ZZPhase_fidelity returned a value outside of [0, 1]."); @@ -857,7 +864,13 @@ Transform decompose_TK2(const TwoQbFidelities &fid, bool allow_swaps) { } } if (fid.ZZMax_fidelity && fid.ZZPhase_fidelity) { - if (*fid.ZZMax_fidelity < (*fid.ZZPhase_fidelity)(.5)) { + double ZZPhase_half = std::visit( + overloaded{// A constant value. + [](double arg) { return arg; }, + // A value depending on the input. + [](std::function arg) { return (arg)(.5); }}, + *fid.ZZPhase_fidelity); + if (*fid.ZZMax_fidelity < ZZPhase_half) { throw std::domain_error( "The ZZMax fidelity cannot be smaller than the ZZPhase(0.5) " "fidelity"); diff --git a/tket/test/src/test_json.cpp b/tket/test/src/test_json.cpp index 9219258c7f..4af2183bca 100644 --- a/tket/test/src/test_json.cpp +++ b/tket/test/src/test_json.cpp @@ -940,6 +940,7 @@ SCENARIO("Test compiler pass serializations") { COMPPASSJSONTEST(KAKDecomposition, KAKDecomposition(OpType::CX, 0.98)) COMPPASSJSONTEST( DecomposeTK2, DecomposeTK2({0.98, std::nullopt, std::nullopt}, false)) + COMPPASSJSONTEST(DecomposeTK2, DecomposeTK2({0.98, 0.98, 0.98})) COMPPASSJSONTEST(ThreeQubitSquash, ThreeQubitSquash(false)) COMPPASSJSONTEST( EulerAngleReduction, gen_euler_pass(OpType::Rx, OpType::Ry, false))