diff --git a/src/quartz/circuitseq/circuitseq.cpp b/src/quartz/circuitseq/circuitseq.cpp index 8f920f88..b14a25a4 100644 --- a/src/quartz/circuitseq/circuitseq.cpp +++ b/src/quartz/circuitseq/circuitseq.cpp @@ -1286,23 +1286,34 @@ CircuitSeq::get_permuted_seq(const std::vector &qubit_permutation, } std::unique_ptr -CircuitSeq::get_suffix_seq(const std::unordered_set &start_gates, +CircuitSeq::get_suffix_seq(const std::vector &frontier, Context *ctx) const { - // Note: we should not do a topological sort because it is possible that - // |start_gates| does not touch all qubits. - // For example, start gates can be T(Q1) when the circuit is - // T(Q1) T(Q3) CX(Q1, Q3) CX(Q0, Q2) CX(Q0, Q1), - // in which case the suffix sequence is T(Q1) CX(Q1, Q3) CX(Q0, Q1). - std::unordered_set visited_gates = start_gates; + // Topological sort from the frontier. + std::queue gates_to_search; + std::unordered_map gate_remaining_in_degree; + for (auto &wire : frontier) { + for (auto &gate : wire->output_gates) { + if (gate_remaining_in_degree.count(gate) == 0) { + gate_remaining_in_degree[gate] = gate->gate->get_num_qubits(); + } + if (!--gate_remaining_in_degree[gate]) { + gates_to_search.push(gate); + } + } + } auto result = std::make_unique(get_num_qubits()); - // The result should be a subsequence of this circuit. - // Note that |gates| is a topological order - for (auto &gate : gates) { - if (visited_gates.count(gate.get()) > 0) { - result->add_gate(gate.get(), ctx); - for (auto &output_wire : gate->output_wires) { - for (auto &output_gate : output_wire->output_gates) { - visited_gates.insert(output_gate); + while (!gates_to_search.empty()) { + CircuitGate *gate = gates_to_search.front(); + gates_to_search.pop(); + result->add_gate(gate, ctx); + for (auto &output_wire : gate->output_wires) { + for (auto &output_gate : output_wire->output_gates) { + if (gate_remaining_in_degree.count(output_gate) == 0) { + gate_remaining_in_degree[output_gate] = + output_gate->gate->get_num_qubits(); + } + if (!--gate_remaining_in_degree[output_gate]) { + gates_to_search.push(output_gate); } } } diff --git a/src/quartz/circuitseq/circuitseq.h b/src/quartz/circuitseq/circuitseq.h index e75a418f..c8516bce 100644 --- a/src/quartz/circuitseq/circuitseq.h +++ b/src/quartz/circuitseq/circuitseq.h @@ -290,12 +290,13 @@ class CircuitSeq { const std::vector &input_param_permutation, Context *ctx, int result_num_qubits = -1) const; /** - * Get a circuit with |start_gates| and all gates topologically after them. - * @param start_gates The first gates at each qubit to include in the - * circuit to return. + * Get a circuit with all gates after a frontier. + * @param frontier A vector of size the same as the number of qubits, storing + * the frontier of wires for each qubit. + * @return The suffix circuit sequence. */ [[nodiscard]] std::unique_ptr - get_suffix_seq(const std::unordered_set &start_gates, + get_suffix_seq(const std::vector &frontier, Context *ctx) const; /** diff --git a/src/quartz/verifier/verifier.cpp b/src/quartz/verifier/verifier.cpp index 018e501e..eecf8e7b 100644 --- a/src/quartz/verifier/verifier.cpp +++ b/src/quartz/verifier/verifier.cpp @@ -63,8 +63,12 @@ bool Verifier::equivalent(Context *ctx, const CircuitSeq *circuit1, } // Try to remove common first gates and record the frontier std::vector qubit_blocked(num_qubits, false); - std::unordered_set leftover_gates_start1; - std::unordered_set leftover_gates_start2; + std::vector frontier1(num_qubits); + std::vector frontier2(num_qubits); + for (int i = 0; i < num_qubits; i++) { + frontier1[i] = circuit1->wires[i].get(); + frontier2[i] = circuit2->wires[i].get(); + } // (Partial) topological sort on circuit1 while (!wires_to_search.empty()) { CircuitWire *wire1 = wires_to_search.front(); @@ -81,29 +85,22 @@ bool Verifier::equivalent(Context *ctx, const CircuitSeq *circuit1, })) { // Block qubits of potential unmatched gates. for (auto &gate1 : wire1->output_gates) { + if (verbose) { + std::cout << "gate1 blocked: " << gate1->to_string() << std::endl; + } // Use a for loop because |wire1->output_gates| can be empty. for (auto &input_wire : gate1->input_wires) { qubit_blocked[input_wire->index] = true; - // If |wires_mapping| does not have |input_wire|, this means that - // the qubit is already blocked before, and |leftover_gates_start2| - // already had an earlier gate at this qubit. - if (wires_mapping.count(input_wire) > 0) { - // Note that this input wire might be not in the search queue now. - // We need to manually add the gate in circuit2 to the frontier. - for (auto &gate : wires_mapping[input_wire]->output_gates) { - leftover_gates_start2.insert(gate); - } - } } - // Use std::unordered_set to deduplicate, similarly hereinafter - leftover_gates_start1.insert(gate1); } for (auto &gate2 : wire2->output_gates) { + if (verbose) { + std::cout << "gate2 blocked: " << gate2->to_string() << std::endl; + } // Use a for loop because |wire2->output_gates| can be empty. for (auto &output_wire : gate2->output_wires) { qubit_blocked[output_wire->index] = true; } - leftover_gates_start2.insert(gate2); } continue; } @@ -120,53 +117,66 @@ bool Verifier::equivalent(Context *ctx, const CircuitSeq *circuit1, // CircuitGate::equivalent()) if (!CircuitGate::equivalent(gate1, gate2, wires_mapping, /*update_mapping=*/true, &wires_to_search)) { + if (verbose) { + std::cout << "gate1 and gate2 not equivalent: " << gate1->to_string() + << " " << gate2->to_string() << std::endl; + } // If not matched, block each qubit of gate1. // Note that we should not block qubits of gate2 because it's not // on the frontier. for (auto &input_wire : gate1->input_wires) { if (input_wire->is_qubit()) { qubit_blocked[input_wire->index] = true; - // Note that this input wire might be not in the search queue now. - // We need to manually add the gate in circuit2 to the frontier. - assert(wires_mapping.count(input_wire) > 0); - for (auto &gate : wires_mapping[input_wire]->output_gates) { - leftover_gates_start2.insert(gate); - } } } - leftover_gates_start1.insert(gate1); - leftover_gates_start2.insert(gate2); + } else { + for (auto &output_wire : gate1->output_wires) { + frontier1[output_wire->index] = output_wire; + } + for (auto &output_wire : gate2->output_wires) { + frontier2[output_wire->index] = output_wire; + } } } } - if (leftover_gates_start1.empty() && leftover_gates_start2.empty()) { - // The two circuits are equivalent. - if (verbose) { - std::cout << "Equivalent: same circuit." << std::endl; - } - return true; - } - - auto c1 = circuit1->get_suffix_seq(leftover_gates_start1, ctx); - auto c2 = circuit2->get_suffix_seq(leftover_gates_start2, ctx); - // We should have removed the same number of gates - assert(circuit1->get_num_gates() - c1->get_num_gates() == - circuit2->get_num_gates() - c2->get_num_gates()); + auto c1 = circuit1->get_suffix_seq(frontier1, ctx); + auto c2 = circuit2->get_suffix_seq(frontier2, ctx); if (verbose) { std::cout << "Removed " << circuit1->get_num_gates() - c1->get_num_gates() << " prefix gates from circuit1 and " << circuit2->get_num_gates() - c2->get_num_gates() << " prefix gates from circuit2." << std::endl; - std::cout << "Leftover gates from circuit1:" << std::endl; - for (auto &gate : leftover_gates_start1) { - std::cout << gate->to_string() << std::endl; + std::cout << "Frontier of circuit1:" << std::endl; + for (auto &wire : frontier1) { + std::cout << "Q" << wire->index << ": "; + if (wire->output_gates.size() == 1) { + std::cout << wire->output_gates[0]->to_string() << std::endl; + } else { + std::cout << "(end)" << std::endl; + } } - std::cout << "Leftover gates from circuit2:" << std::endl; - for (auto &gate : leftover_gates_start2) { - std::cout << gate->to_string() << std::endl; + std::cout << "Frontier of circuit2:" << std::endl; + for (auto &wire : frontier2) { + std::cout << "Q" << wire->index << ": "; + if (wire->output_gates.size() == 1) { + std::cout << wire->output_gates[0]->to_string() << std::endl; + } else { + std::cout << "(end)" << std::endl; + } } } + // We should have removed the same number of gates + assert(circuit1->get_num_gates() - c1->get_num_gates() == + circuit2->get_num_gates() - c2->get_num_gates()); + + if (c1->get_num_gates() == 0 && c2->get_num_gates() == 0) { + // The two circuits are equivalent. + if (verbose) { + std::cout << "Equivalent: same circuit." << std::endl; + } + return true; + } // Remove common last gates while (true) {