From 6460deb7e6fb9ec23372ffa0a582ae6ae83a4ccc Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Tue, 4 Jan 2022 15:59:08 +0100 Subject: [PATCH 1/2] Update fusers. --- lib/fuser_basic.h | 67 +++++++++++++++++------------ lib/fuser_mqubit.h | 87 ++++++++++++++++++++++---------------- tests/fuser_basic_test.cc | 35 +++++++++++++++ tests/fuser_mqubit_test.cc | 35 +++++++++++++++ 4 files changed, 161 insertions(+), 63 deletions(-) diff --git a/lib/fuser_basic.h b/lib/fuser_basic.h index abc006e8..39748bba 100644 --- a/lib/fuser_basic.h +++ b/lib/fuser_basic.h @@ -56,7 +56,7 @@ class BasicGateFuser final : public Fuser { * two-qubit gates will get fused. To respect specific time boundaries while * fusing gates, use the other version of this method below. * @param param Options for gate fusion. - * @param num_qubits The number of qubits acted on by 'gates'. + * @param max_qubit1 The maximum qubit index (plus one) acted on by 'gates'. * @param gates The gates (or pointers to the gates) to be fused. * Gate times of the gates that act on the same qubits should be ordered. * Gates that are out of time order should not cross the time boundaries @@ -66,18 +66,18 @@ class BasicGateFuser final : public Fuser { * acting on a specific pair of qubits which can be applied as a group. */ static std::vector FuseGates(const Parameter& param, - unsigned num_qubits, + unsigned max_qubit1, const std::vector& gates, bool fuse_matrix = true) { return FuseGates( - param, num_qubits, gates.cbegin(), gates.cend(), {}, fuse_matrix); + param, max_qubit1, gates.cbegin(), gates.cend(), {}, fuse_matrix); } /** * Stores sets of gates that can be applied together. Only one- and * two-qubit gates will get fused. * @param param Options for gate fusion. - * @param num_qubits The number of qubits acted on by 'gates'. + * @param max_qubit1 The maximum qubit index (plus one) acted on by 'gates'. * @param gates The gates (or pointers to the gates) to be fused. * Gate times of the gates that act on the same qubits should be ordered. * Gates that are out of time order should not cross the time boundaries @@ -91,10 +91,10 @@ class BasicGateFuser final : public Fuser { */ static std::vector FuseGates( const Parameter& param, - unsigned num_qubits, const std::vector& gates, + unsigned max_qubit1, const std::vector& gates, const std::vector& times_to_split_at, bool fuse_matrix = true) { - return FuseGates(param, num_qubits, gates.cbegin(), gates.cend(), + return FuseGates(param, max_qubit1, gates.cbegin(), gates.cend(), times_to_split_at, fuse_matrix); } @@ -103,7 +103,7 @@ class BasicGateFuser final : public Fuser { * two-qubit gates will get fused. To respect specific time boundaries while * fusing gates, use the other version of this method below. * @param param Options for gate fusion. - * @param num_qubits The number of qubits acted on by gates. + * @param max_qubit1 The maximum qubit index (plus one) acted on by 'gates'. * @param gfirst, glast The iterator range [gfirst, glast) to fuse gates * (or pointers to gates) in. Gate times of the gates that act on the same * qubits should be ordered. Gates that are out of time order should not @@ -113,18 +113,18 @@ class BasicGateFuser final : public Fuser { * acting on a specific pair of qubits which can be applied as a group. */ static std::vector FuseGates( - const Parameter& param, unsigned num_qubits, + const Parameter& param, unsigned max_qubit1, typename std::vector::const_iterator gfirst, typename std::vector::const_iterator glast, bool fuse_matrix = true) { - return FuseGates(param, num_qubits, gfirst, glast, {}, fuse_matrix); + return FuseGates(param, max_qubit1, gfirst, glast, {}, fuse_matrix); } /** * Stores sets of gates that can be applied together. Only one- and * two-qubit gates will get fused. * @param param Options for gate fusion. - * @param num_qubits The number of qubits acted on by gates. + * @param max_qubit1 The maximum qubit index (plus one) acted on by 'gates'. * @param gfirst, glast The iterator range [gfirst, glast) to fuse gates * (or pointers to gates) in. Gate times of the gates that act on the same * qubits should be ordered. Gates that are out of time order should not @@ -138,7 +138,7 @@ class BasicGateFuser final : public Fuser { * acting on a specific pair of qubits which can be applied as a group. */ static std::vector FuseGates( - const Parameter& param, unsigned num_qubits, + const Parameter& param, unsigned max_qubit1, typename std::vector::const_iterator gfirst, typename std::vector::const_iterator glast, const std::vector& times_to_split_at, @@ -162,7 +162,7 @@ class BasicGateFuser final : public Fuser { std::vector gates_seq; // Lattice of gates: qubits "hyperplane" and time direction. - std::vector> gates_lat(num_qubits); + std::vector> gates_lat(max_qubit1); // Current unfused gate. auto gate_it = gfirst; @@ -171,7 +171,7 @@ class BasicGateFuser final : public Fuser { gates_seq.resize(0); gates_seq.reserve(num_gates); - for (unsigned k = 0; k < num_qubits; ++k) { + for (unsigned k = 0; k < max_qubit1; ++k) { gates_lat[k].resize(0); gates_lat[k].reserve(128); } @@ -182,9 +182,7 @@ class BasicGateFuser final : public Fuser { if (gate.time > times[l]) break; - if (GateIsOutOfOrder(gate.time, gate.qubits, gates_lat) - || GateIsOutOfOrder(gate.time, gate.controlled_by, gates_lat)) { - IO::errorf("gate is out of time order.\n"); + if (!ValidateGate(gate, max_qubit1, gates_lat)) { gates_fused.resize(0); return gates_fused; } @@ -193,7 +191,7 @@ class BasicGateFuser final : public Fuser { auto& mea_gates_at_time = measurement_gates[gate.time]; if (mea_gates_at_time.size() == 0) { gates_seq.push_back(&gate); - mea_gates_at_time.reserve(num_qubits); + mea_gates_at_time.reserve(max_qubit1); } mea_gates_at_time.push_back(&gate); @@ -217,7 +215,7 @@ class BasicGateFuser final : public Fuser { } } - std::vector last(num_qubits, 0); + std::vector last(max_qubit1, 0); const RGate* delayed_measurement_gate = nullptr; @@ -281,7 +279,7 @@ class BasicGateFuser final : public Fuser { } } - for (unsigned q = 0; q < num_qubits; ++q) { + for (unsigned q = 0; q < max_qubit1; ++q) { auto l = last[q]; if (l == gates_lat[q].size()) continue; @@ -360,17 +358,32 @@ class BasicGateFuser final : public Fuser { return k; } - template - static bool GateIsOutOfOrder(unsigned time, - const std::vector& qubits, - const GatesLat& gates_lat) { - for (unsigned q : qubits) { - if (!gates_lat[q].empty() && time <= gates_lat[q].back()->time) { - return true; + template + static bool ValidateGate(const Gate2& gate, unsigned max_qubit1, + const GatesLat& gates_lat) { + for (unsigned q : gate.qubits) { + if (q >= max_qubit1) { + IO::errorf("gate qubits are out of range.\n"); + return false; + } + if (!gates_lat[q].empty() && gate.time <= gates_lat[q].back()->time) { + IO::errorf("gate is out of time order.\n"); + return false; + } + } + + for (unsigned q : gate.controlled_by) { + if (q >= max_qubit1) { + IO::errorf("gate qubits are out of range.\n"); + return false; + } + if (!gates_lat[q].empty() && gate.time <= gates_lat[q].back()->time) { + IO::errorf("gate is out of time order.\n"); + return false; } } - return false; + return true; } }; diff --git a/lib/fuser_mqubit.h b/lib/fuser_mqubit.h index 08a40d11..31415653 100644 --- a/lib/fuser_mqubit.h +++ b/lib/fuser_mqubit.h @@ -156,7 +156,7 @@ class MultiQubitGateFuser final : public Fuser { * time boundaries while fusing gates, use the other version of this method * below. * @param param Options for gate fusion. - * @param num_qubits The number of qubits acted on by 'gates'. + * @param max_qubit1 The maximum qubit index (plus one) acted on by 'gates'. * @param gates The gates (or pointers to the gates) to be fused. * Gate times of the gates that act on the same qubits should be ordered. * Gates that are out of time order should not cross the time boundaries @@ -166,17 +166,17 @@ class MultiQubitGateFuser final : public Fuser { * acting on a specific pair of qubits which can be applied as a group. */ static std::vector FuseGates(const Parameter& param, - unsigned num_qubits, + unsigned max_qubit1, const std::vector& gates, bool fuse_matrix = true) { return FuseGates( - param, num_qubits, gates.cbegin(), gates.cend(), {}, fuse_matrix); + param, max_qubit1, gates.cbegin(), gates.cend(), {}, fuse_matrix); } /** * Stores sets of gates that can be applied together. * @param param Options for gate fusion. - * @param num_qubits The number of qubits acted on by 'gates'. + * @param max_qubit1 The maximum qubit index (plus one) acted on by 'gates'. * @param gates The gates (or pointers to the gates) to be fused. * Gate times of the gates that act on the same qubits should be ordered. * Gates that are out of time order should not cross the time boundaries @@ -190,10 +190,10 @@ class MultiQubitGateFuser final : public Fuser { */ static std::vector FuseGates( const Parameter& param, - unsigned num_qubits, const std::vector& gates, + unsigned max_qubit1, const std::vector& gates, const std::vector& times_to_split_at, bool fuse_matrix = true) { - return FuseGates(param, num_qubits, gates.cbegin(), gates.cend(), + return FuseGates(param, max_qubit1, gates.cbegin(), gates.cend(), times_to_split_at, fuse_matrix); } @@ -202,7 +202,7 @@ class MultiQubitGateFuser final : public Fuser { * time boundaries while fusing gates, use the other version of this method * below. * @param param Options for gate fusion. - * @param num_qubits The number of qubits acted on by gates. + * @param max_qubit1 The maximum qubit index (plus one) acted on by 'gates'. * @param gfirst, glast The iterator range [gfirst, glast) to fuse gates * (or pointers to gates) in. Gate times of the gates that act on the same * qubits should be ordered. Gates that are out of time order should not @@ -212,17 +212,17 @@ class MultiQubitGateFuser final : public Fuser { * acting on a specific pair of qubits which can be applied as a group. */ static std::vector FuseGates( - const Parameter& param, unsigned num_qubits, + const Parameter& param, unsigned max_qubit1, typename std::vector::const_iterator gfirst, typename std::vector::const_iterator glast, bool fuse_matrix = true) { - return FuseGates(param, num_qubits, gfirst, glast, {}, fuse_matrix); + return FuseGates(param, max_qubit1, gfirst, glast, {}, fuse_matrix); } /** * Stores sets of gates that can be applied together. * @param param Options for gate fusion. - * @param num_qubits The number of qubits acted on by gates. + * @param max_qubit1 The maximum qubit index (plus one) acted on by 'gates'. * @param gfirst, glast The iterator range [gfirst, glast) to fuse gates * (or pointers to gates) in. Gate times of the gates that act on the same * qubits should be ordered. Gates that are out of time order should not @@ -236,7 +236,7 @@ class MultiQubitGateFuser final : public Fuser { * acting on a specific pair of qubits which can be applied as a group. */ static std::vector FuseGates( - const Parameter& param, unsigned num_qubits, + const Parameter& param, unsigned max_qubit1, typename std::vector::const_iterator gfirst, typename std::vector::const_iterator glast, const std::vector& times_to_split_at, @@ -253,7 +253,7 @@ class MultiQubitGateFuser final : public Fuser { auto epochs = Base::MergeWithMeasurementTimes(gfirst, glast, times_to_split_at); - LinkManager link_manager(num_qubits * num_gates); + LinkManager link_manager(max_qubit1 * num_gates); // Auxillary data structures. // Sequence of intermediate fused gates. @@ -261,10 +261,10 @@ class MultiQubitGateFuser final : public Fuser { // Gate "lattice". std::vector gates_lat; // Sequences of intermediate fused gates ordered by gate size. - std::vector> fgates(num_qubits + 1); + std::vector> fgates(max_qubit1 + 1); gates_seq.reserve(num_gates); - gates_lat.reserve(num_qubits); + gates_lat.reserve(max_qubit1); Scratch scratch; @@ -277,10 +277,10 @@ class MultiQubitGateFuser final : public Fuser { scratch.stack.reserve(8); Stat stat; - stat.num_gates.resize(num_qubits + 1, 0); + stat.num_gates.resize(max_qubit1 + 1, 0); unsigned max_fused_size = std::min(unsigned{6}, param.max_fused_size); - max_fused_size = std::min(max_fused_size, num_qubits); + max_fused_size = std::min(max_fused_size, max_qubit1); auto gate_it = gfirst; @@ -288,9 +288,9 @@ class MultiQubitGateFuser final : public Fuser { for (std::size_t l = 0; l < epochs.size(); ++l) { gates_seq.resize(0); gates_lat.resize(0); - gates_lat.resize(num_qubits, nullptr); + gates_lat.resize(max_qubit1, nullptr); - for (unsigned i = 0; i <= num_qubits; ++i) { + for (unsigned i = 0; i <= max_qubit1; ++i) { fgates[i].resize(0); } @@ -303,9 +303,7 @@ class MultiQubitGateFuser final : public Fuser { if (gate.time > epochs[l]) break; - if (GateIsOutOfOrder(gate.time, gate.qubits, gates_lat) - || GateIsOutOfOrder(gate.time, gate.controlled_by, gates_lat)) { - IO::errorf("gate is out of time order.\n"); + if (!ValidateGate(gate, max_qubit1, gates_lat)) { fused_gates.resize(0); return fused_gates; } @@ -320,8 +318,8 @@ class MultiQubitGateFuser final : public Fuser { gates_seq.push_back({&gate, {}, {}, {}, 0, kMeaCnt}); last_mea_gate = &gates_seq.back(); - last_mea_gate->qubits.reserve(num_qubits); - last_mea_gate->links.reserve(num_qubits); + last_mea_gate->qubits.reserve(max_qubit1); + last_mea_gate->links.reserve(max_qubit1); ++stat.num_fused_mea_gates; } @@ -394,12 +392,12 @@ class MultiQubitGateFuser final : public Fuser { if (max_fused_size > 2) { FuseGateSequences( - max_fused_size, num_qubits, scratch, gates_seq, stat, fused_gates); + max_fused_size, max_qubit1, scratch, gates_seq, stat, fused_gates); } else { unsigned prev_time = 0; std::vector orphaned_gates; - orphaned_gates.reserve(num_qubits); + orphaned_gates.reserve(max_qubit1); for (auto& fgate : gates_seq) { if (fgate.gates.size() == 0) continue; @@ -484,13 +482,13 @@ class MultiQubitGateFuser final : public Fuser { // // max_fused_size = 6: _-_-_ static void FuseGateSequences(unsigned max_fused_size, - unsigned num_qubits, Scratch& scratch, + unsigned max_qubit1, Scratch& scratch, std::vector& gates_seq, Stat& stat, std::vector& fused_gates) { unsigned prev_time = 0; std::vector orphaned_gates; - orphaned_gates.reserve(num_qubits); + orphaned_gates.reserve(max_qubit1); for (auto& fgate : gates_seq) { if (prev_time != fgate.parent->time) { @@ -1031,7 +1029,7 @@ class MultiQubitGateFuser final : public Fuser { if (verbosity < 5) return; IO::messagef("fused gate qubits:\n"); - for (const auto g : fused_gates) { + for (const auto& g : fused_gates) { IO::messagef("%6u ", g.parent->time); if (g.parent->kind == gate::kMeasurement) { IO::messagef("m"); @@ -1052,17 +1050,34 @@ class MultiQubitGateFuser final : public Fuser { } } - template - static bool GateIsOutOfOrder(unsigned time, - const std::vector& qubits, - const GatesLat& gates_lat) { - for (unsigned q : qubits) { - if (gates_lat[q] != nullptr && time <= gates_lat[q]->val->parent->time) { - return true; + template + static bool ValidateGate(const Gate2& gate, unsigned max_qubit1, + const GatesLat& gates_lat) { + for (unsigned q : gate.qubits) { + if (q >= max_qubit1) { + IO::errorf("gate qubits are out of range.\n"); + return false; + } + if (gates_lat[q] != nullptr + && gate.time <= gates_lat[q]->val->parent->time) { + IO::errorf("gate is out of time order.\n"); + return false; + } + } + + for (unsigned q : gate.controlled_by) { + if (q >= max_qubit1) { + IO::errorf("gate qubits are out of range.\n"); + return false; + } + if (gates_lat[q] != nullptr + && gate.time <= gates_lat[q]->val->parent->time) { + IO::errorf("gate is out of time order.\n"); + return false; } } - return false; + return true; } }; diff --git a/tests/fuser_basic_test.cc b/tests/fuser_basic_test.cc index 250d9f30..1eb85c28 100644 --- a/tests/fuser_basic_test.cc +++ b/tests/fuser_basic_test.cc @@ -1438,6 +1438,41 @@ TEST(FuserBasicTest, InvalidTimeOrder) { } } +TEST(FuserBasicTest, QubitsOutOfRange) { + using Gate = GateQSim; + using Fuser = BasicGateFuser; + + Fuser::Parameter param; + param.verbosity = 0; + + { + unsigned num_qubits = 3; + std::vector circuit = { + GateCZ::Create(0, 0, 3), + GateCZ::Create(0, 1, 2), + }; + + auto fused_gates = Fuser::FuseGates( + param, num_qubits, circuit.begin(), circuit.end(), false); + + EXPECT_EQ(fused_gates.size(), 0); + } + + { + unsigned num_qubits = 3; + auto gate = GateZ::Create(0, 2); + std::vector circuit = { + GateCZ::Create(0, 0, 1), + MakeControlledGate({3}, gate), + }; + + auto fused_gates = Fuser::FuseGates( + param, num_qubits, circuit.begin(), circuit.end(), false); + + EXPECT_EQ(fused_gates.size(), 0); + } +} + } // namespace qsim int main(int argc, char** argv) { diff --git a/tests/fuser_mqubit_test.cc b/tests/fuser_mqubit_test.cc index c41c6549..cf49b73b 100644 --- a/tests/fuser_mqubit_test.cc +++ b/tests/fuser_mqubit_test.cc @@ -982,6 +982,41 @@ TEST(FuserMultiQubitTest, InvalidTimeOrder) { } } +TEST(FuserMultiQubitTest, QubitsOutOfRange) { + using Fuser = MultiQubitGateFuser; + + Fuser::Parameter param; + param.verbosity = 0; + + { + unsigned num_qubits = 3; + std::vector circuit = { + CreateDummyGate(0, {0, 3}), + CreateDummyGate(0, {1, 2}), + }; + + param.max_fused_size = 2; + auto fused_gates = Fuser::FuseGates( + param, num_qubits, circuit.begin(), circuit.end(), false); + + EXPECT_EQ(fused_gates.size(), 0); + } + + { + unsigned num_qubits = 3; + std::vector circuit = { + CreateDummyGate(0, {0, 1}), + CreateDummyControlledGate(0, {2}, {3}), + }; + + param.max_fused_size = 2; + auto fused_gates = Fuser::FuseGates( + param, num_qubits, circuit.begin(), circuit.end(), false); + + EXPECT_EQ(fused_gates.size(), 0); + } +} + TEST(FuserMultiQubitTest, OrphanedGates) { using Fuser = MultiQubitGateFuser; From 2b57cab96c1e5b415e68068169f12111c82a27e1 Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Tue, 4 Jan 2022 19:19:37 +0100 Subject: [PATCH 2/2] Recommended fixes. --- lib/fuser_basic.h | 10 ++++++---- lib/fuser_mqubit.h | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/fuser_basic.h b/lib/fuser_basic.h index 39748bba..2fafb14a 100644 --- a/lib/fuser_basic.h +++ b/lib/fuser_basic.h @@ -363,22 +363,24 @@ class BasicGateFuser final : public Fuser { const GatesLat& gates_lat) { for (unsigned q : gate.qubits) { if (q >= max_qubit1) { - IO::errorf("gate qubits are out of range.\n"); + IO::errorf("fuser: gate qubit %u is out of range " + "(should be smaller than %u).\n", q, max_qubit1); return false; } if (!gates_lat[q].empty() && gate.time <= gates_lat[q].back()->time) { - IO::errorf("gate is out of time order.\n"); + IO::errorf("fuser: gate at time %u is out of time order.\n", gate.time); return false; } } for (unsigned q : gate.controlled_by) { if (q >= max_qubit1) { - IO::errorf("gate qubits are out of range.\n"); + IO::errorf("fuser: gate qubit %u is out of range " + "(should be smaller than %u).\n", q, max_qubit1); return false; } if (!gates_lat[q].empty() && gate.time <= gates_lat[q].back()->time) { - IO::errorf("gate is out of time order.\n"); + IO::errorf("fuser: gate at time %u is out of time order.\n", gate.time); return false; } } diff --git a/lib/fuser_mqubit.h b/lib/fuser_mqubit.h index 31415653..4ab4f100 100644 --- a/lib/fuser_mqubit.h +++ b/lib/fuser_mqubit.h @@ -1055,24 +1055,26 @@ class MultiQubitGateFuser final : public Fuser { const GatesLat& gates_lat) { for (unsigned q : gate.qubits) { if (q >= max_qubit1) { - IO::errorf("gate qubits are out of range.\n"); + IO::errorf("fuser: gate qubit %u is out of range " + "(should be smaller than %u).\n", q, max_qubit1); return false; } if (gates_lat[q] != nullptr && gate.time <= gates_lat[q]->val->parent->time) { - IO::errorf("gate is out of time order.\n"); + IO::errorf("fuser: gate at time %u is out of time order.\n", gate.time); return false; } } for (unsigned q : gate.controlled_by) { if (q >= max_qubit1) { - IO::errorf("gate qubits are out of range.\n"); + IO::errorf("fuser: gate qubit %u is out of range " + "(should be smaller than %u).\n", q, max_qubit1); return false; } if (gates_lat[q] != nullptr && gate.time <= gates_lat[q]->val->parent->time) { - IO::errorf("gate is out of time order.\n"); + IO::errorf("fuser: gate at time %u is out of time order.\n", gate.time); return false; } }