Skip to content

Commit ca8f991

Browse files
authored
Gate Reordering and Approximate Comparison (#13)
* ✨ add reorder operations pass to pre-processing * ✨ add functionality to compare the resulting DD to the identity with a given threshold * ✨ introduce new optimization that tries to cancel identical operations in the alternating strategy whenever possible * ⬆️ update qfr
1 parent 184b97a commit ca8f991

8 files changed

+234
-31
lines changed

Diff for: README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,12 @@ The verification procedure can be configured with the following settings and opt
7070
- simulation
7171
- `tolerance`: Numerical tolerance used during computation (`1e-13` per default)
7272
- Settinggs for the ![G \rightarrow \mathbb{I} \leftarrow G'](https://render.githubusercontent.com/render/math?math=G%20%5Crightarrow%20%5Cmathbb%7BI%7D%20%5Cleftarrow%20G') method:
73-
- `strategy`: strategy to use for the scheme
73+
- `strategy`: Strategy to use for the scheme
7474
- naive
7575
- proportional (*default*)
7676
- lookahead
7777
- compilationflow
78+
- `identity_threshold`: Numerical tolerance used for checking the similarity to the identity matrix (`1e-10` per default)
7879
- Settings for the simulation-based method:
7980
- `fidelity`: Fidelity limit for comparison (`0.999` per default)
8081
- `max_sims`: Maximum number of simulations to conduct (`16` per default)
@@ -89,6 +90,7 @@ The verification procedure can be configured with the following settings and opt
8990
- `fuse_single_qubit_gates`: Fuse consecutive single qubit gates (*on* per default)
9091
- `remove_diagonal_gates_before_measure`: Remove diagonal gates before measurements (*off* by default)
9192
- `tranform_dynamic_circuit`: Transform dynamic to static circuit (*off* by default)
93+
- `reorder_operations`: Reorder operations in order to eliminate unnecessary dependencies (*on* by default)
9294

9395
The `qcec.Results` class that is returned by the `verify` function provides `json()` and `csv()` methods to produce JSON or CSV formatted output.
9496

Diff for: include/EquivalenceChecker.hpp

+8-4
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,17 @@ namespace ec {
1919
RIGHT = false };
2020

2121
struct Configuration {
22-
ec::Method method = ec::Method::G_I_Gp;
23-
ec::Strategy strategy = ec::Strategy::Proportional;
24-
dd::fp tolerance = dd::ComplexTable<>::tolerance();
22+
ec::Method method = ec::Method::G_I_Gp;
23+
ec::Strategy strategy = ec::Strategy::Proportional;
24+
dd::fp tolerance = dd::ComplexTable<>::tolerance();
25+
dd::fp identityThreshold = 1e-10;
2526

2627
// configuration options for optimizations
2728
bool fuseSingleQubitGates = true;
2829
bool reconstructSWAPs = true;
2930
bool removeDiagonalGatesBeforeMeasure = false;
3031
bool transformDynamicCircuit = false;
32+
bool reorderOperations = true;
3133

3234
// configuration options for PowerOfSimulation equivalence checker
3335
double fidelity_limit = 0.999;
@@ -40,7 +42,8 @@ namespace ec {
4042
nlohmann::json config{};
4143
config["method"] = ec::toString(method);
4244
if (method == ec::Method::G_I_Gp) {
43-
config["strategy"] = ec::toString(strategy);
45+
config["strategy"] = ec::toString(strategy);
46+
config["identity threshold"] = identityThreshold;
4447
}
4548
config["tolerance"] = tolerance;
4649
config["optimizations"] = {};
@@ -49,6 +52,7 @@ namespace ec {
4952
optimizations["reconstruct swaps"] = reconstructSWAPs;
5053
optimizations["remove diagonal gates before measure"] = removeDiagonalGatesBeforeMeasure;
5154
optimizations["transform dynamic circuit"] = transformDynamicCircuit;
55+
optimizations["reorder operations"] = reorderOperations;
5256
if (method == ec::Method::Simulation) {
5357
config["simulation config"] = {};
5458
auto& simulation = config["simulation config"];

Diff for: include/ImprovedDDEquivalenceChecker.hpp

+66-8
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,72 @@ namespace ec {
3333
/// \return initial matrix
3434
qc::MatrixDD createInitialMatrix();
3535

36-
/// Create the goal matrix used for the G->I<-G' scheme.
37-
/// [1 0] if the qubit is no ancillary
38-
/// [0 1]
39-
///
40-
/// [1 0] for an ancillary that is present in either circuit
41-
/// [0 0]
42-
/// \return goal matrix
43-
qc::MatrixDD createGoalMatrix();
36+
bool gatesAreIdentical(const qc::Permutation& perm1, const qc::Permutation& perm2) {
37+
// exit prematurely whenever one of the circuits is already finished
38+
if (it1 == end1 || it2 == end2)
39+
return false;
40+
41+
const auto& op1 = **it1;
42+
const auto& op2 = **it2;
43+
44+
// check type
45+
if (op1.getType() != op2.getType()) {
46+
return false;
47+
}
48+
49+
// check number of controls
50+
const auto nc1 = op1.getNcontrols();
51+
const auto nc2 = op2.getNcontrols();
52+
if (nc1 != nc2) {
53+
return false;
54+
}
55+
56+
// SWAPs are not handled by this routine
57+
if (op1.getType() == qc::SWAP && nc1 == 0) {
58+
return false;
59+
}
60+
61+
// check parameters
62+
const auto param1 = op1.getParameter();
63+
const auto param2 = op2.getParameter();
64+
for (std::size_t p = 0; p < qc::MAX_PARAMETERS; ++p) {
65+
// it might make sense to use fuzzy comparison here
66+
if (param1[p] != param2[p]) {
67+
return false;
68+
}
69+
}
70+
71+
// check controls
72+
if (nc1 != 0) {
73+
dd::Controls controls1{};
74+
for (const auto& control: op1.getControls()) {
75+
controls1.emplace(dd::Control{perm1.at(control.qubit), control.type});
76+
}
77+
dd::Controls controls2{};
78+
for (const auto& control: op2.getControls()) {
79+
controls2.emplace(dd::Control{perm2.at(control.qubit), control.type});
80+
}
81+
if (controls1 != controls2) {
82+
return false;
83+
}
84+
}
85+
86+
// check targets
87+
std::set<dd::Qubit> targets1{};
88+
for (const auto& target: op1.getTargets()) {
89+
targets1.emplace(perm1.at(target));
90+
}
91+
std::set<dd::Qubit> targets2{};
92+
for (const auto& target: op2.getTargets()) {
93+
targets2.emplace(perm2.at(target));
94+
}
95+
if (targets1 != targets2) {
96+
return false;
97+
}
98+
99+
// operations are identical
100+
return true;
101+
}
44102

45103
public:
46104
ImprovedDDEquivalenceChecker(qc::QuantumComputation& qc1, qc::QuantumComputation& qc2):

Diff for: jkq/qcec/bindings.cpp

+8
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,10 @@ PYBIND11_MODULE(pyqcec, m) {
126126
R"pbdoc(
127127
Numerical tolerance used during computation
128128
)pbdoc")
129+
.def_readwrite("identity_threshold", &ec::Configuration::identityThreshold,
130+
R"pbdoc(
131+
Numerical threshold used for checking the similarity to the identity matrix (default: 1e-10)
132+
)pbdoc")
129133
.def_readwrite("reconstruct_swaps", &ec::Configuration::reconstructSWAPs,
130134
R"pbdoc(
131135
Optimization pass reconstructing SWAP operations
@@ -138,6 +142,10 @@ PYBIND11_MODULE(pyqcec, m) {
138142
R"pbdoc(
139143
Optimization pass that transforms dynamic circuits to static circuits for equivalence checking
140144
)pbdoc")
145+
.def_readwrite("reorder_operations", &ec::Configuration::reorderOperations,
146+
R"pbdoc(
147+
Optimization pass that reorders operations to eliminate unnecessary dependencies
148+
)pbdoc")
141149
.def_readwrite("remove_diagonal_gates_before_measure", &ec::Configuration::removeDiagonalGatesBeforeMeasure,
142150
R"pbdoc(
143151
Optimization pass removing diagonal gates before measurements

Diff for: src/CompilationFlowEquivalenceChecker.cpp

+15-2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ namespace ec {
3333
}
3434

3535
if (it1 != end1 && it2 != end2) {
36+
if (results.result.p->ident && gatesAreIdentical(perm1, perm2)) {
37+
++it1;
38+
++it2;
39+
continue;
40+
}
41+
3642
auto cost1 = costFunction((*it1)->getType(), (*it1)->getControls().size());
3743
auto cost2 = costFunction((*it2)->getType(), (*it2)->getControls().size());
3844

@@ -78,8 +84,15 @@ namespace ec {
7884
results.result = dd->reduceAncillae(results.result, ancillary1, LEFT);
7985
results.result = dd->reduceAncillae(results.result, ancillary2, RIGHT);
8086

81-
auto goal_matrix = createGoalMatrix();
82-
results.equivalence = equals(results.result, goal_matrix);
87+
if (dd->isCloseToIdentity(results.result, config.identityThreshold)) {
88+
if (!results.result.w.approximatelyOne()) {
89+
results.equivalence = Equivalence::EquivalentUpToGlobalPhase;
90+
} else {
91+
results.equivalence = Equivalence::Equivalent;
92+
}
93+
} else {
94+
results.equivalence = Equivalence::NotEquivalent;
95+
}
8396

8497
auto endVerification = std::chrono::steady_clock::now();
8598
std::chrono::duration<double> preprocessingTime = endPreprocessing - start;

Diff for: src/EquivalenceChecker.cpp

+6-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace ec {
1010

1111
EquivalenceChecker::EquivalenceChecker(qc::QuantumComputation& qc1, qc::QuantumComputation& qc2):
1212
qc1(qc1), qc2(qc2) {
13-
// currently this modifies the underlying quantum circuits
13+
// currently, this modifies the underlying quantum circuits
1414
// in the future this might want to be avoided
1515
qc1.stripIdleQubits();
1616
qc2.stripIdleQubits();
@@ -266,6 +266,11 @@ namespace ec {
266266
qc::CircuitOptimizer::singleQubitGateFusion(qc2);
267267
}
268268

269+
if (config.reorderOperations) {
270+
qc::CircuitOptimizer::reorderOperations(qc1);
271+
qc::CircuitOptimizer::reorderOperations(qc2);
272+
}
273+
269274
it1 = qc1.begin();
270275
it2 = qc2.begin();
271276
end1 = qc1.cend();

Diff for: src/ImprovedDDEquivalenceChecker.cpp

+22-15
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,6 @@ namespace ec {
4242
return e;
4343
}
4444

45-
qc::MatrixDD ImprovedDDEquivalenceChecker::createGoalMatrix() {
46-
auto goalMatrix = dd->makeIdent(nqubits);
47-
dd->incRef(goalMatrix);
48-
goalMatrix = dd->reduceAncillae(goalMatrix, ancillary2, RIGHT);
49-
goalMatrix = dd->reduceGarbage(goalMatrix, garbage2, RIGHT);
50-
goalMatrix = dd->reduceAncillae(goalMatrix, ancillary1, LEFT);
51-
goalMatrix = dd->reduceGarbage(goalMatrix, garbage1, LEFT);
52-
return goalMatrix;
53-
}
54-
5545
/// Use dedicated method to check the equivalence of both provided circuits
5646
EquivalenceCheckingResults ImprovedDDEquivalenceChecker::check(const Configuration& config) {
5747
EquivalenceCheckingResults results{};
@@ -99,8 +89,16 @@ namespace ec {
9989
results.result = dd->reduceAncillae(results.result, ancillary1, LEFT);
10090
results.result = dd->reduceAncillae(results.result, ancillary2, RIGHT);
10191

102-
results.equivalence = equals(results.result, createGoalMatrix());
103-
results.maxActive = std::max(results.maxActive, dd->mUniqueTable.getMaxActiveNodes());
92+
if (dd->isCloseToIdentity(results.result, config.identityThreshold)) {
93+
if (!results.result.w.approximatelyOne()) {
94+
results.equivalence = Equivalence::EquivalentUpToGlobalPhase;
95+
} else {
96+
results.equivalence = Equivalence::Equivalent;
97+
}
98+
} else {
99+
results.equivalence = Equivalence::NotEquivalent;
100+
}
101+
results.maxActive = std::max(results.maxActive, dd->mUniqueTable.getMaxActiveNodes());
104102

105103
auto endVerification = std::chrono::steady_clock::now();
106104
std::chrono::duration<double> preprocessingTime = endPreprocessing - start;
@@ -114,6 +112,11 @@ namespace ec {
114112
/// Alternate between LEFT and RIGHT applications
115113
void ImprovedDDEquivalenceChecker::checkNaive(qc::MatrixDD& result, qc::Permutation& perm1, qc::Permutation& perm2) {
116114
while (it1 != end1 && it2 != end2) {
115+
if (result.p->ident && gatesAreIdentical(perm1, perm2)) {
116+
++it1;
117+
++it2;
118+
continue;
119+
}
117120
applyGate(qc1, it1, result, perm1, LEFT);
118121
++it1;
119122
applyGate(qc2, it2, result, perm2, RIGHT);
@@ -123,13 +126,17 @@ namespace ec {
123126

124127
/// Alternate according to the gate count ratio between LEFT and RIGHT applications
125128
void ImprovedDDEquivalenceChecker::checkProportional(qc::MatrixDD& result, qc::Permutation& perm1, qc::Permutation& perm2) {
126-
auto ratio = static_cast<unsigned int>(std::round(
127-
static_cast<double>(std::max(qc1.getNops(), qc2.getNops())) /
128-
static_cast<double>(std::min(qc1.getNops(), qc2.getNops()))));
129+
auto ratio = static_cast<unsigned int>(std::round(static_cast<double>(std::max(qc1.getNops(), qc2.getNops())) / static_cast<double>(std::min(qc1.getNops(), qc2.getNops()))));
129130
auto ratio1 = (qc1.getNops() > qc2.getNops()) ? ratio : 1;
130131
auto ratio2 = (qc1.getNops() > qc2.getNops()) ? 1 : ratio;
131132

132133
while (it1 != end1 && it2 != end2) {
134+
if (result.p->ident && gatesAreIdentical(perm1, perm2)) {
135+
++it1;
136+
++it2;
137+
continue;
138+
}
139+
133140
for (unsigned int i = 0; i < ratio1 && it1 != end1; ++i) {
134141
applyGate(qc1, it1, result, perm1, LEFT);
135142
++it1;

0 commit comments

Comments
 (0)