Skip to content

Commit

Permalink
Greedy strategy for term sequence box (#1466)
Browse files Browse the repository at this point in the history
* Add `Greedy` strategy to `PauliSynthStrat`

* Add greedy synthesis to TermSequenceBox

* regenerate stubs

* Add changelog entry

* bump tket version

* Reformat test

* Add unitary test
  • Loading branch information
yao-cqc authored Jun 25, 2024
1 parent 5071535 commit c089d6d
Show file tree
Hide file tree
Showing 17 changed files with 231 additions and 26 deletions.
20 changes: 14 additions & 6 deletions pytket/binders/circuit/boxes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -368,27 +368,32 @@ void init_boxes(py::module &m) {
&pauli_gadgets,
Transforms::PauliSynthStrat synth_strat,
PauliPartitionStrat partition_strat,
GraphColourMethod graph_colouring,
CXConfigType cx_config) {
GraphColourMethod graph_colouring, CXConfigType cx_config,
double depth_weight) {
std::vector<SymPauliTensor> gadgets;
for (const std::pair<py::tket_custom::SequenceVec<Pauli>, Expr> &g :
pauli_gadgets)
gadgets.push_back(SymPauliTensor(g.first, g.second));
return TermSequenceBox(
gadgets, synth_strat, partition_strat, graph_colouring,
cx_config);
cx_config, depth_weight);
}),
"Construct a set of Pauli exponentials of the "
"form"
" :math:`e^{-\\frac12 i \\pi t_j \\sigma_0 \\otimes "
"\\sigma_1 \\otimes \\cdots}` from Pauli operator strings "
":math:`\\sigma_i \\in \\{I,X,Y,Z\\}` and parameters "
":math:`t_j, j \\in \\{0, 1, \\cdots \\}`.",
":math:`t_j, j \\in \\{0, 1, \\cdots \\}`.\n"
"`depth_weight` controls the degree of depth optimisation and only "
"applies to synthesis_strategy `PauliSynthStrat:Greedy`. "
"`partitioning_strategy`, `graph_colouring`, and `cx_config_type` "
"have no effect if `PauliSynthStrat:Greedy` is used.",
py::arg("pauli_gadgets"),
py::arg("synthesis_strategy") = Transforms::PauliSynthStrat::Sets,
py::arg("partitioning_strategy") = PauliPartitionStrat::CommutingSets,
py::arg("graph_colouring") = GraphColourMethod::Lazy,
py::arg("cx_config_type") = CXConfigType::Tree)
py::arg("cx_config_type") = CXConfigType::Tree,
py::arg("depth_weight") = 0.3)
.def(
"get_circuit",
[](TermSequenceBox &pbox) { return *pbox.to_circuit(); },
Expand All @@ -414,7 +419,10 @@ void init_boxes(py::module &m) {
":return: graph colouring method")
.def(
"get_cx_config", &TermSequenceBox::get_cx_config,
":return: cx decomposition method");
":return: cx decomposition method")
.def(
"get_depth_weight", &TermSequenceBox::get_depth_weight,
":return: depth tuning parameter");

py::enum_<ToffoliBoxSynthStrat>(
m, "ToffoliBoxSynthStrat",
Expand Down
8 changes: 7 additions & 1 deletion pytket/binders/transform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,13 @@ PYBIND11_MODULE(transform, m) {
"from Cowtan et al (https://arxiv.org/abs/1906.01734)")
.value(
"Sets", Transforms::PauliSynthStrat::Sets,
"Synthesise gadgets in commuting sets");
"Synthesise gadgets in commuting sets")
.value(
"Greedy", Transforms::PauliSynthStrat::Greedy,
"Synthesise gadgets using a greedy algorithm adapted from "
"arxiv.org/abs/2103.08602. This strategy is currently only accepted "
"by `TermSequenceBox`. For synthesising general circuits try using "
"`GreedyPauliSimp`.");

py::class_<Transform>(
m, "Transform", "An in-place transformation of a :py:class:`Circuit`.")
Expand Down
2 changes: 1 addition & 1 deletion pytket/conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def package(self):
cmake.install()

def requirements(self):
self.requires("tket/1.3.11@tket/stable")
self.requires("tket/1.3.12@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")
Expand Down
1 change: 1 addition & 0 deletions pytket/docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Unreleased

* Support classical transforms and predicates, and QASM registers, with up to 64
bits. Add an attribute to the pytket module to assert this.
* Add `PauliSynthStrat.Greedy` strategy to `TermSequenceBox`.

1.29.2 (June 2024)
------------------
Expand Down
7 changes: 6 additions & 1 deletion pytket/pytket/_tket/circuit.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3775,9 +3775,10 @@ class TermSequenceBox(Op):
"""
An unordered collection of Pauli exponentials that can be synthesised in any order, causing a change in the unitary operation. Synthesis order depends on the synthesis strategy chosen only.
"""
def __init__(self, pauli_gadgets: typing.Sequence[tuple[typing.Sequence[pytket._tket.pauli.Pauli], sympy.Expr | float]], synthesis_strategy: pytket._tket.transform.PauliSynthStrat = pytket._tket.transform.PauliSynthStrat.Sets, partitioning_strategy: pytket._tket.partition.PauliPartitionStrat = pytket._tket.partition.PauliPartitionStrat.CommutingSets, graph_colouring: pytket._tket.partition.GraphColourMethod = pytket._tket.partition.GraphColourMethod.Lazy, cx_config_type: CXConfigType = CXConfigType.Tree) -> None:
def __init__(self, pauli_gadgets: typing.Sequence[tuple[typing.Sequence[pytket._tket.pauli.Pauli], sympy.Expr | float]], synthesis_strategy: pytket._tket.transform.PauliSynthStrat = pytket._tket.transform.PauliSynthStrat.Sets, partitioning_strategy: pytket._tket.partition.PauliPartitionStrat = pytket._tket.partition.PauliPartitionStrat.CommutingSets, graph_colouring: pytket._tket.partition.GraphColourMethod = pytket._tket.partition.GraphColourMethod.Lazy, cx_config_type: CXConfigType = CXConfigType.Tree, depth_weight: float = 0.3) -> None:
"""
Construct a set of Pauli exponentials of the form :math:`e^{-\\frac12 i \\pi t_j \\sigma_0 \\otimes \\sigma_1 \\otimes \\cdots}` from Pauli operator strings :math:`\\sigma_i \\in \\{I,X,Y,Z\\}` and parameters :math:`t_j, j \\in \\{0, 1, \\cdots \\}`.
`depth_weight` controls the degree of depth optimisation and only applies to synthesis_strategy `PauliSynthStrat:Greedy`. `partitioning_strategy`, `graph_colouring`, and `cx_config_type` have no effect if `PauliSynthStrat:Greedy` is used.
"""
def get_circuit(self) -> Circuit:
"""
Expand All @@ -3787,6 +3788,10 @@ class TermSequenceBox(Op):
"""
:return: cx decomposition method
"""
def get_depth_weight(self) -> float:
"""
:return: depth tuning parameter
"""
def get_graph_colouring_method(self) -> pytket._tket.partition.GraphColourMethod:
"""
:return: graph colouring method
Expand Down
5 changes: 4 additions & 1 deletion pytket/pytket/_tket/transform.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@ class PauliSynthStrat:
Pairwise : Synthesise gadgets using an efficient pairwise strategy from Cowtan et al (https://arxiv.org/abs/1906.01734)
Sets : Synthesise gadgets in commuting sets
Greedy : Synthesise gadgets using a greedy algorithm adapted from arxiv.org/abs/2103.08602. This strategy is currently only accepted by `TermSequenceBox`. For synthesising general circuits try using `GreedyPauliSimp`.
"""
Greedy: typing.ClassVar[PauliSynthStrat] # value = <PauliSynthStrat.Greedy: 3>
Individual: typing.ClassVar[PauliSynthStrat] # value = <PauliSynthStrat.Individual: 0>
Pairwise: typing.ClassVar[PauliSynthStrat] # value = <PauliSynthStrat.Pairwise: 1>
Sets: typing.ClassVar[PauliSynthStrat] # value = <PauliSynthStrat.Sets: 2>
__members__: typing.ClassVar[dict[str, PauliSynthStrat]] # value = {'Individual': <PauliSynthStrat.Individual: 0>, 'Pairwise': <PauliSynthStrat.Pairwise: 1>, 'Sets': <PauliSynthStrat.Sets: 2>}
__members__: typing.ClassVar[dict[str, PauliSynthStrat]] # value = {'Individual': <PauliSynthStrat.Individual: 0>, 'Pairwise': <PauliSynthStrat.Pairwise: 1>, 'Sets': <PauliSynthStrat.Sets: 2>, 'Greedy': <PauliSynthStrat.Greedy: 3>}
def __eq__(self, other: typing.Any) -> bool:
...
def __getstate__(self) -> int:
Expand Down
21 changes: 20 additions & 1 deletion pytket/tests/circuit_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
DecomposeBoxes,
RemoveRedundancies,
)
from pytket.transform import Transform
from pytket.transform import Transform, PauliSynthStrat

import numpy as np
from scipy.linalg import block_diag
Expand Down Expand Up @@ -1551,6 +1551,25 @@ def test_cnrx_cnrz() -> None:
assert np.allclose(c1rx.get_unitary(), crx.get_unitary())


def greedy_TermSequenceBox() -> None:
tseqbox = TermSequenceBox(
[
([Pauli.X, Pauli.I, Pauli.I], 0.3),
([Pauli.I, Pauli.Y, Pauli.I], 0.2),
([Pauli.I, Pauli.I, Pauli.Z], 1.1),
([Pauli.X, Pauli.Z, Pauli.I], 1.8),
],
synthesis_strategy=PauliSynthStrat.Greedy,
depth_weight=0.28,
)
c = tseqbox.get_circuit()
cmds = c.get_commands()
assert cmds[0].op.type == OpType.TK1
assert cmds[1].op.type == OpType.TK1
assert cmds[2].op.type == OpType.TK1
assert c.n_2qb_gates() <= 2


if __name__ == "__main__":
test_circuit_gen()
test_symbolic_ops()
Expand Down
5 changes: 3 additions & 2 deletions schemas/circuit_v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -834,9 +834,10 @@
"synth_strategy",
"partition_strategy",
"graph_colouring",
"cx_config"
"cx_config",
"depth_weight"
],
"maxProperties": 7
"maxProperties": 8
}
},
{
Expand Down
2 changes: 1 addition & 1 deletion tket/conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

class TketConan(ConanFile):
name = "tket"
version = "1.3.11"
version = "1.3.12"
package_type = "library"
license = "Apache 2"
homepage = "https://github.com/CQCL/tket"
Expand Down
8 changes: 7 additions & 1 deletion tket/include/tket/Circuit/PauliExpBoxes.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,8 @@ class TermSequenceBox : public Box {
PauliPartitionStrat partition_strategy =
PauliPartitionStrat::CommutingSets,
GraphColourMethod graph_colouring = GraphColourMethod::Lazy,
CXConfigType cx_configuration = CXConfigType::Tree);
CXConfigType cx_configuration = CXConfigType::Tree,
double depth_weight = 0.3);

/**
* Construct from the empty vector
Expand Down Expand Up @@ -253,6 +254,10 @@ class TermSequenceBox : public Box {
/** Get the cx config parameter (affects box decomposition) */
CXConfigType get_cx_config() const;

/** Tuning parameter for depth optimisation, only applies to
* PauliPartitionStrat::Greedy */
double get_depth_weight() const;

Op_ptr dagger() const override;

Op_ptr transpose() const override;
Expand All @@ -273,6 +278,7 @@ class TermSequenceBox : public Box {
PauliPartitionStrat partition_strategy_;
GraphColourMethod graph_colouring_;
CXConfigType cx_configuration_;
double depth_weight_;
};

/**
Expand Down
14 changes: 14 additions & 0 deletions tket/include/tket/Transformations/GreedyPauliOptimisation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,20 @@ enum class SupportType : unsigned {
*/
Circuit greedy_pauli_graph_synthesis(
const Circuit& circ, double discount_rate = 0.7, double depth_weight = 0.3);

/**
* @brief Synthesise a set of unordered Pauli exponentials by applying Clifford
* gates and single qubit rotations in a greedy fashion. Assume all Pauli
* strings have the same length.
*
* @param unordered_set
* @param depth_weight
* @return Circuit
*/
Circuit greedy_pauli_set_synthesis(
const std::vector<SymPauliTensor>& unordered_set,
double depth_weight = 0.3);

} // namespace GreedyPauliSimp

Transform greedy_pauli_optimisation(
Expand Down
10 changes: 7 additions & 3 deletions tket/include/tket/Transformations/PauliOptimisation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,17 @@ namespace Transforms {

/* Dictates whether synthesis of a PauliGraph should
be done on the Paulis individually, making use of the pairwise
interactions or collecting into mutually commuting sets. */
enum class PauliSynthStrat { Individual, Pairwise, Sets };
interactions or collecting into mutually commuting sets.
The additional greedy strategy applies two-qubit Clifford gates
in a greedy fashion and delays the un-computation to the very end.
*/
enum class PauliSynthStrat { Individual, Pairwise, Sets, Greedy };

NLOHMANN_JSON_SERIALIZE_ENUM(
PauliSynthStrat, {{PauliSynthStrat::Individual, "Individual"},
{PauliSynthStrat::Pairwise, "Pairwise"},
{PauliSynthStrat::Sets, "Sets"}});
{PauliSynthStrat::Sets, "Sets"},
{PauliSynthStrat::Greedy, "Greedy"}});

Transform pairwise_pauli_gadgets(CXConfigType cx_config = CXConfigType::Snake);

Expand Down
33 changes: 26 additions & 7 deletions tket/src/Circuit/PauliExpBoxes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
#include "tket/Converters/PhasePoly.hpp"
#include "tket/Diagonalisation/Diagonalisation.hpp"
#include "tket/Ops/OpJsonFactory.hpp"
#include "tket/Transformations/CliffordOptimisation.hpp"
#include "tket/Transformations/GreedyPauliOptimisation.hpp"

namespace tket {

Expand Down Expand Up @@ -374,13 +376,14 @@ TermSequenceBox::TermSequenceBox(
const std::vector<SymPauliTensor> &pauli_gadgets,
Transforms::PauliSynthStrat synth_strategy,
PauliPartitionStrat partition_strategy, GraphColourMethod graph_colouring,
CXConfigType cx_configuration)
CXConfigType cx_configuration, double depth_weight)
: Box(OpType::TermSequenceBox),
pauli_gadgets_(pauli_gadgets),
synth_strategy_(synth_strategy),
partition_strategy_(partition_strategy),
graph_colouring_(graph_colouring),
cx_configuration_(cx_configuration) {
cx_configuration_(cx_configuration),
depth_weight_(depth_weight) {
// check at least one gadget
if (pauli_gadgets.empty()) {
signature_ = op_signature_t(0, EdgeType::Quantum);
Expand All @@ -404,7 +407,8 @@ TermSequenceBox::TermSequenceBox(const TermSequenceBox &other)
synth_strategy_(other.synth_strategy_),
partition_strategy_(other.partition_strategy_),
graph_colouring_(other.graph_colouring_),
cx_configuration_(other.cx_configuration_) {}
cx_configuration_(other.cx_configuration_),
depth_weight_(other.depth_weight_) {}

TermSequenceBox::TermSequenceBox() : TermSequenceBox({{{}, 0}}) {}

Expand All @@ -431,7 +435,7 @@ Op_ptr TermSequenceBox::dagger() const {
}
return std::make_shared<TermSequenceBox>(
dagger_gadgets, synth_strategy_, partition_strategy_, graph_colouring_,
cx_configuration_);
cx_configuration_, depth_weight_);
}

Op_ptr TermSequenceBox::transpose() const {
Expand All @@ -443,7 +447,7 @@ Op_ptr TermSequenceBox::transpose() const {
}
return std::make_shared<TermSequenceBox>(
transpose_gadgets, synth_strategy_, partition_strategy_, graph_colouring_,
cx_configuration_);
cx_configuration_, depth_weight_);
}

Op_ptr TermSequenceBox::symbol_substitution(
Expand All @@ -454,7 +458,7 @@ Op_ptr TermSequenceBox::symbol_substitution(
}
return std::make_shared<TermSequenceBox>(
symbol_sub_gadgets, synth_strategy_, partition_strategy_,
graph_colouring_, cx_configuration_);
graph_colouring_, cx_configuration_, depth_weight_);
}

bool TermSequenceBox::is_equal(const Op &op_other) const {
Expand All @@ -465,6 +469,7 @@ bool TermSequenceBox::is_equal(const Op &op_other) const {
if (partition_strategy_ != other.partition_strategy_) return false;
if (graph_colouring_ != other.graph_colouring_) return false;
if (cx_configuration_ != other.cx_configuration_) return false;
if (depth_weight_ != other.depth_weight_) return false;
return std::equal(
pauli_gadgets_.begin(), pauli_gadgets_.end(),
other.pauli_gadgets_.begin(), other.pauli_gadgets_.end(),
Expand Down Expand Up @@ -493,6 +498,8 @@ CXConfigType TermSequenceBox::get_cx_config() const {
return cx_configuration_;
}

double TermSequenceBox::get_depth_weight() const { return depth_weight_; }

nlohmann::json TermSequenceBox::to_json(const Op_ptr &op) {
const auto &box = static_cast<const TermSequenceBox &>(*op);
nlohmann::json j = core_box_json(box);
Expand All @@ -506,6 +513,7 @@ nlohmann::json TermSequenceBox::to_json(const Op_ptr &op) {
j["partition_strategy"] = box.get_partition_strategy();
j["graph_colouring"] = box.get_graph_colouring();
j["cx_config"] = box.get_cx_config();
j["depth_weight"] = box.get_depth_weight();

return j;
}
Expand All @@ -521,7 +529,8 @@ Op_ptr TermSequenceBox::from_json(const nlohmann::json &j) {
gadgets, j.at("synth_strategy").get<Transforms::PauliSynthStrat>(),
j.at("partition_strategy").get<PauliPartitionStrat>(),
j.at("graph_colouring").get<GraphColourMethod>(),
j.at("cx_config").get<CXConfigType>());
j.at("cx_config").get<CXConfigType>(),
j.at("depth_weight").get<double>());
return set_box_id(
box,
boost::lexical_cast<boost::uuids::uuid>(j.at("id").get<std::string>()));
Expand Down Expand Up @@ -635,6 +644,16 @@ void TermSequenceBox::generate_circuit() const {
}
break;
}
case Transforms::PauliSynthStrat::Greedy: {
std::vector<SymPauliTensor> unordered_gadgets;
for (const auto &pair : reduced_pauli_gadgets) {
unordered_gadgets.push_back(SymPauliTensor(pair.first, pair.second));
}
circ = Transforms::GreedyPauliSimp::greedy_pauli_set_synthesis(
unordered_gadgets, this->depth_weight_);
Transforms::singleq_clifford_sweep().apply(circ);
break;
}
default: {
throw std::logic_error(
"TermSequenceBox passed an unsupported PauliSynthStrat");
Expand Down
Loading

0 comments on commit c089d6d

Please sign in to comment.