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

Delayed Phase Correction #397

Open
wants to merge 31 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c81eb40
Removed fixed gateset and replaced with parameter.
pehamTom Nov 10, 2023
5eb3747
Check if gateset is Clifford.
pehamTom Nov 10, 2023
8c9d539
Added completens check for single-qubit Gatesets.
pehamTom Nov 10, 2023
1cd55fd
Extracted Gateset in separate file and pass gateset through config.
pehamTom Nov 10, 2023
256f588
Bindings for Gateset.
pehamTom Nov 10, 2023
d218f42
Commit missing file.
pehamTom Nov 13, 2023
af8ee8f
Relax ExactlyOne constraint for depth-optimal synthesis.
pehamTom Nov 13, 2023
eb35989
Always add the none gate.
pehamTom Nov 13, 2023
b2e3a92
Merge branch 'tom-dev' into relax-exactly-one-constraint
pehamTom Nov 13, 2023
848e9d7
Removed templates.
pehamTom Nov 13, 2023
7690afa
Merge branch 'tom-dev' into relax-exactly-one-constraint
pehamTom Nov 13, 2023
d334f58
Made GateSet into an actual set.
pehamTom Nov 13, 2023
267c2b1
Merge branch 'tom-dev' into relax-exactly-one-constraint
pehamTom Nov 13, 2023
f58a738
Can only relax constraints if no Paulis are in the target Gateset.
pehamTom Nov 13, 2023
3b3f028
added setting for delaying paulis.
pehamTom Nov 13, 2023
e6c3348
Extract common functionality of different encoders.
pehamTom Nov 13, 2023
f45ac80
Added new class for handling the encoding for phase correction.
pehamTom Nov 14, 2023
cad2a04
Added files for encoder.
pehamTom Nov 14, 2023
c02c8ed
Encoding and tests for delayed phase correction.
pehamTom Nov 14, 2023
0608ae9
Fixed tests.
pehamTom Nov 14, 2023
e8d4794
🎨 pre-commit fixes
pre-commit-ci[bot] Nov 14, 2023
b8423cc
Fix constexpr error.
pehamTom Nov 14, 2023
995ed9d
Merge remote-tracking branch 'origin/postpone-paulis' into postpone-p…
pehamTom Nov 14, 2023
6a3bafa
🎨 pre-commit fixes
pre-commit-ci[bot] Nov 14, 2023
dac77bb
missing header?
pehamTom Nov 15, 2023
60f64ca
Merge remote-tracking branch 'origin/postpone-paulis' into postpone-p…
pehamTom Nov 15, 2023
4291548
Missing include.
pehamTom Nov 15, 2023
245f55c
🎨 pre-commit fixes
pre-commit-ci[bot] Nov 15, 2023
a0652f0
Linter warnings.
pehamTom Nov 15, 2023
aaeb22f
Merge remote-tracking branch 'origin/postpone-paulis' into postpone-p…
pehamTom Nov 15, 2023
4b48b48
Fixed relation of consistency constraints.
pehamTom Nov 17, 2023
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
Prev Previous commit
Next Next commit
🎨 pre-commit fixes
pre-commit-ci[bot] committed Nov 14, 2023
commit e8d479438d174102ba6bff8c100fa4073c0f494a
21 changes: 12 additions & 9 deletions include/cliffordsynthesis/Utils.hpp
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heads-up: I personally despise Utils files, so please feel free to disagree on this, but I do not think the functionality here should be in a separate file.
All of this can simply go into the GateSet class/file. The isPauli method can even be used there to simplify some if condition.

Original file line number Diff line number Diff line change
@@ -10,14 +10,17 @@

namespace cs {

const GateSet PAULIS {
qc::OpType::None, qc::OpType::X, qc::OpType::Y, qc::OpType::Z,
};

inline bool isPauli(const qc::OpType& gate) {
return PAULIS.containsGate(gate);
}
const GateSet PAULIS{
qc::OpType::None,
qc::OpType::X,
qc::OpType::Y,
qc::OpType::Z,
};

qc::OpType multiplyPaulis(const GateSet& paulis);
inline bool isPauli(const qc::OpType& gate) {
return PAULIS.containsGate(gate);
}

} // namespace cs;
qc::OpType multiplyPaulis(const GateSet& paulis);

} // namespace cs
9 changes: 5 additions & 4 deletions include/cliffordsynthesis/encoding/GateEncoder.hpp
Original file line number Diff line number Diff line change
@@ -27,10 +27,10 @@ class GateEncoder {
const std::size_t timestepLimit,
TableauEncoder::Variables* tableauVars,
std::shared_ptr<logicbase::LogicBlock> logicBlock,
GateSet singleQGates,
bool ignorePhase = false)
GateSet singleQGates, bool ignorePhase = false)
: N(nQubits), S(tableauSize), T(timestepLimit), tvars(tableauVars),
lb(std::move(logicBlock)), singleQubitGates(std::move(singleQGates)), ignoreRChanges(ignorePhase) {
lb(std::move(logicBlock)), singleQubitGates(std::move(singleQGates)),
ignoreRChanges(ignorePhase) {
if (!singleQGates.isValidGateSet()) {
throw qc::QFRException("Invalid gate set");
}
@@ -68,7 +68,8 @@ class GateEncoder {
virtual void encodeSymmetryBreakingConstraints();

// extracting the circuit
qc::QuantumComputation extractCircuitFromModel(Results& res, logicbase::Model& model);
qc::QuantumComputation extractCircuitFromModel(Results& res,
logicbase::Model& model);

[[nodiscard]] auto* getVariables() { return &vars; }

2 changes: 1 addition & 1 deletion include/cliffordsynthesis/encoding/MultiGateEncoder.hpp
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@ class MultiGateEncoder : public GateEncoder {
protected:
logicbase::LogicTerm rChanges{};
logicbase::LogicMatrix xorHelpers{};

void assertConsistency() const override;
void assertGateConstraints() override;
void assertRConstraints(std::size_t pos, std::size_t qubit) override;
74 changes: 38 additions & 36 deletions include/cliffordsynthesis/encoding/PhaseCorrectionEncoder.hpp
Original file line number Diff line number Diff line change
@@ -5,56 +5,58 @@

#pragma once

#include <utility>

#include "LogicTerm/Logic.hpp"
#include "LogicBlock/LogicBlock.hpp"
#include "LogicTerm/Logic.hpp"
#include "LogicUtil/util_logicblock.hpp"
#include "cliffordsynthesis/Tableau.hpp"
#include "operations/OpType.hpp"

#include <memory>
#include <utility>
#include <vector>

namespace cs::encoding {
using namespace logicbase;
class PhaseCorrectionEncoder {
public:
PhaseCorrectionEncoder(const std::size_t nQubits, const std::size_t tableauSize,
Tableau uncorrectedTableau, Tableau targetTableau)
: N{nQubits}, S{tableauSize}, uncorrected(std::move(uncorrectedTableau)), target(std::move(targetTableau)), paulis{N} {
bool success = true;
lb = logicutil::getZ3LogicBlock(success, true);
if (!success) {
FATAL() << "Could not initialize solver engine.";
}
using namespace logicbase;
class PhaseCorrectionEncoder {
public:
PhaseCorrectionEncoder(const std::size_t nQubits,
const std::size_t tableauSize,
Tableau uncorrectedTableau, Tableau targetTableau)
: N{nQubits}, S{tableauSize}, uncorrected(std::move(uncorrectedTableau)),
target(std::move(targetTableau)), paulis{N} {
bool success = true;
lb = logicutil::getZ3LogicBlock(success, true);
if (!success) {
FATAL() << "Could not initialize solver engine.";
}
}

std::vector<qc::OpType> phaseCorrection();

std::vector<qc::OpType> phaseCorrection();
protected:
// number of qubits N
std::size_t N{}; // NOLINT (readability-identifier-naming)
// number of rows in the tableau S
std::size_t S{}; // NOLINT (readability-identifier-naming)
// the logic block to use
std::shared_ptr<LogicBlock> lb{};
Tableau uncorrected{};
Tableau target{};

protected:
// number of qubits N
std::size_t N{}; // NOLINT (readability-identifier-naming)
// number of rows in the tableau S
std::size_t S{}; // NOLINT (readability-identifier-naming)
// the logic block to use
std::shared_ptr<LogicBlock> lb{};
Tableau uncorrected{};
Tableau target{};

logicbase::LogicMatrix paulis{}; //order: I, X, Y, Z
LogicVector initialPhase{};
LogicVector targetPhase{};
logicbase::LogicMatrix paulis{}; // order: I, X, Y, Z
LogicVector initialPhase{};
LogicVector targetPhase{};

LogicMatrix xorHelpers{};
LogicMatrix xorHelpers{};

void splitXorR(const LogicVector& changes);
void splitXorR(const LogicVector& changes);

[[nodiscard]] LogicVector vectorFromBitset(const std::bitset<64>& bs) const;
[[nodiscard]] LogicVector vectorFromBitset(const std::bitset<64>& bs) const;

void encodePauliConstraints();
void encodePauliConstraints();

void createVariables();
void createVariables();

std::vector<qc::OpType> extractResult();
};
} // namespace cs;
std::vector<qc::OpType> extractResult();
};
} // namespace cs::encoding
6 changes: 3 additions & 3 deletions include/cliffordsynthesis/encoding/SATEncoder.hpp
Original file line number Diff line number Diff line change
@@ -77,7 +77,7 @@ class SATEncoder {
[[nodiscard]] logicbase::Result solve() const;
void extractResultsFromModel(Results& res) const;
void cleanup() const;

std::shared_ptr<logicbase::LogicBlock> lb;
std::shared_ptr<TableauEncoder> tableauEncoder;
std::shared_ptr<GateEncoder> gateEncoder;
@@ -90,8 +90,8 @@ class SATEncoder {
std::size_t N{}; // NOLINT (readability-identifier-naming)
// timestep limit T
std::size_t T{}; // NOLINT (readability-identifier-naming)
// number of rows in the tableau S
std::size_t S{}; // NOLINT (readability-identifier-naming)
// number of rows in the tableau S
std::size_t S{}; // NOLINT (readability-identifier-naming)
};

} // namespace cs::encoding
10 changes: 5 additions & 5 deletions include/cliffordsynthesis/encoding/TableauEncoder.hpp
Original file line number Diff line number Diff line change
@@ -20,9 +20,9 @@ class TableauEncoder {
TableauEncoder(const std::size_t nQubits, const std::size_t tableauSize,
const std::size_t timestepLimit,
std::shared_ptr<logicbase::LogicBlock> logicBlock,
bool ignorePhase=false)
: N(nQubits), S(tableauSize), T(timestepLimit),
lb(std::move(logicBlock)), ignoreR(ignorePhase) {}
bool ignorePhase = false)
: N(nQubits), S(tableauSize), T(timestepLimit), lb(std::move(logicBlock)),
ignoreR(ignorePhase) {}

struct Variables {
// variables for the X parts of the tableaus
@@ -59,8 +59,8 @@ class TableauEncoder {
void assertTableau(const Tableau& tableau, std::size_t t);

// extracting the tableau
Tableau extractTableauFromModel(Results& results, std::size_t t,
logicbase::Model& model) const;
Tableau extractTableauFromModel(Results& results, std::size_t t,
logicbase::Model& model) const;

[[nodiscard]] auto* getVariables() { return &vars; }

2 changes: 1 addition & 1 deletion src/cliffordsynthesis/CliffordSynthesizer.cpp
Original file line number Diff line number Diff line change
@@ -44,7 +44,7 @@ void CliffordSynthesizer::synthesize(const Configuration& config) {
encoderConfig.solverParameters = configuration.solverParameters;
encoderConfig.useMultiGateEncoding =
requiresMultiGateEncoding(encoderConfig.targetMetric);
encoderConfig.gateSet = configuration.gateSet;
encoderConfig.gateSet = configuration.gateSet;
encoderConfig.ignoreRChanges = config.delayPaulis;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not name these two options the same?
Wouldn't that be way clearer if there weren't two different ways to refer to the same functionality.


if (configuration.heuristic) {
54 changes: 29 additions & 25 deletions src/cliffordsynthesis/encoding/SATEncoder.cpp
Original file line number Diff line number Diff line change
@@ -8,10 +8,10 @@
#include "LogicUtil/util_logicblock.hpp"
#include "cliffordsynthesis/Tableau.hpp"
#include "cliffordsynthesis/encoding/MultiGateEncoder.hpp"
#include "cliffordsynthesis/encoding/PhaseCorrectionEncoder.hpp"
#include "cliffordsynthesis/encoding/SingleGateEncoder.hpp"
#include "operations/OpType.hpp"
#include "utils/logging.hpp"
#include "cliffordsynthesis/encoding/PhaseCorrectionEncoder.hpp"

#include <chrono>
#include <cstddef>
@@ -56,21 +56,24 @@ void SATEncoder::createFormulation() {
initializeSolver();

S = config.targetTableau->hasDestabilizers() &&
config.initialTableau->hasDestabilizers()
? 2U * N
: N;
config.initialTableau->hasDestabilizers()
? 2U * N
: N;

tableauEncoder = std::make_shared<TableauEncoder>(N, S, T, lb, config.ignoreRChanges);
tableauEncoder =
std::make_shared<TableauEncoder>(N, S, T, lb, config.ignoreRChanges);
tableauEncoder->createTableauVariables();
tableauEncoder->assertTableau(*config.initialTableau, 0U);
tableauEncoder->assertTableau(*config.targetTableau, T);

if (config.useMultiGateEncoding) {
gateEncoder = std::make_shared<MultiGateEncoder>(
N, S, T, tableauEncoder->getVariables(), lb, config.gateSet, config.ignoreRChanges);
N, S, T, tableauEncoder->getVariables(), lb, config.gateSet,
config.ignoreRChanges);
} else {
gateEncoder = std::make_shared<SingleGateEncoder>(
N, S, T, tableauEncoder->getVariables(), lb, config.gateSet, config.ignoreRChanges);
N, S, T, tableauEncoder->getVariables(), lb, config.gateSet,
config.ignoreRChanges);
}
gateEncoder->createSingleQubitGateVariables();
gateEncoder->createTwoQubitGateVariables();
@@ -81,7 +84,7 @@ void SATEncoder::createFormulation() {
}

objectiveEncoder = std::make_shared<ObjectiveEncoder>(
N, T, gateEncoder->getVariables(), lb, config.gateSet);
N, T, gateEncoder->getVariables(), lb, config.gateSet);

if (config.gateLimit.has_value()) {
objectiveEncoder->limitGateCount(*config.gateLimit, std::less_equal{});
@@ -121,31 +124,32 @@ void SATEncoder::extractResultsFromModel(Results& res) const {
tableauEncoder->extractTableauFromModel(res, T, *model);
auto qc = gateEncoder->extractCircuitFromModel(res, *model);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might create an unnecessary copy of the quantum circuit in case ignoreRChanges=false. It could be that RVO elides that copy, but I wouldn't be 100% sure.
Would it be cleaner to refactor the Results class so that it does not just store the OpenQASM string of the circuit and the string representation of the tableau, but the actual circuit and tableau? It would then produce the QASM and Tableau string only when requested.

if (config.ignoreRChanges) {
const auto start = std::chrono::high_resolution_clock::now();
auto tab = *config.targetTableau;
auto qcTab = Tableau(qc, 0, std::numeric_limits<std::size_t>::max(), config.targetTableau->hasDestabilizers());
for(std::size_t row = 0U; row < S; ++row) {
DEBUG() << "Row " << std::to_string(row);
tab[row][2*N] = qcTab.at(row)[2*N];
}
const auto start = std::chrono::high_resolution_clock::now();
auto tab = *config.targetTableau;
auto qcTab = Tableau(qc, 0, std::numeric_limits<std::size_t>::max(),
config.targetTableau->hasDestabilizers());
Comment on lines +134 to +135
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you were to implement the change proposed above, I would guess that this can be skipped completely as the resulting tableau from the circuit is available in the results.

for (std::size_t row = 0U; row < S; ++row) {
DEBUG() << "Row " << std::to_string(row);
tab[row][2 * N] = qcTab.at(row)[2 * N];
}
PhaseCorrectionEncoder phaseCorrectionEncoder(N, S, tab,
*config.targetTableau);
auto paulis = phaseCorrectionEncoder.phaseCorrection();
for(std::size_t row = 0U; row < S; ++row) {
tab[row][2*N] = config.targetTableau->at(row)[2*N];
auto paulis = phaseCorrectionEncoder.phaseCorrection();

for (std::size_t row = 0U; row < S; ++row) {
tab[row][2 * N] = config.targetTableau->at(row)[2 * N];
}

for (std::size_t q = 0U; q < N; ++q) {
if (paulis[q] != qc::OpType::None) {
DEBUG() << "Phase correction for qubit " << q << ": "
<< qc::toString(paulis[q]);
qc.emplace_back<qc::StandardOperation>(N, q, paulis[q]);
}
qc.emplace_back<qc::StandardOperation>(N, q, paulis[q]);
}
}
const auto end = std::chrono::high_resolution_clock::now();
const auto runtime = std::chrono::duration<double>(end - start);
res.setRuntime(res.getRuntime() + runtime.count());
const auto end = std::chrono::high_resolution_clock::now();
const auto runtime = std::chrono::duration<double>(end - start);
res.setRuntime(res.getRuntime() + runtime.count());
res.setResultCircuit(qc);
res.setResultTableau(tab);
res.setDepth(qc.getDepth());
16 changes: 8 additions & 8 deletions src/cliffordsynthesis/encoding/TableauEncoder.cpp
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@ void TableauEncoder::createTableauVariables() {
DEBUG() << "Creating tableau variables.";
vars.x.reserve(T);
vars.z.reserve(T);
if(!ignoreR){
if (!ignoreR) {
vars.r.reserve(T);
}
for (std::size_t t = 0U; t <= T; ++t) {
@@ -36,7 +36,7 @@ void TableauEncoder::createTableauVariables() {
TRACE() << "Creating variable " << zName;
z.emplace_back(lb->makeVariable(zName, CType::BITVECTOR, n));
}
if(!ignoreR){
if (!ignoreR) {
const std::string rName = "r_" + std::to_string(t);
TRACE() << "Creating variable " << rName;
vars.r.emplace_back(lb->makeVariable(rName, CType::BITVECTOR, n));
@@ -58,26 +58,26 @@ void TableauEncoder::assertTableau(const Tableau& tableau,
lb->assertFormula(vars.z[t][a] == LogicTerm(targetZ, n));
}

if(!ignoreR){
if (!ignoreR) {
const auto targetR = tableau.getBVFrom(2U * N);
lb->assertFormula(vars.r[t] == LogicTerm(targetR, n));
lb->assertFormula(vars.r[t] == LogicTerm(targetR, n));
}
}

Tableau TableauEncoder::extractTableauFromModel(Results& results,
const std::size_t t,
Model& model) const {
const std::size_t t,
Model& model) const {
Tableau tableau(N, S > N);
for (std::size_t i = 0; i < N; ++i) {
const auto bvx = model.getBitvectorValue(vars.x[t][i], lb.get());
tableau.populateTableauFrom(bvx, S, i);
const auto bvz = model.getBitvectorValue(vars.z[t][i], lb.get());
tableau.populateTableauFrom(bvz, S, i + N);
}
if(!ignoreR){
if (!ignoreR) {
const auto bvr = model.getBitvectorValue(vars.r[t], lb.get());
tableau.populateTableauFrom(bvr, S, 2 * N);
}
}
results.setResultTableau(tableau);
return tableau;
}
4 changes: 2 additions & 2 deletions src/python/bindings.cpp
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of the new options are missing from the Python stubs for QMAP. Please also add any additions to the bindings there.

Original file line number Diff line number Diff line change
@@ -527,8 +527,8 @@ PYBIND11_MODULE(pyqmap, m) {
"Gate Set to be used for the Synthesis. "
"Defaults to {H, S, Sdg, X, Y, Z, CX}.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is not really true. CX newer really plays into whatever is being set here and there is no way to specify other two-qubit gates. Guess this is mostly a consequence of the inappropriate naming.

.def_readwrite("delay_paulis", &cs::Configuration::delayPaulis,
"Delay Pauli gates to the end of the circuit. "
"Defaults to `false`.")
"Delay Pauli gates to the end of the circuit. "
"Defaults to `false`.")
Comment on lines +530 to +531
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it would be good to add a statement here that comments on whether this preserves optimality.

.def("json", &cs::Configuration::json,
"Returns a JSON-style dictionary of all the information present in "
"the :class:`.Configuration`")
8 changes: 4 additions & 4 deletions test/cliffordsynthesis/test_synthesis.cpp
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am surprised by the lack of further tests asserting the proper behaviour given the size of the changes in this PR.
Is this really enough to ensure that this works as expected?
For example: you introduce single-qubit gate sets in this PR, but you newer test different gate-sets.
That seems very dangerous to me to introduce regressions.

Original file line number Diff line number Diff line change
@@ -354,14 +354,14 @@ TEST_P(SynthesisTest, TestDestabilizerTwoQubitGates) {
}

TEST_P(SynthesisTest, DepthDelayedPhase) {
config.target = TargetMetric::Depth;
config.target = TargetMetric::Depth;
config.linearSearch = true;
config.delayPaulis = true;
config.delayPaulis = true;
synthesizer.synthesize(config);

results = synthesizer.getResults();

EXPECT_LE(results.getDepth(), test.expectedMinimalDepth+1);
EXPECT_LE(results.getDepth(), test.expectedMinimalDepth + 1);
}

TEST(HeuristicTest, basic) {