Skip to content

Commit 8276ae5

Browse files
authored
[verifier] Remove unused qubits when verifying transformation steps (#207)
* [verifier] Remove unused qubits when verifying transformation steps * more output when verbose
1 parent 0fb9c82 commit 8276ae5

File tree

3 files changed

+80
-21
lines changed

3 files changed

+80
-21
lines changed

src/quartz/circuitseq/circuitseq.cpp

+40-15
Original file line numberDiff line numberDiff line change
@@ -1271,14 +1271,16 @@ bool CircuitSeq::to_canonical_representation(const Context *ctx) {
12711271
std::unique_ptr<CircuitSeq>
12721272
CircuitSeq::get_permuted_seq(const std::vector<int> &qubit_permutation,
12731273
const std::vector<int> &input_param_permutation,
1274-
Context *ctx) const {
1274+
Context *ctx, int result_num_qubits) const {
12751275
auto result = std::make_unique<CircuitSeq>(0);
12761276
if (input_param_permutation.empty()) {
1277-
result->clone_from(*this, qubit_permutation, input_param_permutation, ctx);
1277+
result->clone_from(*this, qubit_permutation, input_param_permutation, ctx,
1278+
result_num_qubits);
12781279
} else {
12791280
auto all_param_permutation =
12801281
ctx->get_param_permutation(input_param_permutation);
1281-
result->clone_from(*this, qubit_permutation, all_param_permutation, ctx);
1282+
result->clone_from(*this, qubit_permutation, all_param_permutation, ctx,
1283+
result_num_qubits);
12821284
}
12831285
return result;
12841286
}
@@ -1311,8 +1313,9 @@ CircuitSeq::get_suffix_seq(const std::unordered_set<CircuitGate *> &start_gates,
13111313
void CircuitSeq::clone_from(const CircuitSeq &other,
13121314
const std::vector<int> &qubit_permutation,
13131315
const std::vector<int> &param_permutation,
1314-
const Context *ctx) {
1315-
num_qubits = other.num_qubits;
1316+
const Context *ctx, int result_num_qubits) {
1317+
num_qubits = (result_num_qubits == -1 ? other.num_qubits : result_num_qubits);
1318+
int num_qubits_difference = other.num_qubits - num_qubits;
13161319
original_fingerprint_ = other.original_fingerprint_;
13171320
std::unordered_map<CircuitWire *, CircuitWire *> wires_mapping;
13181321
std::unordered_map<CircuitGate *, CircuitGate *> gates_mapping;
@@ -1337,28 +1340,39 @@ void CircuitSeq::clone_from(const CircuitSeq &other,
13371340
hash_value_valid_ = false;
13381341
}
13391342
if (qubit_permutation.empty()) {
1343+
assert(result_num_qubits == -1);
13401344
for (int i = 0; i < (int)other.wires.size(); i++) {
13411345
wires.emplace_back(std::make_unique<CircuitWire>(*(other.wires[i])));
13421346
assert(wires[i].get() !=
13431347
other.wires[i].get()); // make sure we make a copy
13441348
wires_mapping[other.wires[i].get()] = wires[i].get();
13451349
}
13461350
} else {
1347-
assert(qubit_permutation.size() == num_qubits);
1348-
wires.resize(other.wires.size());
1349-
for (int i = 0; i < num_qubits; i++) {
1350-
assert(qubit_permutation[i] >= 0 && qubit_permutation[i] < num_qubits);
1351+
assert(qubit_permutation.size() == other.num_qubits);
1352+
wires.resize(other.wires.size() - num_qubits_difference);
1353+
for (int i = 0; i < other.num_qubits; i++) {
1354+
if (result_num_qubits == -1) {
1355+
assert(qubit_permutation[i] >= 0 && qubit_permutation[i] < num_qubits);
1356+
} else {
1357+
if (qubit_permutation[i] < 0 || qubit_permutation[i] >= num_qubits) {
1358+
continue;
1359+
}
1360+
}
13511361
wires[qubit_permutation[i]] =
13521362
std::make_unique<CircuitWire>(*(other.wires[i]));
13531363
wires[qubit_permutation[i]]->index =
13541364
qubit_permutation[i]; // update index
13551365
assert(wires[qubit_permutation[i]].get() != other.wires[i].get());
13561366
wires_mapping[other.wires[i].get()] = wires[qubit_permutation[i]].get();
13571367
}
1358-
for (int i = num_qubits; i < (int)other.wires.size(); i++) {
1359-
wires[i] = std::make_unique<CircuitWire>(*(other.wires[i]));
1360-
wires[i]->index = qubit_permutation[wires[i]->index]; // update index
1361-
wires_mapping[other.wires[i].get()] = wires[i].get();
1368+
for (int i = other.num_qubits; i < (int)other.wires.size(); i++) {
1369+
wires[i - num_qubits_difference] =
1370+
std::make_unique<CircuitWire>(*(other.wires[i]));
1371+
// update index
1372+
wires[i - num_qubits_difference]->index =
1373+
qubit_permutation[wires[i - num_qubits_difference]->index];
1374+
wires_mapping[other.wires[i].get()] =
1375+
wires[i - num_qubits_difference].get();
13621376
}
13631377
}
13641378
if (!param_permutation.empty()) {
@@ -1392,8 +1406,19 @@ void CircuitSeq::clone_from(const CircuitSeq &other,
13921406
wire = wires_mapping[wire];
13931407
}
13941408
}
1395-
for (auto &wire : other.outputs) {
1396-
outputs.emplace_back(wires_mapping[wire]);
1409+
if (qubit_permutation.empty()) {
1410+
for (auto &wire : other.outputs) {
1411+
outputs.emplace_back(wires_mapping[wire]);
1412+
}
1413+
} else {
1414+
outputs.resize(num_qubits);
1415+
for (auto &wire : other.outputs) {
1416+
if (wires_mapping.count(wire) > 0) {
1417+
assert(qubit_permutation[wire->index] >= 0 &&
1418+
qubit_permutation[wire->index] < num_qubits);
1419+
outputs[qubit_permutation[wire->index]] = wires_mapping[wire];
1420+
}
1421+
}
13971422
}
13981423
}
13991424

src/quartz/circuitseq/circuitseq.h

+18-5
Original file line numberDiff line numberDiff line change
@@ -272,19 +272,23 @@ class CircuitSeq {
272272
/**
273273
* Permute the qubits and input parameters.
274274
* @param qubit_permutation The qubit permutation. The size must be the
275-
* same as the number of qubits.
275+
* same as the number of qubits. When |result_num_qubits| is not -1,
276+
* all qubits mapped to a number greater than or equal to |result_num_qubits|
277+
* must be unused in any gates in this circuit.
276278
* @param input_param_permutation The input parameter permutation. If the size
277279
* is smaller than the total number of input parameters, this function only
278280
* permutes a prefix of input parameters corresponding to |param_permutation|.
279281
* @param ctx The context, only needed when |input_param_permutation| is not
280282
* empty. When |input_param_permutation| is empty, it is safe to pass in a
281283
* nullptr.
284+
* @param result_num_qubits The number of qubits of the permuted circuit
285+
* sequence. Default is -1, which means to copy from this circuit sequence.
282286
* @return The permuted circuit sequence.
283287
*/
284288
[[nodiscard]] std::unique_ptr<CircuitSeq>
285289
get_permuted_seq(const std::vector<int> &qubit_permutation,
286290
const std::vector<int> &input_param_permutation,
287-
Context *ctx) const;
291+
Context *ctx, int result_num_qubits = -1) const;
288292
/**
289293
* Get a circuit with |start_gates| and all gates topologically after them.
290294
* @param start_gates The first gates at each qubit to include in the
@@ -346,18 +350,25 @@ class CircuitSeq {
346350
* Clone the circuit from another circuit sequence.
347351
* @param other The source circuit sequence.
348352
* @param qubit_permutation The qubit permutation (optional).
349-
* If not empty, the size must be the same as the number of qubits.
353+
* If not empty, the size must be the same as the number of qubits of the
354+
* other circuit sequence. When |result_num_qubits| is not -1, any unmapped
355+
* qubits must be unused (i.e., there should be no gates on that qubit in
356+
* the other circuit sequence). When |result_num_qubits| is -1, this parameter
357+
* must be empty.
350358
* @param param_permutation The parameter permutation (optional).
351359
* If empty, |ctx| is not used and all parameters will be from the same
352360
* context as |other|.
353361
* @param ctx The context, only needed when |param_permutation| is not empty.
354362
* When |param_permutation| is empty, it is safe to pass in a nullptr.
363+
* @param result_num_qubits The number of qubits of the permuted circuit
364+
* sequence. Default is -1, which means to copy from the other (original)
365+
* circuit sequence.
355366
* @return The permuted circuit sequence.
356367
*/
357368
void clone_from(const CircuitSeq &other,
358369
const std::vector<int> &qubit_permutation,
359-
const std::vector<int> &param_permutation,
360-
const Context *ctx);
370+
const std::vector<int> &param_permutation, const Context *ctx,
371+
int result_num_qubits = -1);
361372

362373
/**
363374
* Remove a quantum gate from the graph, remove its output wires by default,
@@ -386,8 +397,10 @@ class CircuitSeq {
386397
std::vector<std::pair<CircuitSeqHashType, PhaseShiftIdType>> *other_hash);
387398

388399
public:
400+
// Invariant: wires[i] is the i-th qubit input
389401
std::vector<std::unique_ptr<CircuitWire>> wires;
390402
std::vector<std::unique_ptr<CircuitGate>> gates;
403+
// Invariant: outputs[i] is the i-th qubit
391404
std::vector<CircuitWire *> outputs;
392405

393406
private:

src/quartz/verifier/verifier.cpp

+22-1
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,26 @@ bool Verifier::equivalent(Context *ctx, const CircuitSeq *circuit1,
192192
}
193193
}
194194

195+
// Remove qubits with no gates to avoid creating 2^num_qubits variables in the
196+
// verifier.
197+
std::vector<int> qubit_permutation(num_qubits, -1);
198+
int remaining_qubits = 0;
199+
for (int i = 0; i < num_qubits; i++) {
200+
if (c1->outputs[i] != c1->wires[i].get() ||
201+
c2->outputs[i] != c2->wires[i].get()) {
202+
// qubit used in at least one of the two circuits
203+
qubit_permutation[i] = remaining_qubits++;
204+
}
205+
}
206+
if (remaining_qubits < num_qubits) {
207+
if (verbose) {
208+
std::cout << "Reducing the number of qubits from " << num_qubits << " to "
209+
<< remaining_qubits << std::endl;
210+
}
211+
c1 = c1->get_permuted_seq(qubit_permutation, {}, ctx, remaining_qubits);
212+
c2 = c2->get_permuted_seq(qubit_permutation, {}, ctx, remaining_qubits);
213+
}
214+
195215
if (verbose) {
196216
std::cout << "Checking Verifier::equivalent() on:" << std::endl;
197217
std::cout << c1->to_string(/*line_number=*/true) << std::endl;
@@ -216,7 +236,8 @@ bool Verifier::equivalent(Context *ctx, const CircuitSeq *circuit1,
216236
std::string("python ") + kQuartzRootPath.string() +
217237
"/src/python/verifier/verify_equivalences.py " +
218238
kQuartzRootPath.string() + "/tmp_before_verify.json " +
219-
kQuartzRootPath.string() + "/tmp_after_verify.json";
239+
kQuartzRootPath.string() + "/tmp_after_verify.json" +
240+
(verbose ? " True True" : "");
220241
system(command_string.c_str());
221242
EquivalenceSet equiv_set;
222243
ret = equiv_set.load_json(ctx,

0 commit comments

Comments
 (0)