Skip to content

Commit 41a5391

Browse files
authored
Improvements for the Paper "Depth-Optimal Synthesis of Clifford Circuits with SAT Solvers" (#304)
## Description This PR introduces a couple of changes simultaneously that were introduced during the work culminating in [this paper](https://arxiv.org/abs/2305.01674). These are: - A parallel heuristic based on vertical circuit decomposition. - Various improvements to the symmetry breaking constraints - Allowing for setting of solver parameters from the Python side - Some smaller fixes and improvements Fixes # (issue) ## Checklist: <!--- This checklist serves as a reminder of a couple of things that ensure your pull request will be merged swiftly. --> - [x] The pull request only contains commits that are related to it. - [x] I have added appropriate tests and documentation. - [x] I have made sure that all CI jobs on GitHub pass. - [x] The pull request introduces no new warnings and follows the project's style guidelines.
2 parents c6e627d + f6c270b commit 41a5391

23 files changed

+689
-152
lines changed

docs/source/Publications.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ Publications
44
*QMAP* is academic software. Thus, many of its built-in algorithms have been published as scientific papers.
55
See :cite:labelpar:`wille2023qmap` for a general overview of *QMAP* and its features.
66

7-
If you use *QMAP* in your work, we would appreciate if you cited :cite:labelpar:`zulehnerEfficientMethodologyMapping2019` when using the heuristic mapper, :cite:labelpar:`willeMappingQuantumCircuits2019` when using the exact mapper, and :cite:labelpar:`schneider2023satEncodingOptimalClifford` when using the Clifford circuit synthesis approach.
7+
If you use *QMAP* in your work, we would appreciate if you cited :cite:labelpar:`zulehnerEfficientMethodologyMapping2019` when using the heuristic mapper, :cite:labelpar:`willeMappingQuantumCircuits2019` when using the exact mapper, and :cite:labelpar:`schneider2023satEncodingOptimalClifford`, :cite:labelpar:`peham2023DepthOptimalSynthesis` when using the Clifford circuit synthesis approach.
88
Furthermore, if you use any of the particular algorithms such as
99

1010
- the heuristic mapping scheme using teleportation :cite:labelpar:`hillmichExlpoitingQuantumTeleportation2021`
1111
- the search space limitation techniques of the exact mapper (some of which are enabled per default) :cite:labelpar:`burgholzer2022limitingSearchSpace`
12-
- the method for finding (near-)optimal subarchitectures :cite:labelpar:`peham2022OptimalSubarchitectures`
12+
- the method for finding (near-)optimal subarchitectures :cite:labelpar:`peham2023OptimalSubarchitectures`
1313

1414
please consider citing their respective papers as well. A full list of related papers is given below.
1515

docs/source/Synthesis.ipynb

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,75 @@
191191
"cell_type": "markdown",
192192
"metadata": {},
193193
"source": [
194-
"The `include_destabilizers` flag guarantees that the unitary of the circuit is preserved during optimization."
194+
"The `include_destabilizers` flag guarantees that the unitary of the circuit is preserved during optimization.\n",
195+
"\n",
196+
"By default *QMAP* generates optimal Clifford circuits with respect to the target metric. This might lead to runtime problems when trying to optimize larger circuits. When optimizing for depth, *QMAP* provides a heuristic that splits the circuits into several independent parts and optimizes them separately. This allows to optimize larger circuits while not guaranteeing that the depth-optimal circuit is synthesized.\n",
197+
"\n",
198+
"The heuristic synthesizer can be used as follows:"
199+
]
200+
},
201+
{
202+
"cell_type": "code",
203+
"execution_count": null,
204+
"metadata": {},
205+
"outputs": [],
206+
"source": [
207+
"from qiskit import QuantumCircuit\n",
208+
"from mqt import qmap\n",
209+
"\n",
210+
"qc = QuantumCircuit(2)\n",
211+
"qc.x(0)\n",
212+
"qc.cx(0, 1)\n",
213+
"qc.x(0)\n",
214+
"qc.s(1)\n",
215+
"qc.x(1)\n",
216+
"qc.cx(1, 0)\n",
217+
"qc.x(1)\n",
218+
"\n",
219+
"qc_opt, results = qmap.optimize_clifford(\n",
220+
" circuit=qc, heuristic=True, split_size=3, include_destabilizers=True, target_metric=\"depth\"\n",
221+
")\n",
222+
"\n",
223+
"qc_opt.draw(output=\"mpl\")"
224+
]
225+
},
226+
{
227+
"cell_type": "markdown",
228+
"metadata": {},
229+
"source": [
230+
"The parameter `split_size` determines how many layers of the circuit are optimized individually.\n",
231+
"\n",
232+
"In this example the synthesized circuit does not have optimal depth as can be checked by running the optimal synthesis method:"
233+
]
234+
},
235+
{
236+
"cell_type": "code",
237+
"execution_count": null,
238+
"metadata": {},
239+
"outputs": [],
240+
"source": [
241+
"from qiskit import QuantumCircuit\n",
242+
"from mqt import qmap\n",
243+
"\n",
244+
"qc = QuantumCircuit(2)\n",
245+
"qc.x(0)\n",
246+
"qc.cx(0, 1)\n",
247+
"qc.x(0)\n",
248+
"qc.s(1)\n",
249+
"qc.x(1)\n",
250+
"qc.cx(1, 0)\n",
251+
"qc.x(1)\n",
252+
"\n",
253+
"qc_opt, results = qmap.optimize_clifford(circuit=qc, heuristic=False, include_destabilizers=True, target_metric=\"depth\")\n",
254+
"\n",
255+
"qc_opt.draw(output=\"mpl\")"
256+
]
257+
},
258+
{
259+
"cell_type": "markdown",
260+
"metadata": {},
261+
"source": [
262+
"However, the heuristic still gives a good depth reduction in many cases."
195263
]
196264
},
197265
{

docs/source/library/Subarchitectures.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Optimal Subarchitectures
22
========================
33

4-
To compute (near-)optimal subarchitectures of quantum computing architectures with restricted connectivity as described in :cite:labelpar:`peham2022OptimalSubarchitectures` the :code:`SubarchitectureOrder` class is provided. This class has functionality to compute the quasi-order that allows for fast computation of optimal subarchitectures.
4+
To compute (near-)optimal subarchitectures of quantum computing architectures with restricted connectivity as described in :cite:labelpar:`peham2023OptimalSubarchitectures` the :code:`SubarchitectureOrder` class is provided. This class has functionality to compute the quasi-order that allows for fast computation of optimal subarchitectures.
55

66
Note that the initial construction of the ordering might take a while for larger architectures.
77

docs/source/refs.bib

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,16 @@ @inproceedings{boteaComplexityQuantumCircuit2018
5151
year = {2018},
5252
}
5353

54-
@inproceedings{peham2022OptimalSubarchitectures,
55-
title = {On Optimal Subarchitectures for Quantum Circuit Mapping},
56-
author = {Peham, Tom and Burgholzer, Lukas and Wille, Robert},
57-
booktitle = {arXiv:2210.09321},
58-
year = {2022},
59-
url = {https://arxiv.org/pdf/2210.09321.pdf},
54+
@article{peham2023OptimalSubarchitectures,
55+
author = {Peham, Tom and Burgholzer, Lukas and Wille, Robert},
56+
title = {On Optimal Subarchitectures for Quantum Circuit Mapping},
57+
year = {2023},
58+
publisher = {Association for Computing Machinery},
59+
address = {New York, NY, USA},
60+
issn = {2643-6809},
61+
url = {https://doi.org/10.1145/3593594},
62+
doi = {10.1145/3593594},
63+
journal = {ACM Transactions on Quantum Computing},
6064
}
6165

6266
@INPROCEEDINGS{schneider2023satEncodingOptimalClifford,
@@ -74,3 +78,11 @@ @inproceedings{wille2023qmap
7478
year = {2023},
7579
url = {https://www.cda.cit.tum.de/files/eda/2023_ispd_mqt_qmap_efficient_quantum_circuit_mapping.pdf}
7680
}
81+
82+
@inproceedings{peham2023DepthOptimalSynthesis,
83+
title = {Depth-Optimal Synthesis of Clifford Circuits with SAT Solvers},
84+
author = {Peham, Tom and Burgholzer, Lukas and Wille, Robert},
85+
booktitle = {arXiv:2305.01674},
86+
year = {2023},
87+
url = {https://arxiv.org/pdf/2305.01674.pdf},
88+
}

include/cliffordsynthesis/CliffordSynthesizer.hpp

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,24 +31,31 @@ class CliffordSynthesizer {
3131
: initialTableau(std::move(initial)),
3232
targetTableau(qc, 0, std::numeric_limits<std::size_t>::max(),
3333
initialTableau.hasDestabilizers()),
34+
initialCircuit(std::make_shared<qc::QuantumComputation>(qc.clone())),
3435
results(qc, targetTableau) {}
3536
explicit CliffordSynthesizer(qc::QuantumComputation& qc,
3637
const bool useDestabilizers = false)
3738
: initialTableau(qc.getNqubits(), useDestabilizers),
3839
targetTableau(qc, 0, std::numeric_limits<std::size_t>::max(),
3940
useDestabilizers),
41+
initialCircuit(std::make_shared<qc::QuantumComputation>(qc.clone())),
4042
results(qc, targetTableau) {}
4143

4244
virtual ~CliffordSynthesizer() = default;
4345

4446
void synthesize(const Configuration& config = {});
4547

46-
[[nodiscard]] Results& getResults() { return results; };
47-
[[nodiscard]] qc::QuantumComputation& getResultCircuit() {
48+
[[nodiscard]] Results& getResults() { return results; };
49+
50+
void initResultCircuitFromResults() {
4851
std::stringstream ss;
4952
ss << results.getResultCircuit();
5053
resultCircuit = std::make_unique<qc::QuantumComputation>();
5154
resultCircuit->import(ss, qc::Format::OpenQASM);
55+
}
56+
57+
[[nodiscard]] qc::QuantumComputation& getResultCircuit() {
58+
initResultCircuitFromResults();
5259
return *resultCircuit;
5360
};
5461
[[nodiscard]] Tableau& getResultTableau() {
@@ -59,8 +66,9 @@ class CliffordSynthesizer {
5966
}
6067

6168
protected:
62-
Tableau initialTableau{};
63-
Tableau targetTableau{};
69+
Tableau initialTableau{};
70+
Tableau targetTableau{};
71+
std::shared_ptr<qc::QuantumComputation> initialCircuit{};
6472

6573
Configuration configuration{};
6674

@@ -84,6 +92,7 @@ class CliffordSynthesizer {
8492
std::size_t upper);
8593
void depthOptimalSynthesis(EncoderConfig config, std::size_t lower,
8694
std::size_t upper);
95+
void depthHeuristicSynthesis();
8796
void twoQubitGateOptimalSynthesis(EncoderConfig config, std::size_t lower,
8897
std::size_t upper);
8998

@@ -114,6 +123,33 @@ class CliffordSynthesizer {
114123
INFO() << "Found optimum: " << lowerBound;
115124
}
116125

126+
template <typename T>
127+
void runLinearSearch(T& value, T lowerBound, T upperBound,
128+
const EncoderConfig& config) {
129+
INFO() << "Running linear search in range [" << lowerBound << ", "
130+
<< upperBound << ")";
131+
132+
if (upperBound == 0U) {
133+
upperBound = std::numeric_limits<std::size_t>::max();
134+
}
135+
for (value = lowerBound; value < upperBound; ++value) {
136+
INFO() << "Trying value " << value << " in range [" << lowerBound << ", "
137+
<< upperBound << ")";
138+
const auto r = callSolver(config);
139+
updateResults(configuration, r, results);
140+
if (r.sat()) {
141+
INFO() << "Found optimum " << value;
142+
return;
143+
}
144+
INFO() << "No solution found. Trying next value.";
145+
}
146+
INFO() << "No solution found in given interval.";
147+
}
148+
149+
static std::shared_ptr<qc::QuantumComputation>
150+
synthesizeSubcircuit(const std::shared_ptr<qc::QuantumComputation>& qc,
151+
std::size_t begin, std::size_t end,
152+
const Configuration& config);
117153
static void updateResults(const Configuration& config,
118154
const Results& newResults, Results& currentResults);
119155
};

include/cliffordsynthesis/Configuration.hpp

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,31 @@
99
#include "nlohmann/json.hpp"
1010

1111
#include <plog/Log.h>
12+
#include <thread>
13+
#include <unordered_map>
14+
#include <variant>
1215

1316
namespace cs {
17+
18+
using SolverParameter = std::variant<bool, std::uint32_t, double, std::string>;
19+
using SolverParameterMap = std::unordered_map<std::string, SolverParameter>;
20+
1421
struct Configuration {
1522
Configuration() = default;
1623

1724
/// General configuration for the synthesis algorithm
1825
std::size_t initialTimestepLimit = 0U;
1926
std::size_t minimalTimesteps = 0U;
2027
bool useMaxSAT = false;
28+
bool linearSearch = false;
2129
TargetMetric target = TargetMetric::Gates;
2230
bool useSymmetryBreaking = true;
2331
bool dumpIntermediateResults = false;
2432
std::string intermediateResultsPath = "./";
2533
plog::Severity verbosity = plog::Severity::warning;
2634

2735
/// Settings for the SAT solver
28-
std::size_t nThreads = 1U;
36+
SolverParameterMap solverParameters = {};
2937

3038
/// Settings for depth-optimal synthesis
3139
bool minimizeGatesAfterDepthOptimization = false;
@@ -35,22 +43,40 @@ struct Configuration {
3543
double gateLimitFactor = 1.1;
3644
bool minimizeGatesAfterTwoQubitGateOptimization = false;
3745

46+
// Settings for the heuristic solver
47+
bool heuristic = false;
48+
std::size_t splitSize = 5U;
49+
std::size_t nThreadsHeuristic = std::thread::hardware_concurrency();
50+
3851
[[nodiscard]] nlohmann::json json() const {
3952
nlohmann::json j;
4053
j["initial_timestep_limit"] = initialTimestepLimit;
4154
j["minimal_timesteps"] = minimalTimesteps;
4255
j["use_max_sat"] = useMaxSAT;
56+
j["linear_search"] = linearSearch;
4357
j["target_metric"] = toString(target);
4458
j["use_symmetry_breaking"] = useSymmetryBreaking;
45-
j["n_threads"] = nThreads;
4659
j["minimize_gates_after_depth_optimization"] =
4760
minimizeGatesAfterDepthOptimization;
4861
j["try_higher_gate_limit_for_two_qubit_gate_optimization"] =
4962
tryHigherGateLimitForTwoQubitGateOptimization;
5063
j["gate_limit_factor"] = gateLimitFactor;
5164
j["minimize_gates_after_two_qubit_gate_optimization"] =
5265
minimizeGatesAfterTwoQubitGateOptimization;
53-
66+
j["heuristic"] = heuristic;
67+
j["split_size"] = splitSize;
68+
j["n_threads_heuristic"] = nThreadsHeuristic;
69+
if (!solverParameters.empty()) {
70+
nlohmann::json solverParametersJson;
71+
for (const auto& entry : solverParameters) {
72+
std::visit(
73+
[&solverParametersJson, &entry](const auto& v) {
74+
solverParametersJson[entry.first] = v;
75+
},
76+
entry.second);
77+
}
78+
j["solver_parameters"] = solverParametersJson;
79+
}
5480
return j;
5581
}
5682

include/cliffordsynthesis/Tableau.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ class Tableau {
121121
return !(lhs == rhs);
122122
}
123123

124+
[[nodiscard]] bool isIdentityTableau() const;
125+
124126
void createDiagonalTableau(std::size_t nQ, bool includeDestabilizers = false);
125127

126128
friend std::ostream& operator<<(std::ostream& os, const Tableau& dt) {

include/cliffordsynthesis/encoding/GateEncoder.hpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ class GateEncoder {
5959
static constexpr std::array<qc::OpType, 7> SINGLE_QUBIT_GATES = {
6060
qc::OpType::None, qc::OpType::X, qc::OpType::Y, qc::OpType::Z,
6161
qc::OpType::H, qc::OpType::S, qc::OpType::Sdag};
62+
6263
[[nodiscard]] static constexpr std::size_t
6364
gateToIndex(const qc::OpType type) {
6465
for (std::size_t i = 0; i < SINGLE_QUBIT_GATES.size(); ++i) {
@@ -69,6 +70,36 @@ class GateEncoder {
6970
return 0;
7071
}
7172

73+
template <qc::OpType Gate>
74+
[[nodiscard]] static constexpr bool containsGate() {
75+
for (const auto& g : // NOLINT(readability-use-anyofallof)
76+
SINGLE_QUBIT_GATES) {
77+
if (g == Gate) {
78+
return true;
79+
}
80+
}
81+
return false;
82+
}
83+
84+
[[nodiscard]] static constexpr bool containsX() {
85+
return containsGate<qc::OpType::X>();
86+
}
87+
[[nodiscard]] static constexpr bool containsY() {
88+
return containsGate<qc::OpType::Y>();
89+
}
90+
[[nodiscard]] static constexpr bool containsZ() {
91+
return containsGate<qc::OpType::Z>();
92+
}
93+
[[nodiscard]] static constexpr bool containsH() {
94+
return containsGate<qc::OpType::H>();
95+
}
96+
[[nodiscard]] static constexpr bool containsS() {
97+
return containsGate<qc::OpType::S>();
98+
}
99+
[[nodiscard]] static constexpr bool containsSdag() {
100+
return containsGate<qc::OpType::Sdag>();
101+
}
102+
72103
protected:
73104
// number of qubits N
74105
std::size_t N{}; // NOLINT (readability-identifier-naming)

include/cliffordsynthesis/encoding/MultiGateEncoder.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ class MultiGateEncoder : public GateEncoder {
1717
using GateEncoder::GateEncoder;
1818

1919
protected:
20-
logicbase::LogicTerm rChanges{};
20+
logicbase::LogicTerm rChanges{};
21+
logicbase::LogicMatrix xorHelpers{};
2122

2223
void assertConsistency() const override;
2324
void assertGateConstraints() override;
@@ -32,6 +33,7 @@ class MultiGateEncoder : public GateEncoder {
3233
std::size_t qubit) override;
3334
void assertTwoQubitGateOrderConstraints(std::size_t pos, std::size_t ctrl,
3435
std::size_t trgt) override;
36+
void splitXorR(const logicbase::LogicTerm& changes, std::size_t pos);
3537
};
3638

3739
} // namespace cs::encoding

include/cliffordsynthesis/encoding/ObjectiveEncoder.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class ObjectiveEncoder {
3737

3838
void optimizeGateCount(bool includeSingleQubitGates = true) const;
3939

40-
void optimizeDepth(bool includeSingleQubitGates = true) const;
40+
void optimizeDepth() const;
4141

4242
protected:
4343
// number of qubits N

0 commit comments

Comments
 (0)