From e60275caae4886bcbb2c5a4e1b435547b2ceb467 Mon Sep 17 00:00:00 2001 From: Hiroshi Date: Tue, 26 Jan 2021 20:55:00 +0900 Subject: [PATCH] add fusion varitations --- src/transpile/fusion.hpp | 703 ++++++++++++------ .../backends/qasm_simulator/qasm_fusion.py | 44 +- 2 files changed, 520 insertions(+), 227 deletions(-) diff --git a/src/transpile/fusion.hpp b/src/transpile/fusion.hpp index ae84c0cab8..161e4d9378 100644 --- a/src/transpile/fusion.hpp +++ b/src/transpile/fusion.hpp @@ -32,6 +32,400 @@ using oplist_t = std::vector; using opset_t = Operations::OpSet; using reg_t = std::vector; +class FusionMethod { +public: + // Return name of method + virtual std::string name() = 0; + + // Aggregate a subcircuit of operations into a single operation + virtual op_t generate_operation(std::vector& fusioned_ops) const { + std::set fusioned_qubits; + for (auto & op: fusioned_ops) + fusioned_qubits.insert(op.qubits.begin(), op.qubits.end()); + + reg_t remapped2orig(fusioned_qubits.begin(), fusioned_qubits.end()); + std::unordered_map orig2remapped; + reg_t arg_qubits; + arg_qubits.assign(fusioned_qubits.size(), 0); + for (size_t i = 0; i < remapped2orig.size(); i++) { + orig2remapped[remapped2orig[i]] = i; + arg_qubits[i] = i; + } + + // Remap qubits + for (auto & op: fusioned_ops) + for (size_t i = 0; i < op.qubits.size(); i++) + op.qubits[i] = orig2remapped[op.qubits[i]]; + + auto fusioned_op = generate_operation_internal(fusioned_ops, arg_qubits); + + // Revert qubits + for (size_t i = 0; i < fusioned_op.qubits.size(); i++) + fusioned_op.qubits[i] = remapped2orig[fusioned_op.qubits[i]]; + + return fusioned_op; + }; + + virtual op_t generate_operation_internal(const std::vector& fusioned_ops, + const reg_t &fusioned_qubits) const = 0; + + virtual bool can_apply(const op_t& op, uint_t max_fused_qubits) const = 0; + + virtual bool can_ignore(const op_t& op) const { + switch (op.type) { + case optype_t::barrier: + return true; + case optype_t::gate: + return op.name == "id" || op.name == "u0"; + default: + return false; + } + } + + static FusionMethod& find_method(const Circuit& circ, + const opset_t &allowed_opset, + const bool allow_superop, + const bool allow_kraus); + + static bool exist_non_unitary(const std::vector& fusioned_ops) { + for (auto & op: fusioned_ops) + if (noise_opset_.contains(op.type)) + return true; + return false; + }; + +private: + const static Operations::OpSet noise_opset_; +}; + +const Operations::OpSet FusionMethod::noise_opset_( + {Operations::OpType::kraus, + Operations::OpType::superop, + Operations::OpType::reset}, + {}, {} +); + +class UnitaryFusion : public FusionMethod { +public: + virtual std::string name() { return "unitary"; }; + + virtual op_t generate_operation_internal(const std::vector& fusioned_ops, + const reg_t &qubits) const { + // Run simulation + RngEngine dummy_rng; + ExperimentResult dummy_result; + + // Unitary simulation + QubitUnitary::State<> unitary_simulator; + unitary_simulator.initialize_qreg(qubits.size()); + unitary_simulator.apply_ops(fusioned_ops, dummy_result, dummy_rng); + return Operations::make_unitary(qubits, unitary_simulator.qreg().move_to_matrix(), + std::string("fusion")); + }; + + virtual bool can_apply(const op_t& op, uint_t max_fused_qubits) const { + if (op.conditional) + return false; + switch (op.type) { + case optype_t::matrix: + return op.mats.size() == 1 && op.qubits.size() <= max_fused_qubits; + case optype_t::kraus: + case optype_t::reset: + case optype_t::superop: { + return false; + } + case optype_t::gate: { + if (op.qubits.size() > max_fused_qubits) + return false; + return QubitUnitary::StateOpSet.contains_gates(op.name); + } + case optype_t::measure: + case optype_t::bfunc: + case optype_t::roerror: + case optype_t::snapshot: + case optype_t::barrier: + default: + return false; + } + }; +}; + +class SuperOpFusion : public UnitaryFusion { +public: + virtual std::string name() { return "superop"; }; + + virtual op_t generate_operation_internal(const std::vector& fusioned_ops, + const reg_t &qubits) const { + + if (!exist_non_unitary(fusioned_ops)) + return UnitaryFusion::generate_operation_internal(fusioned_ops, qubits); + + // Run simulation + RngEngine dummy_rng; + ExperimentResult dummy_result; + + // For both Kraus and SuperOp method we simulate using superoperator + // simulator + QubitSuperoperator::State<> superop_simulator; + superop_simulator.initialize_qreg(qubits.size()); + superop_simulator.apply_ops(fusioned_ops, dummy_result, dummy_rng); + auto superop = superop_simulator.qreg().move_to_matrix(); + + return Operations::make_superop(qubits, std::move(superop)); + }; + + virtual bool can_apply(const op_t& op, uint_t max_fused_qubits) const { + if (op.conditional) + return false; + switch (op.type) { + case optype_t::kraus: + case optype_t::reset: + case optype_t::superop: { + return op.qubits.size() <= max_fused_qubits; + } + case optype_t::gate: { + if (op.qubits.size() > max_fused_qubits) + return false; + return QubitSuperoperator::StateOpSet.contains_gates(op.name); + } + default: + return UnitaryFusion::can_apply(op, max_fused_qubits); + } + }; +}; + +class KrausFusion : public UnitaryFusion { +public: + virtual std::string name() { return "kraus"; }; + + virtual op_t generate_operation_internal(const std::vector& fusioned_ops, + const reg_t &qubits) const { + + if (!exist_non_unitary(fusioned_ops)) + return UnitaryFusion::generate_operation_internal(fusioned_ops, qubits); + + // Run simulation + RngEngine dummy_rng; + ExperimentResult dummy_result; + + // For both Kraus and SuperOp method we simulate using superoperator + // simulator + QubitSuperoperator::State<> superop_simulator; + superop_simulator.initialize_qreg(qubits.size()); + superop_simulator.apply_ops(fusioned_ops, dummy_result, dummy_rng); + auto superop = superop_simulator.qreg().move_to_matrix(); + + // If Kraus method we convert superop to canonical Kraus representation + size_t dim = 1 << qubits.size(); + return Operations::make_kraus(qubits, Utils::superop2kraus(superop, dim)); + }; + + virtual bool can_apply(const op_t& op, uint_t max_fused_qubits) const { + if (op.conditional) + return false; + switch (op.type) { + case optype_t::kraus: + case optype_t::reset: + case optype_t::superop: { + return op.qubits.size() <= max_fused_qubits; + } + case optype_t::gate: { + if (op.qubits.size() > max_fused_qubits) + return false; + return QubitSuperoperator::StateOpSet.contains_gates(op.name); + } + default: + return UnitaryFusion::can_apply(op, max_fused_qubits); + } + }; +}; + +FusionMethod& FusionMethod::find_method(const Circuit& circ, + const opset_t &allowed_opset, + const bool allow_superop, + const bool allow_kraus) { + static UnitaryFusion unitary; + static SuperOpFusion superOp; + static KrausFusion kraus; + + if (allow_superop && allowed_opset.contains(optype_t::superop) && + (circ.opset().contains(optype_t::kraus) + || circ.opset().contains(optype_t::superop) + || circ.opset().contains(optype_t::reset))) { + return superOp; + } else if (allow_kraus && allowed_opset.contains(optype_t::kraus) && + (circ.opset().contains(optype_t::kraus) + || circ.opset().contains(optype_t::superop))) { + return kraus; + } else { + return unitary; + } +} + +class Fuser { +public: + virtual void set_config(const json_t &config) = 0; + + virtual void set_metadata(ExperimentResult &result) const { }; //nop + + virtual bool aggregate_operations(oplist_t& ops, + const int fusion_start, + const int fusion_end, + const uint_t max_fused_qubits, + const FusionMethod& method) const = 0; +}; + +class CostBasedFusion : public Fuser { +public: + CostBasedFusion() = default; + + virtual void set_config(const json_t &config) override; + + virtual void set_metadata(ExperimentResult &result) const override; + + virtual bool aggregate_operations(oplist_t& ops, + const int fusion_start, + const int fusion_end, + const uint_t max_fused_qubits, + const FusionMethod& method) const override; + +private: + bool is_diagonal(const oplist_t& ops, + const uint_t from, + const uint_t until) const; + + double estimate_cost(const oplist_t& ops, + const uint_t from, + const uint_t until) const; + + void add_fusion_qubits(reg_t& fusion_qubits, const op_t& op) const; + +private: + bool active = true; + double cost_factor = 1.8; +}; + +template +class NQubitFusion : public Fuser { +public: + virtual void set_config(const json_t &config) override; + + virtual bool aggregate_operations(oplist_t& ops, + const int fusion_start, + const int fusion_end, + const uint_t max_fused_qubits, + const FusionMethod& method) const override; + + bool exclude_entangled_qubits(std::vector& fusing_qubits, + const op_t& tgt_op) const; +private: + bool active = true; + uint_t qubit_threshold = 5; +}; + +template +void NQubitFusion::set_config(const json_t &config) { + if (JSON::check_key("fusion_enable.n_qubits", config)) + JSON::get_value(active, "fusion_enable.n_qubits", config); + + std::stringstream opt_name; + opt_name << "fusion_enable." << N << "_qubits"; + + if (JSON::check_key(opt_name.str(), config)) + JSON::get_value(active, opt_name.str(), config); +} + +template +bool NQubitFusion::exclude_entangled_qubits(std::vector& fusing_qubits, + const op_t& tgt_op) const { + bool included = true; + for (const auto qubit: tgt_op.qubits) + included &= (std::find(fusing_qubits.begin(), fusing_qubits.end(), qubit) != fusing_qubits.end()); + + if (included) + return false; + + for (const int op_qubit: tgt_op.qubits) { + auto found = std::find(fusing_qubits.begin(), fusing_qubits.end(), op_qubit); + if (found != fusing_qubits.end()) + fusing_qubits.erase(found); + } + return true; +} + +template +bool NQubitFusion::aggregate_operations(oplist_t& ops, + const int fusion_start, + const int fusion_end, + const uint_t max_fused_qubits, + const FusionMethod& method) const { + if (!active) + return false; + + std::vector>> targets; + bool fused = false; + + for (uint_t op_idx = fusion_start; op_idx < fusion_end; ++op_idx) { + // skip operations to be ignored + if (!method.can_apply(ops[op_idx], max_fused_qubits) || ops[op_idx].type == optype_t::nop) + continue; + + // 1. find a N-qubit operation + if (ops[op_idx].qubits.size() != N) + continue; + + std::vector fusing_op_idxs = { op_idx }; + + std::vector fusing_qubits; + fusing_qubits.insert(fusing_qubits.end(), ops[op_idx].qubits.begin(), ops[op_idx].qubits.end()); + + // 2. fuse operations with backwarding + for (int fusing_op_idx = op_idx - 1; fusing_op_idx >= fusion_start; --fusing_op_idx) { + auto& tgt_op = ops[fusing_op_idx]; + if (tgt_op.type == optype_t::nop) + continue; + if (!method.can_apply(tgt_op, max_fused_qubits)) + break; + // check all the qubits are in fusing_qubits + if (!exclude_entangled_qubits(fusing_qubits, tgt_op)) + fusing_op_idxs.push_back(fusing_op_idx); // All the qubits of tgt_op are in fusing_qubits + else if (fusing_qubits.empty()) + break; + } + + std::reverse(fusing_op_idxs.begin(), fusing_op_idxs.end()); + + // 3. fuse operations with forwarding + for (int fusing_op_idx = op_idx + 1; fusing_op_idx < fusion_end; ++fusing_op_idx) { + auto& tgt_op = ops[fusing_op_idx]; + if (tgt_op.type == optype_t::nop) + continue; + if (!method.can_apply(tgt_op, max_fused_qubits)) + break; + // check all the qubits are in fusing_qubits + if (!exclude_entangled_qubits(fusing_qubits, tgt_op)) + fusing_op_idxs.push_back(fusing_op_idx); // All the qubits of tgt_op are in fusing_qubits + else if (fusing_qubits.empty()) + break; + } + + if (fusing_op_idxs.size() <= 1) + continue; + + // 4. copy operations while setting them as nop + std::vector fusing_ops; + for (auto fusing_op_idx : fusing_op_idxs) { + fusing_ops.push_back(ops[fusing_op_idx]); //copy + ops[fusing_op_idx].type = optype_t::nop; + } + + // 5. generate a fused operation + ops[op_idx] = method.generate_operation(fusing_ops); + fused = true; + } + + return fused; +} class Fusion : public CircuitOptimization { public: @@ -49,15 +443,8 @@ class Fusion : public CircuitOptimization { * - fusion_cost_factor (double): a cost function to estimate an aggregate * gate [Default: 1.8] */ - Fusion(uint_t _max_qubit = 5, uint_t _threshold = 14, double _cost_factor = 1.8) - : max_qubit(_max_qubit), threshold(_threshold), cost_factor(_cost_factor) {} + Fusion(); - // Allowed fusion methods: - // - Unitary: only fuse gates into unitary instructions - // - SuperOp: fuse gates, reset, kraus, and superops into kraus instuctions - // - Kraus: fuse gates, reset, kraus, and superops into kraus instuctions - enum class Method {unitary, kraus, superop}; - void set_config(const json_t &config) override; virtual void set_parallelization(uint_t num) { parallelization_ = num; }; @@ -70,9 +457,9 @@ class Fusion : public CircuitOptimization { ExperimentResult &result) const override; // Qubit threshold for activating fusion pass - uint_t max_qubit; - uint_t threshold; - double cost_factor; + uint_t max_qubit = 5; + uint_t threshold = 14; + bool verbose = false; bool active = true; bool allow_superop = false; @@ -84,57 +471,27 @@ class Fusion : public CircuitOptimization { uint_t parallel_threshold_ = 10000; private: - bool can_ignore(const op_t& op) const; - - bool can_apply_fusion(const op_t& op, - uint_t max_max_fused_qubits, - Method method) const; - - double get_cost(const op_t& op) const; - void optimize_circuit(Circuit& circ, - Noise::NoiseModel& noise, + const Noise::NoiseModel& noise, const opset_t &allowed_opset, - uint_t ops_start, - uint_t ops_end) const; - - bool aggregate_operations(oplist_t& ops, - const int fusion_start, - const int fusion_end, - uint_t max_fused_qubits, - Method method) const; - - // Aggregate a subcircuit of operations into a single operation - op_t generate_fusion_operation(const std::vector& fusioned_ops, - const reg_t &num_qubits, - Method method) const; - - bool is_diagonal(const oplist_t& ops, - const uint_t from, - const uint_t until) const; - - double estimate_cost(const oplist_t& ops, - const uint_t from, - const uint_t until) const; - - void add_fusion_qubits(reg_t& fusion_qubits, const op_t& op) const; + const uint_t ops_start, + const uint_t ops_end, + const std::shared_ptr fuser, + const FusionMethod& method) const; #ifdef DEBUG void dump(const Circuit& circuit) const; #endif private: - const static Operations::OpSet noise_opset_; + std::vector> fusers; }; - -const Operations::OpSet Fusion::noise_opset_( - {Operations::OpType::kraus, - Operations::OpType::superop, - Operations::OpType::reset}, - {}, {} -); - +Fusion::Fusion() { + fusers.push_back(std::make_shared>()); + fusers.push_back(std::make_shared>()); + fusers.push_back(std::make_shared()); +} void Fusion::set_config(const json_t &config) { @@ -152,9 +509,9 @@ void Fusion::set_config(const json_t &config) { if (JSON::check_key("fusion_threshold", config_)) JSON::get_value(threshold, "fusion_threshold", config_); - if (JSON::check_key("fusion_cost_factor", config)) - JSON::get_value(cost_factor, "fusion_cost_factor", config); - + for (std::shared_ptr fuser: fusers) + fuser->set_config(config_); + if (JSON::check_key("fusion_allow_kraus", config)) JSON::get_value(allow_kraus, "fusion_allow_kraus", config); @@ -182,7 +539,6 @@ void Fusion::optimize_circuit(Circuit& circ, result.metadata.add(true, "fusion", "enabled"); result.metadata.add(threshold, "fusion", "threshold"); - result.metadata.add(cost_factor, "fusion", "cost_factor"); result.metadata.add(max_qubit, "fusion", "max_fused_qubits"); // Check qubit threshold @@ -190,185 +546,97 @@ void Fusion::optimize_circuit(Circuit& circ, result.metadata.add(false, "fusion", "applied"); return; } + // Determine fusion method - // TODO: Support Kraus fusion method - Method method = Method::unitary; - if (allow_superop && allowed_opset.contains(optype_t::superop) && - (circ.opset().contains(optype_t::kraus) - || circ.opset().contains(optype_t::superop) - || circ.opset().contains(optype_t::reset))) { - method = Method::superop; - } else if (allow_kraus && allowed_opset.contains(optype_t::kraus) && - (circ.opset().contains(optype_t::kraus) - || circ.opset().contains(optype_t::superop))) { - method = Method::kraus; - } - if (method == Method::unitary) { - result.metadata.add("unitary", "fusion", "method"); - } else if (method == Method::superop) { - result.metadata.add("superop", "fusion", "method"); - } else if (method == Method::kraus) { - result.metadata.add("kraus", "fusion", "method"); - } + FusionMethod& method = FusionMethod::find_method(circ, allowed_opset, allow_superop, allow_kraus); + result.metadata.add(method.name(), "fusion", "method"); - if (circ.ops.size() < parallel_threshold_ || parallelization_ <= 1) { - optimize_circuit(circ, noise, allowed_opset, 0, circ.ops.size()); - } else { - // determine unit for each OMP thread - int_t unit = circ.ops.size() / parallelization_; - if (circ.ops.size() % parallelization_) - ++unit; + bool applied = false; + for (std::shared_ptr fuser: fusers) { + + fuser->set_metadata(result); + + if (circ.ops.size() < parallel_threshold_ || parallelization_ <= 1) { + optimize_circuit(circ, noise, allowed_opset, 0, circ.ops.size(), fuser, method); + result.metadata.add(1, "fusion", "parallelization"); + } else { + // determine unit for each OMP thread + int_t unit = circ.ops.size() / parallelization_; + if (circ.ops.size() % parallelization_) + ++unit; #pragma omp parallel for if (parallelization_ > 1) num_threads(parallelization_) - for (int_t i = 0; i < parallelization_; i++) { - int_t start = unit * i; - int_t end = std::min(start + unit, (int_t) circ.ops.size()); - optimize_circuit(circ, noise, allowed_opset, start, end); + for (int_t i = 0; i < parallelization_; i++) { + int_t start = unit * i; + int_t end = std::min(start + unit, (int_t) circ.ops.size()); + optimize_circuit(circ, noise, allowed_opset, start, end, fuser, method); + } + result.metadata.add(parallelization_, "fusion", "parallelization"); } - } - - result.metadata.add(parallelization_, "fusion", "parallelization"); - auto timer_stop = clock_t::now(); - result.metadata.add(std::chrono::duration(timer_stop - timer_start).count(), "fusion", "time_taken"); + size_t idx = 0; + for (size_t i = 0; i < circ.ops.size(); ++i) { + if (circ.ops[i].type != optype_t::nop) { + if (i != idx) + circ.ops[idx] = circ.ops[i]; + ++idx; + } + } - size_t idx = 0; - for (size_t i = 0; i < circ.ops.size(); ++i) { - if (circ.ops[i].type != optype_t::nop) { - if (i != idx) - circ.ops[idx] = circ.ops[i]; - ++idx; + if (idx != circ.ops.size()) { + applied = true; + circ.ops.erase(circ.ops.begin() + idx, circ.ops.end()); + circ.set_params(); } } + result.metadata.add(applied, "fusion", "applied"); + if (applied && verbose) + result.metadata.add(circ.ops, "fusion", "output_ops"); - if (idx == circ.ops.size()) { - result.metadata.add(false, "fusion", "applied"); - } else { - circ.ops.erase(circ.ops.begin() + idx, circ.ops.end()); - result.metadata.add(true, "fusion", "applied"); - circ.set_params(); - - if (verbose) - result.metadata.add(circ.ops, "fusion", "output_ops"); - } + auto timer_stop = clock_t::now(); + result.metadata.add(std::chrono::duration(timer_stop - timer_start).count(), "fusion", "time_taken"); } void Fusion::optimize_circuit(Circuit& circ, - Noise::NoiseModel& noise, + const Noise::NoiseModel& noise, const opset_t &allowed_opset, - uint_t ops_start, - uint_t ops_end) const { - - // Determine fusion method - // TODO: Support Kraus fusion method - Method method = Method::unitary; - if (allow_superop && allowed_opset.contains(optype_t::superop) && - (circ.opset().contains(optype_t::kraus) - || circ.opset().contains(optype_t::superop) - || circ.opset().contains(optype_t::reset))) { - method = Method::superop; - } else if (allow_kraus && allowed_opset.contains(optype_t::kraus) && - (circ.opset().contains(optype_t::kraus) - || circ.opset().contains(optype_t::superop))) { - method = Method::kraus; - } + const uint_t ops_start, + const uint_t ops_end, + const std::shared_ptr fuser, + const FusionMethod& method) const { uint_t fusion_start = ops_start; uint_t op_idx; for (op_idx = ops_start; op_idx < ops_end; ++op_idx) { - if (can_ignore(circ.ops[op_idx])) + if (method.can_ignore(circ.ops[op_idx])) continue; - if (!can_apply_fusion(circ.ops[op_idx], max_qubit, method) || op_idx == (ops_end - 1)) { - aggregate_operations(circ.ops, fusion_start, op_idx, max_qubit, method); + if (!method.can_apply(circ.ops[op_idx], max_qubit) || op_idx == (ops_end - 1)) { + fuser->aggregate_operations(circ.ops, fusion_start, op_idx, max_qubit, method); fusion_start = op_idx + 1; } } } -bool Fusion::can_ignore(const op_t& op) const { - switch (op.type) { - case optype_t::barrier: - return true; - case optype_t::gate: - return op.name == "id" || op.name == "u0"; - default: - return false; - } -} - -bool Fusion::can_apply_fusion(const op_t& op, uint_t max_fused_qubits, Method method) const { - if (op.conditional) - return false; - switch (op.type) { - case optype_t::matrix: - return op.mats.size() == 1 && op.qubits.size() <= max_fused_qubits; - case optype_t::kraus: - case optype_t::reset: - case optype_t::superop: { - return method != Method::unitary && op.qubits.size() <= max_fused_qubits; - } - case optype_t::gate: { - if (op.qubits.size() > max_fused_qubits) - return false; - return (method == Method::unitary) - ? QubitUnitary::StateOpSet.contains_gates(op.name) - : QubitSuperoperator::StateOpSet.contains_gates(op.name); - } - case optype_t::measure: - case optype_t::bfunc: - case optype_t::roerror: - case optype_t::snapshot: - case optype_t::barrier: - default: - return false; - } -} - -double Fusion::get_cost(const op_t& op) const { - if (can_ignore(op)) - return .0; - else - return cost_factor; +void CostBasedFusion::set_metadata(ExperimentResult &result) const { + result.metadata.add(cost_factor, "fusion", "cost_factor"); } +void CostBasedFusion::set_config(const json_t &config) { -op_t Fusion::generate_fusion_operation(const std::vector& fusioned_ops, - const reg_t &qubits, - Method method) const { - // Run simulation - RngEngine dummy_rng; - ExperimentResult dummy_result; - - if (method == Method::unitary) { - // Unitary simulation - QubitUnitary::State<> unitary_simulator; - unitary_simulator.initialize_qreg(qubits.size()); - unitary_simulator.apply_ops(fusioned_ops, dummy_result, dummy_rng); - return Operations::make_unitary(qubits, unitary_simulator.move_to_matrix(), - std::string("fusion")); - } - - // For both Kraus and SuperOp method we simulate using superoperator - // simulator - QubitSuperoperator::State<> superop_simulator; - superop_simulator.initialize_qreg(qubits.size()); - superop_simulator.apply_ops(fusioned_ops, dummy_result, dummy_rng); - auto superop = superop_simulator.move_to_matrix(); - - if (method == Method::superop) { - return Operations::make_superop(qubits, std::move(superop)); - } + if (JSON::check_key("fusion_cost_factor", config)) + JSON::get_value(cost_factor, "fusion_cost_factor", config); - // If Kraus method we convert superop to canonical Kraus representation - size_t dim = 1 << qubits.size(); - return Operations::make_kraus(qubits, Utils::superop2kraus(superop, dim)); + if (JSON::check_key("fusion_enable.cost_based", config)) + JSON::get_value(active, "fusion_enable.cost_based", config); } -bool Fusion::aggregate_operations(oplist_t& ops, +bool CostBasedFusion::aggregate_operations(oplist_t& ops, const int fusion_start, const int fusion_end, - uint_t max_fused_qubits, - Method method) const { + const uint_t max_fused_qubits, + const FusionMethod& method) const { + if (!active) + return false; // costs[i]: estimated cost to execute from 0-th to i-th in original.ops std::vector costs; @@ -377,14 +645,14 @@ bool Fusion::aggregate_operations(oplist_t& ops, // set costs and fusion_to of fusion_start fusion_to.push_back(fusion_start); - costs.push_back(get_cost(ops[fusion_start])); + costs.push_back(method.can_ignore(ops[fusion_start])? .0 : cost_factor); bool applied = false; // calculate the minimal path to each operation in the circuit for (int i = fusion_start + 1; i < fusion_end; ++i) { // init with fusion from i-th to i-th fusion_to.push_back(i); - costs.push_back(costs[i - fusion_start - 1] + get_cost(ops[i])); + costs.push_back(costs[i - fusion_start - 1] + (method.can_ignore(ops[i])? .0 : cost_factor)); for (int num_fusion = 2; num_fusion <= static_cast (max_fused_qubits); ++num_fusion) { // calculate cost if {num_fusion}-qubit fusion is applied @@ -416,9 +684,7 @@ bool Fusion::aggregate_operations(oplist_t& ops, // generate a new circuit with the minimal path to the last operation in the circuit for (int i = fusion_end - 1; i >= fusion_start;) { - int to = fusion_to[i - fusion_start]; - if (to != i) { std::vector fusioned_ops; std::set fusioned_qubits; @@ -428,23 +694,8 @@ bool Fusion::aggregate_operations(oplist_t& ops, ops[j].type = optype_t::nop; } if (!fusioned_ops.empty()) { - // We need to remap qubits in fusion subcircuits for simulation - // TODO: This could be done above during the fusion cost calculation reg_t qubits(fusioned_qubits.begin(), fusioned_qubits.end()); - std::unordered_map qubit_mapping; - for (size_t j = 0; j < qubits.size(); j++) { - qubit_mapping[qubits[j]] = j; - } - // Remap qubits and determine method - bool non_unitary = false; - for (auto & op: fusioned_ops) { - non_unitary |= noise_opset_.contains(op.type); - for (size_t j = 0; j < op.qubits.size(); j++) { - op.qubits[j] = qubit_mapping[op.qubits[j]]; - } - } - Method required_method = (non_unitary) ? method : Method::unitary; - ops[i] = generate_fusion_operation(fusioned_ops, qubits, required_method); + ops[i] = method.generate_operation(fusioned_ops); } } i = to - 1; @@ -456,7 +707,7 @@ bool Fusion::aggregate_operations(oplist_t& ops, // Gate-swap optimized helper functions //------------------------------------------------------------------------------ -bool Fusion::is_diagonal(const std::vector& ops, +bool CostBasedFusion::is_diagonal(const std::vector& ops, const uint_t from, const uint_t until) const { @@ -485,7 +736,7 @@ bool Fusion::is_diagonal(const std::vector& ops, return true; } -double Fusion::estimate_cost(const std::vector& ops, +double CostBasedFusion::estimate_cost(const std::vector& ops, const uint_t from, const uint_t until) const { if (is_diagonal(ops, from, until)) @@ -512,7 +763,7 @@ double Fusion::estimate_cost(const std::vector& ops, return pow(cost_factor, (double) std::max(fusion_qubits.size() - 1, size_t(1))); } -void Fusion::add_fusion_qubits(reg_t& fusion_qubits, const op_t& op) const { +void CostBasedFusion::add_fusion_qubits(reg_t& fusion_qubits, const op_t& op) const { for (const auto &qubit: op.qubits){ if (find(fusion_qubits.begin(), fusion_qubits.end(), qubit) == fusion_qubits.end()){ fusion_qubits.push_back(qubit); diff --git a/test/terra/backends/qasm_simulator/qasm_fusion.py b/test/terra/backends/qasm_simulator/qasm_fusion.py index 838810cc5c..91c5253583 100644 --- a/test/terra/backends/qasm_simulator/qasm_fusion.py +++ b/test/terra/backends/qasm_simulator/qasm_fusion.py @@ -14,9 +14,10 @@ """ # pylint: disable=no-member import copy +import numpy as np from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit -from qiskit.circuit.library import QuantumVolume, QFT +from qiskit.circuit.library import QuantumVolume, QFT, RealAmplitudes from qiskit.compiler import assemble, transpile from qiskit.providers.aer import QasmSimulator from qiskit.providers.aer.noise import NoiseModel @@ -89,6 +90,8 @@ def fusion_options(self, enabled=None, threshold=None, verbose=None, backend_options['max_parallel_threads'] = parallelization backend_options['max_parallel_shots'] = 1 backend_options['max_parallel_state_update'] = parallelization + backend_options['fusion_enable.n_qubits'] = False + backend_options['fusion_enable.cost_based'] = True return backend_options def fusion_metadata(self, result): @@ -407,6 +410,7 @@ def test_fusion_qft(self): self.SIMULATOR, shots=shots, seed_simulator=1) + print(circuit) backend_options = self.fusion_options(enabled=False, threshold=1) result_disabled = self.SIMULATOR.run( @@ -463,3 +467,41 @@ def test_fusion_parallelization(self): result_serial.get_counts(circuit), delta=0.0, msg="parallelized fusion was failed") + + def test_fusion_two_qubits(self): + """Test Fusion parallelization option""" + shots = 100 + num_qubits = 8 + reps = 3 + + circuit = RealAmplitudes(num_qubits=num_qubits, entanglement='linear', reps=reps) + param_binds = {} + for param in circuit.parameters: + param_binds[param] = np.random.random() + + circuit = transpile(circuit.bind_parameters(param_binds), + backend=self.SIMULATOR, + optimization_level=0) + circuit.measure_all() + + qobj = assemble([circuit], + self.SIMULATOR, + shots=shots, + seed_simulator=1) + + backend_options = self.fusion_options(enabled=True, threshold=1) + backend_options['fusion_verbose'] = True + + backend_options['fusion_enable.2_qubits'] = False + result_disabled = self.SIMULATOR.run(qobj, **backend_options).result() + meta_disabled = self.fusion_metadata(result_disabled) + + backend_options['fusion_enable.2_qubits'] = True + result_enabled = self.SIMULATOR.run(qobj, **backend_options).result() + meta_enabled = self.fusion_metadata(result_enabled) + + self.assertTrue(getattr(result_disabled, 'success', 'False')) + self.assertTrue(getattr(result_enabled, 'success', 'False')) + + self.assertTrue(len(meta_enabled['output_ops']) if 'output_ops' in meta_enabled else len(circuit.ops) < + len(meta_disabled['output_ops']) if 'output_ops' in meta_disabled else len(circuit.ops)) \ No newline at end of file