From fea069754ddf548edb784fea12d00d214b508b98 Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Tue, 24 Nov 2020 20:43:07 +0100 Subject: [PATCH 1/4] Add multi-qubit fuser. --- apps/qsim_amplitudes.cc | 16 +- apps/qsim_base.cc | 16 +- apps/qsim_von_neumann.cc | 17 +- docs/usage.md | 30 +- lib/BUILD | 14 +- lib/fuser_mqubit.h | 1087 ++++++++++++++++++++++++++++++++++++ tests/BUILD | 15 + tests/fuser_mqubit_test.cc | 295 ++++++++++ tests/make.sh | 3 + 9 files changed, 1465 insertions(+), 28 deletions(-) create mode 100644 lib/fuser_mqubit.h create mode 100644 tests/fuser_mqubit_test.cc diff --git a/apps/qsim_amplitudes.cc b/apps/qsim_amplitudes.cc index ef8f6142..f794d086 100644 --- a/apps/qsim_amplitudes.cc +++ b/apps/qsim_amplitudes.cc @@ -24,7 +24,7 @@ #include "../lib/bitstring.h" #include "../lib/circuit_qsim_parser.h" #include "../lib/formux.h" -#include "../lib/fuser_basic.h" +#include "../lib/fuser_mqubit.h" #include "../lib/gates_qsim.h" #include "../lib/io_file.h" #include "../lib/run_qsim.h" @@ -34,7 +34,7 @@ constexpr char usage[] = "usage:\n ./qsim_amplitudes -c circuit_file " "-d times_to_save_results -i input_files " "-o output_files -s seed -t num_threads " - "-v verbosity\n"; + "-f max_fused_size -v verbosity\n"; struct Options { std::string circuit_file; @@ -43,6 +43,7 @@ struct Options { std::vector output_files; unsigned seed = 1; unsigned num_threads = 1; + unsigned max_fused_size = 2; unsigned verbosity = 0; }; @@ -55,7 +56,7 @@ Options GetOptions(int argc, char* argv[]) { return std::atoi(word.c_str()); }; - while ((k = getopt(argc, argv, "c:d:i:s:o:t:v:")) != -1) { + while ((k = getopt(argc, argv, "c:d:i:s:o:t:f:v:")) != -1) { switch (k) { case 'c': opt.circuit_file = optarg; @@ -75,6 +76,9 @@ Options GetOptions(int argc, char* argv[]) { case 't': opt.num_threads = std::atoi(optarg); break; + case 'f': + opt.max_fused_size = std::atoi(optarg); + break; case 'v': opt.verbosity = std::atoi(optarg); break; @@ -161,6 +165,8 @@ int main(int argc, char* argv[]) { using Simulator = qsim::Simulator; using StateSpace = Simulator::StateSpace; using State = StateSpace::State; + using Fuser = MultiQubitGateFuser>; + using Runner = QSimRunner; auto measure = [&opt, &circuit]( unsigned k, const StateSpace& state_space, const State& state) { @@ -172,9 +178,9 @@ int main(int argc, char* argv[]) { } }; - using Runner = QSimRunner>, Simulator>; - Runner::Parameter param; + param.max_fused_size = opt.max_fused_size; + param.fuser_verbosity = opt.verbosity; param.seed = opt.seed; param.num_threads = opt.num_threads; param.verbosity = opt.verbosity; diff --git a/apps/qsim_base.cc b/apps/qsim_base.cc index 0a85ec07..dbf99297 100644 --- a/apps/qsim_base.cc +++ b/apps/qsim_base.cc @@ -21,7 +21,7 @@ #include "../lib/circuit_qsim_parser.h" #include "../lib/formux.h" -#include "../lib/fuser_basic.h" +#include "../lib/fuser_mqubit.h" #include "../lib/gates_qsim.h" #include "../lib/io_file.h" #include "../lib/run_qsim.h" @@ -32,18 +32,20 @@ struct Options { unsigned maxtime = std::numeric_limits::max(); unsigned seed = 1; unsigned num_threads = 1; + unsigned max_fused_size = 2; unsigned verbosity = 0; }; Options GetOptions(int argc, char* argv[]) { constexpr char usage[] = "usage:\n ./qsim_base -c circuit -d maxtime " - "-s seed -t threads -v verbosity\n"; + "-s seed -t threads -f max_fused_size " + "-v verbosity\n"; Options opt; int k; - while ((k = getopt(argc, argv, "c:d:s:t:v:")) != -1) { + while ((k = getopt(argc, argv, "c:d:s:t:f:v:")) != -1) { switch (k) { case 'c': opt.circuit_file = optarg; @@ -57,6 +59,9 @@ Options GetOptions(int argc, char* argv[]) { case 't': opt.num_threads = std::atoi(optarg); break; + case 'f': + opt.max_fused_size = std::atoi(optarg); + break; case 'v': opt.verbosity = std::atoi(optarg); break; @@ -112,7 +117,8 @@ int main(int argc, char* argv[]) { using Simulator = qsim::Simulator; using StateSpace = Simulator::StateSpace; using State = StateSpace::State; - using Runner = QSimRunner>, Simulator>; + using Fuser = MultiQubitGateFuser>; + using Runner = QSimRunner; StateSpace state_space(opt.num_threads); State state = state_space.Create(circuit.num_qubits); @@ -125,6 +131,8 @@ int main(int argc, char* argv[]) { state_space.SetStateZero(state); Runner::Parameter param; + param.max_fused_size = opt.max_fused_size; + param.fuser_verbosity = opt.verbosity; param.seed = opt.seed; param.num_threads = opt.num_threads; param.verbosity = opt.verbosity; diff --git a/apps/qsim_von_neumann.cc b/apps/qsim_von_neumann.cc index fdc0db9b..4bc68f59 100644 --- a/apps/qsim_von_neumann.cc +++ b/apps/qsim_von_neumann.cc @@ -23,7 +23,7 @@ #include "../lib/circuit_qsim_parser.h" #include "../lib/formux.h" -#include "../lib/fuser_basic.h" +#include "../lib/fuser_mqubit.h" #include "../lib/gates_qsim.h" #include "../lib/io_file.h" #include "../lib/run_qsim.h" @@ -34,18 +34,20 @@ struct Options { unsigned maxtime = std::numeric_limits::max(); unsigned seed = 1; unsigned num_threads = 1; + unsigned max_fused_size = 2; unsigned verbosity = 0; }; Options GetOptions(int argc, char* argv[]) { constexpr char usage[] = "usage:\n ./qsim_von_neumann -c circuit -d maxtime " - "-s seed -t threads -v verbosity\n"; + "-s seed -t threads -f max_fused_size " + "-v verbosity\n"; Options opt; int k; - while ((k = getopt(argc, argv, "c:d:s:t:v:")) != -1) { + while ((k = getopt(argc, argv, "c:d:s:t:f:v:")) != -1) { switch (k) { case 'c': opt.circuit_file = optarg; @@ -59,6 +61,9 @@ Options GetOptions(int argc, char* argv[]) { case 't': opt.num_threads = std::atoi(optarg); break; + case 'f': + opt.max_fused_size = std::atoi(optarg); + break; case 'v': opt.verbosity = std::atoi(optarg); break; @@ -97,6 +102,8 @@ int main(int argc, char* argv[]) { using Simulator = qsim::Simulator; using StateSpace = Simulator::StateSpace; using State = StateSpace::State; + using Fuser = MultiQubitGateFuser>; + using Runner = QSimRunner; auto measure = [&opt, &circuit]( unsigned k, const StateSpace& state_space, const State& state) { @@ -113,9 +120,9 @@ int main(int argc, char* argv[]) { IO::messagef("entropy=%g\n", entropy); }; - using Runner = QSimRunner>, Simulator>; - Runner::Parameter param; + param.max_fused_size = opt.max_fused_size; + param.fuser_verbosity = opt.verbosity; param.seed = opt.seed; param.num_threads = opt.num_threads; param.verbosity = opt.verbosity; diff --git a/docs/usage.md b/docs/usage.md index 2f174870..ab797906 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -11,14 +11,15 @@ Sample circuits are provided in [circuits](/circuits). ## qsim_base usage ``` -./qsim_base.x -c circuit_file -d maxtime -t num_threads -v verbosity +./qsim_base.x -c circuit_file -d maxtime -t num_threads -f max_fused_size -v verbosity ``` -| Flag | Description | +| Flag | Description | |-------|------------| -|`-c circuit_file` | circuit file to run| +|`-c circuit_file` | circuit file to run| |`-d maxtime` | maximum time | |`-t num_threads` | number of threads to use| +|`-f max_fused_size` | maximum fused gate size| |`-v verbosity` | verbosity level (0,1,>1)| qsim_base computes all the amplitudes and just prints the first eight of them @@ -32,15 +33,16 @@ Example: ## qsim_von_neumann usage ``` -./qsim_von_neumann.x -c circuit_file -d maxtime -t num_threads -v verbosity +./qsim_von_neumann.x -c circuit_file -d maxtime -t num_threads -f max_fused_size -v verbosity ``` -| Flag | Description | +| Flag | Description | |-------|------------| -|`-c circuit_file` | circuit file to run| +|`-c circuit_file` | circuit file to run| |`-d maxtime` | maximum time | |`-t num_threads` | number of threads to use| +|`-f max_fused_size` | maximum fused gate size| |`-v verbosity` | verbosity level (0,1,>1)| qsim_von_neumann computes all the amplitudes and calculates the von Neumann @@ -58,17 +60,19 @@ Example: ./qsim_amplitudes.x -c circuit_file \ -d times_to_save_results \ -i input_files \ - -o output_files \ + -o output_files \ + -f max_fused_size \ -t num_threads -v verbosity ``` -| Flag | Description | +| Flag | Description | |-------|------------| -|`-c circuit_file` | circuit file to run| +|`-c circuit_file` | circuit file to run| |`-d times_to_save_results` | comma-separated list of circuit times to save results at| |`-i input_files` | comma-separated list of bitstring input files| |`-o output_files` | comma-separated list of amplitude output files| |`-t num_threads` | number of threads to use| +|`-f max_fused_size` | maximum fused gate size| |`-v verbosity` | verbosity level (0,1,>1)| qsim_amplitudes reads input files of bitstrings, computes the corresponding @@ -94,9 +98,9 @@ Example: -t num_threads -v verbosity ``` -| Flag | Description | +| Flag | Description | |-------|------------| -|`-c circuit_file` | circuit file to run| +|`-c circuit_file` | circuit file to run| |`-d maxtime` | maximum time | |`-k part1_qubits` | comma-separated list of qubit indices for part 1 | |`-w prefix`| prefix value | @@ -173,9 +177,9 @@ maximum "time". -t num_threads -v verbosity ``` -| Flag | Description | +| Flag | Description | |-------|------------| -|`-c circuit_file` | circuit file to run| +|`-c circuit_file` | circuit file to run| |`-d maxtime` | maximum time | |`-k part1_qubits` | comma-separated list of qubit indices for part 1 | |`-w prefix`| prefix value | diff --git a/lib/BUILD b/lib/BUILD index 8d6014cd..bab9d6b0 100644 --- a/lib/BUILD +++ b/lib/BUILD @@ -11,8 +11,9 @@ cc_library( "circuit_qsim_parser.h", "circuit.h", "formux.h", - "fuser_basic.h", "fuser.h", + "fuser_basic.h", + "fuser_mqubit.h", "gate.h", "gate_appl.h", "gates_cirq.h", @@ -52,6 +53,7 @@ cc_library( "formux.h", "fuser.h", "fuser_basic.h", + "fuser_mqubit.h", "gate.h", "gate_appl.h", "gates_qsim.h", @@ -88,6 +90,7 @@ cc_library( "formux.h", "fuser.h", "fuser_basic.h", + "fuser_mqubit.h", "gate.h", "gate_appl.h", "gates_qsim.h", @@ -241,6 +244,15 @@ cc_library( ], ) +cc_library( + name = "fuser_mqubit", + hdrs = ["fuser_mqubit.h"], + deps = [ + ":fuser", + ":gate", + ], +) + ### Helper libraries to run qsim and qsimh ### cc_library( diff --git a/lib/fuser_mqubit.h b/lib/fuser_mqubit.h new file mode 100644 index 00000000..b7ee7502 --- /dev/null +++ b/lib/fuser_mqubit.h @@ -0,0 +1,1087 @@ +// Copyright 2019 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef FUSER_MQUBIT_H_ +#define FUSER_MQUBIT_H_ + +#include +#include +#include +#include +#include + +#include "gate.h" +#include "fuser.h" + +namespace qsim { + +/** + * Multi-qubit gate fuser. + * Measurement gates with equal times are fused together. + * User-defined controlled gates (controlled_by.size() > 0) are not fused. + */ +template +class MultiQubitGateFuser { + private: + // Auxillary classes and structs. + + // Manages doubly-linked lists. + template + class LinkManagerT { + public: + struct Link { + T val; + Link* next; + Link* prev; + }; + + explicit LinkManagerT(uint64_t size) { + links_.reserve(size); + } + + Link* AddBack(const T& t, Link* link) { + if (link == nullptr) { + links_.push_back({t, nullptr, nullptr}); + } else { + links_.push_back({t, link->next, link}); + link->next = &links_.back(); + } + + return &links_.back(); + } + + static void Delete(const Link* link) { + if (link->prev != nullptr) { + link->prev->next = link->next; + } + if (link->next != nullptr) { + link->next->prev = link->prev; + } + } + + private: + std::vector links_; + }; + + // Intermediate representation of a fused gate. + struct GateF { + using Link = typename LinkManagerT::Link; + const Gate* parent; + std::vector qubits; + std::vector gates; // Gates that get fused to this gate. + std::vector links; // Gate "lattice" links. + uint64_t mask; // Qubit mask. + unsigned visited; + }; + + using LinkManager = LinkManagerT; + using Link = typename LinkManager::Link; + + struct Stat { + unsigned num_mea_gates = 0; + unsigned num_fused_mea_gates = 0; + unsigned num_fused_gates = 0; + unsigned num_controlled_gates = 0; + std::vector num_gates; + }; + + // Gate that is added to a sequence of gates to fuse together. + struct GateA { + GateF* gate; + std::vector qubits; // Added qubits. + std::vector links; // Added lattice links. + }; + + struct Scratch { + std::vector data; + std::vector prev1; + std::vector prev2; + std::vector next1; + std::vector next2; + std::vector longest_seq; + std::vector stack; + std::vector gates; + unsigned count = 0; + }; + + public: + using GateFused = qsim::GateFused; + + /** + * User-specified parameters for gate fusion. + */ + struct Parameter { + /** + * Maximum number of qubits in a fused gate. It can take values from 2 to + * 6 (0 and 1 are equivalent to 2). It is not recommended to use 5 or 6. + */ + unsigned max_fused_size = 2; + unsigned fuser_verbosity = 0; + }; + + /** + * Stores ordered sets of gates that can be applied together. Note that + * gates fused with this method are not multiplied together until + * ApplyFusedGate is called on the output. 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 gates The gates to be fused. + * @return A vector of fused gate objects. Each element is a set of gates + * 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 std::vector& gates) { + return FuseGates(param, num_qubits, gates.cbegin(), gates.cend(), {}); + } + + /** + * Stores ordered sets of gates that can be applied together. Note that + * gates fused with this method are not multiplied together until + * ApplyFusedGate is called on the output. + * @param param Options for gate fusion. + * @param num_qubits The number of qubits acted on by 'gates'. + * @param gates The gates to be fused. Gate times should be ordered. + * @param times_to_split_at Ordered list of time steps at which to separate + * fused gates. Each element of the output will contain gates from a single + * 'window' in this list. + * @return A vector of fused gate objects. Each element is a set of gates + * 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 std::vector& gates, + const std::vector& times_to_split_at) { + return FuseGates(param, num_qubits, gates.cbegin(), gates.cend(), + times_to_split_at); + } + + /** + * Stores ordered sets of gates that can be applied together. Note that + * gates fused with this method are not multiplied together until + * ApplyFusedGate is called on the output. 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 gfirst, glast The iterator range [gfirst, glast) to fuse gates in. + * Gate times should be ordered. + * @return A vector of fused gate objects. Each element is a set of gates + * acting on a specific pair of qubits which can be applied as a group. + */ + static std::vector FuseGates( + const Parameter& param, unsigned num_qubits, + typename std::vector::const_iterator gfirst, + typename std::vector::const_iterator glast) { + return FuseGates(param, num_qubits, gfirst, glast, {}); + } + + /** + * Stores ordered sets of gates that can be applied together. Note that + * gates fused with this method are not multiplied together until + * ApplyFusedGate is called on the output. + * @param param Options for gate fusion. + * @param num_qubits The number of qubits acted on by gates. + * @param gfirst, glast The iterator range [gfirst, glast) to fuse gates in. + * Gate times should be ordered. + * @param times_to_split_at Ordered list of time steps at which to separate + * fused gates. Each element of the output will contain gates from a single + * 'window' in this list. + * @return A vector of fused gate objects. Each element is a set of gates + * acting on a specific pair of qubits which can be applied as a group. + */ + static std::vector FuseGates( + const Parameter& param, unsigned num_qubits, + typename std::vector::const_iterator gfirst, + typename std::vector::const_iterator glast, + const std::vector& times_to_split_at) { + std::vector fused_gates; + + if (gfirst >= glast) return fused_gates; + + std::size_t num_gates = glast - gfirst; + + fused_gates.reserve(num_gates); + + // Merge with measurement gate times to separate fused gates at. + auto epochs = MergeWithMeasurementTimes(gfirst, glast, times_to_split_at); + + LinkManager link_manager(num_qubits * num_gates); + + // Auxillary data structures. + // Sequence of intermediate fused gates. + std::vector gates_seq; + // Gate "lattice". + std::vector gates_lat; + // Sequences of intermediate fused gates ordered by gate size. + std::vector> fgates(num_qubits + 1); + + gates_seq.reserve(num_gates); + gates_lat.reserve(num_qubits); + + Stat stat; + stat.num_gates.resize(num_qubits + 1, 0); + + unsigned max_fused_size = std::min(unsigned{6}, param.max_fused_size); + max_fused_size = std::min(max_fused_size, num_qubits); + + auto gate_it = gfirst; + + // Iterate over epochs. + for (std::size_t l = 0; l < epochs.size(); ++l) { + gates_seq.resize(0); + gates_lat.resize(0); + gates_lat.resize(num_qubits, nullptr); + + for (unsigned i = 0; i <= num_qubits; ++i) { + fgates[i].resize(0); + } + + uint64_t max_gate_size = 0; + GateF* last_mea_gate = nullptr; + + auto prev_time = gate_it->time; + + // Iterate over input gates. + for (; gate_it < glast; ++gate_it) { + const auto& gate = *gate_it; + + if (gate.time > epochs[l]) break; + + if (gate.time < prev_time) { + // This function assumes that gate times are ordered. + // Just stop if this is not the case. + IO::errorf("gate times should be ordered.\n"); + fused_gates.resize(0); + return fused_gates; + } + + prev_time = gate.time; + + // Fill in auxillary data structures. + + if (gate.kind == gate::kMeasurement) { + // Measurement gate. + + if (last_mea_gate == nullptr + || last_mea_gate->parent->time != gate.time) { + gates_seq.push_back({&gate, {}, {}, {}, 0, 997}); + last_mea_gate = &gates_seq.back(); + + last_mea_gate->qubits.reserve(num_qubits); + last_mea_gate->links.reserve(num_qubits); + + ++stat.num_fused_mea_gates; + } + + for (auto q : gate.qubits) { + last_mea_gate->qubits.push_back(q); + last_mea_gate->mask |= uint64_t{1} << q; + gates_lat[q] = link_manager.AddBack(last_mea_gate, gates_lat[q]); + last_mea_gate->links.push_back(gates_lat[q]); + } + + last_mea_gate->gates.push_back(&gate); + + ++stat.num_mea_gates; + } else { + gates_seq.push_back({&gate, {}, {}, {}, 0, 0}); + auto& fgate = gates_seq.back(); + + if (gate.controlled_by.size() == 0) { + if (max_gate_size < gate.qubits.size()) { + max_gate_size = gate.qubits.size(); + } + + unsigned num_gate_qubits = gate.qubits.size(); + unsigned size = std::max(max_fused_size, num_gate_qubits); + + fgate.qubits.reserve(size); + fgate.links.reserve(size); + fgate.gates.reserve(4 * size); + fgate.links.reserve(size); + + if (fgates[num_gate_qubits].empty()) { + fgates[num_gate_qubits].reserve(num_gates); + } + fgates[num_gate_qubits].push_back(&fgate); + + ++stat.num_gates[num_gate_qubits]; + } else { + // Controlled gate. + // Controlled gates are not fused with other gates. + + uint64_t size = gate.qubits.size() + gate.controlled_by.size(); + + fgate.qubits.reserve(gate.qubits.size()); + fgate.links.reserve(size); + + fgate.visited = 997; + fgate.gates.push_back(&gate); + + ++stat.num_controlled_gates; + } + + for (auto q : gate.qubits) { + fgate.qubits.push_back(q); + fgate.mask |= uint64_t{1} << q; + gates_lat[q] = link_manager.AddBack(&fgate, gates_lat[q]); + fgate.links.push_back(gates_lat[q]); + } + + for (auto q : gate.controlled_by) { + fgate.mask |= uint64_t{1} << q; + gates_lat[q] = link_manager.AddBack(&fgate, gates_lat[q]); + fgate.links.push_back(gates_lat[q]); + } + } + } + + // Fuse large gates with smaller gates. + FuseGates(max_gate_size, fgates); + + if (max_fused_size > 2) { + FuseGateSequences( + max_fused_size, num_qubits, gates_seq, stat, fused_gates); + } else { + for (auto& fgate : gates_seq) { + if (fgate.gates.size() > 0) { + fused_gates.push_back({fgate.parent->kind, fgate.parent->time, + std::move(fgate.qubits), fgate.parent, + std::move(fgate.gates)}); + + if (fgate.visited != 997) { + ++stat.num_fused_gates; + } + } + } + } + } + + PrintStat(param.fuser_verbosity, stat, fused_gates); + + return fused_gates; + } + + private: + static std::vector MergeWithMeasurementTimes( + typename std::vector::const_iterator gfirst, + typename std::vector::const_iterator glast, + const std::vector& times) { + std::vector epochs; + epochs.reserve(glast - gfirst + times.size()); + + std::size_t last = 0; + + for (auto gate_it = gfirst; gate_it < glast; ++gate_it) { + const auto& gate = *gate_it; + + if (gate.kind == gate::kMeasurement + && (epochs.size() == 0 || epochs.back() < gate.time)) { + epochs.push_back(gate.time); + } + + if (last < times.size() && gate.time > times[last]) { + while (last < times.size() && times[last] <= gate.time) { + unsigned prev = times[last++]; + epochs.push_back(prev); + while (last < times.size() && times[last] <= prev) ++last; + } + } + } + + if (epochs.size() == 0 || epochs.back() < (glast - 1)->time) { + epochs.push_back((glast - 1)->time); + } + + return epochs; + } + + // Fuse large gates with smaller gates. + static void FuseGates(uint64_t max_gate_size, + std::vector>& fgates) { + // Traverse gates in order of decreasing size. + for (uint64_t i = 0; i < max_gate_size; ++i) { + std::size_t pos = 0; + + for (auto fgate : fgates[max_gate_size - i]) { + if (fgate->visited > 0) continue; + + fgates[max_gate_size - i][pos++] = fgate; + + fgate->visited = 1; + + FusePrev(0, *fgate); + fgate->gates.push_back(fgate->parent); + FuseNext(0, *fgate); + } + + fgates[max_gate_size - i].resize(pos); + } + } + + // Try to fuse gate sequences as follows. Gate time goes from left to right. + // + // max_fused_size = 3: + // | + // | + // + // max_fused_size = 4: + // | + // | + // | + // + // max_fused_size = 5: + // | + // | + // | + // | + // + // max_fused_size = 6: + // | + // | + // | + // | + // | + // + static void FuseGateSequences(unsigned max_fused_size, unsigned num_qubits, + std::vector& gates_seq, Stat& stat, + std::vector& fused_gates) { + Scratch scratch; + + scratch.data.reserve(1024); + scratch.prev1.reserve(32); + scratch.prev2.reserve(32); + scratch.next1.reserve(32); + scratch.next2.reserve(32); + scratch.longest_seq.reserve(8); + scratch.stack.reserve(8); + + unsigned prev_time = 0; + + std::vector orphaned_gates; + orphaned_gates.reserve(num_qubits); + + for (auto& fgate : gates_seq) { + if (prev_time != fgate.parent->time) { + if (orphaned_gates.size() > 0) { + FuseOrphanedGates(max_fused_size, stat, orphaned_gates, fused_gates); + orphaned_gates.resize(0); + } + + prev_time = fgate.parent->time; + } + + if (fgate.visited == 999 || fgate.gates.size() == 0) continue; + + if (fgate.visited == 997 || fgate.qubits.size() >= max_fused_size + || fgate.parent->unfusible) { + if (fgate.visited != 997) { + ++stat.num_fused_gates; + } + + fgate.visited = 999; + + fused_gates.push_back({fgate.parent->kind, fgate.parent->time, + std::move(fgate.qubits), fgate.parent, + std::move(fgate.gates)}); + + continue; + } + + + if (fgate.qubits.size() == 1 && max_fused_size > 1) { + orphaned_gates.push_back(&fgate); + continue; + } + + scratch.data.resize(0); + scratch.gates.resize(0); + scratch.count = 0; + + MakeGateSequence(max_fused_size, scratch, fgate); + + if (scratch.gates.size() == 0) { + orphaned_gates.push_back(&fgate); + } else { + for (auto fgate : scratch.gates) { + std::sort(fgate->qubits.begin(), fgate->qubits.end()); + + fused_gates.push_back({fgate->parent->kind, fgate->parent->time, + std::move(fgate->qubits), fgate->parent, + std::move(fgate->gates)}); + + ++stat.num_fused_gates; + } + } + } + + if (orphaned_gates.size() > 0) { + FuseOrphanedGates(max_fused_size, stat, orphaned_gates, fused_gates); + } + } + + static void FuseOrphanedGates(unsigned max_fused_size, Stat& stat, + std::vector& orphaned_gates, + std::vector& fused_gates) { + unsigned count = 0; + + for (std::size_t i = 0; i < orphaned_gates.size(); ++i) { + auto ogate1 = orphaned_gates[i]; + + if (ogate1->visited == 999) continue; + + ogate1->visited = 999; + + for (std::size_t j = i + 1; j < orphaned_gates.size(); ++j) { + auto ogate2 = orphaned_gates[j]; + + if (ogate2->visited == 999) continue; + + ++count; + + unsigned cur_size = ogate1->qubits.size() + ogate2->qubits.size(); + + if (cur_size <= max_fused_size) { + ogate2->visited = 999; + + for (auto q : ogate2->qubits) { + ogate1->qubits.push_back(q); + ogate1->mask |= uint64_t{1} << q; + } + + for (auto l : ogate2->links) { + ogate1->links.push_back(l); + } + + for (auto gate : ogate2->gates) { + ogate1->gates.push_back(gate); + } + } + + if (cur_size == max_fused_size) { + break; + } + } + + FuseNext(1, *ogate1); + + std::sort(ogate1->qubits.begin(), ogate1->qubits.end()); + + fused_gates.push_back({ogate1->parent->kind, ogate1->parent->time, + std::move(ogate1->qubits), ogate1->parent, + std::move(ogate1->gates)}); + + ++stat.num_fused_gates; + } + } + + static void MakeGateSequence( + unsigned max_fused_size, Scratch& scratch, GateF& fgate) { + unsigned level = 2 + scratch.count; + + FindLongestGateSequence(max_fused_size, level, scratch, fgate); + + auto longest_seq = scratch.longest_seq; + + if (longest_seq.size() == 1 && scratch.count == 0) { + fgate.visited = 1; + return; + } + + ++scratch.count; + + for (auto p : longest_seq) { + p->gate->visited = 900; + + for (auto q : p->qubits) { + fgate.qubits.push_back(q); + fgate.mask |= uint64_t{1} << q; + } + + for (auto l : p->links) { + fgate.links.push_back(l); + } + } + + // Compress links. + for (auto& link : fgate.links) { + while (link->prev != nullptr && link->prev->val->visited == 900) { + link = link->prev; + } + + while (link->next != nullptr && link->next->val->visited == 900) { + LinkManager::Delete(link->next); + } + } + + for (auto p : longest_seq) { + p->gate->visited = level; + } + + if (longest_seq.size() >= 3) { + AddGatesFromNext(longest_seq[2]->gate->gates, fgate); + } + + if (longest_seq.size() >= 5) { + AddGatesFromNext(longest_seq[4]->gate->gates, fgate); + } + + if (longest_seq.size() >= 2) { + // May call MakeGateSequence recursively. + AddGatesFromPrev(max_fused_size, *longest_seq[1]->gate, scratch, fgate); + } + + if (longest_seq.size() >= 4) { + // May call MakeGateSequence recursively. + AddGatesFromPrev(max_fused_size, *longest_seq[3]->gate, scratch, fgate); + } + + for (auto p : longest_seq) { + p->gate->visited = 999; + } + + FuseNext(1, fgate); + + scratch.gates.push_back(&fgate); + } + + static void AddGatesFromNext(std::vector& gates, GateF& fgate) { + for (auto gate : gates) { + fgate.gates.push_back(gate); + } + } + + static void AddGatesFromPrev(unsigned max_fused_size, const GateF& pfgate, + Scratch& scratch, GateF& fgate) { + for (auto gate : pfgate.gates) { + fgate.gates.push_back(gate); + } + + for (auto link : pfgate.links) { + if (link->prev == nullptr) continue; + + auto pgate = link->prev->val; + + if (pgate->visited == 1) { + MakeGateSequence(max_fused_size, scratch, *pgate); + } + } + } + + static void FindLongestGateSequence( + unsigned max_fused_size, unsigned level, Scratch& scratch, GateF& fgate) { + scratch.data.push_back({&fgate, {}, {}}); + + scratch.longest_seq.resize(0); + scratch.longest_seq.push_back(&scratch.data.back()); + + scratch.stack.resize(0); + scratch.stack.push_back(&scratch.data.back()); + + unsigned cur_size = fgate.qubits.size(); + fgate.visited = level; + + unsigned max_size = cur_size; + + GetNextAvailableGates(max_fused_size, cur_size, fgate, nullptr, + scratch.data, scratch.next1); + + for (auto n1 : scratch.next1) { + unsigned cur_size2 = cur_size + n1->qubits.size(); + if (cur_size2 > max_fused_size) continue; + + bool feasible = GetPrevAvailableGates(max_fused_size, cur_size, + level, *n1->gate, nullptr, + scratch.data, scratch.prev1); + + if (!feasible) continue; + + if (scratch.prev1.size() == 0 && max_fused_size > 3) continue; + + if (cur_size2 == max_fused_size) { + std::swap(scratch.longest_seq, scratch.stack); + scratch.longest_seq.push_back(n1); + return; + } + + n1->gate->visited = level; + cur_size = cur_size2; + scratch.stack.push_back(n1); + + if (cur_size > max_size) { + scratch.longest_seq = scratch.stack; + max_size = cur_size; + } + + for (auto p1 : scratch.prev1) { + unsigned cur_size2 = cur_size + p1->qubits.size(); + + if (cur_size2 > max_fused_size) { + continue; + } else if (cur_size2 == max_fused_size) { + std::swap(scratch.longest_seq, scratch.stack); + scratch.longest_seq.push_back(p1); + return; + } + + p1->gate->visited = level; + cur_size = cur_size2; + scratch.stack.push_back(p1); + + if (cur_size > max_size) { + scratch.longest_seq = scratch.stack; + max_size = cur_size; + } + + GetNextAvailableGates(max_fused_size, cur_size, *p1->gate, &fgate, + scratch.data, scratch.next2); + + for (auto n2 : scratch.next2) { + unsigned cur_size2 = cur_size + n2->qubits.size(); + if (cur_size2 > max_fused_size) continue; + + bool feasible = GetPrevAvailableGates(max_fused_size, cur_size, + level, *n2->gate, n1->gate, + scratch.data, scratch.prev2); + + if (!feasible) continue; + + if (cur_size2 == max_fused_size) { + std::swap(scratch.longest_seq, scratch.stack); + scratch.longest_seq.push_back(n2); + return; + } + + n2->gate->visited = level; + cur_size = cur_size2; + scratch.stack.push_back(n2); + + if (cur_size > max_size) { + scratch.longest_seq = scratch.stack; + max_size = cur_size; + } + + for (auto p2 : scratch.prev2) { + unsigned cur_size2 = cur_size + p2->qubits.size(); + + if (cur_size2 > max_fused_size) { + continue; + } else if (cur_size2 == max_fused_size) { + std::swap(scratch.longest_seq, scratch.stack); + scratch.longest_seq.push_back(p2); + return; + } + + if (cur_size2 > max_size) { + scratch.stack.push_back(p2); + scratch.longest_seq = scratch.stack; + scratch.stack.pop_back(); + max_size = cur_size2; + } + } + + n2->gate->visited = 1; + cur_size -= n2->qubits.size(); + scratch.stack.pop_back(); + } + + p1->gate->visited = 1; + cur_size -= p1->qubits.size(); + scratch.stack.pop_back(); + } + + n1->gate->visited = 1; + cur_size -= n1->qubits.size(); + scratch.stack.pop_back(); + } + } + + static void GetNextAvailableGates(unsigned max_fused_size, unsigned cur_size, + const GateF& pgate1, const GateF* pgate2, + std::vector& scratch, + std::vector& next_gates) { + next_gates.resize(0); + + for (auto link : pgate1.links) { + if (link->next == nullptr) continue; + + auto ngate = link->next->val; + + if (ngate->visited > 1 || ngate->parent->unfusible) continue; + + GateA next = {ngate, {}, {}}; + next.qubits.reserve(8); + next.links.reserve(8); + + GetAddedQubits(pgate1, pgate2, *ngate, next); + + if (cur_size + next.qubits.size() > max_fused_size) continue; + + scratch.push_back(std::move(next)); + next_gates.push_back(&scratch.back()); + } + } + + static bool GetPrevAvailableGates(unsigned max_fused_size, + unsigned cur_size, unsigned level, + const GateF& ngate1, const GateF* ngate2, + std::vector& scratch, + std::vector& prev_gates) { + prev_gates.resize(0); + + for (auto link : ngate1.links) { + if (link->prev == nullptr) continue; + + auto pgate = link->prev->val; + + if (pgate->visited > 997 || pgate->visited == level) continue; + + if (pgate->visited > 1 || pgate->parent->unfusible) { + prev_gates.resize(0); + return false; + } + + GateA prev = {pgate, {}, {}}; + prev.qubits.reserve(8); + prev.links.reserve(8); + + GetAddedQubits(ngate1, ngate2, *pgate, prev); + + bool all_prev_visited = true; + + for (auto link : pgate->links) { + if (link->prev == nullptr) continue; + + if (link->prev->val->visited <= 997) { + all_prev_visited = false; + break; + } + } + + if (!all_prev_visited) { + prev_gates.resize(0); + return false; + } + + if (cur_size + prev.qubits.size() > max_fused_size) continue; + + if (all_prev_visited) { + scratch.push_back(std::move(prev)); + prev_gates.push_back(&scratch.back()); + } + } + + return true; + } + + static void GetAddedQubits(const GateF& fgate0, const GateF* fgate1, + const GateF& fgate2, GateA& added) { + for (std::size_t i = 0; i < fgate2.qubits.size(); ++i) { + unsigned q2 = fgate2.qubits[i]; + + bool have_added_qubits = true; + + for (auto q0 : fgate0.qubits) { + if (q0 == q2) { + have_added_qubits = false; + break; + } + } + + if (have_added_qubits && fgate1 != nullptr) { + for (auto q1 : fgate1->qubits) { + if (q1 == q2) { + have_added_qubits = false; + break; + } + } + } + + if (have_added_qubits) { + added.qubits.push_back(q2); + added.links.push_back(fgate2.links[i]); + } + } + } + + // Fuse smaller gates with fgate back in gate time. + static void FusePrev(unsigned level, GateF& fgate) { + std::vector gates; + gates.reserve(fgate.gates.capacity()); + + uint64_t bad_mask = 0; + auto links = fgate.links; + + bool may_have_gates_to_fuse = true; + + while (may_have_gates_to_fuse) { + may_have_gates_to_fuse = false; + + std::sort(links.begin(), links.end(), + [](const Link* l, const Link* r) -> bool { + auto lp = l->prev; + auto rp = r->prev; + + if (lp != nullptr && rp != nullptr) { + return lp->val->parent->time > rp->val->parent->time; + } else { + return lp != nullptr || rp == nullptr; + } + }); + + for (auto link : links) { + if (link->prev == nullptr) continue; + + auto p = link->prev->val; + + if (!QubitsAreIn(fgate.mask, p->mask) || (p->mask & bad_mask) != 0 + || p->visited > level || p->parent->unfusible) { + bad_mask |= p->mask; + } else { + AddGates(level, *p, gates); + + may_have_gates_to_fuse = true; + break; + } + } + } + + for (auto it = gates.rbegin(); it != gates.rend(); ++it) { + fgate.gates.push_back(*it); + } + } + + // Fuse smaller gates with fgate forward in gate time. + static void FuseNext(unsigned level, GateF& fgate) { + uint64_t bad_mask = 0; + auto links = fgate.links; + + bool may_have_gates_to_fuse = true; + + while (may_have_gates_to_fuse) { + may_have_gates_to_fuse = false; + + auto time = fgate.parent->time; + + std::sort(links.begin(), links.end(), + [&time](const Link* l, const Link* r) -> bool { + auto ln = l->next; + auto rn = r->next; + + if (ln != nullptr && rn != nullptr) { + return ln->val->parent->time < rn->val->parent->time; + } else { + return ln != nullptr || rn == nullptr; + } + }); + + for (auto link : links) { + if (link->next == nullptr) continue; + + auto n = link->next->val; + + if (!QubitsAreIn(fgate.mask, n->mask) || (n->mask & bad_mask) != 0 + || n->visited > level || n->parent->unfusible) { + bad_mask |= n->mask; + } else { + AddGates(level, *n, fgate.gates); + + may_have_gates_to_fuse = true; + break; + } + } + } + } + + static void AddGates( + unsigned level, GateF& fgate, std::vector& gates) { + fgate.visited = level == 0 ? 1 : 999; + + if (level == 0) { + gates.push_back(fgate.parent); + } else { + for (auto gate :fgate.gates) { + gates.push_back(gate); + } + } + + for (auto link : fgate.links) { + LinkManager::Delete(link); + } + } + + static bool QubitsAreIn(uint64_t mask0, uint64_t mask) { + return ((mask0 | mask) ^ mask0) == 0; + } + + static void PrintStat(unsigned verbosity, const Stat& stat, + const std::vector& fused_gates) { + if (verbosity > 0) { + if (stat.num_controlled_gates > 0) { + IO::messagef("%lu controlled gates\n", stat.num_controlled_gates); + } + + if (stat.num_mea_gates > 0) { + IO::messagef("%lu measurement gates", stat.num_mea_gates); + if (stat.num_fused_mea_gates == stat.num_mea_gates) { + IO::messagef("\n"); + } else { + IO::messagef(" are fused into %lu gates\n", stat.num_fused_mea_gates); + } + } + + bool first = true; + for (unsigned i = 1; i < stat.num_gates.size(); ++i) { + if (stat.num_gates[i] > 0) { + if (first) { + first = false; + } else { + IO::messagef(", "); + } + IO::messagef("%u %u-qubit", stat.num_gates[i], i); + } + } + + IO::messagef(" gates are fused into %lu gates\n", stat.num_fused_gates); + + if (verbosity > 1) { + IO::messagef("fused gate qubits:\n"); + for (const auto g : fused_gates) { + IO::messagef("%6u ", g.parent->time); + if (g.parent->kind == gate::kMeasurement) { + IO::messagef("m"); + } else if (g.parent->controlled_by.size() > 0) { + IO::messagef("c"); + for (auto q : g.parent->controlled_by) { + IO::messagef("%3u", q); + } + IO::messagef(" t"); + } else { + IO::messagef(" "); + } + + for (auto q : g.qubits) { + IO::messagef("%3u", q); + } + IO::messagef("\n"); + } + } + } + } +}; + +} // namespace qsim + +#endif // FUSER_MQUBIT_H_ diff --git a/tests/BUILD b/tests/BUILD index ed8d4a79..b3c296fa 100644 --- a/tests/BUILD +++ b/tests/BUILD @@ -55,6 +55,21 @@ cc_test( ], ) +cc_test( + name = "fuser_mqubit_test", + srcs = ["fuser_mqubit_test.cc"], + copts = select({ + ":windows": windows_copts, + "//conditions:default": [], + }), + deps = [ + "@com_google_googletest//:gtest_main", + "//lib:fuser_mqubit", + "//lib:gate", + "//lib:io", + ], +) + cc_library( name = "gates_cirq_testfixture", hdrs = ["gates_cirq_testfixture.h"], diff --git a/tests/fuser_mqubit_test.cc b/tests/fuser_mqubit_test.cc new file mode 100644 index 00000000..7f944bfb --- /dev/null +++ b/tests/fuser_mqubit_test.cc @@ -0,0 +1,295 @@ +// Copyright 2019 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "gtest/gtest.h" + +#include "../lib/fuser_mqubit.h" +#include "../lib/gate.h" +#include "../lib/io.h" + +namespace qsim { + +namespace { + +enum DummyGateKind { + kGateOther = 0, + kMeasurement = gate::kMeasurement, +}; + +struct DummyGate { + using GateKind = DummyGateKind; + + GateKind kind; + unsigned time; + std::vector qubits; + std::vector controlled_by; + bool unfusible; +}; + +DummyGate CreateDummyGate(unsigned time, std::vector&& qubits) { + return {kGateOther, time, std::move(qubits), {}, false}; +} + +DummyGate CreateDummyMeasurementGate(unsigned time, + std::vector&& qubits) { + return {kMeasurement, time, std::move(qubits), {}, false}; +} + +DummyGate CreateDummyControlledGate(unsigned time, + std::vector&& qubits, + std::vector&& controlled_by) { + return {kGateOther, time, std::move(qubits), std::move(controlled_by), false}; +} + +std::vector GenQubits(unsigned num_qubits, std::mt19937& rgen, + unsigned& n, std::vector& available) { + std::vector qubits; + qubits.reserve(num_qubits); + + for (unsigned i = 0; i < num_qubits; ++i) { + unsigned k = rgen() % n--; + qubits.push_back(available[k]); + std::swap(available[k], available[n]); + } + + return qubits; +} + +constexpr double p1 = 0.2; +constexpr double p2 = 0.6 + p1; +constexpr double p3 = 0.08 + p2; +constexpr double p4 = 0.05 + p3; +constexpr double p5 = 0.035 + p4; +constexpr double p6 = 0.025 + p5; +constexpr double pc = 0.005 + p6; +constexpr double pm = 0.005 + pc; + +constexpr double pu = 0.002; + +void AddToCircuit(unsigned time, + std::uniform_real_distribution& distr, + std::mt19937& rgen, unsigned& n, + std::vector& available, + std::vector& circuit) { + double r = distr(rgen); + + if (r < p1) { + circuit.push_back(CreateDummyGate(time, GenQubits(1, rgen, n, available))); + } else if (r < p2) { + circuit.push_back(CreateDummyGate(time, GenQubits(2, rgen, n, available))); + } else if (r < p3) { + circuit.push_back(CreateDummyGate(time, GenQubits(3, rgen, n, available))); + } else if (r < p4) { + circuit.push_back(CreateDummyGate(time, GenQubits(4, rgen, n, available))); + } else if (r < p5) { + circuit.push_back(CreateDummyGate(time, GenQubits(5, rgen, n, available))); + } else if (r < p6) { + circuit.push_back(CreateDummyGate(time, GenQubits(6, rgen, n, available))); + } else if (r < pc) { + auto qs = GenQubits(1 + rgen() % 3, rgen, n, available); + auto cqs = GenQubits(1 + rgen() % 3, rgen, n, available); + circuit.push_back( + CreateDummyControlledGate(time, std::move(qs), std::move(cqs))); + } else if (r < pm) { + unsigned num_mea_gates = 0; + unsigned max_num_mea_gates = 1 + rgen() % 5; + + while (n > 0 && num_mea_gates < max_num_mea_gates) { + unsigned k = 1 + rgen() % 12; + if (k > n) k = n; + circuit.push_back( + CreateDummyMeasurementGate(time, GenQubits(k, rgen, n, available))); + ++num_mea_gates; + } + } + + if (r < p6 && distr(rgen) < pu) { + circuit.back().unfusible = true; + } +} + +std::vector GenerateRandomCircuit1(unsigned num_qubits, + unsigned depth) { + std::vector circuit; + circuit.reserve(depth); + + std::mt19937 rgen(1); + std::uniform_real_distribution distr(0, 1); + + std::vector available(num_qubits, 0); + + for (unsigned time = 0; time < depth; ++time) { + for (unsigned k = 0; k < num_qubits; ++k) { + available[k] = k; + } + + unsigned n = num_qubits; + + AddToCircuit(time, distr, rgen, n, available, circuit); + } + + return circuit; +} + +std::vector GenerateRandomCircuit2(unsigned num_qubits, + unsigned depth, + unsigned max_fused_gate_size) { + std::vector circuit; + circuit.reserve(num_qubits * depth); + + std::mt19937 rgen(1); + std::uniform_real_distribution distr(0, 1); + + std::vector available(num_qubits, 0); + + for (unsigned time = 0; time < depth; ++time) { + for (unsigned k = 0; k < num_qubits; ++k) { + available[k] = k; + } + + unsigned n = num_qubits; + + while (n > max_fused_gate_size) { + AddToCircuit(time, distr, rgen, n, available, circuit); + } + } + + return circuit; +} + +template +bool TestFusedGates(unsigned num_qubits, + const std::vector& gates, + const std::vector& fused_gates) { + std::vector times(num_qubits, 0); + std::vector gate_map(gates.size(), 0); + + // Test if gate times are ordered correctly. + for (auto g : fused_gates) { + if (g.parent->controlled_by.size() > 0 && g.gates.size() > 1) { + return false; + } + + for (auto p : g.gates) { + auto k = (std::size_t(p) - std::size_t(gates.data())) / sizeof(*p); + + if (k >= gate_map.size()) { + return false; + } + + ++gate_map[k]; + + if (p->kind == gate::kMeasurement) { + if (g.parent->kind != gate::kMeasurement || g.parent->time != p->time) { + return false; + } + } + + for (auto q : p->qubits) { + if (p->time < times[q]) { + return false; + } + times[q] = p->time; + } + + for (auto q : p->controlled_by) { + if (p->time < times[q]) { + return false; + } + times[q] = p->time; + } + } + } + + // Test if all gates are present only once. + for (auto m : gate_map) { + if (m != 1) { + return false; + } + } + + return true; +} + +} // namespace + +TEST(FuserMultiQubitTest, RandomCircuit1) { + using Fuser = MultiQubitGateFuser; + + unsigned num_qubits = 30; + unsigned depth = 100000; + + // Random circuit of 100000 gates. + auto circuit = GenerateRandomCircuit1(num_qubits, depth); + + Fuser::Parameter param; + param.fuser_verbosity = 0; + + for (unsigned q = 2; q <= 6; ++q) { + param.max_fused_size = q; + auto fused_gates = Fuser::FuseGates( + param, num_qubits, circuit.begin(), circuit.end()); + + EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); + } + + for (unsigned q = 2; q <= 6; ++q) { + param.max_fused_size = q; + auto fused_gates = Fuser::FuseGates( + param, num_qubits, circuit.begin(), circuit.end(), + {5000, 7000, 25000, 37000}); + + EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); + } +} + +TEST(FuserMultiQubitTest, RandomCircuit2) { + using Fuser = MultiQubitGateFuser; + + unsigned num_qubits = 40; + unsigned depth = 6400; + + // Random circuit of approximately 100000 gates. + auto circuit = GenerateRandomCircuit2(num_qubits, depth, 6); + + Fuser::Parameter param; + param.fuser_verbosity = 0; + + for (unsigned q = 2; q <= 6; ++q) { + param.max_fused_size = q; + auto fused_gates = Fuser::FuseGates( + param, num_qubits, circuit.begin(), circuit.end()); + + EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); + } + + for (unsigned q = 2; q <= 6; ++q) { + param.max_fused_size = q; + auto fused_gates = Fuser::FuseGates( + param, num_qubits, circuit.begin(), circuit.end(), {300, 700, 2400}); + + EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); + } +} + +} // namespace qsim + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/make.sh b/tests/make.sh index b4ca9401..6b2b6991 100755 --- a/tests/make.sh +++ b/tests/make.sh @@ -23,6 +23,7 @@ path_to_lib=googletest/lib g++ -O3 -I$path_to_include -L$path_to_lib -o bitstring_test.x bitstring_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -o circuit_qsim_parser_test.x circuit_qsim_parser_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -o fuser_basic_test.x fuser_basic_test.cc -lgtest -lpthread +g++ -O3 -I$path_to_include -L$path_to_lib -o fuser_mqubit_test.x fuser_mqubit_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -o gates_qsim_test.x gates_qsim_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -o matrix_test.x matrix_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -mavx2 -mfma -fopenmp -o hybrid_test.x hybrid_test.cc -lgtest -lpthread @@ -34,3 +35,5 @@ g++ -O3 -I$path_to_include -L$path_to_lib -msse4 -fopenmp -o simulator_sse_test. g++ -O3 -I$path_to_include -L$path_to_lib -mavx2 -mfma -fopenmp -o statespace_avx_test.x statespace_avx_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -fopenmp -o statespace_basic_test.x statespace_basic_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -msse4 -fopenmp -o statespace_sse_test.x statespace_sse_test.cc -lgtest -lpthread +g++ -O3 -I$path_to_include -L$path_to_lib -fopenmp -o unitary_calculator_basic_test.x unitary_calculator_basic_test.cc -lgtest -lpthread +g++ -O3 -I$path_to_include -L$path_to_lib -fopenmp -o unitaryspace_basic_test.x unitaryspace_basic_test.cc -lgtest -lpthread From 7a65a760b800948dee0f277ac55651526bec1af1 Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Wed, 25 Nov 2020 18:02:53 +0100 Subject: [PATCH 2/4] Add multi-qubit fuser. --- apps/qsim_amplitudes.cc | 1 - apps/qsim_base.cc | 1 - apps/qsim_von_neumann.cc | 1 - lib/fuser_basic.h | 4 +- lib/fuser_mqubit.h | 5 +- lib/hybrid.h | 1 - lib/run_qsim.h | 1 - tests/BUILD | 4 ++ tests/fuser_mqubit_test.cc | 102 +++++++++++++++++++++++++++++++++---- 9 files changed, 103 insertions(+), 17 deletions(-) diff --git a/apps/qsim_amplitudes.cc b/apps/qsim_amplitudes.cc index f794d086..bd180e1f 100644 --- a/apps/qsim_amplitudes.cc +++ b/apps/qsim_amplitudes.cc @@ -180,7 +180,6 @@ int main(int argc, char* argv[]) { Runner::Parameter param; param.max_fused_size = opt.max_fused_size; - param.fuser_verbosity = opt.verbosity; param.seed = opt.seed; param.num_threads = opt.num_threads; param.verbosity = opt.verbosity; diff --git a/apps/qsim_base.cc b/apps/qsim_base.cc index dbf99297..bd1c51a1 100644 --- a/apps/qsim_base.cc +++ b/apps/qsim_base.cc @@ -132,7 +132,6 @@ int main(int argc, char* argv[]) { Runner::Parameter param; param.max_fused_size = opt.max_fused_size; - param.fuser_verbosity = opt.verbosity; param.seed = opt.seed; param.num_threads = opt.num_threads; param.verbosity = opt.verbosity; diff --git a/apps/qsim_von_neumann.cc b/apps/qsim_von_neumann.cc index 4bc68f59..5e41c027 100644 --- a/apps/qsim_von_neumann.cc +++ b/apps/qsim_von_neumann.cc @@ -122,7 +122,6 @@ int main(int argc, char* argv[]) { Runner::Parameter param; param.max_fused_size = opt.max_fused_size; - param.fuser_verbosity = opt.verbosity; param.seed = opt.seed; param.num_threads = opt.num_threads; param.verbosity = opt.verbosity; diff --git a/lib/fuser_basic.h b/lib/fuser_basic.h index 9f97548a..fa3eaa30 100644 --- a/lib/fuser_basic.h +++ b/lib/fuser_basic.h @@ -35,7 +35,9 @@ struct BasicGateFuser final { * User-specified parameters for gate fusion. * BasicGateFuser does not use any parameters. */ - struct Parameter {}; + struct Parameter { + unsigned verbosity = 0; + }; /** * Stores ordered sets of gates, each acting on two qubits, that can be diff --git a/lib/fuser_mqubit.h b/lib/fuser_mqubit.h index b7ee7502..c5925a2a 100644 --- a/lib/fuser_mqubit.h +++ b/lib/fuser_mqubit.h @@ -127,7 +127,7 @@ class MultiQubitGateFuser { * 6 (0 and 1 are equivalent to 2). It is not recommended to use 5 or 6. */ unsigned max_fused_size = 2; - unsigned fuser_verbosity = 0; + unsigned verbosity = 0; }; /** @@ -357,6 +357,7 @@ class MultiQubitGateFuser { } else { for (auto& fgate : gates_seq) { if (fgate.gates.size() > 0) { + // Assume fgate.qubits (gate.qubits) are sorted. fused_gates.push_back({fgate.parent->kind, fgate.parent->time, std::move(fgate.qubits), fgate.parent, std::move(fgate.gates)}); @@ -369,7 +370,7 @@ class MultiQubitGateFuser { } } - PrintStat(param.fuser_verbosity, stat, fused_gates); + PrintStat(param.verbosity, stat, fused_gates); return fused_gates; } diff --git a/lib/hybrid.h b/lib/hybrid.h index d5054e9c..f69b049c 100644 --- a/lib/hybrid.h +++ b/lib/hybrid.h @@ -125,7 +125,6 @@ struct HybridSimulator final { */ unsigned num_root_gatexs; unsigned num_threads; - unsigned verbosity = 0; }; template diff --git a/lib/run_qsim.h b/lib/run_qsim.h index 1b160493..bc6cac90 100644 --- a/lib/run_qsim.h +++ b/lib/run_qsim.h @@ -45,7 +45,6 @@ struct QSimRunner final { */ uint64_t seed; unsigned num_threads; - unsigned verbosity; }; /** diff --git a/tests/BUILD b/tests/BUILD index b3c296fa..279ba1ce 100644 --- a/tests/BUILD +++ b/tests/BUILD @@ -64,9 +64,13 @@ cc_test( }), deps = [ "@com_google_googletest//:gtest_main", + "//lib:formux", "//lib:fuser_mqubit", "//lib:gate", + "//lib:gate_appl", "//lib:io", + "//lib:matrix", + "//lib:simulator", ], ) diff --git a/tests/fuser_mqubit_test.cc b/tests/fuser_mqubit_test.cc index 7f944bfb..d7564a52 100644 --- a/tests/fuser_mqubit_test.cc +++ b/tests/fuser_mqubit_test.cc @@ -12,15 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include +#include +#include #include #include #include #include "gtest/gtest.h" +#include "../lib/formux.h" #include "../lib/fuser_mqubit.h" #include "../lib/gate.h" +#include "../lib/gate_appl.h" #include "../lib/io.h" +#include "../lib/matrix.h" +#include "../lib/simmux.h" namespace qsim { @@ -36,24 +43,27 @@ struct DummyGate { GateKind kind; unsigned time; + uint64_t cmask; std::vector qubits; std::vector controlled_by; + Matrix matrix; bool unfusible; }; DummyGate CreateDummyGate(unsigned time, std::vector&& qubits) { - return {kGateOther, time, std::move(qubits), {}, false}; + return {kGateOther, time, 0, std::move(qubits), {}, {}, false}; } DummyGate CreateDummyMeasurementGate(unsigned time, std::vector&& qubits) { - return {kMeasurement, time, std::move(qubits), {}, false}; + return {kMeasurement, time, 0, std::move(qubits), {}, {}, false}; } DummyGate CreateDummyControlledGate(unsigned time, std::vector&& qubits, std::vector&& controlled_by) { - return {kGateOther, time, std::move(qubits), std::move(controlled_by), false}; + return + {kGateOther, time, 0, std::move(qubits), std::move(controlled_by), {}, false}; } std::vector GenQubits(unsigned num_qubits, std::mt19937& rgen, @@ -75,9 +85,9 @@ constexpr double p2 = 0.6 + p1; constexpr double p3 = 0.08 + p2; constexpr double p4 = 0.05 + p3; constexpr double p5 = 0.035 + p4; -constexpr double p6 = 0.025 + p5; -constexpr double pc = 0.005 + p6; -constexpr double pm = 0.005 + pc; +constexpr double p6 = 0.02 + p5; +constexpr double pc = 0.0075 + p6; +constexpr double pm = 0.0075 + pc; constexpr double pu = 0.002; @@ -121,6 +131,12 @@ void AddToCircuit(unsigned time, if (r < p6 && distr(rgen) < pu) { circuit.back().unfusible = true; } + + auto& gate = circuit.back(); + + if (gate.kind != gate::kMeasurement) { + std::sort(gate.qubits.begin(), gate.qubits.end()); + } } std::vector GenerateRandomCircuit1(unsigned num_qubits, @@ -152,7 +168,7 @@ std::vector GenerateRandomCircuit2(unsigned num_qubits, std::vector circuit; circuit.reserve(num_qubits * depth); - std::mt19937 rgen(1); + std::mt19937 rgen(2); std::uniform_real_distribution distr(0, 1); std::vector available(num_qubits, 0); @@ -238,7 +254,7 @@ TEST(FuserMultiQubitTest, RandomCircuit1) { auto circuit = GenerateRandomCircuit1(num_qubits, depth); Fuser::Parameter param; - param.fuser_verbosity = 0; + param.verbosity = 0; for (unsigned q = 2; q <= 6; ++q) { param.max_fused_size = q; @@ -268,7 +284,7 @@ TEST(FuserMultiQubitTest, RandomCircuit2) { auto circuit = GenerateRandomCircuit2(num_qubits, depth, 6); Fuser::Parameter param; - param.fuser_verbosity = 0; + param.verbosity = 0; for (unsigned q = 2; q <= 6; ++q) { param.max_fused_size = q; @@ -287,6 +303,74 @@ TEST(FuserMultiQubitTest, RandomCircuit2) { } } +TEST(FuserMultiQubitTest, Simulation) { + using Fuser = MultiQubitGateFuser; + + unsigned num_qubits = 12; + unsigned depth = 200; + + auto circuit = GenerateRandomCircuit2(num_qubits, depth, 6); + + std::mt19937 rgen(1); + std::uniform_real_distribution distr(0, 1); + + for (auto& gate : circuit) { + if (gate.controlled_by.size() > 0) { + gate.cmask = (uint64_t{1} << gate.controlled_by.size()) - 1; + } + + unsigned size = unsigned{1} << (2 * gate.qubits.size() + 1); + gate.matrix.reserve(size); + + // Random gate matrices. + for (unsigned i = 0; i < size; ++i) { + gate.matrix.push_back(2 * distr(rgen) - 1); + } + }; + + using StateSpace = typename Simulator::StateSpace; + + Simulator simulator(1); + StateSpace state_space(1); + + auto state0 = state_space.Create(num_qubits); + state_space.SetStateZero(state0); + + // Simulate unfused gates. + for (const auto& gate : circuit) { + ApplyGate(simulator, gate, state0); + // Renormalize the state to prevent floating point overflow. + state_space.Multiply(1.0 / std::sqrt(state_space.Norm(state0)), state0); + } + + Fuser::Parameter param; + param.verbosity = 0; + + auto state1 = state_space.Create(num_qubits); + + for (unsigned q = 2; q <= 6; ++q) { + state_space.SetStateZero(state1); + + param.max_fused_size = q; + auto fused_gates = Fuser::FuseGates( + param, num_qubits, circuit.begin(), circuit.end()); + + EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); + + // Simulate fused gates. + for (const auto& gate : fused_gates) { + ApplyFusedGate(simulator, gate, state1); + // Renormalize the state to prevent floating point overflow. + state_space.Multiply(1.0 / std::sqrt(state_space.Norm(state1)), state1); + } + + unsigned size = 1 << (num_qubits + 1); + for (unsigned i = 0; i < size; ++i) { + EXPECT_NEAR(state0.get()[i], state1.get()[i], 1e-6); + } + } +} + } // namespace qsim int main(int argc, char** argv) { From d802391da1e96a7e441fc95acc7e44cae18ca63b Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Wed, 25 Nov 2020 21:28:19 +0100 Subject: [PATCH 3/4] Recommended fixes. --- lib/fuser_mqubit.h | 395 ++++++++++++++++++++------------------------- 1 file changed, 175 insertions(+), 220 deletions(-) diff --git a/lib/fuser_mqubit.h b/lib/fuser_mqubit.h index c5925a2a..16a33d37 100644 --- a/lib/fuser_mqubit.h +++ b/lib/fuser_mqubit.h @@ -74,9 +74,13 @@ class MultiQubitGateFuser { std::vector links_; }; + struct GateF; + + using LinkManager = LinkManagerT; + using Link = typename LinkManager::Link; + // Intermediate representation of a fused gate. struct GateF { - using Link = typename LinkManagerT::Link; const Gate* parent; std::vector qubits; std::vector gates; // Gates that get fused to this gate. @@ -85,8 +89,19 @@ class MultiQubitGateFuser { unsigned visited; }; - using LinkManager = LinkManagerT; - using Link = typename LinkManager::Link; + // Possible values for visited in GateF. + // Note that MakeGateSequence assignes values from kSecond to the number of + // gates in the sequence plus one, see below. + enum Visited { + kZero = 0, // Start value for "normal" gates. + kFirst = 1, // Valut after the first pass for partially fused + // "normal" gates. + kSecond = 2, // Start value to assign values in MakeGateSequence. + kCompress = 99999997, // Used to compress links. + kMeaCnt = 99999998, // Start value for controlled or measurement gates. + kFinal = 99999999, // Value after the second pass for fused "normal" + // gates or for controlled and measurement gates. + }; struct Stat { unsigned num_mea_gates = 0; @@ -124,7 +139,8 @@ class MultiQubitGateFuser { struct Parameter { /** * Maximum number of qubits in a fused gate. It can take values from 2 to - * 6 (0 and 1 are equivalent to 2). It is not recommended to use 5 or 6. + * 6 (0 and 1 are equivalent to 2). It is not recommended to use 5 or 6 as + * that might degrade performance for not very fast machines. */ unsigned max_fused_size = 2; unsigned verbosity = 0; @@ -230,6 +246,16 @@ class MultiQubitGateFuser { gates_seq.reserve(num_gates); gates_lat.reserve(num_qubits); + Scratch scratch; + + scratch.data.reserve(1024); + scratch.prev1.reserve(32); + scratch.prev2.reserve(32); + scratch.next1.reserve(32); + scratch.next2.reserve(32); + scratch.longest_seq.reserve(8); + scratch.stack.reserve(8); + Stat stat; stat.num_gates.resize(num_qubits + 1, 0); @@ -276,7 +302,7 @@ class MultiQubitGateFuser { if (last_mea_gate == nullptr || last_mea_gate->parent->time != gate.time) { - gates_seq.push_back({&gate, {}, {}, {}, 0, 997}); + gates_seq.push_back({&gate, {}, {}, {}, 0, kMeaCnt}); last_mea_gate = &gates_seq.back(); last_mea_gate->qubits.reserve(num_qubits); @@ -296,7 +322,7 @@ class MultiQubitGateFuser { ++stat.num_mea_gates; } else { - gates_seq.push_back({&gate, {}, {}, {}, 0, 0}); + gates_seq.push_back({&gate, {}, {}, {}, 0, kZero}); auto& fgate = gates_seq.back(); if (gate.controlled_by.size() == 0) { @@ -327,7 +353,7 @@ class MultiQubitGateFuser { fgate.qubits.reserve(gate.qubits.size()); fgate.links.reserve(size); - fgate.visited = 997; + fgate.visited = kMeaCnt; fgate.gates.push_back(&gate); ++stat.num_controlled_gates; @@ -353,7 +379,7 @@ class MultiQubitGateFuser { if (max_fused_size > 2) { FuseGateSequences( - max_fused_size, num_qubits, gates_seq, stat, fused_gates); + max_fused_size, num_qubits, scratch, gates_seq, stat, fused_gates); } else { for (auto& fgate : gates_seq) { if (fgate.gates.size() > 0) { @@ -362,7 +388,7 @@ class MultiQubitGateFuser { std::move(fgate.qubits), fgate.parent, std::move(fgate.gates)}); - if (fgate.visited != 997) { + if (fgate.visited != kMeaCnt) { ++stat.num_fused_gates; } } @@ -417,11 +443,11 @@ class MultiQubitGateFuser { std::size_t pos = 0; for (auto fgate : fgates[max_gate_size - i]) { - if (fgate->visited > 0) continue; + if (fgate->visited > kZero) continue; fgates[max_gate_size - i][pos++] = fgate; - fgate->visited = 1; + fgate->visited = kFirst; FusePrev(0, *fgate); fgate->gates.push_back(fgate->parent); @@ -432,43 +458,20 @@ class MultiQubitGateFuser { } } - // Try to fuse gate sequences as follows. Gate time goes from left to right. - // - // max_fused_size = 3: - // | - // | + // Try to fuse gate sequences as follows. Gate time goes from bottom to top. + // Gates are fused either from left to right or from right to left. // - // max_fused_size = 4: - // | - // | - // | + // max_fused_size = 3: _- or -_ // - // max_fused_size = 5: - // | - // | - // | - // | + // max_fused_size = 4: _-_ // - // max_fused_size = 6: - // | - // | - // | - // | - // | + // max_fused_size = 5: _-_- or -_-_ // - static void FuseGateSequences(unsigned max_fused_size, unsigned num_qubits, + // max_fused_size = 6: _-_-_ + static void FuseGateSequences(unsigned max_fused_size, + unsigned num_qubits, Scratch& scratch, std::vector& gates_seq, Stat& stat, std::vector& fused_gates) { - Scratch scratch; - - scratch.data.reserve(1024); - scratch.prev1.reserve(32); - scratch.prev2.reserve(32); - scratch.next1.reserve(32); - scratch.next2.reserve(32); - scratch.longest_seq.reserve(8); - scratch.stack.reserve(8); - unsigned prev_time = 0; std::vector orphaned_gates; @@ -484,15 +487,15 @@ class MultiQubitGateFuser { prev_time = fgate.parent->time; } - if (fgate.visited == 999 || fgate.gates.size() == 0) continue; + if (fgate.visited == kFinal || fgate.gates.size() == 0) continue; - if (fgate.visited == 997 || fgate.qubits.size() >= max_fused_size + if (fgate.visited == kMeaCnt || fgate.qubits.size() >= max_fused_size || fgate.parent->unfusible) { - if (fgate.visited != 997) { + if (fgate.visited != kMeaCnt) { ++stat.num_fused_gates; } - fgate.visited = 999; + fgate.visited = kFinal; fused_gates.push_back({fgate.parent->kind, fgate.parent->time, std::move(fgate.qubits), fgate.parent, @@ -541,21 +544,21 @@ class MultiQubitGateFuser { for (std::size_t i = 0; i < orphaned_gates.size(); ++i) { auto ogate1 = orphaned_gates[i]; - if (ogate1->visited == 999) continue; + if (ogate1->visited == kFinal) continue; - ogate1->visited = 999; + ogate1->visited = kFinal; for (std::size_t j = i + 1; j < orphaned_gates.size(); ++j) { auto ogate2 = orphaned_gates[j]; - if (ogate2->visited == 999) continue; + if (ogate2->visited == kFinal) continue; ++count; unsigned cur_size = ogate1->qubits.size() + ogate2->qubits.size(); if (cur_size <= max_fused_size) { - ogate2->visited = 999; + ogate2->visited = kFinal; for (auto q : ogate2->qubits) { ogate1->qubits.push_back(q); @@ -590,21 +593,21 @@ class MultiQubitGateFuser { static void MakeGateSequence( unsigned max_fused_size, Scratch& scratch, GateF& fgate) { - unsigned level = 2 + scratch.count; + unsigned level = kSecond + scratch.count; FindLongestGateSequence(max_fused_size, level, scratch, fgate); auto longest_seq = scratch.longest_seq; if (longest_seq.size() == 1 && scratch.count == 0) { - fgate.visited = 1; + fgate.visited = kFirst; return; } ++scratch.count; for (auto p : longest_seq) { - p->gate->visited = 900; + p->gate->visited = kCompress; for (auto q : p->qubits) { fgate.qubits.push_back(q); @@ -618,11 +621,11 @@ class MultiQubitGateFuser { // Compress links. for (auto& link : fgate.links) { - while (link->prev != nullptr && link->prev->val->visited == 900) { + while (link->prev != nullptr && link->prev->val->visited == kCompress) { link = link->prev; } - while (link->next != nullptr && link->next->val->visited == 900) { + while (link->next != nullptr && link->next->val->visited == kCompress) { LinkManager::Delete(link->next); } } @@ -650,7 +653,7 @@ class MultiQubitGateFuser { } for (auto p : longest_seq) { - p->gate->visited = 999; + p->gate->visited = kFinal; } FuseNext(1, fgate); @@ -675,7 +678,7 @@ class MultiQubitGateFuser { auto pgate = link->prev->val; - if (pgate->visited == 1) { + if (pgate->visited == kFirst) { MakeGateSequence(max_fused_size, scratch, *pgate); } } @@ -717,14 +720,7 @@ class MultiQubitGateFuser { return; } - n1->gate->visited = level; - cur_size = cur_size2; - scratch.stack.push_back(n1); - - if (cur_size > max_size) { - scratch.longest_seq = scratch.stack; - max_size = cur_size; - } + Push(level, cur_size2, cur_size, max_size, scratch, n1); for (auto p1 : scratch.prev1) { unsigned cur_size2 = cur_size + p1->qubits.size(); @@ -737,14 +733,7 @@ class MultiQubitGateFuser { return; } - p1->gate->visited = level; - cur_size = cur_size2; - scratch.stack.push_back(p1); - - if (cur_size > max_size) { - scratch.longest_seq = scratch.stack; - max_size = cur_size; - } + Push(level, cur_size2, cur_size, max_size, scratch, p1); GetNextAvailableGates(max_fused_size, cur_size, *p1->gate, &fgate, scratch.data, scratch.next2); @@ -765,14 +754,7 @@ class MultiQubitGateFuser { return; } - n2->gate->visited = level; - cur_size = cur_size2; - scratch.stack.push_back(n2); - - if (cur_size > max_size) { - scratch.longest_seq = scratch.stack; - max_size = cur_size; - } + Push(level, cur_size2, cur_size, max_size, scratch, n2); for (auto p2 : scratch.prev2) { unsigned cur_size2 = cur_size + p2->qubits.size(); @@ -793,22 +775,34 @@ class MultiQubitGateFuser { } } - n2->gate->visited = 1; - cur_size -= n2->qubits.size(); - scratch.stack.pop_back(); + Pop(cur_size, scratch, n2); } - p1->gate->visited = 1; - cur_size -= p1->qubits.size(); - scratch.stack.pop_back(); + Pop(cur_size, scratch, p1); } - n1->gate->visited = 1; - cur_size -= n1->qubits.size(); - scratch.stack.pop_back(); + Pop(cur_size, scratch, n1); + } + } + + static void Push(unsigned level, unsigned cur_size2, unsigned& cur_size, + unsigned& max_size, Scratch& scratch, GateA* agate) { + agate->gate->visited = level; + cur_size = cur_size2; + scratch.stack.push_back(agate); + + if (cur_size > max_size) { + scratch.longest_seq = scratch.stack; + max_size = cur_size; } } + static void Pop(unsigned& cur_size, Scratch& scratch, GateA* agate) { + agate->gate->visited = kFirst; + cur_size -= agate->qubits.size(); + scratch.stack.pop_back(); + } + static void GetNextAvailableGates(unsigned max_fused_size, unsigned cur_size, const GateF& pgate1, const GateF* pgate2, std::vector& scratch, @@ -820,7 +814,7 @@ class MultiQubitGateFuser { auto ngate = link->next->val; - if (ngate->visited > 1 || ngate->parent->unfusible) continue; + if (ngate->visited > kFirst || ngate->parent->unfusible) continue; GateA next = {ngate, {}, {}}; next.qubits.reserve(8); @@ -847,9 +841,9 @@ class MultiQubitGateFuser { auto pgate = link->prev->val; - if (pgate->visited > 997 || pgate->visited == level) continue; + if (pgate->visited == kFinal || pgate->visited == level) continue; - if (pgate->visited > 1 || pgate->parent->unfusible) { + if (pgate->visited > kFirst || pgate->parent->unfusible) { prev_gates.resize(0); return false; } @@ -865,7 +859,7 @@ class MultiQubitGateFuser { for (auto link : pgate->links) { if (link->prev == nullptr) continue; - if (link->prev->val->visited <= 997) { + if (link->prev->val->visited <= kMeaCnt) { all_prev_visited = false; break; } @@ -892,72 +886,28 @@ class MultiQubitGateFuser { for (std::size_t i = 0; i < fgate2.qubits.size(); ++i) { unsigned q2 = fgate2.qubits[i]; - bool have_added_qubits = true; + if (std::find(fgate0.qubits.begin(), fgate0.qubits.end(), q2) + != fgate0.qubits.end()) continue; - for (auto q0 : fgate0.qubits) { - if (q0 == q2) { - have_added_qubits = false; - break; - } - } + if (fgate1 != nullptr + && std::find(fgate1->qubits.begin(), fgate1->qubits.end(), q2) + != fgate1->qubits.end()) continue; - if (have_added_qubits && fgate1 != nullptr) { - for (auto q1 : fgate1->qubits) { - if (q1 == q2) { - have_added_qubits = false; - break; - } - } - } - - if (have_added_qubits) { - added.qubits.push_back(q2); - added.links.push_back(fgate2.links[i]); - } + added.qubits.push_back(q2); + added.links.push_back(fgate2.links[i]); } } // Fuse smaller gates with fgate back in gate time. - static void FusePrev(unsigned level, GateF& fgate) { + static void FusePrev(unsigned pass, GateF& fgate) { std::vector gates; gates.reserve(fgate.gates.capacity()); - uint64_t bad_mask = 0; - auto links = fgate.links; - - bool may_have_gates_to_fuse = true; - - while (may_have_gates_to_fuse) { - may_have_gates_to_fuse = false; - - std::sort(links.begin(), links.end(), - [](const Link* l, const Link* r) -> bool { - auto lp = l->prev; - auto rp = r->prev; - - if (lp != nullptr && rp != nullptr) { - return lp->val->parent->time > rp->val->parent->time; - } else { - return lp != nullptr || rp == nullptr; - } - }); - - for (auto link : links) { - if (link->prev == nullptr) continue; + auto neighbor = [](const Link* link) -> const Link* { + return link->prev; + }; - auto p = link->prev->val; - - if (!QubitsAreIn(fgate.mask, p->mask) || (p->mask & bad_mask) != 0 - || p->visited > level || p->parent->unfusible) { - bad_mask |= p->mask; - } else { - AddGates(level, *p, gates); - - may_have_gates_to_fuse = true; - break; - } - } - } + FusePrevOrNext>(pass, neighbor, fgate, gates); for (auto it = gates.rbegin(); it != gates.rend(); ++it) { fgate.gates.push_back(*it); @@ -965,7 +915,17 @@ class MultiQubitGateFuser { } // Fuse smaller gates with fgate forward in gate time. - static void FuseNext(unsigned level, GateF& fgate) { + static void FuseNext(unsigned pass, GateF& fgate) { + auto neighbor = [](const Link* link) -> const Link* { + return link->next; + }; + + FusePrevOrNext>(pass, neighbor, fgate, fgate.gates); + } + + template + static void FusePrevOrNext(unsigned pass, Neighbor neighb, GateF& fgate, + std::vector& gates) { uint64_t bad_mask = 0; auto links = fgate.links; @@ -974,30 +934,42 @@ class MultiQubitGateFuser { while (may_have_gates_to_fuse) { may_have_gates_to_fuse = false; - auto time = fgate.parent->time; - std::sort(links.begin(), links.end(), - [&time](const Link* l, const Link* r) -> bool { - auto ln = l->next; - auto rn = r->next; + [&neighb](const Link* l, const Link* r) -> bool { + auto ln = neighb(l); + auto rn = neighb(r); if (ln != nullptr && rn != nullptr) { - return ln->val->parent->time < rn->val->parent->time; + return R()(ln->val->parent->time, rn->val->parent->time); } else { return ln != nullptr || rn == nullptr; } }); for (auto link : links) { - if (link->next == nullptr) continue; + auto n = neighb(link); + + if (n == nullptr) continue; - auto n = link->next->val; + auto g = n->val; - if (!QubitsAreIn(fgate.mask, n->mask) || (n->mask & bad_mask) != 0 - || n->visited > level || n->parent->unfusible) { - bad_mask |= n->mask; + if (!QubitsAreIn(fgate.mask, g->mask) || (g->mask & bad_mask) != 0 + || g->visited > pass || g->parent->unfusible) { + bad_mask |= g->mask; } else { - AddGates(level, *n, fgate.gates); + g->visited = pass == 0 ? kFirst : kFinal; + + if (pass == 0) { + gates.push_back(g->parent); + } else { + for (auto gate : g->gates) { + gates.push_back(gate); + } + } + + for (auto link : g->links) { + LinkManager::Delete(link); + } may_have_gates_to_fuse = true; break; @@ -1006,79 +978,62 @@ class MultiQubitGateFuser { } } - static void AddGates( - unsigned level, GateF& fgate, std::vector& gates) { - fgate.visited = level == 0 ? 1 : 999; - - if (level == 0) { - gates.push_back(fgate.parent); - } else { - for (auto gate :fgate.gates) { - gates.push_back(gate); - } - } - - for (auto link : fgate.links) { - LinkManager::Delete(link); - } - } - static bool QubitsAreIn(uint64_t mask0, uint64_t mask) { return ((mask0 | mask) ^ mask0) == 0; } static void PrintStat(unsigned verbosity, const Stat& stat, const std::vector& fused_gates) { - if (verbosity > 0) { - if (stat.num_controlled_gates > 0) { - IO::messagef("%lu controlled gates\n", stat.num_controlled_gates); + if (verbosity == 0) return; + + if (stat.num_controlled_gates > 0) { + IO::messagef("%lu controlled gates\n", stat.num_controlled_gates); + } + + if (stat.num_mea_gates > 0) { + IO::messagef("%lu measurement gates", stat.num_mea_gates); + if (stat.num_fused_mea_gates == stat.num_mea_gates) { + IO::messagef("\n"); + } else { + IO::messagef(" are fused into %lu gates\n", stat.num_fused_mea_gates); } + } - if (stat.num_mea_gates > 0) { - IO::messagef("%lu measurement gates", stat.num_mea_gates); - if (stat.num_fused_mea_gates == stat.num_mea_gates) { - IO::messagef("\n"); + bool first = true; + for (unsigned i = 1; i < stat.num_gates.size(); ++i) { + if (stat.num_gates[i] > 0) { + if (first) { + first = false; } else { - IO::messagef(" are fused into %lu gates\n", stat.num_fused_mea_gates); + IO::messagef(", "); } + IO::messagef("%u %u-qubit", stat.num_gates[i], i); } + } - bool first = true; - for (unsigned i = 1; i < stat.num_gates.size(); ++i) { - if (stat.num_gates[i] > 0) { - if (first) { - first = false; - } else { - IO::messagef(", "); - } - IO::messagef("%u %u-qubit", stat.num_gates[i], i); - } - } + IO::messagef(" gates are fused into %lu gates\n", stat.num_fused_gates); - IO::messagef(" gates are fused into %lu gates\n", stat.num_fused_gates); - - if (verbosity > 1) { - IO::messagef("fused gate qubits:\n"); - for (const auto g : fused_gates) { - IO::messagef("%6u ", g.parent->time); - if (g.parent->kind == gate::kMeasurement) { - IO::messagef("m"); - } else if (g.parent->controlled_by.size() > 0) { - IO::messagef("c"); - for (auto q : g.parent->controlled_by) { - IO::messagef("%3u", q); - } - IO::messagef(" t"); - } else { - IO::messagef(" "); - } + if (verbosity == 1) return; - for (auto q : g.qubits) { - IO::messagef("%3u", q); - } - IO::messagef("\n"); + IO::messagef("fused gate qubits:\n"); + for (const auto g : fused_gates) { + IO::messagef("%6u ", g.parent->time); + if (g.parent->kind == gate::kMeasurement) { + IO::messagef("m"); + } else if (g.parent->controlled_by.size() > 0) { + IO::messagef("c"); + for (auto q : g.parent->controlled_by) { + IO::messagef("%3u", q); } + IO::messagef(" t"); + } else { + IO::messagef(" "); + } + + for (auto q : g.qubits) { + IO::messagef("%3u", q); } + IO::messagef("\n"); } } }; From 114e86894bbae714816176c5c56ec1b58604f232 Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Wed, 25 Nov 2020 22:50:17 +0100 Subject: [PATCH 4/4] Recommended fixes. --- lib/fuser_mqubit.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fuser_mqubit.h b/lib/fuser_mqubit.h index 16a33d37..c924c326 100644 --- a/lib/fuser_mqubit.h +++ b/lib/fuser_mqubit.h @@ -94,7 +94,7 @@ class MultiQubitGateFuser { // gates in the sequence plus one, see below. enum Visited { kZero = 0, // Start value for "normal" gates. - kFirst = 1, // Valut after the first pass for partially fused + kFirst = 1, // Value after the first pass for partially fused // "normal" gates. kSecond = 2, // Start value to assign values in MakeGateSequence. kCompress = 99999997, // Used to compress links.