From 62efe420d724db2fe4a6ce01777a9d362e44cda3 Mon Sep 17 00:00:00 2001
From: pablolh
Date: Fri, 18 Nov 2022 17:54:37 +0100
Subject: [PATCH] Upgrade mapper/router to new IR
- New IR in place of old IR
- New DDG is used in place of old scheduler
- Various code improvements (e.g. return result instead of modify
reference parameter)
---
CMakeLists.txt | 1 +
include/ql/com/ddg/build.h | 6 +-
include/ql/com/ddg/ops.h | 2 +
include/ql/com/ddg/types.h | 6 +
include/ql/com/map/reference_updater.h | 33 +
include/ql/ir/compat/gate.h | 20 +-
include/ql/ir/ir.h | 5 +
include/ql/ir/ops.h | 143 ++-
include/ql/ir/swap_parameters.h | 26 +
include/ql/pass/map/qubits/map/map.h | 4 +-
src/ql/com/ddg/build.cc | 12 +-
src/ql/com/ddg/dot.cc | 8 +-
src/ql/com/ddg/ops.cc | 28 +
src/ql/com/ddg/tests/ddg.cc | 2 +-
src/ql/com/map/reference_updater.cc | 44 +
src/ql/com/options.cc | 4 +-
src/ql/ir/cqasm/read.cc | 24 +-
src/ql/ir/old_to_new.cc | 40 +-
src/ql/ir/ops.cc | 54 +-
src/ql/pass/ana/visualize/detail/types.h | 2 +-
src/ql/pass/map/qubits/map/detail/alter.cc | 291 +----
src/ql/pass/map/qubits/map/detail/alter.h | 216 +---
.../pass/map/qubits/map/detail/free_cycle.cc | 200 +---
.../pass/map/qubits/map/detail/free_cycle.h | 189 ++-
src/ql/pass/map/qubits/map/detail/future.cc | 285 +++--
src/ql/pass/map/qubits/map/detail/future.h | 132 +--
src/ql/pass/map/qubits/map/detail/mapper.cc | 1025 +++--------------
src/ql/pass/map/qubits/map/detail/mapper.h | 331 ++----
src/ql/pass/map/qubits/map/detail/options.cc | 23 -
src/ql/pass/map/qubits/map/detail/options.h | 45 +-
src/ql/pass/map/qubits/map/detail/past.cc | 672 ++---------
src/ql/pass/map/qubits/map/detail/past.h | 312 +----
src/ql/pass/map/qubits/map/map.cc | 160 +--
src/ql/pass/map/qubits/place_mip/place_mip.cc | 22 +-
.../pass/opt/const_prop/detail/propagate.cc | 2 +-
.../pass/sch/list_schedule/list_schedule.cc | 2 +-
src/ql/pass/sch/schedule/detail/scheduler.cc | 2 -
tests/test_mapper.cc | 2 +-
tests/test_mapper.py | 6 +-
tests/test_mapper_rig.json | 4 +-
tests/test_multi_core.py | 170 +--
tests/test_multi_core_4x4_full.json | 4 +-
tests/visualizer/visualizer_example1.py | 2 +-
tests/visualizer/visualizer_example6.py | 2 +-
44 files changed, 1302 insertions(+), 3261 deletions(-)
create mode 100644 include/ql/com/map/reference_updater.h
create mode 100644 include/ql/ir/swap_parameters.h
create mode 100644 src/ql/com/map/reference_updater.cc
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5c9ffac01..0dd7ba64c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -245,6 +245,7 @@ add_library(ql
"${CMAKE_CURRENT_SOURCE_DIR}/src/ql/com/sch/scheduler.cc"
"${CMAKE_CURRENT_SOURCE_DIR}/src/ql/com/map/expression_mapper.cc"
"${CMAKE_CURRENT_SOURCE_DIR}/src/ql/com/map/qubit_mapping.cc"
+ "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/com/map/reference_updater.cc"
"${CMAKE_CURRENT_SOURCE_DIR}/src/ql/com/dec/unitary.cc"
"${CMAKE_CURRENT_SOURCE_DIR}/src/ql/com/dec/rules.cc"
"${CMAKE_CURRENT_SOURCE_DIR}/src/ql/com/dec/structure.cc"
diff --git a/include/ql/com/ddg/build.h b/include/ql/com/ddg/build.h
index 0070f2e4c..33c9392c4 100644
--- a/include/ql/com/ddg/build.h
+++ b/include/ql/com/ddg/build.h
@@ -21,7 +21,7 @@ class EventGatherer {
/**
* Reference to the root of the IR.
*/
- ir::Ref ir;
+ ir::PlatformRef platform;
/**
* The actual event list.
@@ -49,7 +49,7 @@ class EventGatherer {
/**
* Constructs an object reference gatherer.
*/
- explicit EventGatherer(const ir::Ref &ir);
+ explicit EventGatherer(const ir::PlatformRef &p);
/**
* Returns the contained list of object accesses.
@@ -116,7 +116,7 @@ class EventGatherer {
* node in the final schedule, and such that the sign indicates the direction
*/
void build(
- const ir::Ref &ir,
+ const ir::PlatformRef &platform,
const ir::BlockBaseRef &block,
utils::Bool commute_multi_qubit = true,
utils::Bool commute_single_qubit = true
diff --git a/include/ql/com/ddg/ops.h b/include/ql/com/ddg/ops.h
index c4ca95449..1b42ad150 100644
--- a/include/ql/com/ddg/ops.h
+++ b/include/ql/com/ddg/ops.h
@@ -68,6 +68,8 @@ void clear(const ir::BlockBaseRef &block);
*/
void reverse(const ir::BlockBaseRef &block);
+void add_remaining(const ir::BlockBaseRef &block);
+
} // namespace ddg
} // namespace com
} // namespace ql
diff --git a/include/ql/com/ddg/types.h b/include/ql/com/ddg/types.h
index 1efb03e86..6812d1703 100644
--- a/include/ql/com/ddg/types.h
+++ b/include/ql/com/ddg/types.h
@@ -423,6 +423,12 @@ struct Graph {
};
+struct Remaining {
+ utils::UInt remaining = 0;
+
+ Remaining(utils::UInt r) : remaining(r) {};
+};
+
} // namespace ddg
} // namespace com
} // namespace ql
diff --git a/include/ql/com/map/reference_updater.h b/include/ql/com/map/reference_updater.h
new file mode 100644
index 000000000..76a781c45
--- /dev/null
+++ b/include/ql/com/map/reference_updater.h
@@ -0,0 +1,33 @@
+#include "ql/ir/ir.h"
+
+namespace ql {
+namespace com {
+namespace map {
+
+class ReferenceUpdater : public ir::RecursiveVisitor {
+public:
+ using Callback = std::function;
+
+ ReferenceUpdater(ir::PlatformRef p, const utils::Vec &m,
+ Callback c = {}) : platform(p), mapping(m), callback(c) {}
+
+ void visit_node(ir::Node &node) override {};
+
+ void visit_reference(ir::Reference &ref) override;
+
+ // Gate operands may be virtual qubits, but in the instruction type it's always real qubit indices.
+ void visit_instruction_type(ir::InstructionType &t) override {};
+
+private:
+ ir::PlatformRef platform;
+ const utils::Vec &mapping;
+ Callback callback{};
+};
+
+void mapInstruction(const ir::PlatformRef &platform, const utils::Vec &mapping, const ir::CustomInstructionRef &instr, ReferenceUpdater::Callback callback = {});
+
+void mapProgram(const ir::PlatformRef &platform, const utils::Vec &mapping, const ir::ProgramRef &program);
+
+}
+}
+}
\ No newline at end of file
diff --git a/include/ql/ir/compat/gate.h b/include/ql/ir/compat/gate.h
index 725d6fa10..8fd344ee3 100644
--- a/include/ql/ir/compat/gate.h
+++ b/include/ql/ir/compat/gate.h
@@ -9,6 +9,7 @@
#include "ql/utils/json.h"
#include "ql/utils/misc.h"
#include "ql/utils/tree.h"
+#include "ql/ir/swap_parameters.h"
namespace ql {
namespace ir {
@@ -70,23 +71,6 @@ std::ostream &operator<<(std::ostream &os, ConditionType condition_type);
const utils::UInt MAX_CYCLE = utils::MAX;
-struct SwapParamaters {
- utils::Bool part_of_swap = false;
- // at the end of the swap r0 stores v0 and r1 stores v1
- utils::Int r0 = -1;
- utils::Int r1 = -1;
- utils::Int v0 = -1;
- utils::Int v1 = -1;
-
- // default constructor
- SwapParamaters() {}
-
- // initializer list
- SwapParamaters(utils::Bool _part_of_swap, utils::Int _r0, utils::Int _r1, utils::Int _v0, utils::Int _v1)
- : part_of_swap(_part_of_swap), r0(_r0), r1(_r1), v0(_v0), v1(_v1)
- {}
-};
-
/**
* gate interface
*/
@@ -98,7 +82,7 @@ class Gate : public utils::Node {
utils::Vec breg_operands; // bit operands e.g. assigned to by measure; cond_operands are separate
utils::Vec cond_operands; // 0, 1 or 2 bit operands of condition
ConditionType condition = ConditionType::ALWAYS; // defines condition and by that number of bit operands of condition
- SwapParamaters swap_params; // if the gate is part of a swap/move, this will contain the real and virtual qubits involved
+ SwapParameters swap_params; // if the gate is part of a swap/move, this will contain the real and virtual qubits involved
utils::Int int_operand = 0; // FIXME: move to class 'classical'
utils::UInt duration = 0;
utils::Real angle = 0.0; // for arbitrary rotations
diff --git a/include/ql/ir/ir.h b/include/ql/ir/ir.h
index 0b8811c9b..a67010e20 100644
--- a/include/ql/ir/ir.h
+++ b/include/ql/ir/ir.h
@@ -57,6 +57,11 @@ using StatementRef = utils::One;
*/
using InstructionRef = utils::One;
+/**
+ * Reference to a custom instruction.
+ */
+using CustomInstructionRef = utils::One;
+
/**
* Link to a (custom) instruction type.
*/
diff --git a/include/ql/ir/ops.h b/include/ql/ir/ops.h
index 1bbbb3016..cff17e268 100644
--- a/include/ql/ir/ops.h
+++ b/include/ql/ir/ops.h
@@ -108,7 +108,7 @@ InstructionTypeLink add_instruction_type(
* created, an empty link is returned.
*/
InstructionTypeLink find_instruction_type(
- const Ref &ir,
+ const PlatformRef &platform,
const utils::Str &name,
const utils::Vec &types,
const utils::Vec &writable,
@@ -148,7 +148,7 @@ InstructionTypeLink find_instruction_type(
* from the old to new IR. See find_instruction_type().
*/
InstructionRef make_instruction(
- const Ref &ir,
+ const PlatformRef &platform,
const utils::Str &name,
const utils::Any &operands,
const ExpressionRef &condition = {},
@@ -245,7 +245,7 @@ utils::One make_function_call(
/**
* Returns the number of qubits in the main qubit register.
*/
-utils::UInt get_num_qubits(const Ref &ir);
+utils::UInt get_num_qubits(const PlatformRef &platform);
/**
* Makes an integer literal using the given or default integer type.
@@ -255,17 +255,17 @@ utils::One make_int_lit(const Ref &ir, utils::Int i, const DataTypeL
/**
* Makes an integer literal using the given or default integer type.
*/
-utils::One make_uint_lit(const Ref &ir, utils::UInt i, const DataTypeLink &type = {});
+utils::One make_uint_lit(const PlatformRef &platform, utils::UInt i, const DataTypeLink &type = {});
/**
* Makes an bit literal using the given or default bit type.
*/
-utils::One make_bit_lit(const Ref &ir, utils::Bool b, const DataTypeLink &type = {});
+utils::One make_bit_lit(const PlatformRef &platform, utils::Bool b, const DataTypeLink &type = {});
/**
* Makes a qubit reference to the main qubit register.
*/
-utils::One make_qubit_ref(const Ref &ir, utils::UInt idx);
+utils::One make_qubit_ref(const PlatformRef &platform, utils::UInt idx);
/**
* Makes a reference to the implicit measurement bit associated with a qubit in
@@ -277,7 +277,7 @@ utils::One make_bit_ref(const Ref &ir, utils::UInt idx);
* Makes a reference to the specified object using literal indices.
*/
utils::One make_reference(
- const Ref &ir,
+ const PlatformRef &platform,
const ObjectLink &obj,
utils::Vec indices = {}
);
@@ -316,5 +316,134 @@ utils::UInt get_duration_of_block(const BlockBaseRef &block);
*/
utils::UInt get_number_of_qubits_involved(const InstructionRef &insn);
+class OperandsHelper {
+public:
+ OperandsHelper(const PlatformRef p, const CustomInstruction &instruction) : platform(p), instr(instruction) {};
+
+ utils::UInt getQubit(utils::UInt operandIndex) {
+ const auto& op = getOperand(operandIndex);
+
+ const auto& ref = op.as_reference();
+ if (!ref) {
+ QL_FATAL("Operand #" << operandIndex << " of instruction " << instr.instruction_type->name << " is not a reference.");
+ }
+
+ if (ref->target != platform->qubits) {
+ QL_FATAL("Operand #" << operandIndex << " of instruction " << instr.instruction_type->name << " is not a qubit.");
+ }
+
+ return ref->indices[0].as()->value;
+ }
+
+ utils::UInt getFloat(utils::UInt operandIndex) {
+ const auto& op = getOperand(operandIndex);
+
+ const auto real = op.as_real_literal();
+
+ if (!real) {
+ QL_FATAL("Operand #" << operandIndex << " of instruction " << instr.instruction_type->name << " is not a float.");
+ }
+
+ return real->value;
+ }
+
+ utils::UInt getInt(utils::UInt operandIndex) {
+ const auto& op = getOperand(operandIndex);
+
+ const auto integer = op.as_int_literal();
+
+ if (!integer) {
+ QL_FATAL("Operand #" << operandIndex << " of instruction " << instr.instruction_type->name << " is not an integer.");
+ }
+
+ return integer->value;
+ }
+
+ utils::UInt numberOfQubitOperands() {
+ auto& instr_type = *instr.instruction_type;
+ while (!instr_type.generalization.empty()) {
+ instr_type = *instr_type.generalization;
+ }
+
+ utils::UInt nQubitOperands = 0;
+ for (const auto& op: instr_type.operand_types) {
+ if (op->data_type->type() == NodeType::QubitType) {
+ ++nQubitOperands;
+ }
+ }
+
+ return nQubitOperands;
+ }
+
+ utils::UInt get1QGateOperand() {
+ QL_ASSERT(numberOfQubitOperands() == 1);
+
+ for (utils::UInt i = 0; i < totalNumberOfOperands(); ++i) {
+ const auto& op = getOperand(i);
+
+ const auto ref = op.as_reference();
+ if (ref && ref->target == platform->qubits) {
+ return ref->indices[0].as()->value;
+ }
+ }
+
+ QL_FATAL("This is a bug");
+ }
+
+ std::pair get2QGateOperands() {
+ QL_ASSERT(numberOfQubitOperands() == 2);
+
+ utils::UInt q1 = utils::MAX;
+ utils::UInt q2 = utils::MAX;
+
+ for (utils::UInt i = 0; i < totalNumberOfOperands(); ++i) {
+ const auto& op = getOperand(i);
+
+ const auto ref = op.as_reference();
+ if (ref && ref->target == platform->qubits) {
+ if (q1 == utils::MAX) {
+ q1 = ref->indices[0].as()->value;
+ } else if (q2 == utils::MAX) {
+ q2 = ref->indices[0].as()->value;
+ QL_ASSERT(q1 != q2);
+ } else {
+ QL_FATAL("Gate has more than 2 qubit operands!");
+ }
+ }
+ }
+ return std::make_pair(q1, q2);
+ }
+
+ bool isNN2QGate(std::function v2r) {
+ auto qubits = get2QGateOperands();
+
+ return platform->topology->get_min_hops(v2r(qubits.first), v2r(qubits.second)) == 1;
+ }
+
+private:
+ const utils::UInt totalNumberOfOperands() {
+ return instr.instruction_type->template_operands.size() + instr.operands.size();
+ }
+
+ const Expression& getOperand(utils::UInt operandIndex) {
+ const auto& templateOperands = instr.instruction_type->template_operands;
+ const auto nTemplateOperands = templateOperands.size();
+
+ if (operandIndex < nTemplateOperands) {
+ return *templateOperands[operandIndex];
+ }
+
+ const auto nTotalOperands = nTemplateOperands + instr.operands.size();
+ if (operandIndex >= nTotalOperands) {
+ QL_FATAL("Tried to access operand #" << operandIndex << " of instruction " << instr.instruction_type->name << " which has only " << nTotalOperands << " operands.");
+ }
+
+ return *instr.operands[operandIndex - nTemplateOperands];
+ }
+
+ const PlatformRef platform;
+ const CustomInstruction &instr;
+};
+
} // namespace ir
} // namespace ql
diff --git a/include/ql/ir/swap_parameters.h b/include/ql/ir/swap_parameters.h
new file mode 100644
index 000000000..5f0b18478
--- /dev/null
+++ b/include/ql/ir/swap_parameters.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include "ql/utils/num.h"
+
+namespace ql {
+namespace ir {
+
+struct SwapParameters {
+ utils::Bool part_of_swap = false;
+ // at the end of the swap r0 stores v0 and r1 stores v1
+ utils::Int r0 = -1;
+ utils::Int r1 = -1;
+ utils::Int v0 = -1;
+ utils::Int v1 = -1;
+
+ // default constructor
+ SwapParameters() {}
+
+ // initializer list
+ SwapParameters(utils::Bool _part_of_swap, utils::Int _r0, utils::Int _r1, utils::Int _v0, utils::Int _v1)
+ : part_of_swap(_part_of_swap), r0(_r0), r1(_r1), v0(_v0), v1(_v1)
+ {}
+};
+
+}
+}
diff --git a/include/ql/pass/map/qubits/map/map.h b/include/ql/pass/map/qubits/map/map.h
index 76253e370..259491d4d 100644
--- a/include/ql/pass/map/qubits/map/map.h
+++ b/include/ql/pass/map/qubits/map/map.h
@@ -21,7 +21,7 @@ struct Options;
/**
* Qubit mapper pass.
*/
-class MapQubitsPass : public pmgr::pass_types::ProgramTransformation {
+class MapQubitsPass : public pmgr::pass_types::Transformation {
static bool is_pass_registered;
private:
@@ -70,7 +70,7 @@ class MapQubitsPass : public pmgr::pass_types::ProgramTransformation {
* Runs the qubit mapper.
*/
utils::Int run(
- const ir::compat::ProgramRef &program,
+ const ir::Ref &ir,
const pmgr::pass_types::Context &context
) const override;
diff --git a/src/ql/com/ddg/build.cc b/src/ql/com/ddg/build.cc
index d25386b5a..07bc7630b 100644
--- a/src/ql/com/ddg/build.cc
+++ b/src/ql/com/ddg/build.cc
@@ -16,7 +16,7 @@ namespace ddg {
/**
* Constructs an object reference gatherer.
*/
-EventGatherer::EventGatherer(const ir::Ref &ir) : ir(ir) {}
+EventGatherer::EventGatherer(const ir::PlatformRef &p) : platform(p) {}
/**
* Returns the contained dependency list.
@@ -55,7 +55,7 @@ void EventGatherer::add_reference(
case ir::prim::OperandMode::MEASURE: {
QL_ASSERT(reference->data_type->as_qubit_type());
auto copy = reference->copy().as();
- copy->data_type = ir->platform->implicit_bit_type;
+ copy->data_type = platform->implicit_bit_type;
add_reference(ir::prim::OperandMode::WRITE, copy);
mode = ir::prim::OperandMode::WRITE;
break;
@@ -480,13 +480,13 @@ class Builder {
* Creates a new builder.
*/
Builder(
- const ir::Ref &ir,
+ const ir::PlatformRef &platform,
const ir::BlockBaseRef &block,
utils::Bool commute_multi_qubit,
utils::Bool commute_single_qubit
) :
block(block),
- gatherer(ir),
+ gatherer(platform),
order_accumulator(0)
{
gatherer.disable_multi_qubit_commutation = !commute_multi_qubit;
@@ -531,12 +531,12 @@ class Builder {
* node in the final schedule, and such that the sign indicates the direction
*/
void build(
- const ir::Ref &ir,
+ const ir::PlatformRef &platform,
const ir::BlockBaseRef &block,
utils::Bool commute_multi_qubit,
utils::Bool commute_single_qubit
) {
- Builder(ir, block, commute_multi_qubit, commute_single_qubit).build();
+ Builder(platform, block, commute_multi_qubit, commute_single_qubit).build();
}
} // namespace ddg
diff --git a/src/ql/com/ddg/dot.cc b/src/ql/com/ddg/dot.cc
index f0d7d6814..b13a71b15 100644
--- a/src/ql/com/ddg/dot.cc
+++ b/src/ql/com/ddg/dot.cc
@@ -59,7 +59,13 @@ void dump_dot(
// Write the graph nodes.
for (const auto &it : statements) {
os << line_prefix << " n" << it.first;
- os << " [ label=cycle << "
";
+
+ std::string remaining = "";
+ if (it.second->has_annotation()) {
+ remaining = ", remaining " + std::to_string(it.second->get_annotation().remaining);
+ }
+
+ os << " [ label=cycle << remaining << "
";
auto desc = ir::describe(it.second);
desc = utils::replace_all(desc, "<", "<");
desc = utils::replace_all(desc, ">", ">");
diff --git a/src/ql/com/ddg/ops.cc b/src/ql/com/ddg/ops.cc
index 21574f33d..9e0477cac 100644
--- a/src/ql/com/ddg/ops.cc
+++ b/src/ql/com/ddg/ops.cc
@@ -134,6 +134,34 @@ void reverse(const ir::BlockBaseRef &block) {
reverse_statement(graph.sink);
}
+void add_remaining(const ir::BlockBaseRef &block) {
+ get_sink(block)->set_annotation({ 0 });
+
+ std::set toVisit;
+ toVisit.insert(get_sink(block));
+
+ while(!toVisit.empty()) {
+ auto current = *toVisit.begin();
+ toVisit.erase(toVisit.begin());
+
+ auto currentRemaining = current->get_annotation().remaining;
+
+ for (const auto &node_edge: get_node(current)->predecessors) {
+ QL_ASSERT(node_edge.second->weight >= 0 && "Cannot compute remaining on reversed DDG");
+ auto remaining = (utils::UInt) node_edge.second->weight + currentRemaining;
+
+ if (node_edge.first->has_annotation()) {
+ auto& annot = node_edge.first->get_annotation().remaining;
+ annot = std::max(remaining, annot);
+ } else {
+ node_edge.first->set_annotation({ remaining });
+ }
+
+ toVisit.insert(node_edge.first);
+ }
+ }
+}
+
} // namespace ddg
} // namespace com
} // namespace ql
diff --git a/src/ql/com/ddg/tests/ddg.cc b/src/ql/com/ddg/tests/ddg.cc
index 84ddf3867..24c3ab413 100644
--- a/src/ql/com/ddg/tests/ddg.cc
+++ b/src/ql/com/ddg/tests/ddg.cc
@@ -28,7 +28,7 @@ int main() {
auto ir = ir::convert_old_to_new(program);
- com::ddg::build(ir, ir->program->blocks[0]);
+ com::ddg::build(ir->platform, ir->program->blocks[0]);
com::ddg::check_consistency(ir->program->blocks[0]);
com::ddg::dump_dot(ir->program->blocks[0]);
com::ddg::reverse(ir->program->blocks[0]);
diff --git a/src/ql/com/map/reference_updater.cc b/src/ql/com/map/reference_updater.cc
new file mode 100644
index 000000000..0506b293a
--- /dev/null
+++ b/src/ql/com/map/reference_updater.cc
@@ -0,0 +1,44 @@
+#include "ql/com/map/reference_updater.h"
+
+#include "ql/ir/ops.h"
+
+namespace ql {
+namespace com {
+namespace map {
+
+void ReferenceUpdater::visit_reference(ir::Reference &ref) {
+ if (ref.target == platform->qubits && ref.data_type == platform->qubits->data_type) {
+ QL_ASSERT(ref.indices.size() == 1);
+ auto& virt = ref.indices[0].as()->value;
+ virt = mapping[virt];
+ if (callback) {
+ callback(virt);
+ }
+ }
+}
+
+void mapInstruction(const ir::PlatformRef &platform, const utils::Vec &mapping, const ir::CustomInstructionRef &instr, ReferenceUpdater::Callback callback) {
+ QL_ASSERT(instr->instruction_type->generalization.empty() && "Instruction to map should be in the most generalized form, since it's using virtual qubit indices as operands");
+
+ ReferenceUpdater visitor(platform, mapping, callback);
+ instr->visit(visitor);
+
+ specialize_instruction(instr);
+}
+
+void mapProgram(const ir::PlatformRef &platform, const utils::Vec &mapping, const ir::ProgramRef &program) {
+ for (const auto& block: program->blocks) {
+ for (const auto& st: block->statements) {
+ auto cinsn = st.as();
+
+ if (!cinsn.empty()) {
+ mapInstruction(platform, mapping, cinsn);
+ }
+ }
+ }
+}
+
+
+}
+}
+}
diff --git a/src/ql/com/options.cc b/src/ql/com/options.cc
index 059718fda..6343b48c4 100644
--- a/src/ql/com/options.cc
+++ b/src/ql/com/options.cc
@@ -213,7 +213,7 @@ Options make_ql_options() {
"whether the heuristic mapper will be run, and if so, which heuristic "
"it should use. When `no`, MIP-based placement is also disabled.",
"no",
- {"no", "base", "baserc", "minextend", "minextendrc", "maxfidelity"}
+ {"no", "base", "minextend", "maxfidelity"}
);
options.add_int(
@@ -251,7 +251,7 @@ Options make_ql_options() {
"to the mapper pass documentation for `lookahead_mode` for more "
"information.",
"noroutingfirst",
- {"no", "1qfirst", "noroutingfirst", "all"}
+ {"no", "noroutingfirst", "all"}
);
options.add_enum(
diff --git a/src/ql/ir/cqasm/read.cc b/src/ql/ir/cqasm/read.cc
index e8bf8bc8f..039b3198d 100644
--- a/src/ql/ir/cqasm/read.cc
+++ b/src/ql/ir/cqasm/read.cc
@@ -438,7 +438,7 @@ static ExpressionRef convert_expression(
}
if (auto cb = cq_expr->as_const_bool()) {
- return make_bit_lit(ir, cb->value, as_type);
+ return make_bit_lit(ir->platform, cb->value, as_type);
} else if (cq_expr->as_const_axis()) {
QL_USER_ERROR("OpenQL does not support cQASM's axis data type");
} else if (auto ci = cq_expr->as_const_int()) {
@@ -514,7 +514,7 @@ static ExpressionRef convert_expression(
);
}
if (as_type.empty() || as_type == ir->platform->qubits->data_type) {
- return make_qubit_ref(ir, convert_index(qr->index[sgmq_index]));
+ return make_qubit_ref(ir->platform, convert_index(qr->index[sgmq_index]));
} else if (as_type == ir->platform->default_bit_type) {
return make_bit_ref(ir, convert_index(qr->index[sgmq_index]));
} else {
@@ -550,7 +550,7 @@ static ExpressionRef convert_expression(
"' to type " << as_type->name
);
}
- return make_reference(ir, ql_object, {});
+ return make_reference(ir->platform, ql_object, {});
} else if (auto fn = cq_expr->as_function()) {
if (auto ql_object = fn->get_annotation_ptr()) {
@@ -559,7 +559,7 @@ static ExpressionRef convert_expression(
for (const auto &cq_operand : fn->operands) {
ql_indices.push_back(convert_index(cq_operand));
}
- auto ref = make_reference(ir, *ql_object, ql_indices);
+ auto ref = make_reference(ir->platform, *ql_object, ql_indices);
if (auto ql_type = fn->get_annotation_ptr()) {
ref->data_type = *ql_type;
}
@@ -607,7 +607,7 @@ static utils::One convert_set_instruction(
"does not match type of right-hand side (" << ql_rhs_type->name << ")"
);
}
- return utils::make(ql_lhs, ql_rhs, ir::make_bit_lit(ir, true));
+ return utils::make(ql_lhs, ql_rhs, ir::make_bit_lit(ir->platform, true));
}
/**
@@ -734,7 +734,7 @@ static void convert_block(
ql_operands.add(convert_expression(ir, cq_operand, sgmq_size, sgmq_index));
}
}
- ql_insns.push_back(make_instruction(ir, cq_insn->name, ql_operands, ql_condition));
+ ql_insns.push_back(make_instruction(ir->platform, cq_insn->name, ql_operands, ql_condition));
} else if (
!options.measure_all_target.empty() &&
@@ -746,9 +746,9 @@ static void convert_block(
QL_ASSERT(ir->platform->qubits->shape.size() == 1);
for (utils::UInt q = 0; q < ir->platform->qubits->shape[0]; q++) {
ql_insns.push_back(make_instruction(
- ir,
+ ir->platform,
options.measure_all_target,
- {make_qubit_ref(ir, q)},
+ {make_qubit_ref(ir->platform, q)},
ql_condition.clone()
));
}
@@ -789,7 +789,7 @@ static void convert_block(
ql_operands[1] = x;
}
- ql_insns.push_back(make_instruction(ir, cq_insn->name, ql_operands, ql_condition));
+ ql_insns.push_back(make_instruction(ir->platform, cq_insn->name, ql_operands, ql_condition));
}
}
@@ -819,7 +819,7 @@ static void convert_block(
if (!ql_condition.empty()) {
ql_cond_insn->condition = ql_condition.clone();
} else {
- ql_cond_insn->condition = make_bit_lit(ir, true);
+ ql_cond_insn->condition = make_bit_lit(ir->platform, true);
}
}
} else if (!ql_condition.empty()) {
@@ -848,7 +848,7 @@ static void convert_block(
) {
// Figure out which objects are being used by this bundle.
- com::ddg::EventGatherer eg{ir};
+ com::ddg::EventGatherer eg{ir->platform};
for (const auto &ql_insn : ql_bundle) {
eg.add_statement(ql_insn);
}
@@ -872,7 +872,7 @@ static void convert_block(
// Construct barriers sensitive to all used objects and add them
// to the front and back of the "bundle".
- auto ql_barrier_begin = make_instruction(ir, "barrier", ql_operands);
+ auto ql_barrier_begin = make_instruction(ir->platform, "barrier", ql_operands);
auto ql_barrier_end = ql_barrier_begin.clone();
ql_barrier_begin->cycle = ql_bundle.front()->cycle;
ql_barrier_end->cycle = ql_bundle.back()->cycle;
diff --git a/src/ql/ir/old_to_new.cc b/src/ql/ir/old_to_new.cc
index 4bb093eca..3722270ad 100644
--- a/src/ql/ir/old_to_new.cc
+++ b/src/ql/ir/old_to_new.cc
@@ -61,7 +61,7 @@ static ExpressionRef parse_instruction_parameter(
// registers.
auto implicit_breg = false;
if (name == "b") {
- auto num_qubits = get_num_qubits(ir);
+ auto num_qubits = get_num_qubits(ir->platform);
if (index < num_qubits) {
implicit_breg = true;
name = "q";
@@ -88,7 +88,7 @@ static ExpressionRef parse_instruction_parameter(
"register index out of range"
);
}
- auto ref = make_reference(ir, obj, {index});
+ auto ref = make_reference(ir->platform, obj, {index});
if (implicit_breg) {
ref->data_type = ir->platform->implicit_bit_type;
}
@@ -101,7 +101,7 @@ static ExpressionRef parse_instruction_parameter(
"no register exists with that name"
);
}
- return make_reference(ir, obj);
+ return make_reference(ir->platform, obj);
}
}
@@ -836,7 +836,7 @@ Ref convert_old_to_new(const compat::PlatformRef &old) {
target = decomp->parameters[idx];
} else if (utils::starts_with(sub_insn_param, "q")) {
auto idx = utils::parse_uint(sub_insn_param.substr(1));
- if (idx >= get_num_qubits(ir)) {
+ if (idx >= get_num_qubits(ir->platform)) {
throw utils::Exception(
"gate decomposition parameter " +
sub_insn_param + " is out of range"
@@ -850,12 +850,12 @@ Ref convert_old_to_new(const compat::PlatformRef &old) {
sub_insn_param + "; must be q or %"
);
}
- sub_insn_operands.add(make_reference(ir, target, indices));
+ sub_insn_operands.add(make_reference(ir->platform, target, indices));
}
// Build the instruction.
auto sub_insn_node = make_instruction(
- ir,
+ ir->platform,
sub_insn_name,
sub_insn_operands,
{},
@@ -995,7 +995,7 @@ static ExpressionRef convert_operand(
case compat::ClassicalOperandType::REGISTER:
return make_reference(
- ir,
+ ir->platform,
find_physical_object(ir, "creg"),
{op.as_register().id}
);
@@ -1141,7 +1141,7 @@ static InstructionRef convert_gate(
// Convert the gate's qubit operands.
utils::Any qubit_operands;
for (auto idx : gate->operands) {
- qubit_operands.add(make_qubit_ref(ir, idx));
+ qubit_operands.add(make_qubit_ref(ir->platform, idx));
}
// Convert the gate's creg operands.
@@ -1150,7 +1150,7 @@ static InstructionRef convert_gate(
auto creg_object = find_physical_object(ir, "creg");
QL_ASSERT(!creg_object.empty());
for (auto idx : gate->creg_operands) {
- creg_operands.add(make_reference(ir, creg_object, {idx}));
+ creg_operands.add(make_reference(ir->platform, creg_object, {idx}));
}
}
@@ -1160,7 +1160,7 @@ static InstructionRef convert_gate(
utils::Any breg_operands;
ExpressionRef condition;
auto breg_object = find_physical_object(ir, "breg");
- auto num_qubits = get_num_qubits(ir);
+ auto num_qubits = get_num_qubits(ir->platform);
// Convert breg operands.
for (auto idx : gate->breg_operands) {
@@ -1168,7 +1168,7 @@ static InstructionRef convert_gate(
breg_operands.add(make_bit_ref(ir, idx));
} else {
QL_ASSERT(!breg_object.empty());
- breg_operands.add(make_reference(ir, breg_object, {idx - num_qubits}));
+ breg_operands.add(make_reference(ir->platform, breg_object, {idx - num_qubits}));
}
}
@@ -1179,7 +1179,7 @@ static InstructionRef convert_gate(
cond_operands.add(make_bit_ref(ir, idx));
} else {
QL_ASSERT(!breg_object.empty());
- cond_operands.add(make_reference(ir, breg_object, {idx - num_qubits}));
+ cond_operands.add(make_reference(ir->platform, breg_object, {idx - num_qubits}));
}
}
switch (gate->condition) {
@@ -1190,7 +1190,7 @@ static InstructionRef convert_gate(
// made conditional.
break;
case compat::ConditionType::NEVER:
- condition = make_bit_lit(ir, false);
+ condition = make_bit_lit(ir->platform, false);
break;
case compat::ConditionType::UNARY:
condition = cond_operands[0];
@@ -1252,11 +1252,11 @@ static InstructionRef convert_gate(
if (name == "wait" || name == "barrier" || gate->type() == compat::GateType::WAIT) {
auto duration = utils::div_ceil(gate->duration, old->platform->cycle_time);
utils::Any operands;
- operands.add(make_uint_lit(ir, duration));
+ operands.add(make_uint_lit(ir->platform, duration));
operands.extend(qubit_operands);
operands.extend(creg_operands);
operands.extend(breg_operands);
- return make_instruction(ir, "wait", operands, condition);
+ return make_instruction(ir->platform, "wait", operands, condition);
}
// Handle the classical gates from the remnants of CC-light.
@@ -1332,7 +1332,7 @@ static InstructionRef convert_gate(
}
#endif
// Try to make an instruction for the name and operand list we found.
- auto insn = make_instruction(ir, name, operands, condition, true, true);
+ auto insn = make_instruction(ir->platform, name, operands, condition, true, true);
if (!insn.empty()) {
return insn;
}
@@ -1462,7 +1462,7 @@ static InstructionRef convert_gate(
// since the function above already returns it. However, code reuse is
// also worth something, and execution only gets here for the first gate
// of a particular type.
- return make_instruction(ir, name, operands, condition);
+ return make_instruction(ir->platform, name, operands, condition);
} catch (utils::Exception &e) {
e.add_context("while converting gate " + name);
@@ -1569,9 +1569,9 @@ static utils::Str convert_kernels(
// Create a static for loop and add it to the block.
block->statements.emplace(
- make_reference(ir, make_temporary(ir, ir->platform->default_int_type)),
- make_uint_lit(ir, iteration_count - 1),
- make_uint_lit(ir, (utils::UInt) 0),
+ make_reference(ir->platform, make_temporary(ir, ir->platform->default_int_type)),
+ make_uint_lit(ir->platform, iteration_count - 1),
+ make_uint_lit(ir->platform, (utils::UInt) 0),
sub_block,
cycle
);
diff --git a/src/ql/ir/ops.cc b/src/ql/ir/ops.cc
index ae7fb835a..bd7366a61 100644
--- a/src/ql/ir/ops.cc
+++ b/src/ql/ir/ops.cc
@@ -119,7 +119,7 @@ ObjectLink find_physical_object(const Ref &ir, const utils::Str &name) {
* be generated appropriately).
*/
static utils::Pair add_or_find_instruction_type(
- const Ref &ir,
+ const PlatformRef &platform,
const utils::One &instruction_type,
const utils::Any &template_operands = {}
) {
@@ -136,8 +136,8 @@ static utils::Pair add_or_find_instruction_typ
}
// Search for an existing matching instruction.
- auto begin = ir->platform->instructions.get_vec().begin();
- auto end = ir->platform->instructions.get_vec().end();
+ auto begin = platform->instructions.get_vec().begin();
+ auto end = platform->instructions.get_vec().end();
auto pos = std::lower_bound(begin, end, instruction_type, compare_by_name);
auto already_exists = false;
for (; pos != end && (*pos)->name == instruction_type->name; ++pos) {
@@ -170,7 +170,7 @@ static utils::Pair add_or_find_instruction_typ
// the original from instruction_type at the end.
clone->decompositions.reset();
- pos = ir->platform->instructions.get_vec().insert(pos, clone);
+ pos = platform->instructions.get_vec().insert(pos, clone);
added_anything = true;
} else {
@@ -260,7 +260,7 @@ InstructionTypeLink add_instruction_type(
) {
// Defer to add_or_find_instruction_type().
- auto result = add_or_find_instruction_type(ir, instruction_type, template_operands);
+ auto result = add_or_find_instruction_type(ir->platform, instruction_type, template_operands);
// If we didn't add anything because a matching specialization of a matching
// instruction already existed, either throw an error or return the existing
@@ -284,7 +284,7 @@ InstructionTypeLink add_instruction_type(
* empty link is returned.
*/
InstructionTypeLink find_instruction_type(
- const Ref &ir,
+ const PlatformRef &platform,
const utils::Str &name,
const utils::Vec &types,
const utils::Vec &writable,
@@ -293,8 +293,8 @@ InstructionTypeLink find_instruction_type(
QL_ASSERT(types.size() == writable.size());
// Search for a matching instruction.
- auto begin = ir->platform->instructions.get_vec().begin();
- auto end = ir->platform->instructions.get_vec().end();
+ auto begin = platform->instructions.get_vec().begin();
+ auto end = platform->instructions.get_vec().end();
auto first = std::lower_bound(
begin, end,
utils::make(name),
@@ -363,7 +363,7 @@ InstructionTypeLink find_instruction_type(
// Insert the instruction just after all the other instructions with this
// name, i.e. at pos, to maintain sort order.
- ir->platform->instructions.get_vec().insert(pos, ityp);
+ platform->instructions.get_vec().insert(pos, ityp);
return ityp;
}
@@ -401,9 +401,9 @@ InstructionTypeLink find_instruction_type(
* from the old to new IR. See find_instruction_type().
*/
InstructionRef make_instruction(
- const Ref &ir,
+ const PlatformRef &platform,
const utils::Str &name,
- const utils::Any &operands,
+ const utils::Any &operands,
const ExpressionRef &condition,
utils::Bool return_empty_on_failure,
utils::Bool generate_overload_if_needed
@@ -501,7 +501,7 @@ InstructionRef make_instruction(
writable.push_back(operand->as_reference() != nullptr);
}
custom_insn->instruction_type = find_instruction_type(
- ir,
+ platform,
name,
types,
writable,
@@ -534,7 +534,7 @@ InstructionRef make_instruction(
// Set the condition, if applicable.
if (auto cond_insn = insn->as_conditional_instruction()) {
if (condition.empty()) {
- cond_insn->condition = make_bit_lit(ir, true);
+ cond_insn->condition = make_bit_lit(platform, true);
} else {
cond_insn->condition = condition;
}
@@ -558,7 +558,7 @@ InstructionRef make_set_instruction(
const ExpressionRef &rhs,
const ExpressionRef &condition
) {
- return make_instruction(ir, "set", {lhs, rhs}, condition);
+ return make_instruction(ir->platform, "set", {lhs, rhs}, condition);
}
/**
@@ -652,7 +652,7 @@ InstructionTypeLink add_decomposition_rule(
) {
// Defer to add_or_find_instruction_type().
- auto result = add_or_find_instruction_type(ir, instruction_type, template_operands);
+ auto result = add_or_find_instruction_type(ir->platform, instruction_type, template_operands);
// If we didn't add anything because a matching specialization of a matching
// instruction already existed, just add the incoming decomposition rules to
@@ -787,9 +787,9 @@ utils::One make_function_call(
/**
* Returns the number of qubits in the main qubit register.
*/
-utils::UInt get_num_qubits(const Ref &ir) {
- QL_ASSERT(ir->platform->qubits->shape.size() == 1);
- return ir->platform->qubits->shape[0];
+utils::UInt get_num_qubits(const PlatformRef &platform) {
+ QL_ASSERT(platform->qubits->shape.size() == 1);
+ return platform->qubits->shape[0];
}
/**
@@ -822,13 +822,13 @@ utils::One make_int_lit(
* Makes an integer literal using the given or default integer type.
*/
utils::One make_uint_lit(
- const Ref &ir,
+ const PlatformRef &platorm,
utils::UInt i,
const DataTypeLink &type
) {
auto typ = type;
if (typ.empty()) {
- typ = ir->platform->default_int_type;
+ typ = platorm->default_int_type;
}
auto int_type = typ.as();
if (int_type.empty()) {
@@ -848,13 +848,13 @@ utils::One make_uint_lit(
* Makes an bit literal using the given or default bit type.
*/
utils::One make_bit_lit(
- const Ref &ir,
+ const PlatformRef &platform,
utils::Bool b,
const DataTypeLink &type
) {
auto typ = type;
if (typ.empty()) {
- typ = ir->platform->default_bit_type;
+ typ = platform->default_bit_type;
}
auto bit_type = typ.as();
if (bit_type.empty()) {
@@ -868,8 +868,8 @@ utils::One make_bit_lit(
/**
* Makes a qubit reference to the main qubit register.
*/
-utils::One make_qubit_ref(const Ref &ir, utils::UInt idx) {
- return make_reference(ir, ir->platform->qubits, {idx});
+utils::One make_qubit_ref(const PlatformRef &platform, utils::UInt idx) {
+ return make_reference(platform, platform->qubits, {idx});
}
/**
@@ -882,7 +882,7 @@ utils::One make_bit_ref(const Ref &ir, utils::UInt idx) {
"platform does not support implicit measurement bits for qubits"
);
}
- auto ref = make_qubit_ref(ir, idx);
+ auto ref = make_qubit_ref(ir->platform, idx);
ref->data_type = ir->platform->implicit_bit_type;
return ref;
}
@@ -891,7 +891,7 @@ utils::One make_bit_ref(const Ref &ir, utils::UInt idx) {
* Makes a reference to the specified object using literal indices.
*/
utils::One make_reference(
- const Ref &ir,
+ const PlatformRef &platform,
const ObjectLink &obj,
utils::Vec indices
) {
@@ -912,7 +912,7 @@ utils::One make_reference(
"index out of range making reference to '" + obj->name + "'"
);
}
- ref->indices.add(make_uint_lit(ir, indices[i]));
+ ref->indices.add(make_uint_lit(platform, indices[i]));
}
return ref;
}
diff --git a/src/ql/pass/ana/visualize/detail/types.h b/src/ql/pass/ana/visualize/detail/types.h
index 48920e567..60215e7a2 100644
--- a/src/ql/pass/ana/visualize/detail/types.h
+++ b/src/ql/pass/ana/visualize/detail/types.h
@@ -114,7 +114,7 @@ struct GateProperties {
utils::Str name;
utils::Vec operands;
utils::Vec creg_operands;
- ir::compat::SwapParamaters swap_params; // FIXME: change once mapper is moved to new IR.
+ ir::SwapParameters swap_params;
utils::Int durationInCycles;
utils::Int cycle;
utils::Vec codewords; // std::vector codewords; // index 0 is right and index 1 is left, in case of multi-qubit gate
diff --git a/src/ql/pass/map/qubits/map/detail/alter.cc b/src/ql/pass/map/qubits/map/detail/alter.cc
index df04f74fa..ae2a40472 100644
--- a/src/ql/pass/map/qubits/map/detail/alter.cc
+++ b/src/ql/pass/map/qubits/map/detail/alter.cc
@@ -1,12 +1,5 @@
-/** \file
- * Alter implementation.
- */
-
#include "alter.h"
-// uncomment next line to enable multi-line dumping
-// #define MULTI_LINE_LOG_DEBUG
-
namespace ql {
namespace pass {
namespace map {
@@ -14,251 +7,83 @@ namespace qubits {
namespace map {
namespace detail {
-/**
- * This should only be called after a virgin construction and not after
- * cloning a path.
- */
-void Alter::initialize(const ir::compat::KernelRef &k, const OptionsRef &opt) {
- QL_DOUT("Alter::initialize(number of qubits=" << k->platform->qubit_count);
- platform = k->platform;
- kernel = k;
- options = opt;
-
- nq = platform->qubit_count;
- ct = platform->cycle_time;
- // total, fromSource and fromTarget start as empty vectors
- past.initialize(kernel, options); // initializes past to empty
- score_valid = false; // will not print score for now
-}
-
-/**
- * Print path as s followed by path of the form [0->1->2].
- */
-static void partial_print(
- const utils::Str &s,
- const utils::Vec &path
-) {
- if (!path.empty()) {
- std::cout << s << path.to_string("[", "->", "]");
- }
-}
-
-/**
- * Prints the state of this Alter, prefixed by s.
- */
-void Alter::print(const utils::Str &s) const {
- // std::cout << s << "- " << targetgp->qasm();
- std::cout << s << "- " << target_gate->qasm();
- if (from_source.empty() && from_target.empty()) {
- partial_print(", total path:", total);
- } else {
- partial_print(", path from source:", from_source);
- partial_print(", from target:", from_target);
- }
- if (score_valid) {
- std::cout << ", score=" << score;
- }
- // past.Print("past in Alter");
- std::cout << std::endl;
-}
-
-/**
- * Prints the state of this Alter, prefixed by s, only when the logging
- * verbosity is at least debug.
- */
-void Alter::debug_print(const utils::Str &s) const {
-#ifdef MULTI_LINE_LOG_DEBUG
- QL_IF_LOG_DEBUG {
- QL_DOUT("Printing current Alter's state: ");
- print(s);
- }
-#else
- QL_DOUT("Printing current Alter's state (disabled)");
-#endif
-}
-
-/**
- * Prints a state of a whole list of Alters, prefixed by s.
- */
-void Alter::print(const utils::Str &s, const utils::List &la) {
- utils::Int started = 0;
- for (auto &a : la) {
- if (started == 0) {
- started = 1;
- std::cout << s << "[" << la.size() << "]={" << std::endl;
- }
- a.print("");
- }
- if (started == 1) {
- std::cout << "}" << std::endl;
- }
-}
-
-/**
- * Prints a state of a whole list of Alters, prefixed by s, only when the
- * logging verbosity is at least debug.
- */
-void Alter::debug_print(const utils::Str &s, const utils::List &la) {
-#ifdef MULTI_LINE_LOG_DEBUG
- QL_IF_LOG_DEBUG {
- QL_DOUT("Print list of Alters: ");
- print(s, la);
- }
-#else
- QL_DOUT("Print list of Alters (disabled)");
-#endif
-}
-
-/**
- * Adds a node to the path in front, extending its length with one.
- */
-void Alter::add_to_front(utils::UInt q) {
- total.insert(total.begin(), q); // hopelessly inefficient
-}
-
-/**
- * Add swap gates for the current path to the given past, up to the maximum
- * specified by the swap selection mode. This past can be a path-local one
- * or the main past. After having added them, schedule the result into that
- * past.
- */
-void Alter::add_swaps(Past &past, SwapSelectionMode mode) const {
- // QL_DOUT("Addswaps " << mapselectswapsopt);
+Alter::Alter(ir::PlatformRef pl, const ir::BlockBaseRef &b, const OptionsRef &opt, ir::CustomInstructionRef g,
+ std::shared_ptr> pa, std::list::iterator l,
+ std::list::reverse_iterator r) :
+ platform(pl),
+ block(b),
+ options(opt),
+ target_gate(g),
+ path(std::move(pa)),
+ leftOpIt(l), rightOpIt(r) {}
+
+void Alter::add_swaps(Past &past) const {
+ const auto& mode = options->swap_selection_mode;
if (mode == SwapSelectionMode::ONE || mode == SwapSelectionMode::ALL) {
- utils::UInt num_added = 0;
- utils::UInt max_num_to_add = (mode == SwapSelectionMode::ONE ? 1 : ir::compat::MAX_CYCLE);
+ utils::UInt max_num_to_add = (mode == SwapSelectionMode::ONE ? 1 : utils::MAX);
- utils::UInt from_source_qubit;
- utils::UInt to_source_qubit;
- from_source_qubit = from_source[0];
- for (utils::UInt i = 1; i < from_source.size() && num_added < max_num_to_add; i++) {
- to_source_qubit = from_source[i];
- past.add_swap(from_source_qubit, to_source_qubit);
- from_source_qubit = to_source_qubit;
- num_added++;
+ utils::UInt swaps_added = 0;
+ for (auto it = path->begin();
+ swaps_added < max_num_to_add && it != leftOpIt;
+ ++swaps_added, it = std::next(it)) {
+ past.add_swap(*it, *std::next(it));
}
- utils::UInt from_target_qubit;
- utils::UInt to_target_qubit;
- from_target_qubit = from_target[0];
- for (utils::UInt i = 1; i < from_target.size() && num_added < max_num_to_add; i++) {
- to_target_qubit = from_target[i];
- past.add_swap(from_target_qubit, to_target_qubit);
- from_target_qubit = to_target_qubit;
- num_added++;
+ swaps_added = 0;
+ for (auto it = path->rbegin();
+ swaps_added < max_num_to_add && it != rightOpIt;
+ ++swaps_added, it = std::next(it)) {
+ past.add_swap(*it, *std::next(it));
}
} else {
QL_ASSERT(mode == SwapSelectionMode::EARLIEST);
- if (from_source.size() >= 2 && from_target.size() >= 2) {
- if (past.is_first_swap_earliest(from_source[0], from_source[1],
- from_target[0], from_target[1])) {
- past.add_swap(from_source[0], from_source[1]);
+
+ if (leftOpIt != path->begin() && rightOpIt != path->rbegin()) {
+ if (past.is_first_swap_earliest(*path->begin(), *std::next(path->begin()),
+ *path->rbegin(), *std::next(path->rbegin()))) {
+ past.add_swap(*path->begin(), *std::next(path->begin()));
} else {
- past.add_swap(from_target[0], from_target[1]);
+ past.add_swap(*path->rbegin(), *std::next(path->rbegin()));
}
- } else if (from_source.size() >= 2) {
- past.add_swap(from_source[0], from_source[1]);
- } else if (from_target.size() >= 2) {
- past.add_swap(from_target[0], from_target[1]);
+ } else if (leftOpIt != path->begin()) {
+ past.add_swap(*path->begin(), *std::next(path->begin()));
+ } else if (rightOpIt != path->rbegin()) {
+ past.add_swap(*path->rbegin(), *std::next(path->rbegin()));
}
}
-
- past.schedule();
}
-/**
- * Compute cycle extension of the current alternative in curr_past relative
- * to the given base past.
- *
- * extend can be called in a deep exploration where pasts have been
- * extended, each one on top of a previous one, starting from the base past.
- * The curr_past here is the last extended one, i.e. on top of which this
- * extension should be done; the base_past is the ultimate base past
- * relative to which the total extension is to be computed.
- *
- * Do this by adding the swaps described by this alternative to an
- * alternative-local copy of the current past. Keep this resulting past in
- * the current alternative (for later use). Compute the total extension of
- * all pasts relative to the base past, and store this extension in the
- * alternative's score for later use.
- */
-void Alter::extend(const Past &curr_past, const Past &base_past) {
- // QL_DOUT("... clone past, add swaps, compute overall score and keep it all in current alternative");
- past = curr_past; // explicitly clone currPast to an alternative-local copy of it, Alter.past
- // QL_DOUT("... adding swaps to alternative-local past ...");
- add_swaps(past, SwapSelectionMode::ALL);
- // QL_DOUT("... done adding/scheduling swaps to alternative-local past");
-
- if (options->heuristic == Heuristic::MAX_FIDELITY) {
- QL_FATAL("Mapper option maxfidelity has been disabled");
- // score = quick_fidelity(past.lg);
- } else {
- score = past.get_max_free_cycle() - base_past.get_max_free_cycle();
- }
- score_valid = true;
+void Alter::extend(Past curr_past, const Past &base_past) {
+ QL_ASSERT(!score_valid && "Alter::extend() can only be called once!");
+ add_swaps(curr_past);
+ set_score(curr_past.get_max_free_cycle() - base_past.get_max_free_cycle());
}
-/**
- * Split the path. Starting from the representation in the total attribute,
- * generate all split path variations where each path is split once at any
- * hop in it. The intention is that the mapped two-qubit gate can be placed
- * at the position of that hop. All result paths are added/appended to the
- * given result list.
- *
- * When at the hop of a split a two-qubit gate cannot be placed, the split
- * is not done there. This means at the end that, when all hops are
- * inter-core, no split is added to the result.
- *
- * distance=5 means length=6 means 4 swaps + 1 CZ gate, e.g.
- * index in total: 0 1 2 length-3 length-2 length-1
- * qubit: 2 -> 5 -> 7 -> 3 -> 1 CZ 4
- */
-void Alter::split(utils::List &result) const {
- // QL_DOUT("Split ...");
-
- utils::UInt length = total.size();
- QL_ASSERT (length >= 2); // distance >= 1 so path at least: source -> target
- for (utils::UInt rightopi = length - 1; rightopi >= 1; rightopi--) {
- utils::UInt leftopi = rightopi - 1;
- QL_ASSERT (leftopi >= 0);
- // QL_DOUT("... leftopi=" << leftopi);
- // leftopi is the index in total that holds the qubit that becomes the left operand of the gate
- // rightopi is the index in total that holds the qubit that becomes the right operand of the gate
- // rightopi == leftopi + 1
- if (platform->topology->is_inter_core_hop(total[leftopi], total[rightopi])) {
- // an inter-core hop cannot execute a two-qubit gate, so is not a valid alternative
- // QL_DOUT("... skip inter-core hop from qubit=" << total[leftopi] << " to qubit=" << total[rightopi]);
- continue;
+utils::List Alter::create_from_path(const ir::PlatformRef &platform, const ir::BlockBaseRef &block, const OptionsRef &options, ir::CustomInstructionRef gate, std::list path) {
+ utils::List result;
+
+ std::shared_ptr> shared_path(new std::list(path));
+
+ QL_ASSERT (shared_path->size() >= 2);
+
+ auto leftOpIt = shared_path->begin();
+ auto rightOpIt = std::prev(std::prev(shared_path->rend()));
+ while (leftOpIt != std::prev(shared_path->end())) {
+ QL_ASSERT(*std::next(leftOpIt) == *rightOpIt);
+ QL_ASSERT(platform->topology->get_distance(*leftOpIt, *rightOpIt) == 1);
+
+ // An inter-core hop cannot execute a two-qubit gate, so is not a valid alternative.
+ if (!platform->topology->is_inter_core_hop(*leftOpIt, *rightOpIt)) {
+ result.push_back(Alter(platform, block, options, gate, shared_path, leftOpIt, rightOpIt));
}
- Alter na = *this; // na is local copy of the current path, including total
- // na = *this; // na is local copy of the current path, including total
- // na.DPRINT("... copy of current alter");
-
- // fromSource will contain the path with qubits at indices 0 to leftopi
- // fromTarget will contain the path with qubits at indices rightopi to length-1, reversed
- // reversal of fromTarget is done since swaps need to be generated starting at the target
- utils::UInt fromi, toi;
-
- na.from_source.resize(leftopi + 1);
- // QL_DOUT("... fromSource size=" << na.fromSource.size());
- for (fromi = 0, toi = 0; fromi <= leftopi; fromi++, toi++) {
- // QL_DOUT("... fromSource: fromi=" << fromi << " toi=" << toi);
- na.from_source[toi] = na.total[fromi];
+ if (rightOpIt != shared_path->rbegin()) {
+ rightOpIt = std::prev(rightOpIt);
}
-
- na.from_target.resize(length - leftopi - 1);
- // QL_DOUT("... fromTarget size=" << na.fromTarget.size());
- for (fromi = length-1, toi = 0; fromi > leftopi; fromi--, toi++) {
- // QL_DOUT("... fromTarget: fromi=" << fromi << " toi=" << toi);
- na.from_target[toi] = na.total[fromi];
- }
-
- // na.DPRINT("... copy of alter after split");
- result.push_back(na);
- // QL_DOUT("... added to result list");
- // DPRINT("... current alter after split");
+ leftOpIt = std::next(leftOpIt);
}
+
+ return result;
}
} // namespace detail
diff --git a/src/ql/pass/map/qubits/map/detail/alter.h b/src/ql/pass/map/qubits/map/detail/alter.h
index 655c01d40..7aa097e1f 100644
--- a/src/ql/pass/map/qubits/map/detail/alter.h
+++ b/src/ql/pass/map/qubits/map/detail/alter.h
@@ -1,17 +1,6 @@
-/** \file
- * Alter implementation.
- */
-
#pragma once
-#include "ql/utils/num.h"
-#include "ql/utils/str.h"
-#include "ql/utils/vec.h"
-#include "ql/utils/list.h"
-#include "ql/ir/compat/compat.h"
-#include "ql/rmgr/manager.h"
#include "options.h"
-#include "free_cycle.h"
#include "past.h"
namespace ql {
@@ -21,190 +10,81 @@ namespace qubits {
namespace map {
namespace detail {
-/**
- * Alter: one alternative way to make two real qbits (operands of a 2-qubit
- * gate) nearest neighbor (NN).
- *
- * Of these two qubits, the first qubit is called the source, the second is
- * called the target qubit. The Alter stores a series of real qubit indices;
- * qubits/indices are equivalent to the nodes in the grid. An Alter represents
- * a 2-qubit gate and a path through the grid from source to target qubit, with
- * each hop between qubits/nodes only between neighboring nodes in the grid. The
- * intention is that all but one of the hops translate into swaps, and that the
- * one hop that remains will be the place to do the 2-qubit gate.
- *
- * Actually, the Alter goes through several stages.
- *
- * - First, for the given 2-qubit gate that is stored in targetgp, while
- * finding a path from its source to its target, the current path is kept in
- * total. from_source, from_target, past and score are not used; past is a
- * clone of the main past.
- * - Paths are found starting from the source node, and aiming to reach the
- * target node, each time adding one additional hop to the path. from_source,
- * from_target, and score are still empty and not used.
- * - Each time another continuation of the path is found, the current Alter is
- * cloned and the difference continuation represented in the total attribute;
- * it all starts with an empty Alter. from_source, from_target, and score are
- * still empty and not used.
- * - Once all alternative total paths for the 2-qubit gate from source to
- * target have been found, each of these is split again in all possible ways
- * (to ILP overlap swaps from source and target); the split is the place
- * where the two-qubit gate is put.
- * - The alternative splits are made separate Alters and for each of these the
- * two partial paths are stored in fromSource and fromTarget. A partial path
- * stores its starting and end nodes (so contains 1 hop less than its
- * length). The partial path of the target operand is reversed, so it starts
- * at the target qubit.
- * - We add swaps to past following the recipe in fromSource and fromTarget.
- * This extends past. Also, we compute score as the latency extension caused
- * by these swaps.
- *
- * At the end, we have a list of Alters, each with a private Past, and a private
- * latency extension. The partial paths represent lists of swaps to be inserted.
- * The initial two-qubit gate gets the qubits at the ends of the partial paths
- * as operands. The main selection criterium from the Alters is to select the
- * one with the minimum latency extension. Having done that, the other Alters
- * can be discarded, and the selected one committed to the main Past.
- */
class Alter {
public:
-
- /**
- * Descriptions of resources for scheduling.
- */
- ir::compat::PlatformRef platform;
-
- /**
- * Kernel pointer to allow calling kernel private methods.
- */
- ir::compat::KernelRef kernel;
-
- /**
- * Reference to the parsed mapper pass options record.
- */
- OptionsRef options;
-
- /**
- * Number of qubits.
- */
- utils::UInt nq;
-
/**
- * Cycle time, multiplier from cycles to nanoseconds.
+ * Add swap gates for the current path to the given past, up to the maximum
+ * specified by the swap selection mode. This past can be a path-local one
+ * or the main past. After having added them, schedule the result into that
+ * past.
*/
- utils::UInt ct;
+ void add_swaps(Past &past) const;
/**
- * The gate that this variation aims to make nearest-neighbor.
+ * Compute cycle extension of the current alternative in curr_past relative
+ * to base_past.
+ *
+ * extend can be called in a deep exploration where pasts have been
+ * extended, each one on top of a previous one, starting from the base past.
+ * The curr_past here is the last extended one, i.e. on top of which this
+ * extension should be done; the base_past is the ultimate base past
+ * relative to which the total extension is to be computed.
+ *
+ * Do this by adding the swaps described by this alternative, and fill score.
*/
- ir::compat::GateRef target_gate;
+ void extend(Past curr_past, const Past &base_past);
/**
- * The full path, including source and target nodes.
+ * Split the given routing path into alters where the target gate is executed at every possible hop along the path.
+ *
+ * When at one hop along the path a two-qubit gate cannot be placed, the split
+ * is not done there. This means at the end that, when all hops are
+ * inter-core, the resulting list of alters is empty.
*/
- utils::Vec total;
+ static utils::List create_from_path(const ir::PlatformRef &platform, const ir::BlockBaseRef &block, const OptionsRef &options, ir::CustomInstructionRef gate, std::list path);
- /**
- * The partial path after split, starting at source.
- */
- utils::Vec from_source;
+ ir::CustomInstructionRef get_target_gate() {
+ return target_gate;
+ }
- /**
- * The partial path after split, starting at target, backward.
- */
- utils::Vec from_target;
+ utils::UInt get_score() const {
+ QL_ASSERT(score_valid);
+ return score;
+ }
- /**
- * Cloned main past, extended with swaps from this path.
- */
- Past past;
+ void set_score(utils::UInt s) {
+ score = s;
+ score_valid = true;
+ }
- /**
- * The latency extension caused by the path.
- */
- utils::Real score;
+private:
+ Alter(ir::PlatformRef pl, const ir::BlockBaseRef &b, const OptionsRef &opt, ir::CustomInstructionRef g, std::shared_ptr> pa,
+ std::list::iterator l, std::list::reverse_iterator r);
- /**
- * Initially false, true after assignment to score.
- */
- utils::Bool score_valid;
-
- /**
- * This should only be called after a virgin construction and not after
- * cloning a path.
- */
- void initialize(const ir::compat::KernelRef &k, const OptionsRef &opt);
-
- /**
- * Prints the state of this Alter, prefixed by s.
- */
- void print(const utils::Str &s) const;
-
- /**
- * Prints the state of this Alter, prefixed by s, only when the logging
- * verbosity is at least debug.
- */
- void debug_print(const utils::Str &s) const;
+ ir::PlatformRef platform;
+ ir::BlockBaseRef block;
+ OptionsRef options;
/**
- * Prints a state of a whole list of Alters, prefixed by s.
+ * The gate that this variation aims to make nearest-neighbor.
*/
- static void print(const utils::Str &s, const utils::List &la);
+ ir::CustomInstructionRef target_gate;
- /**
- * Prints a state of a whole list of Alters, prefixed by s, only when the
- * logging verbosity is at least debug.
- */
- static void debug_print(const utils::Str &s, const utils::List &la);
+ std::shared_ptr> path;
- /**
- * Adds a node to the path in front, extending its length with one.
- */
- void add_to_front(utils::UInt q);
+ std::list::iterator leftOpIt;
- /**
- * Add swap gates for the current path to the given past, up to the maximum
- * specified by the swap selection mode. This past can be a path-local one
- * or the main past. After having added them, schedule the result into that
- * past.
- */
- void add_swaps(Past &past, SwapSelectionMode mode) const;
+ std::list::reverse_iterator rightOpIt;
/**
- * Compute cycle extension of the current alternative in curr_past relative
- * to the given base past.
- *
- * extend can be called in a deep exploration where pasts have been
- * extended, each one on top of a previous one, starting from the base past.
- * The curr_past here is the last extended one, i.e. on top of which this
- * extension should be done; the base_past is the ultimate base past
- * relative to which the total extension is to be computed.
- *
- * Do this by adding the swaps described by this alternative to an
- * alternative-local copy of the current past. Keep this resulting past in
- * the current alternative (for later use). Compute the total extension of
- * all pasts relative to the base past, and store this extension in the
- * alternative's score for later use.
+ * The latency extension caused by the path.
*/
- void extend(const Past &curr_past, const Past &base_past);
+ utils::UInt score = 0;
/**
- * Split the path. Starting from the representation in the total attribute,
- * generate all split path variations where each path is split once at any
- * hop in it. The intention is that the mapped two-qubit gate can be placed
- * at the position of that hop. All result paths are added/appended to the
- * given result list.
- *
- * When at the hop of a split a two-qubit gate cannot be placed, the split
- * is not done there. This means at the end that, when all hops are
- * inter-core, no split is added to the result.
- *
- * distance=5 means length=6 means 4 swaps + 1 CZ gate, e.g.
- * index in total: 0 1 2 length-3 length-2 length-1
- * qubit: 2 -> 5 -> 7 -> 3 -> 1 CZ 4
+ * Initially false, true after assignment to score.
*/
- void split(utils::List &result) const;
-
+ utils::Bool score_valid = false;
};
} // namespace detail
diff --git a/src/ql/pass/map/qubits/map/detail/free_cycle.cc b/src/ql/pass/map/qubits/map/detail/free_cycle.cc
index a4abeb78b..c4ac2a4ef 100644
--- a/src/ql/pass/map/qubits/map/detail/free_cycle.cc
+++ b/src/ql/pass/map/qubits/map/detail/free_cycle.cc
@@ -1,9 +1,7 @@
-/** \file
- * FreeCycle implementation.
- */
-
#include "free_cycle.h"
+#include "ql/ir/ops.h"
+
// uncomment next line to enable multi-line dumping
// #define MULTI_LINE_LOG_DEBUG
@@ -17,45 +15,14 @@ namespace detail {
/**
* Initializes this FreeCycle object.
*/
-void FreeCycle::initialize(const ir::compat::PlatformRef &p, const OptionsRef &opt) {
- QL_DOUT("FreeCycle::initialize()");
- auto rm = rmgr::Manager::from_defaults(p); // allocated here and copied below to rm because of platform parameter
- // JvS: I have no idea what ^ means
- QL_DOUT("... created FreeCycle initialize local resource_manager");
+void FreeCycle::initialize(const ir::PlatformRef &p, const OptionsRef &opt) {
+ auto rm = *p->resources;
options = opt;
platform = p;
- nq = platform->qubit_count;
- nb = platform->breg_count;
- ct = platform->cycle_time;
- QL_DOUT("... FreeCycle: nq=" << nq << ", nb=" << nb << ", ct=" << ct << "), initializing to all 0 cycles");
- fcv.clear();
- fcv.resize(nq+nb, 1); // this 1 implies that cycle of first gate will be 1 and not 0; OpenQL convention!?!?
- QL_DOUT("... about to copy FreeCycle initialize local resource_manager to FreeCycle member rm");
- rs = rm.build(rmgr::Direction::FORWARD);
- QL_DOUT("... done copy FreeCycle initialize local resource_manager to FreeCycle member rm");
-}
-/**
- * Returns the depth of the FreeCycle map. Equals the max of all entries
- * minus the min of all entries not used yet; would be used to compute the
- * max size of a top window on the past.
- */
-utils::UInt FreeCycle::get_depth() const {
- return get_max() - get_min();
-}
+ fcv.clear();
-/**
- * Returns the minimum cycle of the FreeCycle map; equals the min of all
- * entries.
- */
-utils::UInt FreeCycle::get_min() const {
- utils::UInt min_free_cycle = ir::compat::MAX_CYCLE;
- for (const auto &v : fcv) {
- if (v < min_free_cycle) {
- min_free_cycle = v;
- }
- }
- return min_free_cycle;
+ rs = rm.build(rmgr::Direction::FORWARD);
}
/**
@@ -64,58 +31,20 @@ utils::UInt FreeCycle::get_min() const {
*/
utils::UInt FreeCycle::get_max() const {
utils::UInt max_free_cycle = 0;
- for (const auto &v : fcv) {
- if (max_free_cycle < v) {
- max_free_cycle = v;
+ for (const auto &p : fcv) {
+ if (max_free_cycle < p.second) {
+ max_free_cycle = p.second;
}
}
return max_free_cycle;
}
-/**
- * Prints the state of this object along with the given string.
- */
-void FreeCycle::print(const utils::Str &s) const {
- utils::UInt min_free_cycle = get_min();
- utils::UInt max_free_cycle = get_max();
- std::cout << "... FreeCycle" << s << ":";
- for (utils::UInt i = 0; i < nq; i++) {
- utils::UInt v = fcv[i];
- std::cout << " [" << i << "]=";
- if (v == min_free_cycle) {
- std::cout << "_";
- }
- if (v == max_free_cycle) {
- std::cout << "^";
- }
- std::cout << v;
- }
- std::cout << std::endl;
- // rm.Print("... in FreeCycle: ");
-}
-
-/**
- * Calls print only if the loglevel is debug or more verbose.
- */
-void FreeCycle::debug_print(const utils::Str &s) const {
-#ifdef MULTI_LINE_LOG_DEBUG
- QL_IF_LOG_DEBUG {
- QL_DOUT("Print FreeCycle state: ");
- print(s);
- }
-#else
- QL_DOUT("Print FreeCycle state (disabled)");
-#endif
-}
-
-
/**
* Return whether gate with first operand qubit r0 can be scheduled earlier
* than with operand qubit r1.
*/
-utils::Bool FreeCycle::is_first_operand_earlier(utils::UInt r0, utils::UInt r1) const {
- QL_DOUT("... fcv[" << r0 << "]=" << fcv[r0] << " fcv[" << r1 << "]=" << fcv[r1] << " is_first_operand_earlier=" << (fcv[r0] < fcv[r1]));
- return fcv[r0] < fcv[r1];
+utils::Bool FreeCycle::is_qubit_free_before(utils::UInt r0, utils::UInt r1) const {
+ return get_for_qubit(r0) < get_for_qubit(r1);
}
/**
@@ -127,105 +56,70 @@ utils::Bool FreeCycle::is_first_swap_earliest(
utils::UInt fr0,
utils::UInt fr1,
utils::UInt sr0,
- utils::UInt sr1
-) const {
+ utils::UInt sr1) const {
if (options->reverse_swap_if_better) {
- if (fcv[fr0] < fcv[fr1]) {
- utils::UInt tmp = fr1; fr1 = fr0; fr0 = tmp;
+ if (get_for_qubit(fr0) < get_for_qubit(fr1)) {
+ std::swap(fr0, fr1);
}
- if (fcv[sr0] < fcv[sr1]) {
- utils::UInt tmp = sr1; sr1 = sr0; sr0 = tmp;
+
+ if (get_for_qubit(sr0) < get_for_qubit(sr1)) {
+ std::swap(sr0, sr1);
}
}
- utils::UInt start_cycle_first_swap = utils::max(fcv[fr0] - 1, fcv[fr1]);
- utils::UInt start_cycle_second_swap = utils::max(fcv[sr0] - 1, fcv[sr1]);
- QL_DOUT("... fcv[" << fr0 << "]=" << fcv[fr0] << " fcv[" << fr1 << "]=" << fcv[fr1] << " start=" << start_cycle_first_swap << " fcv[" << sr0 << "]=" << fcv[sr0] << " fcv[" << sr1 << "]=" << fcv[sr1] << " start=" << start_cycle_second_swap << " is_first_swap_earliest=" << (start_cycle_first_swap < start_cycle_second_swap));
+ auto start_cycle_first_swap = utils::max(get_for_qubit(fr0) - 1, get_for_qubit(fr1));
+ auto start_cycle_second_swap = utils::max(get_for_qubit(sr0) - 1, get_for_qubit(sr1));
+
return start_cycle_first_swap < start_cycle_second_swap;
}
/**
* Returns what the start cycle would be when we would schedule the given
- * gate, ignoring resource constraints. gate operands are real qubit indices
- * and breg indices. Purely functional, doesn't affect state.
+ * gate, ignoring resource constraints. Purely functional, doesn't affect state.
*/
-utils::UInt FreeCycle::get_start_cycle_no_rc(const ir::compat::GateRef &g) const {
+utils::UInt FreeCycle::get_start_cycle(const ir::CustomInstructionRef &g) const {
utils::UInt start_cycle = 1;
- for (auto qreg : g->operands) {
- start_cycle = utils::max(start_cycle, fcv[qreg]);
- }
- for (auto breg : g->breg_operands) {
- start_cycle = utils::max(start_cycle, fcv[nq + breg]);
- }
- if (g->is_conditional()) {
- for (auto breg : g->cond_operands) {
- start_cycle = utils::max(start_cycle, fcv[nq + breg]);
+ for (auto op : g->operands) {
+ const auto &ref = op.as();
+ if (!ref.empty()) {
+ start_cycle = utils::max(start_cycle, get_for_reference(*ref));
}
}
- QL_ASSERT (start_cycle < ir::compat::MAX_CYCLE);
-
- return start_cycle;
-}
-/**
- * Returns what the start cycle would be when we would schedule the given
- * gate. gate operands are real qubit indices and breg indices. Purely
- * functional, doesn't affect state.
- */
-utils::UInt FreeCycle::get_start_cycle(const ir::compat::GateRef &g) const {
- utils::UInt start_cycle = get_start_cycle_no_rc(g);
-
- if (options->heuristic == Heuristic::BASE_RC || options->heuristic == Heuristic::MIN_EXTEND_RC) {
- utils::UInt base_start_cycle = start_cycle;
-
- while (start_cycle < ir::compat::MAX_CYCLE) {
- // QL_DOUT("Startcycle for " << g->qasm() << ": available? at startCycle=" << startCycle);
- if (rs->available(start_cycle, g)) {
- // QL_DOUT(" ... [" << startCycle << "] resources available for " << g->qasm());
- break;
- } else {
- // QL_DOUT(" ... [" << startCycle << "] Busy resource for " << g->qasm());
- start_cycle++;
- }
- }
- if (base_start_cycle != start_cycle) {
- // QL_DOUT(" ... from [" << baseStartCycle << "] to [" << startCycle-1 << "] busy resource(s) for " << g->qasm());
- }
+ // FIXME: this is limited...
+ const auto& cond = g->condition;
+ const auto &ref = cond.as();
+ if (!ref.empty()) {
+ start_cycle = utils::max(start_cycle, get_for_reference(*ref));
}
- QL_ASSERT (start_cycle < ir::compat::MAX_CYCLE);
return start_cycle;
}
/**
- * Schedules the given gate in the FreeCycle map. The gate operands are real
- * qubit indices and breg indices. The FreeCycle map is updated, but not the
- * resource map. This is done because add_no_rc is used to represent just gate
+ * Schedules the given gate in the FreeCycle map. The FreeCycle map is updated.
+ * This is done because add is used to represent just gate
* dependencies, avoiding a build of a dep graph.
*/
-void FreeCycle::add_no_rc(const ir::compat::GateRef &g, utils::UInt startCycle) {
- utils::UInt duration = (g->duration+ct-1)/ct; // rounded-up unsigned integer division
+void FreeCycle::add(const ir::CustomInstructionRef &g, utils::UInt startCycle) {
+ utils::UInt duration = g->instruction_type->duration;
utils::UInt freeCycle = startCycle + duration;
- for (auto qreg : g->operands) {
- fcv[qreg] = freeCycle;
- }
- for (auto breg : g->breg_operands) {
- fcv[nq+breg] = freeCycle;
+ for (auto op : g->operands) {
+ const auto &ref = op.as();
+ if (!ref.empty()) {
+ get_for_reference(*ref) = freeCycle;
+ }
}
}
-/**
- * Schedules the given gate in the FreeCycle and resource maps. The gate
- * operands are real qubit indices and breg indices. Both the FreeCycle map
- * and the resource map are updated. startcycle must be the result of an
- * earlier StartCycle call (with rc!)
- */
-void FreeCycle::add(const ir::compat::GateRef &g, utils::UInt start_cycle) {
- add_no_rc(g, start_cycle);
+utils::UInt FreeCycle::cycle_extension(const ir::CustomInstructionRef &g) const {
+ ir::OperandsHelper ops(platform, *g);
+
+ QL_ASSERT(ops.numberOfQubitOperands() == 1);
+ auto duration = g->instruction_type->duration;
+ auto operand = ops.get1QGateOperand();
- if (options->heuristic == Heuristic::BASE_RC || options->heuristic == Heuristic::MIN_EXTEND_RC) {
- rs->reserve(start_cycle, g);
- }
+ return std::max((utils::UInt) 0, get_for_qubit(operand) + duration - get_max());
}
} // namespace detail
diff --git a/src/ql/pass/map/qubits/map/detail/free_cycle.h b/src/ql/pass/map/qubits/map/detail/free_cycle.h
index 8eacb7b6a..825e0b16b 100644
--- a/src/ql/pass/map/qubits/map/detail/free_cycle.h
+++ b/src/ql/pass/map/qubits/map/detail/free_cycle.h
@@ -1,16 +1,11 @@
-/** \file
- * FreeCycle implementation.
- */
-
#pragma once
#include "ql/utils/num.h"
#include "ql/utils/str.h"
#include "ql/utils/vec.h"
#include "ql/utils/opt.h"
-#include "ql/ir/compat/compat.h"
+#include "ql/ir/ops.h"
#include "ql/rmgr/manager.h"
-#include "ql/com/map/qubit_mapping.h"
#include "options.h"
namespace ql {
@@ -21,151 +16,121 @@ namespace map {
namespace detail {
/**
- * FreeCycle: map each real qubit to the first cycle that it is free for use.
- *
- * In scheduling gates, qubit dependencies cause latencies. For each real qubit,
- * the first cycle that it is free to use is the cycle that the last gate that
- * was scheduled in the qubit, has just finished (i.e. in the previous cycle);
- * the map serves as a summary to ease scheduling next gates.
- *
- * Likewise, while mapping, swaps are scheduled just before a non-NN two-qubit
- * gate. Moreover, such swaps may involve real qubits on the path between the
- * real operand qubits of the gate, which may be different from the real operand
- * qubits. The evaluation of which path of swaps is best is, among other data,
- * based on which path causes the latency of the whole circuit to be extended
- * the least. This latency extension is measured from the data in the FreeCycle
- * map; so a FreeCycle map is part of each path of swaps that is evaluated for a
- * particular non-NN 2-qubit gate next to a FreeCycle map that is part of the
- * output stream (the main past).
+ * FreeCycle: map each reference to real qubit to the first cycle that it is free for use.
*
- * Since gate durations are in nanoseconds, and one cycle is some fixed number
- * of nanoseconds, the duration is converted to a rounded-up number of cycles
- * when computing the added latency.
+ * This allows to compute, for each routing alternative, what the cost in terms of circuit depth
+ * will be - the goal of the minextend option in this router being to minimize the overall circuit depth.
*/
class FreeCycle {
-private:
+public:
+ void initialize(const ir::PlatformRef &p, const OptionsRef &opt);
/**
- * Platform description.
+ * Returns the maximum cycle of the FreeCycle map; that is, the cycle where all scheduled
+ * operations are completed.
*/
- ir::compat::PlatformRef platform;
+ utils::UInt get_max() const;
/**
- * Parsed mapper pass options.
+ * Return whether qubit r0 is available strictly before qubit r1.
*/
- OptionsRef options;
+ utils::Bool is_qubit_free_before(utils::UInt r0, utils::UInt r1) const;
/**
- * Map is (nq+nb) long; after initialization, will always be the same.
+ * Returns whether swap(fr0,fr1) start earlier than a swap(sr0,sr1). Is
+ * really a short-cut ignoring config file and perhaps several other
+ * details.
*/
- utils::UInt nq;
+ utils::Bool is_first_swap_earliest(
+ utils::UInt fr0,
+ utils::UInt fr1,
+ utils::UInt sr0,
+ utils::UInt sr1) const;
/**
- * Bregs are in map (behind qubits) to track dependences around conditions.
- *
- * FIXME JvS: why qubits and bregs, but not cregs?
+ * Returns at what cycle the given gate can start when scheduled.
*/
- utils::UInt nb;
+ utils::UInt get_start_cycle(const ir::CustomInstructionRef &g) const;
/**
- * Multiplication factor from cycles to nano-seconds (unit of duration).
+ * Schedules the given gate in the FreeCycle map. The cycle map is updated.
+ * startcycle must be obtained by get_start_cycle.
*/
- utils::UInt ct;
+ void add(const ir::CustomInstructionRef &g, utils::UInt start_cycle);
/**
- * fcv[real qubit index i]: qubit i is free from this cycle on.
+ * Returns the max cycle extension if g is scheduled.
*/
- utils::Vec fcv;
+ utils::UInt cycle_extension(const ir::CustomInstructionRef &g) const;
- /**
- * Actual resources occupied by scheduled gates, if resource-aware.
- */
- utils::Opt rs;
+private:
+ static std::array get_indices(const ir::Reference &ref) {
+ std::array result;
-public:
+ for (utils::UInt i = 0; i < ref.indices.size(); ++i) {
+ if (i >= result.size()) {
+ QL_FATAL("Cannot handle more than 5 dimensions");
+ }
- /**
- * Initializes this FreeCycle object.
- */
- void initialize(const ir::compat::PlatformRef &p, const OptionsRef &opt);
+ auto* int_lit = ref.indices[i]->as_int_literal();
- /**
- * Returns the depth of the FreeCycle map. Equals the max of all entries
- * minus the min of all entries not used yet; would be used to compute the
- * max size of a top window on the past.
- */
- utils::UInt get_depth() const;
+ if (!int_lit) {
+ QL_FATAL("Indices must be int lit");
+ }
- /**
- * Returns the minimum cycle of the FreeCycle map; equals the min of all
- * entries.
- */
- utils::UInt get_min() const;
+ result[i] = int_lit->value;
+ }
- /**
- * Returns the maximum cycle of the FreeCycle map; equals the max of all
- * entries.
- */
- utils::UInt get_max() const;
+ return result;
+ }
- /**
- * Prints the state of this object along with the given string.
- */
- void print(const utils::Str &s) const;
+ utils::UInt& get_for_qubit(utils::UInt i) {
+ return get_for_reference(*make_qubit_ref(platform, i));
+ }
- /**
- * Calls print only if the loglevel is debug or more verbose.
- */
- void debug_print(const utils::Str &s) const;
+ utils::UInt get_for_qubit(utils::UInt i) const {
+ return get_for_reference(*make_qubit_ref(platform, i));
+ }
- /**
- * Return whether gate with first operand qubit r0 can be scheduled earlier
- * than with operand qubit r1.
- */
- utils::Bool is_first_operand_earlier(utils::UInt r0, utils::UInt r1) const;
+ utils::UInt& get_for_reference(const ir::Reference &ref) {
+ auto it = std::find_if(fcv.begin(), fcv.end(), [&ref](std::pair p) {
+ return p.first.equals(ref);
+ });
- /**
- * Returns whether swap(fr0,fr1) start earlier than a swap(sr0,sr1). Is
- * really a short-cut ignoring config file and perhaps several other
- * details.
- */
- utils::Bool is_first_swap_earliest(
- utils::UInt fr0,
- utils::UInt fr1,
- utils::UInt sr0,
- utils::UInt sr1
- ) const;
+ if (it == fcv.end()) {
+ fcv.push_back(std::make_pair(ref, 1));
+ // It's important that fcv is a list so that references are not invalidated.
+ return fcv.back().second;
+ }
- /**
- * Returns what the start cycle would be when we would schedule the given
- * gate, ignoring resource constraints. gate operands are real qubit indices
- * and breg indices. Purely functional, doesn't affect state.
- */
- utils::UInt get_start_cycle_no_rc(const ir::compat::GateRef &g) const;
+ return it->second;
+ }
- /**
- * Returns what the start cycle would be when we would schedule the given
- * gate. gate operands are real qubit indices and breg indices. Purely
- * functional, doesn't affect state.
- */
- utils::UInt get_start_cycle(const ir::compat::GateRef &g) const;
+ utils::UInt get_for_reference(const ir::Reference &ref) const {
+ auto it = std::find_if(fcv.begin(), fcv.end(), [&ref](std::pair p) {
+ return p.first.equals(ref);
+ });
+
+ if (it == fcv.end()) {
+ return 0;
+ }
+
+ return it->second;
+ }
+
+ ir::PlatformRef platform;
+ OptionsRef options;
/**
- * Schedules the given gate in the FreeCycle map. The gate operands are real
- * qubit indices and breg indices. The FreeCycle map is updated, but not the
- * resource map. This is done because add_no_rc is used to represent just gate
- * dependencies, avoiding a build of a dep graph.
+ * The map from qubit references to the first cycle index where the given qubit is available.
+ * This is encoded as an association list, which avoids the burden of defining a hash or an ordering.
*/
- void add_no_rc(const ir::compat::GateRef &g, utils::UInt startCycle);
+ std::list> fcv;
/**
- * Schedules the given gate in the FreeCycle and resource maps. The gate
- * operands are real qubit indices and breg indices. Both the FreeCycle map
- * and the resource map are updated. startcycle must be the result of an
- * earlier StartCycle call (with rc!)
+ * Resources: unused! The scheduling doesn't take resource constraints into account.
*/
- void add(const ir::compat::GateRef &g, utils::UInt start_cycle);
-
+ utils::Opt rs;
};
} // namespace detail
diff --git a/src/ql/pass/map/qubits/map/detail/future.cc b/src/ql/pass/map/qubits/map/detail/future.cc
index 26379aa51..bcf163f4a 100644
--- a/src/ql/pass/map/qubits/map/detail/future.cc
+++ b/src/ql/pass/map/qubits/map/detail/future.cc
@@ -1,10 +1,11 @@
-/** \file
- * Future implementation.
- */
-
#include "future.h"
#include "ql/utils/filesystem.h"
+#include "ql/com/ddg/build.h"
+#include "ql/com/ddg/types.h"
+#include "ql/com/ddg/dot.h"
+#include "ql/ir/describe.h"
+#include "ql/ir/ops.h"
namespace ql {
namespace pass {
@@ -13,143 +14,201 @@ namespace qubits {
namespace map {
namespace detail {
-/**
- * Program-wide initialization function.
- */
-void Future::initialize(const ir::compat::PlatformRef &p, const OptionsRef &opt) {
- // QL_DOUT("Future::Init ...");
- platform = p;
- options = opt;
- // QL_DOUT("Future::Init [DONE]");
+class GateIterator {
+public:
+ virtual ~GateIterator() = default;
+
+ virtual void advance(const ir::CustomInstructionRef& gate) = 0;
+
+ virtual utils::List getCurrent() = 0;
+
+ static std::unique_ptr make(const ir::PlatformRef& platform, const ir::BlockBaseRef &block, const OptionsRef &options) {
+ if (options->lookahead_mode == LookaheadMode::DISABLED) {
+ return makeCircuitOrderIterator(block);
+ }
+
+ // FIXME: platform shouldn't be needed. Currently DDG needs implicit_bit_type to work, unfortunate...
+ // implicit_bit_type should just be bit_type.
+ return makeTopologicalOrderIterator(platform, block, options);
+ }
+
+ virtual std::unique_ptr clone() = 0;
+
+private:
+ static std::unique_ptr makeCircuitOrderIterator(const ir::BlockBaseRef &block);
+
+ static std::unique_ptr makeTopologicalOrderIterator(const ir::PlatformRef& platform, const ir::BlockBaseRef &block, const OptionsRef &options);
+};
+
+class CircuitOrderGateIterator : public GateIterator {
+public:
+ CircuitOrderGateIterator(const ir::BlockBaseRef &b) : block(b) {
+ it = block->statements.begin();
+ }
+
+ virtual void advance(const ir::CustomInstructionRef& gate) override {
+ QL_ASSERT(gate == it->as());
+ ++it;
+ }
+
+ virtual utils::List getCurrent() override {
+ if (it == block->statements.end()) {
+ return {};
+ }
+
+ auto asCustomInstruction = it->as();
+
+ if (asCustomInstruction.empty()) {
+ QL_FATAL("Statement currently not supported by router: " << ir::describe(*it));
+ }
+
+ return { asCustomInstruction };
+ }
+
+ virtual std::unique_ptr clone() {
+ return std::unique_ptr(new CircuitOrderGateIterator(*this));
+ }
+
+private:
+ const ir::BlockBaseRef block;
+ utils::Many::iterator it;
+};
+
+std::unique_ptr GateIterator::makeCircuitOrderIterator(const ir::BlockBaseRef &block) {
+ return std::unique_ptr(new CircuitOrderGateIterator(block));
}
-/**
- * Set/switch input to the provided kernel.
- */
-void Future::set_kernel(const ir::compat::KernelRef &kernel, const utils::Ptr &sched) {
- QL_DOUT("Future::set_kernel ...");
- approx_gates_total = kernel->gates.size();
- approx_gates_remaining = approx_gates_total;
- scheduler = sched;
- if (options->lookahead_mode == LookaheadMode::DISABLED) {
- input_gatepv = kernel->gates; // copy to free original circuit to allow outputing to
- input_gatepp = input_gatepv.begin(); // iterator set to start of input circuit copy
- } else {
- scheduler->init(
- kernel,
- options->output_prefix,
- options->commute_multi_qubit,
- options->commute_single_qubit,
- options->enable_criticality
- );
-
- // and so also the original circuit can be output to after this
- for (auto &gp : kernel->gates) {
- scheduled.set(gp) = false; // none were scheduled
+class TopologicalOrderGateIterator : public GateIterator {
+public:
+ TopologicalOrderGateIterator(const ir::PlatformRef& platform, const ir::BlockBaseRef &b, const OptionsRef &options) :
+ block(b) {
+ // Build DDG and add it as annotation to IR.
+ com::ddg::build(platform, block, options->commute_multi_qubit, options->commute_single_qubit);
+
+ if (options->tie_break_method == TieBreakMethod::CRITICAL) {
+ com::ddg::add_remaining(block);
}
- scheduled.set(scheduler->instruction[scheduler->s]) = false; // also the dummy nodes not
- scheduled.set(scheduler->instruction[scheduler->t]) = false;
- avlist.clear();
- avlist.push_back(scheduler->s);
- scheduler->set_remaining(rmgr::Direction::FORWARD); // to know criticality
if (options->write_dot_graphs) {
- utils::Str map_dot;
+ utils::StrStrm dot_graph;
utils::StrStrm fname;
- scheduler->get_dot(map_dot);
+ com::ddg::dump_dot(block, dot_graph);
- fname << options->output_prefix << kernel->name << "_" << "mapper" << ".dot";
- QL_IOUT("writing " << "mapper" << " dependence graph dot file to '" << fname.str() << "' ...");
- utils::OutFile(fname.str()).write(map_dot);
+ fname << options->output_prefix << "_" << "mapper" << ".dot"; // FIXME: uniquify for multiple blocks
+ utils::OutFile(fname.str()).write(dot_graph.str());
+ }
+
+ const auto& graph = block->get_annotation();
+ done.insert(graph.source);
+ for (const auto& node_edge: graph.source->get_annotation()->successors) {
+ const auto& succ = node_edge.first;
+ QL_ASSERT(succ->get_annotation()->predecessors.count(graph.source) == 1);
+ if (succ->get_annotation()->predecessors.size() == 1) {
+ next.push_back(succ);
+ };
}
}
- QL_DOUT("Future::set_kernel [DONE]");
-}
-/**
- * Get from avlist all gates that are non-quantum into nonqlg. Non-quantum
- * gates include classical and dummy (SOURCE/SINK). Return whether some
- * non-quantum gate was found.
- */
-utils::Bool Future::get_non_quantum_gates(utils::List &nonqlg) const {
- nonqlg.clear();
- if (options->lookahead_mode == LookaheadMode::DISABLED) {
- ir::compat::GateRef gate = *input_gatepp;
- if (ir::compat::GateRefs::const_iterator(input_gatepp) != input_gatepv.end()) {
- if (
- gate->type() == ir::compat::GateType::CLASSICAL
- || gate->type() == ir::compat::GateType::DUMMY
- ) {
- nonqlg.push_back(gate);
+ virtual void advance(const ir::CustomInstructionRef& gate) override {
+ QL_ASSERT(std::find(next.begin(), next.end(), gate) != next.end());
+ next.remove(gate);
+ done.insert(gate);
+
+ for (const auto& node_edge: gate->get_annotation()->successors) {
+ if (node_edge.first->as_sentinel_statement()) {
+ QL_ASSERT(node_edge.first == block->get_annotation().sink);
+ continue;
}
- }
- } else {
- for (auto n : avlist) {
- ir::compat::GateRef gate = scheduler->instruction[n];
- if (
- gate->type() == ir::compat::GateType::CLASSICAL
- || gate->type() == ir::compat::GateType::DUMMY
- ) {
- nonqlg.push_back(gate);
+
+ auto succ = node_edge.first.as();
+
+ if (succ.empty()) {
+ QL_FATAL("Statement currently not supported by router: " << ir::describe(*node_edge.first));
}
- }
- }
- return !nonqlg.empty();
-}
-/**
- * Get all gates from avlist into qlg. Return whether some gate was found.
- */
-utils::Bool Future::get_gates(utils::List &qlg) const {
- qlg.clear();
- if (options->lookahead_mode == LookaheadMode::DISABLED) {
- if (input_gatepp != input_gatepv.end()) {
- ir::compat::GateRef gp = *input_gatepp;
- if (gp->operands.size() > 2) {
- QL_FATAL(" gate: " << gp->qasm() << " has more than 2 operand qubits; please decompose such gates first before mapping.");
+ bool allPredAreDone = true;
+ for (const auto& pred: succ->get_annotation()->predecessors) {
+ if (done.count(pred.first) < 1) {
+ allPredAreDone = false;
+ }
}
- qlg.push_back(gp);
- }
- } else {
- for (auto n : avlist) {
- ir::compat::GateRef gp = scheduler->instruction[n];
- if (gp->operands.size() > 2) {
- QL_FATAL(" gate: " << gp->qasm() << " has more than 2 operand qubits; please decompose such gates first before mapping.");
+
+ if (allPredAreDone) {
+ next.push_back(succ);
}
- qlg.push_back(gp);
}
}
- return !qlg.empty();
+
+ virtual std::unique_ptr clone() {
+ return std::unique_ptr(new TopologicalOrderGateIterator(*this));
+ }
+
+ virtual utils::List getCurrent() override {
+ return next;
+ }
+
+private:
+ const ir::BlockBaseRef block;
+ std::set done;
+ std::list next;
+};
+
+std::unique_ptr GateIterator::makeTopologicalOrderIterator(const ir::PlatformRef& platform, const ir::BlockBaseRef &block, const OptionsRef &options) {
+ return std::unique_ptr(new TopologicalOrderGateIterator(platform, block, options));
+}
+
+Future::Future(const ir::PlatformRef &p, const OptionsRef &opt, const ir::BlockBaseRef &block) :
+ platform(p), options(opt), gateIterator(GateIterator::make(platform, block, options)),
+ approx_gates_total(block->statements.size()), approx_gates_remaining(approx_gates_total) {}
+
+Future::Future(const Future& rhs) :
+ platform(rhs.platform), options(rhs.options), gateIterator(rhs.gateIterator->clone()),
+ approx_gates_total(rhs.approx_gates_total), approx_gates_remaining(rhs.approx_gates_remaining) {};
+
+Future::~Future() = default;
+
+utils::List Future::get_schedulable_gates() const {
+ return gateIterator->getCurrent();
}
-/**
- * Indicates that a gate currently in avlist has been mapped, can be
- * taken out of the avlist, and that its successors can be made available.
- */
-void Future::completed_gate(const ir::compat::GateRef &gate) {
- QL_DOUT("Mapped input gate: " << gate->qasm() );
+void Future::completed_gate(const ir::CustomInstructionRef &gate) {
+ QL_DOUT("Mapped input gate: " << ir::describe(gate));
- if (approx_gates_remaining) {
+ if (approx_gates_remaining > 0) {
approx_gates_remaining--;
}
- if (options->lookahead_mode == LookaheadMode::DISABLED) {
- input_gatepp = std::next(input_gatepp);
- } else {
- scheduler->take_available(scheduler->node.at(gate), avlist, scheduled, rmgr::Direction::FORWARD);
+
+ gateIterator->advance(gate);
+}
+
+utils::Real Future::get_progress() {
+ utils::Real progress = 1.0;
+ if (approx_gates_total) {
+ progress -= ((utils::Real)approx_gates_remaining / (utils::Real)approx_gates_total);
}
+
+ return progress;
}
-/**
- * Return the most critical gate in lag (provided lookahead is enabled).
- * This is used in tiebreak, when every other option has failed to make a
- * distinction.
- */
-ir::compat::GateRef Future::get_most_critical(const utils::List &lag) const {
+ir::CustomInstructionRef Future::get_most_critical(const std::vector &gates) const {
+ QL_ASSERT(!gates.empty());
+
if (options->lookahead_mode == LookaheadMode::DISABLED) {
- return lag.front();
+ return gates.front();
} else {
- return scheduler->find_mostcritical(lag);
+ utils::UInt maxRemaining = 0;
+ ir::CustomInstructionRef result;
+ for (const auto& gate: gates) {
+ auto remaining = gate->get_annotation().remaining;
+
+ if (remaining > maxRemaining) {
+ maxRemaining = remaining;
+ result = gate;
+ }
+ }
+
+ return result;
}
}
diff --git a/src/ql/pass/map/qubits/map/detail/future.h b/src/ql/pass/map/qubits/map/detail/future.h
index 3d84ac3fc..2083a3596 100644
--- a/src/ql/pass/map/qubits/map/detail/future.h
+++ b/src/ql/pass/map/qubits/map/detail/future.h
@@ -1,17 +1,5 @@
-/** \file
- * Future implementation.
- */
-
#pragma once
-#include "ql/utils/num.h"
-#include "ql/utils/str.h"
-#include "ql/utils/ptr.h"
-#include "ql/utils/map.h"
-#include "ql/utils/vec.h"
-#include "ql/utils/list.h"
-#include "ql/ir/compat/compat.h"
-#include "ql/pass/sch/schedule/detail/scheduler.h"
#include "options.h"
#include "past.h"
#include "alter.h"
@@ -23,129 +11,63 @@ namespace qubits {
namespace map {
namespace detail {
-// Shorthand.
-using Scheduler = pass::sch::schedule::detail::Scheduler;
+class GateIterator;
/**
* Future: input window for mapper.
*
- * The future window shows the gates that still must be mapped as the
- * availability list of a list scheduler that would work on a dependency graph
- * representation of each input circuit. This future window is initialized once
- * for the whole program, and gets a method call when it should switch to a new
- * circuit (corresponding to a new kernel). In each circuit and thus each
- * dependency graph the gates (including classical instruction) are found; the
- * dependency graph models their dependencies and also whether they act as
- * barriers, an example of the latter being a classical branch. The availability
- * list with gates (including classical instructions) is the main interface to
- * the mapper, i.e. the mapper selects one or more element(s) from it to map
- * next; it may even create alternatives for each combination of available
- * gates. The gates in the list have attributes like criticality, which can be
- * exploited by the mapper. The dependency graph and the availability list
- * operations are provided by the Scheduler class.
- *
- * The future is a window because in principle it could be implemented
- * incrementally, i.e. that the dependence graph would be extended when an
- * attribute gets below a threshold, e.g. when successors of a gate are
- * interrogated for a particular attribute. A problem might be that criticality
- * requires having seen the end of the circuit, but the space overhead of this
- * attribute is much less than that of a full dependence graph. The
- * implementation below is not incremental: it creates the dep graph for a
- * circuit completely.
+ * The future window shows the gates that remain to be be mapped in that block.
+ * The order in which the gates are routed is either linear, following circuit order,
+ * or by topological order. The dependency graph is provided by com::ddg.
*
- * The implementation below just selects the most critical gate from the
- * availability list as next candidate to map, the idea being that any
- * collateral damage of mapping this gate will have a lower probability of
- * increasing circuit depth than taking a non-critical gate as first one to map.
- * Later implementations may become more sophisticated.
- *
- * With the lookahead_mode option disabled, the future window's dependency
- * graphs (scheduled and avlist) are not used. Instead, a copy of the input
- * circuit (input_gatepv) is created and iterated over (input_gatepp).
*/
+
class Future {
public:
+ Future(const ir::PlatformRef &p, const OptionsRef &opt, const ir::BlockBaseRef &block);
- /**
- * The platform being mapped to.
- */
- ir::compat::PlatformRef platform;
+ Future(const Future& rhs);
- /**
- * The parsed option structure for the mapping pass.
- */
- OptionsRef options;
-
- /**
- * A pointer, since dependency graph doesn't change (TODO: document better)
- */
- utils::Ptr scheduler;
-
- /**
- * Input circuit when not using scheduler based avlist.
- */
- ir::compat::GateRefs input_gatepv;
-
- /**
- * State: has gate been scheduled, here: done from future?
- */
- utils::Map scheduled;
+ ~Future();
/**
- * State: the nodes/gates which are available for mapping now.
+ * Get all gates whose routing should be attempted next.
*/
- utils::List avlist;
+ utils::List get_schedulable_gates() const;
/**
- * State: alternative iterator in input_gatepv.
+ * Indicates that a gate obtained by get_schedulable_gates() has been mapped, can be
+ * taken out of the remaining gates, and that its successor(s) can be made available.
*/
- ir::compat::GateRefs::iterator input_gatepp;
+ void completed_gate(const ir::CustomInstructionRef &gate);
/**
- * Approximate total number of gates to begin with.
- */
- utils::UInt approx_gates_total;
-
- /**
- * Approximate total number of gates remaining.
- */
- utils::UInt approx_gates_remaining;
-
- /**
- * Program-wide initialization function.
+ * Return the most critical gate in lag (provided lookahead is enabled).
+ * This is used in tiebreak, when every other option has failed to make a
+ * distinction.
*/
- void initialize(const ir::compat::PlatformRef &p, const OptionsRef &opt);
+ ir::CustomInstructionRef get_most_critical(const std::vector &gates) const;
- /**
- * Set/switch input to the provided kernel.
- */
- void set_kernel(const ir::compat::KernelRef &kernel, const utils::Ptr &sched);
+ utils::Real get_progress();
- /**
- * Get from avlist all gates that are non-quantum into nonqlg. Non-quantum
- * gates include classical and dummy (SOURCE/SINK). Return whether some
- * non-quantum gate was found.
- */
- utils::Bool get_non_quantum_gates(utils::List &nonqlg) const;
+private:
+ ir::PlatformRef platform;
+ OptionsRef options;
/**
- * Get all gates from avlist into qlg. Return whether some gate was found.
+ * The gate iterator (circuit or topological order) used to obtain the next gate to route/map.
*/
- utils::Bool get_gates(utils::List &qlg) const;
+ std::unique_ptr gateIterator;
/**
- * Indicates that a gate currently in avlist has been mapped, can be
- * taken out of the avlist, and that its successors can be made available.
+ * Initial number of gates to process (to know progress).
*/
- void completed_gate(const ir::compat::GateRef &gate);
+ utils::UInt approx_gates_total = 0;
/**
- * Return the most critical gate in lag (provided lookahead is enabled).
- * This is used in tiebreak, when every other option has failed to make a
- * distinction.
+ * Approximate total number of gates remaining (to know progress).
*/
- ir::compat::GateRef get_most_critical(const utils::List &lag) const;
-
+ utils::UInt approx_gates_remaining = 0;
};
} // namespace detail
diff --git a/src/ql/pass/map/qubits/map/detail/mapper.cc b/src/ql/pass/map/qubits/map/detail/mapper.cc
index 0df4fdd6c..ba35c1dc8 100644
--- a/src/ql/pass/map/qubits/map/detail/mapper.cc
+++ b/src/ql/pass/map/qubits/map/detail/mapper.cc
@@ -1,15 +1,10 @@
-/** \file
- * OpenQL virtual to real qubit mapping and routing.
- */
-
#include "mapper.h"
#include
#include "ql/utils/filesystem.h"
#include "ql/pass/ana/statistics/annotations.h"
-
-// uncomment next line to enable multi-line dumping
-// #define MULTI_LINE_LOG_DEBUG
+#include "ql/com/ddg/dot.h"
+#include "ql/ir/ops.h"
namespace ql {
namespace pass {
@@ -18,9 +13,6 @@ namespace qubits {
namespace map {
namespace detail {
-/**
- * String conversion for PathStrategy.
- */
std::ostream &operator<<(std::ostream &os, PathStrategy p) {
switch (p) {
case PathStrategy::ALL: os << "all"; break;
@@ -32,67 +24,25 @@ std::ostream &operator<<(std::ostream &os, PathStrategy p) {
return os;
}
-
using namespace utils;
using namespace com;
-/**
- * Find shortest paths between src and tgt in the grid, bounded by a
- * particular strategy. path is a linked-list node representing the complete
- * path from the initial src qubit to src in reverse order, not including src;
- * it will be null for the initial call. budget is the maximum number of hops
- * allowed in the path from src and is at least distance to tgt, but can be
- * higher when not all hops qualify for doing a two-qubit gate or to find
- * more than just the shortest paths. This recursively calls itself with src
- * replaced with its neighbors (and additional bookkeeping) until src equals
- * tgt, adding all alternatives to the alters list as it goes. For each path,
- * the alters are further split into all feasible alternatives for the
- * location of the non-nearest-neighbor two-qubit gate that started the
- * routing request. If max_alters is nonzero, recursion will stop once the
- * total number of entries in alters reaches or surpasses the limit (it may
- * surpass due to the checks only happening before splitting).
- */
-void Mapper::gen_shortest_paths(
- const ir::compat::GateRef &gate,
- RawPtr path,
+List Mapper::gen_shortest_paths(
+ const ir::CustomInstructionRef &gate,
+ std::list path,
UInt src,
UInt tgt,
UInt budget,
- List &alters,
UInt max_alters,
PathStrategy strategy
) {
-
- // List that will get the result of a recursive gen_shortest_paths() call.
- List sub_alters;
-
- QL_DOUT("gen_shortest_paths: src=" << src << " tgt=" << tgt << " budget=" << budget << " which=" << strategy);
- QL_ASSERT(alters.empty());
-
+ QL_ASSERT(path.empty() || path.back() != src);
+ path.push_back(src);
+
if (src == tgt) {
-
- // Found target qubit. Create a virgin Alter and initialize it to become
- // an empty path, then add src to this path (so that it becomes a
- // distance 0 path with one qubit, src) and add the Alter to the result
- // list.
- Alter a;
- a.initialize(kernel, options);
- a.target_gate = gate;
- a.add_to_front(src);
- while (path) {
- a.add_to_front(path->qubit);
- path = path->prev;
- }
- a.split(alters);
- a.debug_print("... empty path after adding to result list");
- Alter::debug_print("... result list after adding empty path", alters);
- QL_DOUT("... will return now");
- return;
+ return Alter::create_from_path(platform, block, options, gate, path);
}
- Path sub_path_node = {src, path};
- RawPtr sub_path = &sub_path_node;
-
// Start looking around at neighbors for serious paths.
UInt d = platform->topology->get_distance(src, tgt);
QL_DOUT("gen_shortest_paths: distance(src=" << src << ", tgt=" << tgt << ") = " << d);
@@ -103,40 +53,18 @@ void Mapper::gen_shortest_paths(
// src->n is one hop, budget from n is one less so distance(n,tgt) <= budget-1 (i.e. distance < budget)
// when budget==d, this defaults to distance(n,tgt) <= d-1
auto neighbors = platform->topology->get_neighbors(src);
-#ifdef MULTI_LINE_LOG_DEBUG
- QL_IF_LOG_DEBUG {
- QL_DOUT("gen_shortest_path, neighbor relations after normalizing, before iterating");
- for (auto dn : neighbors) {
- QL_DOUT("..." << dn << " ");
- }
- }
-#else
- QL_DOUT("gen_shortest_path, neighbor relations after normalizing, before iterating (disabled)");
-#endif
+
neighbors.remove_if([this,budget,tgt](const UInt& n) { return platform->topology->get_distance(n, tgt) >= budget; });
-#ifdef MULTI_LINE_LOG_DEBUG
- QL_IF_LOG_DEBUG {
- QL_DOUT("gen_shortest_paths: ... after reducing to steps within budget, nbl: ");
- for (auto dn : neighbors) {
- QL_DOUT("..." << dn << " ");
- }
- }
-#else
- QL_DOUT("gen_shortest_paths: ... after reducing to steps within budget, nbl (disabled) ");
-#endif
// Update the neighbor list according to the path strategy.
if (strategy == PathStrategy::RANDOM) {
-
// Shuffle the neighbor list. We have to go through a vector to do that,
// otherwise std::shuffle doesn't work.
utils::Vec neighbors_vec{neighbors.begin(), neighbors.end()};
std::shuffle(neighbors_vec.begin(), neighbors_vec.end(), rng);
neighbors.clear();
neighbors.insert(neighbors.begin(), neighbors_vec.begin(), neighbors_vec.end());
-
} else {
-
// Rotate neighbor list nbl such that largest difference between angles
// of adjacent elements is beyond back(). This only makes sense when
// there is an underlying xy grid; when not, only the ALL strategy is
@@ -156,19 +84,9 @@ void Mapper::gen_shortest_paths(
}
-#ifdef MULTI_LINE_LOG_DEBUG
- QL_IF_LOG_DEBUG {
- QL_DOUT("gen_shortest_path, neighbor relations after normalizing, before iterating");
- for (auto dn : neighbors) {
- QL_DOUT("..." << dn << " ");
- }
- }
-#else
- QL_DOUT("gen_shortest_path, neighbor relations after normalizing, before iterating (disabled)");
-#endif
-
// For all resulting neighbors, find all continuations of a shortest path by
// recursively calling ourselves.
+ List result;
for (auto &n : neighbors) {
PathStrategy new_strategy = strategy;
@@ -187,147 +105,69 @@ void Mapper::gen_shortest_paths(
// max_alters is 0 there is no limit.
UInt max_sub_alters = 0;
if (max_alters > 0) {
- QL_ASSERT(max_alters > alters.size());
- max_sub_alters = max_alters - alters.size();
+ QL_ASSERT(max_alters > result.size());
+ max_sub_alters = max_alters - result.size();
}
// Get list of possible paths in budget-1 from n to tgt in sub_alters.
- gen_shortest_paths(gate, sub_path, n, tgt, budget - 1, sub_alters, max_sub_alters, new_strategy);
-
- // Move all of sub_alters to alters, and make sub_alters empty.
- alters.splice(alters.end(), sub_alters);
+ auto rec = gen_shortest_paths(gate, path, n, tgt, budget - 1, max_sub_alters, new_strategy);
+ result.splice(result.end(), rec);
// Check whether we've found enough alternatives already.
- if (max_alters && alters.size() >= max_alters) {
+ if (max_alters && result.size() >= max_alters) {
break;
}
-
}
- QL_DOUT("... gen_shortest_paths: returning from call of: " << "src=" << src << " tgt=" << tgt << " budget=" << budget << " which=" << strategy);
+ return result;
}
-/**
- * Find shortest paths in the grid for making the given gate
- * nearest-neighbor, from qubit src to qubit tgt, with an alternative for
- * each one. This starts off the recursion done by the above overload of
- * gen_shortest_paths(), and then generates new alternatives for each
- * possible "split" of each path.
- *
- * Steps:
- * - Compute budget. Usually it is distance but it can be higher such as
- * for multi-core.
- * - Reduce the number of paths depending on the path selection option.
- * - When not all shortest paths found are valid, take these out.
- * - Paths are further split because each split may give rise to a separate
- * alternative. A split is a hop where the two-qubit gate is assumed to
- * be done. After splitting each alternative contains two lists, one
- * before and one after (reversed) the envisioned two-qubit gate; all
- * result alternatives are such that a two-qubit gate can be placed at
- * the split
- *
- * The end result is a list of alternatives (in alters) suitable for being
- * evaluated for any routing metric.
- */
-void Mapper::gen_shortest_paths(const ir::compat::GateRef &gate, UInt src, UInt tgt, List &alters) {
-
- // Compute budget.
+List Mapper::gen_shortest_paths(const ir::CustomInstructionRef &gate, UInt src, UInt tgt) {
+ QL_ASSERT(src != tgt);
+
UInt budget = platform->topology->get_min_hops(src, tgt);
+ auto compute = [&gate, src, tgt, budget, this](PathStrategy s) {
+ return gen_shortest_paths(gate, {}, src, tgt, budget, options->max_alters, s);
+ };
- // Generate paths using the configured path selection strategy.
if (options->path_selection_mode == PathSelectionMode::ALL) {
- gen_shortest_paths(gate, nullptr, src, tgt, budget, alters, options->max_alters, PathStrategy::ALL);
- } else if (options->path_selection_mode == PathSelectionMode::BORDERS) {
- gen_shortest_paths(gate, nullptr, src, tgt, budget, alters, options->max_alters, PathStrategy::LEFT_RIGHT);
- } else if (options->path_selection_mode == PathSelectionMode::RANDOM) {
- gen_shortest_paths(gate, nullptr, src, tgt, budget, alters, options->max_alters, PathStrategy::RANDOM);
- } else {
- QL_FATAL("Unknown value of path selection mode option " << options->path_selection_mode);
+ return compute(PathStrategy::ALL);
+ }
+
+ if (options->path_selection_mode == PathSelectionMode::BORDERS) {
+ return compute(PathStrategy::LEFT_RIGHT);
+ }
+
+ if (options->path_selection_mode == PathSelectionMode::RANDOM) {
+ return compute(PathStrategy::RANDOM);
}
- // Note: path split used to be here. Now it's done greedily by
- // gen_shortest_paths().
-
-}
-
-/**
- * Generates all possible variations of making the given gate
- * nearest-neighbor, starting from given past (with its mappings), and
- * return the found variations by appending them to the given list of
- * Alters.
- */
-void Mapper::gen_alters_gate(const ir::compat::GateRef &gate, List &alters, Past &past) {
-
- // Interpret virtual operands in past's current map.
- auto &q = gate->operands;
- QL_ASSERT (q.size() == 2);
- UInt src = past.get_real_qubit(q[0]);
- UInt tgt = past.get_real_qubit(q[1]);
-
- QL_DOUT("gen_alters_gate: " << gate->qasm() << " in real (q" << src << ",q" << tgt << ") at get_min_hops=" << platform->topology->get_min_hops(src, tgt));
- past.debug_print_fc();
-
- // Find shortest paths from src to tgt, and split these.
- gen_shortest_paths(gate, src, tgt, alters);
- QL_ASSERT(!alters.empty());
- // Alter::DPRINT("... after GenShortestPaths", la);
-
+ QL_FATAL("Unknown value of path selection mode option " << options->path_selection_mode);
}
-/**
- * Generates all possible variations of making the given gates
- * nearest-neighbor, starting from given past (with its mappings), and
- * return the found variations by appending them to the given list of
- * Alters. Depending on the lookahead option, only take the first (most
- * critical) gate, or take all gates.
- */
-void Mapper::gen_alters(
- const utils::List &gates,
- List &alters,
- Past &past
-) {
- if (options->lookahead_mode == LookaheadMode::ALL) {
-
- // Create alternatives for each gate.
- QL_DOUT("gen_alters, " << gates.size() << " 2q gates; create an alternative for each");
- for (const auto &gate : gates) {
+List Mapper::gen_alters_gate(const ir::CustomInstructionRef &gate, Past &past) {
+ auto qubits = ir::OperandsHelper(platform, *gate).get2QGateOperands();
- // Generate all possible variations to make gate nearest-neighbor
- // in current v2r mapping ("past").
- QL_DOUT("gen_alters: create alternatives for: " << gate->qasm());
- gen_alters_gate(gate, alters, past);
+ UInt src = past.get_real_qubit(qubits.first);
+ UInt tgt = past.get_real_qubit(qubits.second);
- }
-
- } else {
-
- // Only take the first gate in in the list, the most critical one, and
- // generate alternatives for it.
- ir::compat::GateRef gate = gates.front();
+ return gen_shortest_paths(gate, src, tgt);
+}
- // Generate all possible variations to make gate nearest-neighbor, in
- // current v2r mapping ("past").
- QL_DOUT("gen_alters, " << gates.size() << " 2q gates; take first: " << gate->qasm());
- gen_alters_gate(gate, alters, past);
+List Mapper::gen_alters(const utils::List &gates, Past &past) {
+ if (options->lookahead_mode != LookaheadMode::ALL) {
+ return gen_alters_gate(gates.front(), past);
+ }
+ List result;
+ for (const auto &gate : gates) {
+ auto gate_alters = gen_alters_gate(gate, past);
+ result.splice(result.end(), gate_alters);
}
-}
-/**
- * Seeds the random number generator with the current time in microseconds.
- */
-void Mapper::random_init() {
- auto ts = std::chrono::duration_cast(
- std::chrono::system_clock::now().time_since_epoch()
- ).count();
- // QL_DOUT("Seeding random generator with " << ts );
- rng.seed(ts);
+ return result;
}
-/**
- * Chooses an Alter from the list based on the configured tie-breaking
- * strategy.
- */
Alter Mapper::tie_break_alter(List &alters, Future &future) {
QL_ASSERT(!alters.empty());
@@ -337,35 +177,26 @@ Alter Mapper::tie_break_alter(List &alters, Future &future) {
switch (options->tie_break_method) {
case TieBreakMethod::CRITICAL: {
- List lag;
+ std::vector target_gates;
+ target_gates.reserve(alters.size());
for (auto &a : alters) {
- lag.push_back(a.target_gate);
+ target_gates.push_back(a.get_target_gate());
}
- ir::compat::GateRef gate = future.get_most_critical(lag);
- QL_ASSERT(!gate.empty());
+ ir::CustomInstructionRef gate = future.get_most_critical(target_gates);
+
for (auto &a : alters) {
- if (a.target_gate.get_ptr() == gate.get_ptr()) {
- // QL_DOUT(" ... took first alternative with most critical target gate");
+ if (a.get_target_gate().get_ptr() == gate.get_ptr()) {
return a;
}
}
- return alters.front();
+
+ QL_FATAL("This should not happen");
}
case TieBreakMethod::RANDOM: {
- Alter res;
std::uniform_int_distribution<> dis(0, (alters.size() - 1));
UInt choice = dis(rng);
- UInt i = 0;
- for (auto &a : alters) {
- if (i == choice) {
- res = a;
- break;
- }
- i++;
- }
- // QL_DOUT(" ... took random draw " << choice << " from 0.." << (la.size()-1));
- return res;
+ return *(std::next(alters.begin(), choice));
}
case TieBreakMethod::LAST:
@@ -374,468 +205,140 @@ Alter Mapper::tie_break_alter(List &alters, Future &future) {
case TieBreakMethod::FIRST:
default:
return alters.front();
-
}
}
-/**
- * Map the gate/operands of a gate that has been routed or doesn't require
- * routing.
- */
-void Mapper::map_routed_gate(const ir::compat::GateRef &gate, Past &past) {
- QL_DOUT("map_routed_gate on virtual: " << gate->qasm());
-
- // make_real() maps the qubit operands of the gate and optionally updates
- // its gate name. When the gate name was updated, a new gate with that name
- // is created; when that new gate is a composite gate, it is immediately
- // decomposed (by gate creation). The resulting gate/expansion (anyhow a
- // sequence of gates) is collected in circuit.
- ir::compat::GateRefs gates;
- past.make_real(gate, gates);
- for (const auto &new_gate : gates) {
- QL_DOUT(" ... new mapped real gate, about to be added to past: " << new_gate->qasm());
- past.add_and_schedule(new_gate);
- }
-
+void Mapper::map_routed_gate(const ir::CustomInstructionRef &gate, Past &past) {
+ auto cloned_gate = gate->clone();
+ past.make_real(cloned_gate);
+ past.add(cloned_gate);
}
-/**
- * Commit the given Alter, generating swaps in the past and taking it out
- * of future when done with it. Depending on configuration, this might not
- * actually place the target gate for the given alternative yet, because
- * only part of the swap chain is generated; in this case, swaps are added
- * to past, but future is not updated.
- */
void Mapper::commit_alter(Alter &alter, Future &future, Past &past) {
+ const auto& target = alter.get_target_gate();
- // The target two-qubit-gate, now not yet nearest-neighbor.
- ir::compat::GateRef target = alter.target_gate;
- alter.debug_print("... commit_alter, alternative to commit, will add swaps and then map target 2q gate");
-
- // Add swaps to the past to make the target gate nearest-neighbor.
- alter.add_swaps(past, options->swap_selection_mode);
+ alter.add_swaps(past);
// When only some swaps were added (based on configuration), the target
// might not yet be nearest-neighbor, so recheck.
- auto &q = target->operands;
- if (platform->topology->get_min_hops(past.get_real_qubit(q[0]), past.get_real_qubit(q[1])) == 1) {
-
- // Target is nearest-neighbor, so we;re done with this gate.
- // QL_DOUT("... CommitAlter, target 2q is NN, map it and done: " << resgp->qasm());
+ auto ops = ir::OperandsHelper(platform, *target);
+ QL_ASSERT(ops.numberOfQubitOperands() == 2);
+ if (ops.isNN2QGate([&past](utils::UInt q) { return past.get_real_qubit(q); })) {
map_routed_gate(target, past);
future.completed_gate(target);
-
- } else {
- // QL_DOUT("... CommitAlter, target 2q is not NN yet, keep it: " << resgp->qasm());
}
-
}
-/**
- * Find gates available for scheduling that do not require routing, take
- * them out, and map them. Ultimately, either no available gates remain, or
- * only gates that require routing remain. Return false when no gates remain
- * at all, and true when any gates remain; those gates are returned in
- * the gates list.
- *
- * Behavior depends on the value of the lookahead_mode option and on
- * also_nn_two_qubit_gate. When the latter is true, and lookahead_mode is...
- * - DISABLED: while next in circuit is non-quantum or single-qubit, map
- * that gate. Return when we find a two-qubit gate (nearest-neighbor or
- * not). In this case, get_non_quantum_gates only returns a non-quantum
- * gate when it is next in circuit.
- * - ONE_QUBIT_GATE_FIRST: while next in circuit is non-quantum or
- * single-qubit, map that gate. Return the most critical two-qubit gate
- * (nearest-neighbor or not).
- * - NO_ROUTING_FIRST: while next in circuit is non-quantum, single-qubit,
- * or nearest-neighbor two-qubit, map that gate. Return the most critical
- * non-nearest-neighbor two-qubit gate.
- * - ALL: while next in circuit is non-quantum, single-qubit, or
- * nearest-neighbor two-qubit, map that gate. Return ALL
- * non-nearest-neighbor two-qubit gates.
- *
- * When also_nn_two_qubit_gate is false, behavior is the same, except
- * nearest-neighbor two-qubit gates behave as if they're not
- * nearest-neighbor.
- */
-Bool Mapper::map_mappable_gates(
+List Mapper::map_mappable_gates(
Future &future,
Past &past,
- List &gates,
Bool also_nn_two_qubit_gates
) {
+ utils::List available_gates;
+ while (!(available_gates = future.get_schedulable_gates()).empty()) {
+ routing_progress.feed(future.get_progress());
- // List of non-quantum gates in avlist.
- List av_non_quantum_gates;
+ bool hasMappedGate = false;
+ for (const auto &gate : available_gates) {
+ ir::OperandsHelper ops(platform, *gate);
- // List of (remaining) gates in avlist.
- List av_gates;
+ // FIXME: wait gates as well.
+ hasMappedGate = ops.numberOfQubitOperands() < 2 || (
+ also_nn_two_qubit_gates && ops.isNN2QGate([&past](utils::UInt q) { return past.get_real_qubit(q); }));
- QL_DOUT("map_mappable_gates entry");
- while (true) {
-
- // Print progress every once in a while if we're taking long.
- Real progress = 1.0;
- if (future.approx_gates_total) {
- progress -= ((Real)future.approx_gates_remaining / (Real)future.approx_gates_total);
- }
- routing_progress.feed(progress);
-
- // Handle non-quantum gates that need to be done first.
- if (future.get_non_quantum_gates(av_non_quantum_gates)) {
-
- // List of gates available for scheduling contains non-quantum
- // gates, and get_non_quantum_gates() indicates these (in
- // av_non_quantum_gates) must be done first.
- QL_DOUT("map_mappable_gates, there is a set of non-quantum gates");
- for (const auto &gate : av_non_quantum_gates) {
- QL_DOUT(". non-quantum gate: " << gate->qasm());
-
- // here add code to map qubit use of any non-quantum instruction????
- // dummy gates are nonq gates internal to OpenQL such as SOURCE/SINK; don't output them
- if (gate->type() != ir::compat::GateType::DUMMY) {
- // past only can contain quantum gates, so non-quantum gates must by-pass Past
- QL_DOUT(".. non-quantum gate (not a dummy) to be flushed to outlist: " << gate->qasm());
- past.bypass(gate); // this flushes past.lg first to outlg
- }
- QL_DOUT(".. non-quantum gate to be set to completed: " << gate->qasm());
- future.completed_gate(gate); // so on avlist= nonNN2q -> NN2q -> 1q -> nonq: the nonq is done first
- QL_DOUT(". non-quantum gate: " << gate->qasm() << " [DONE]");
-
- }
- QL_DOUT("map_mappable_gates, there is a set of non-quantum gates [DONE]");
- continue;
-
- }
-
- // Get available gates.
- if (!future.get_gates(av_gates)) {
-
- // No more gates available for scheduling.
- QL_DOUT("map_mappable_gates, no gates anymore, return");
- gates.clear();
- return false;
-
- }
-
- // Quantum gates are available for scheduling, and
- // get_non_quantum_gates/get_gates indicate these (in av_gates) must be
- // done now. Look for gates that never require routing (single-qubit and
- // wait gates).
- Bool found = false;
- for (const auto &gate : av_gates) {
- if (gate->type() == ir::compat::GateType::WAIT || gate->operands.size() == 1) {
-
- // A quantum gate that never requires routing was found.
- QL_DOUT(". never-requiring mapping quantum gate to be mapped: " << gate->qasm());
+ if (hasMappedGate) {
map_routed_gate(gate, past);
- QL_DOUT(". never-requiring mapping quantum gate to be set to completed: " << gate->qasm());
future.completed_gate(gate);
- found = true;
- // so on avlist= nonNN2q -> NN2q -> 1q: the 1q is done first
break;
-
}
}
- if (found) {
- continue;
- }
-
- // av_gates only contains 2q gates, that may require routing.
- if (also_nn_two_qubit_gates) {
-
- // When there is a 2q in qlg that is mappable already, map it.
- // When there is more than one, take most critical one first
- // (because qlg is ordered, most critical first).
- for (const auto &gate : av_gates) {
-
- // Interpret virtual operands in current map.
- auto &q = gate->operands;
- UInt src = past.get_real_qubit(q[0]);
- UInt tgt = past.get_real_qubit(q[1]);
-
- // Find minimum number of hops between real counterparts.
- UInt d = platform->topology->get_min_hops(src, tgt);
-
- if (d == 1) {
- // Just one hop, so gate is already nearest-neighbor and can
- // be mapped.
- QL_DOUT(". NN gate, to be mapped: " << gate->qasm() << " in real (q" << src << ",q" << tgt << ")");
- map_routed_gate(gate, past);
- QL_DOUT(". NN gate, to be set to completed: " << gate->qasm() );
- future.completed_gate(gate);
- found = true; // a 2q quantum gate was found that was mappable
- // so on avlist= nonNN2q -> NN2q: the NN2q is done first
- break;
-
- }
- }
- if (found) {
-
- // Found a mappable two-qubit gate and mapped it. Don't map more
- // mappable two-qubit gates (they might not be critical, the now
- // available single-qubit gates may hide a more critical
- // two-qubit gate), but deal with all available non-quantum and
- // single-qubit gates first, and only when non of those remain,
- // map a next mappable two-qubit (now most critical) gate.
- QL_DOUT(". map_mappable_gates, found and mapped an easy quantum gate, continuing ...");
- continue;
-
- }
- QL_DOUT(". map_mappable_gates, only nonNN 2q gates remain: ...");
- } else {
- QL_DOUT(". map_mappable_gates, only 2q gates remain (nonNN and NN): ...");
- }
-
- // Only two-qubit gates remain in the available gate list (when
- // also_nn_two_qubit_gate these are furthermore necessarily
- // non-nearest-neighbor, otherwise they might also be nearest-neighbor).
- gates = av_gates;
-
-#ifdef MULTI_LINE_LOG_DEBUG
- QL_IF_LOG_DEBUG {
- for (const auto &gate : gates) {
- QL_DOUT(". map_mappable gates, 2q gates returned: " << gate->qasm());
- }
+ if (!hasMappedGate) {
+ // Remaining gates require actual routing.
+ return available_gates;
}
-#else
- QL_DOUT("map_mappable gates, 2q gates returned (disabled)");
-#endif
-
- return true;
}
+
+ return {};
}
-/**
- * Select an Alter based on the selected heuristic.
- *
- * - If BASE[_RC], consider all Alters as equivalent, and thus apply the
- * tie-breaking strategy to all.
- * - If MIN_EXTEND[_RC], consider Alters with the minimal cycle extension
- * of the given past (or some factor of that amount, ordered by
- * increasing cycle extension) and recurse. When the recursion depth
- * limit is reached, apply the tie-breaking strategy.
- *
- * For recursion, past is the speculative past, and base_past is the past
- * we've already committed to, and should thus measure fitness against.
- */
-void Mapper::select_alter(
+
+Alter Mapper::select_alter(
List &alters,
- Alter &result,
Future &future,
Past &past,
Past &base_past,
UInt recursion_depth
) {
- // alters are all alternatives we enter with. There must be at least one.
QL_ASSERT(!alters.empty());
- QL_DOUT("select_alter ENTRY level=" << recursion_depth << " from " << alters.size() << " alternatives");
-
- // Handle the basic strategy, where we just tie-break on all alters without
- // recusing.
- if (options->heuristic == Heuristic::BASE || options->heuristic == Heuristic::BASE_RC) {
- Alter::debug_print(
- "... select_alter base (equally good/best) alternatives:", alters);
- result = tie_break_alter(alters, future);
- result.debug_print("... the selected Alter is");
- // QL_DOUT("SelectAlter DONE level=" << level << " from " << la.size() << " alternatives");
- return;
+ if (options->heuristic == Heuristic::BASE) {
+ return tie_break_alter(alters, future);
}
- QL_ASSERT(
- options->heuristic == Heuristic::MIN_EXTEND ||
- options->heuristic == Heuristic::MIN_EXTEND_RC ||
- options->heuristic == Heuristic::MAX_FIDELITY
- );
+ QL_ASSERT(options->heuristic == Heuristic::MIN_EXTEND);
- // Compute a score for each alternative relative to base_past, and sort the
- // alternatives based on it, minimum first.
for (auto &a : alters) {
- a.debug_print("Considering extension by alternative: ...");
- a.extend(past, base_past); // locally here, past will be cloned and kept in alter
- // and the extension stored into the a.score
+ a.extend(past, base_past); // This fills a.score.
}
- alters.sort([this](const Alter &a1, const Alter &a2) { return a1.score < a2.score; });
- Alter::debug_print(
- "... select_alter sorted all entry alternatives after extension:", alters);
-
- // Reduce sorted list of alternatives to list of good alternatives, suitable
- // to find in recursion which is/are really best. This need not be only
- // those with minimum cycle extension; it can be more, based on the
- // recursion_width_factor option. However, this easily, as each level
- // exponentially builds more alternatives. To curb this,
- // recursion_width_exponent can be set to a value less than one, to
- // multiplicatively reduce the width for each recursion level.
- List good_alters = alters;
- good_alters.remove_if([this,alters](const Alter& a) { return a.score != alters.front().score; });
- Real factor = options->recursion_width_factor * utils::pow(options->recursion_width_exponent, recursion_depth);
- Real keep_real = utils::max(1.0, utils::ceil(factor * good_alters.size()));
- UInt keep = (keep_real < static_cast(utils::MAX)) ? static_cast(keep_real) : utils::MAX;
- if (keep != good_alters.size()) {
- if (keep < alters.size()) {
- good_alters = alters;
- List::iterator ia;
- ia = good_alters.begin();
- advance(ia, keep);
- good_alters.erase(ia, good_alters.end());
- } else {
- good_alters = alters;
- }
+ alters.sort([this](const Alter &a1, const Alter &a2) { return a1.get_score() < a2.get_score(); });
+
+ auto factor = options->recursion_width_factor * utils::pow(options->recursion_width_exponent, recursion_depth);
+ auto keep_real = utils::max(1.0, utils::ceil(factor * alters.size()));
+ auto keep = (keep_real < static_cast(utils::MAX)) ? static_cast(keep_real) : utils::MAX;
+
+ QL_ASSERT(keep > 0);
+
+ while (alters.size() > keep) {
+ alters.pop_back();
}
- // QL_DOUT("SelectAlter mapselectmaxwidth=" << mapselectmaxwidthopt << " level=" << level << " reduced la to gla");
- Alter::debug_print("... select_alter good alternatives before recursion:", good_alters);
- // When maxlevel has been reached, stop the recursion, and choose from the
- // best minextend/maxfidelity alternatives.
- if (recursion_depth >= options->recursion_depth_limit) {
+ QL_ASSERT(alters.size() <= keep);
- // Reduce list of good alternatives to list of minextend/maxfidelity
- // best alternatives (bla), and make a choice from that list to return
- // as result.
- List best_alters = good_alters;
- best_alters.remove_if([this,good_alters](const Alter& a) { return a.score != good_alters.front().score; });
- Alter::debug_print(
- "... select_alter reduced to best alternatives to choose result from:",
- best_alters);
- result = tie_break_alter(best_alters, future);
- result.debug_print("... the selected Alter (STOPPING RECURSION) is");
- // QL_DOUT("SelectAlter DONE level=" << level << " from " << bla.size() << " best alternatives");
- return;
+ if (recursion_depth >= options->recursion_depth_limit) {
+ while (alters.back().get_score() > alters.front().get_score()) {
+ alters.pop_back();
+ }
+ return tie_break_alter(alters, future);
}
- // Otherwise, prepare using recursion to choose from the good alternatives,
- // i.e. make a recursion step looking ahead to decide which alternative is
- // best.
- //
- // For each good alternative, lookahead for next non-nearest-neighbor
- // two-qubit gates, and compare them for their alternative mappings; the
- // lookahead alternative with the least overall extension (i.e. relative to
- // base_past) is chosen, and the current alternative on top of which it was
- // built is chosen at the current level, unwinding the recursion.
- //
- // Recursion could stop above because the maximum depth was reached, but it
- // can also stop because the end of the circuit has been reached (i.e. no
- // non-nearest-neighbor two-qubit gates remain.
- //
- // When there is only one good alternative, we still want to know its
- // minimum extension to compare with competitors, since that is not just a
- // local figure but the extension from base_past. So with only one
- // alternative we may still go into recursion below. This means that
- // recursion always goes to the maximum depth or to the end of the circuit.
- // This anomaly may need correction.
- // QL_DOUT("... SelectAlter level=" << level << " entering recursion with " << gla.size() << " good alternatives");
- for (auto &a : good_alters) {
- a.debug_print("... ... considering alternative:");
- Future sub_future = future; // copy!
- Past sub_past = past; // copy!
+ for (auto &a : alters) {
+ // Copy of current state for recursion.
+ Future sub_future = future;
+ Past sub_past = past;
+
commit_alter(a, sub_future, sub_past);
- a.debug_print(
- "... ... committed this alternative first before recursion:");
-
- // Whether there are still non-nearest-neighbor two-qubit gates to map.
- Bool gates_remain;
-
- // List of non-nearest-neighbor two-qubit gates taken from the available
- // gate list, as returned from map_mappable_gates.
- List gates;
-
- // In recursion, look at recurse_on_nn_two_qubit option:
- // - map_mappable_gates with recurse_on_nn_two_qubit==true is greedy and
- // immediately maps each single-qubit and nearest-neighbor two-qubit
- // gate;
- // - map_mappable_gates with recurse_on_nn_two_qubit==false is not
- // greedy, mapping only the single-qubit gates.
- //
- // When true and when the lookahead mode is NO_ROUTING_FIRST or ALL, let
- // map_mappable_gates stop mapping only on finding a non-nearest
- // two-qubit gate. Otherwise, let map_mappable_gates stop mapping on any
- // two-qubit gate. This creates more clear recursion: one two-qubit gate
- // at a time, instead of a possible empty set of nearest-neighbor
- // two-qubit gates followed by a non-nearest neighbor two-qubit gate.
- // When a nearest-neighbor gate is found, this is perfect; this is not
- // seen when immediately mapping all non-nearest two-qubit gates. So the
- // goal is to prove that recurse_on_nn_two_qubit should be no at this
- // place, in the recursion step, but not at level 0!
+
Bool also_nn_two_qubit_gates = options->recurse_on_nn_two_qubit
&& (
options->lookahead_mode == LookaheadMode::NO_ROUTING_FIRST
|| options->lookahead_mode == LookaheadMode::ALL
);
- // Map all easy gates. Remainder is returned in gates.
- gates_remain = map_mappable_gates(sub_future, sub_past, gates, also_nn_two_qubit_gates);
-
- if (gates_remain) {
-
- // End of circuit not yet reached, recurse.
- QL_DOUT("... ... select_alter level=" << recursion_depth << ", committed + mapped easy gates, now facing " << gates.size() << " 2q gates to evaluate next");
+ auto gates = map_mappable_gates(sub_future, sub_past, also_nn_two_qubit_gates);
- // Generate the next set of alternative routing actions.
- List sub_alters;
- gen_alters(gates, sub_alters, sub_past);
- QL_DOUT("... ... select_alter level=" << recursion_depth << ", generated for these 2q gates " << sub_alters.size() << " alternatives; RECURSE ... ");
-
- // Select the best alternative from the list by recursion.
- Alter sub_result;
- select_alter(sub_alters, sub_result, sub_future, sub_past, base_past, recursion_depth + 1);
- sub_result.debug_print("... ... select_alter, generated for these 2q gates ... ; RECURSE DONE; resulting alternative ");
-
- // The extension of deep recursion is treated as extension at the
- // current level; by this an alternative that started bad may be
- // compensated by deeper alters.
- a.score = sub_result.score;
+ if (!gates.empty()) {
+ auto sub_alters = gen_alters(gates, sub_past);
+ QL_ASSERT(!sub_alters.empty() && "No suitable routing path");
+ auto sub_result = select_alter(sub_alters, sub_future, sub_past, base_past, recursion_depth + 1);
+ a.set_score(sub_result.get_score());
} else {
-
- // Reached end of circuit while speculating.
- QL_DOUT("... ... select_alter level=" << recursion_depth << ", no gates to evaluate next; RECURSION BOTTOM");
- if (options->heuristic == Heuristic::MAX_FIDELITY) {
- QL_FATAL("Mapper option maxfidelity has been disabled");
- // a.score = quick_fidelity(past_copy.lg);
- } else {
- a.score = sub_past.get_max_free_cycle() -
- base_past.get_max_free_cycle();
- }
- a.debug_print(
- "... ... select_alter, after committing this alternative, mapped easy gates, no gates to evaluate next; RECURSION BOTTOM");
-
+ a.set_score(sub_past.get_max_free_cycle() - base_past.get_max_free_cycle());
}
- a.debug_print("... ... DONE considering alternative:");
}
- // Sort list of good alternatives on score resulting after recursion.
- good_alters.sort([this](const Alter &a1, const Alter &a2) { return a1.score < a2.score; });
- Alter::debug_print("... select_alter sorted alternatives after recursion:", good_alters);
-
- // Reduce list of good alternatives of before recursion to list of equally
- // minimal best alternatives now, and make a choice from that list to return
- // as result.
- List best_alters = good_alters;
- best_alters.remove_if([this,good_alters](const Alter& a) { return a.score != good_alters.front().score; });
- Alter::debug_print("... select_alter equally best alternatives on return of RECURSION:", best_alters);
- result = tie_break_alter(best_alters, future);
- result.debug_print("... the selected Alter is");
- // QL_DOUT("... SelectAlter level=" << level << " selecting from " << bla.size() << " equally good alternatives above DONE");
- QL_DOUT("select_alter DONE level=" << recursion_depth << " from " << alters.size() << " alternatives");
-
-}
+ alters.sort([this](const Alter &a1, const Alter &a2) { return a1.get_score() < a2.get_score(); });
-/**
- * Given the states of past and future, map all mappable gates and find the
- * non-mappable ones. For those, evaluate what to do next and do it. During
- * recursion, comparison is done with the base past (bottom of recursion
- * stack), and past is the last past (top of recursion stack) relative to
- * which the mapping is done.
- */
-void Mapper::map_gates(Future &future, Past &past, Past &base_past) {
+ while (alters.back().get_score() > alters.front().get_score()) {
+ alters.pop_back();
+ }
- // List of non-mappable gates taken from avlist, as returned by
- // map_mappable_gates.
- List gates;
+ return tie_break_alter(alters, future);
+}
+utils::Any Mapper::route_gates(Future &future, Past &past) {
Bool also_nn_two_qubit_gates = (
options->lookahead_mode == LookaheadMode::NO_ROUTING_FIRST
|| options->lookahead_mode == LookaheadMode::ALL
@@ -843,271 +346,81 @@ void Mapper::map_gates(Future &future, Past &past, Past &base_past) {
routing_progress = Progress("router", 1000);
- // Handle all the gates one by one. map_mappable_gates returns false when no
- // gates remain.
- while (map_mappable_gates(future, past, gates, also_nn_two_qubit_gates)) {
+ List gates;
+ while (!(gates = map_mappable_gates(future, past, also_nn_two_qubit_gates)).empty()) {
+ auto alters = gen_alters(gates, past);
+ QL_ASSERT(!alters.empty() && "No suitable routing path");
- // All gates in the gates list are two-qubit quantum gates that cannot
- // be mapped yet. Select which one(s) to (partially) route, according to
- // one of the known strategies. The only requirement on the code below
- // is that at least something is done that decreases the problem.
+ auto selected_alter = select_alter(alters, future, past, past, 0);
- // Generate all alternative routes.
- List alters;
- gen_alters(gates, alters, past);
-
- // Select the best one based on the configured strategy.
- Alter alter;
- select_alter(alters, alter, future, past, base_past, 0);
-
- // Commit to selected alternative. This adds all or just one swap
- // (depending on configuration) to THIS past, and schedules them/it in.
- commit_alter(alter, future, past);
-
- // Print progress every once in a while if we're taking long.
- Real progress = 1.0;
- if (future.approx_gates_total) {
- progress -= ((Real)future.approx_gates_remaining / (Real)future.approx_gates_total);
- }
- routing_progress.feed(progress);
+ commit_alter(selected_alter, future, past);
+ routing_progress.feed(future.get_progress());
}
routing_progress.complete();
-}
-
-/**
- * Map the kernel's circuit's gates in the provided context (v2r maps),
- * updating circuit and v2r maps.
- */
-void Mapper::route(const ir::compat::KernelRef &k, com::map::QubitMapping &v2r) {
-
- // Future window, presents input in available list.
- Future future;
-
- // Past window, contains output schedule, storing all gates until taken out.
- Past past;
-
- // Scheduler instance (from src/scheduler.h) used for its dependency graph.
- utils::Ptr sched;
- sched.emplace();
-
- // Initialize the future window with the incoming circuit.
- future.initialize(platform, options);
- future.set_kernel(k, sched);
-
- // Future has now copied kernel->c to private data, making kernel->c ready
- // for use by Past::new_gate(), for the kludge we need because gates can
- // only be constructed in the context of and at the end of a kernel.
- k->gates.reset();
- kernel = k;
- past.initialize(kernel, options);
- past.import_mapping(v2r);
-
- // Perform the actual mapping.
- map_gates(future, past, past);
-
- // Flush all gates to the output window.
- past.flush_all();
-
- // Copy the gates into the kernel's circuit.
- // mainPast.DPRINT("end mapping");
- QL_DOUT("... retrieving outCirc from mainPast.outlg; swapping outCirc with kernel.c, kernel.c contains output circuit");
- k->gates.reset();
- past.flush_to_circuit(k->gates);
-
- // The mapper also schedules internally, including any decompositions it
- // does to make things primitive. Thus, cycle numbers are now valid.
- k->cycles_valid = true;
-
- // Update the virtual to real qubit mapping to what it is after all the
- // kernel's routing actions.
- past.export_mapping(v2r);
-
- // Store statistics gathered by the past before it goes out of scope.
- num_swaps_added = past.get_num_swaps_added();
- num_moves_added = past.get_num_moves_added();
+ return past.flush_to_circuit();
}
-/**
- * Decomposes all gates in the circuit that have a definition with _prim
- * appended to its name. The mapper does this after routing.
- */
-void Mapper::decompose_to_primitives(const ir::compat::KernelRef &k) {
- QL_DOUT("decompose_to_primitives circuit ...");
-
- // Copy to allow kernel.c use by Past.new_gate.
- ir::compat::GateRefs circuit = k->gates;
- k->gates.reset();
-
- // Output window in which gates are scheduled.
- Past past;
- past.initialize(k, options);
+Mapper::RoutingStatistics Mapper::route(ir::BlockBaseRef block, com::map::QubitMapping &v2r) {
+ Future future(platform, options, block);
- for (const auto &gate : circuit) {
-
- // Decompose gate into prim_circuit. On failure, this copies the
- // original gate directly into it.
- ir::compat::GateRefs prim_gates;
- past.make_primitive(gate, prim_gates);
-
- // Schedule the potentially decomposed gates.
- for (const auto &prim_gate : prim_gates) {
- past.add_and_schedule(prim_gate);
- }
+ Past past(platform, options);
+ past.import_mapping(v2r);
- }
+ block->statements = route_gates(future, past);
- // Update the output circuit based on the scheduling result.
- past.flush_all();
- past.flush_to_circuit(k->gates);
- k->cycles_valid = true;
+ past.export_mapping(v2r_out);
- QL_DOUT("decompose_to_primitives circuit [DONE]");
-}
+ Mapper::RoutingStatistics stats;
+
+ stats.num_swaps_added = past.get_num_swaps_added();
+ stats.num_moves_added = past.get_num_moves_added();
-/**
- * Initialize the data structures in this class that don't change from
- * kernel to kernel.
- */
-void Mapper::initialize(const ir::compat::PlatformRef &p, const OptionsRef &opt) {
- // QL_DOUT("Mapping initialization ...");
- // QL_DOUT("... Grid initialization: platform qubits->coordinates, ->neighbors, distance ...");
- platform = p;
- options = opt;
- nq = p->qubit_count;
- nc = p->creg_count;
- nb = p->breg_count;
- random_init();
- // QL_DOUT("... platform/real number of qubits=" << nq << ");
- cycle_time = p->cycle_time;
-
- // QL_DOUT("Mapping initialization [DONE]");
+ return stats;
}
-/**
- * Runs routing and decomposition to primitives for
- * the given kernel.
- *
- * TODO: split off the decomposition to primitives.
- */
-void Mapper::map_kernel(const ir::compat::KernelRef &k) {
- QL_DOUT("Mapping kernel " << k->name << " [START]");
- QL_DOUT("... kernel original virtual number of qubits=" << k->qubit_count);
- kernel.reset(); // no new_gates until kernel.c has been copied
-
- QL_DOUT("Mapper::Map before v2r.initialize: assume_initialized=" << options->assume_initialized);
-
- // TODO: unify all incoming v2rs into v2r to compute kernel input mapping.
- // Right now there is no inter-kernel mapping yet, so just take the
- // program's initial mapping for each kernel.
-
+Mapper::RoutingStatistics Mapper::map_block(ir::BlockBaseRef block) {
com::map::QubitMapping v2r{
- nq,
+ get_num_qubits(platform),
true,
options->assume_initialized ? com::map::QubitState::INITIALIZED : com::map::QubitState::NONE
};
-#ifdef MULTI_LINE_LOG_DEBUG
- QL_IF_LOG_DEBUG {
- QL_DOUT("v2r dump after initialization:");
- v2r.dump_state();
- }
-#else
- QL_DOUT("v2r dump after initialization (disabled)");
-#endif
- // Save the input qubit map for reporting.
v2r_in = v2r;
+ return route(block, v2r);
+}
- // Perform heuristic routing.
- QL_DOUT("Mapper::Map before route: assume_initialized=" << options->assume_initialized);
- route(k, v2r); // updates kernel.c with swaps, maps all gates, updates v2r map
-#ifdef MULTI_LINE_LOG_DEBUG
- QL_IF_LOG_DEBUG {
- QL_DOUT("v2r dump after heuristics");
- v2r.dump_state();
+void Mapper::map(ir::ProgramRef program) {
+ if (program->blocks.size() >= 2) {
+ QL_FATAL("Inter-block mapping is not implemented. The mapper/router will only work for programs which consist of a single block.");
+ QL_ASSERT(false);
+ return;
}
-#else
- QL_DOUT("v2r dump after heuristics (disabled)");
-#endif
-
- // Save the routed qubit map for reporting. This is the resulting qubit map
- // at the *end* of the kernel.
- v2r_out = v2r;
- // Decompose to primitive instructions as specified in the config file.
- decompose_to_primitives(k);
+ auto& block = program->blocks[0];
- k->qubit_count = nq; // bluntly copy nq (==#real qubits), so that all kernels get the same qubit_count
- k->creg_count = nc; // same for number of cregs and bregs, although we don't really map those
- k->breg_count = nb;
+ using clock = std::chrono::high_resolution_clock;
+ auto t1 = clock::now();
- QL_DOUT("Mapping kernel " << k->name << " [DONE]");
-}
+ auto stats = map_block(block.as());
-/**
- * Runs mapping for the given program.
- *
- * TODO: inter-kernel mapping is NOT SUPPORTED; each kernel is mapped
- * individually. That means that the resulting program is garbage if any
- * quantum state was originally maintained from kernel to kernel!
- */
-void Mapper::map(const ir::compat::ProgramRef &prog, const OptionsRef &opt) {
+ auto t2 = clock::now();
+ std::chrono::duration time_span = t2 - t1;
+ auto time_taken = time_span.count();
- // Shorthand.
using pass::ana::statistics::AdditionalStats;
-
- // Perform program-wide initialization.
- initialize(prog->platform, opt);
-
- // Map kernel by kernel, adding statistics all the while.
- UInt total_swaps = 0;
- UInt total_moves = 0;
- Real total_time_taken = 0.0;
- for (const auto &k : prog->kernels) {
- QL_IOUT("Mapping kernel: " << k->name);
-
- // Start interval timer for measuring time taken for this kernel.
- Real time_taken = 0.0;
- using namespace std::chrono;
- high_resolution_clock::time_point t1 = high_resolution_clock::now();
-
- // Actually do the mapping.
- map_kernel(k);
-
- // Stop the interval timer.
- high_resolution_clock::time_point t2 = high_resolution_clock::now();
- duration time_span = t2 - t1;
- time_taken = time_span.count();
-
- // Push mapping statistics into the kernel.
- AdditionalStats::push(k, "swaps added: " + to_string(num_swaps_added));
- AdditionalStats::push(k, "of which moves added: " + to_string(num_moves_added));
- AdditionalStats::push(k, "virt2real map before mapper:" + to_string(v2r_in.get_virt_to_real()));
- AdditionalStats::push(k, "virt2real map after mapper:" + to_string(v2r_out.get_virt_to_real()));
- AdditionalStats::push(k, "realqubit states before mapper:" + to_string(v2r_in.get_state()));
- AdditionalStats::push(k, "realqubit states after mapper:" + to_string(v2r_out.get_state()));
- AdditionalStats::push(k, "time taken: " + to_string(time_taken));
-
- // Update total statistical counters.
- total_swaps += num_swaps_added;
- total_moves += num_moves_added;
- total_time_taken += time_taken;
-
- }
-
- // Push mapping statistics into the program.
- AdditionalStats::push(prog, "Total no. of swaps: " + to_string(total_swaps));
- AdditionalStats::push(prog, "Total no. of moves of swaps: " + to_string(total_moves));
- AdditionalStats::push(prog, "Total time taken: " + to_string(total_time_taken));
-
- // Kernel qubit/creg/breg counts will have been updated to the platform
- // counts, so we need to do the same for the program.
- prog->qubit_count = platform->qubit_count;
- prog->creg_count = platform->creg_count;
- prog->breg_count = platform->breg_count;
-
+ AdditionalStats::push(block, "swaps added: " + to_string(stats.num_swaps_added));
+ AdditionalStats::push(block, "of which moves added: " + to_string(stats.num_moves_added));
+ AdditionalStats::push(block, "virt2real map before mapper:" + to_string(v2r_in.get_virt_to_real()));
+ AdditionalStats::push(block, "virt2real map after mapper:" + to_string(v2r_out.get_virt_to_real()));
+ AdditionalStats::push(block, "realqubit states before mapper:" + to_string(v2r_in.get_state()));
+ AdditionalStats::push(block, "realqubit states after mapper:" + to_string(v2r_out.get_state()));
+ AdditionalStats::push(block, "time taken: " + to_string(time_taken));
+ AdditionalStats::push(program, "Total no. of swaps added by router pass: " + to_string(stats.num_swaps_added));
+ AdditionalStats::push(program, "Total no. of moves added by router pass: " + to_string(stats.num_moves_added));
+ AdditionalStats::push(program, "Total time taken by router pass: " + to_string(time_taken));
}
} // namespace detail
diff --git a/src/ql/pass/map/qubits/map/detail/mapper.h b/src/ql/pass/map/qubits/map/detail/mapper.h
index b8e480406..b5021bef0 100644
--- a/src/ql/pass/map/qubits/map/detail/mapper.h
+++ b/src/ql/pass/map/qubits/map/detail/mapper.h
@@ -1,17 +1,8 @@
-/** \file
- * OpenQL virtual to real qubit mapping and routing.
- */
-
#pragma once
#include
-#include "ql/utils/num.h"
-#include "ql/utils/str.h"
-#include "ql/utils/vec.h"
-#include "ql/utils/list.h"
-#include "ql/utils/map.h"
#include "ql/utils/progress.h"
-#include "ql/ir/compat/compat.h"
+#include "ql/ir/ir.h"
#include "ql/com/map/qubit_mapping.h"
#include "options.h"
#include "free_cycle.h"
@@ -26,20 +17,6 @@ namespace qubits {
namespace map {
namespace detail {
-// Note on the use of constructors and Init functions for classes of the mapper
-// -----------------------------------------------------------------------------
-// Almost all classes of the mapper have one or more members that require
-// initialization using a value that was passed on to the Mapper.initialize
-// function as a parameter (i.e. platform, cycle_time). Dealing with those
-// initializations in the nested constructors was cumbersome. Hence, the
-// constructors create just skeleton objects which need explicit initialization
-// before use. Such initialization is provided by a class local initialize
-// function for a virgin object, or by copying an existing object to it.
-// The constructors are trivial by this and can be synthesized by default.
-//
-// Construction of skeleton objects requires the used classes to provide such
-// (non-parameterized) constructors.
-
/**
* Strategy options for finding routing paths.
*/
@@ -77,9 +54,6 @@ enum class PathStrategy {
};
-/**
- * String conversion for PathStrategy.
- */
std::ostream &operator<<(std::ostream &os, PathStrategy p);
/**
@@ -88,43 +62,24 @@ std::ostream &operator<<(std::ostream &os, PathStrategy p);
*
* All gates must be unary or two-qubit gates. The operands are virtual qubit
* indices. After mapping, all virtual qubit operands have been mapped to real
- * qubit operands.
- *
- * For the mapper to work, the number of virtual qubits (nvq) must be less equal
- * to the number of real qubits (nrq): nvq <= nrq; the mapper assumes that the
- * virtual qubit operands (vqi) are encoded as a number 0 <= vqi < nvq, and that
- * the real qubit operands (rqi) are encoded as a number 0 <= rqi < nrq. The nrq
- * is given by the platform, nvq is given by the kernel. The mapper ignores the
- * latter (0 <= vqi < nvq was tested when creating the gates), and assumes vqi,
- * nvq, rqi and nrq to be of the same type (utils::UInt) 0<=qi), or live (the state is anything
- * in use by the kernel). The states start out as none or initialized,
+ * in use by the block). The states start out as none or initialized,
* depending on whether the mapper is configured such that it's allowed to
* assume that the qubits have already been initialized before the start of
- * the kernel. Any quantum gate presented to the mapper by the input circuit
+ * the block. Any quantum gate presented to the mapper by the input circuit
* puts the state of its qubits into the live state, except prepz if the
* mapper is configured such that it treats it as an initialization. The
* initialized state is used to replace swap gates (3 CNOTs) with moves
@@ -134,108 +89,72 @@ std::ostream &operator<<(std::ostream &os, PathStrategy p);
* garbage state, the mapper will see if initializing it does not increase
* circuit length too much (the threshold is configurable).
*
- * Classical registers are ignored by the mapper currently. TO BE DONE.
- *
- * The mapping is done in the context of a grid of qubits defined by the given
- * platform. The information about this grid lives in platform.topology.
+ * The mapping is done in the context of a graph of qubits defined by the
+ * platform. The description of this graph/grid lives in platform->topology.
*
- * Each kernel in the program is independently mapped (see the map_kernel
- * method), ignoring inter-kernel control flow and thereby the requirement to
- * pass on the current mapping.
+ * The mapper/router currently supports only programs consisting of a single block.
*
- * Anticipating inter-kernel mapping, the mapper maintains a kernel input
- * mapping coming from the context, and produces a kernel output mapping for the
- * context; the mapper updates the kernel's circuit from virtual to real.
+ * Anticipating inter-block mapping, the mapper maintains a block input
+ * mapping coming from the context, and produces a block output mapping for the
+ * context; the mapper updates the block's circuit from virtual to real.
*
- * Without inter-kernel control flow, the flow is as follows.
- * - Mapping starts from a 1 to 1 mapping of virtual to real qubits (the kernel
- * input mapping), in which all virtual qubits are initialized to a fixed
- * constant state (|0>/inited), suitable for replacing swap by move.
- * - Use heuristics to map the input,
- * mapping the virtual gates to (sets of) real gates, and outputing the new
- * map and the new virtuals' state
- * - Optionally decompose swap and/or cnot gates in the real circuit to
- * primitives (make_primitive).
- *
- * Inter-kernel control flow and consequent mapping dependence between kernels
- * is not implemented. TO BE DONE. The design of mapping multiple kernels is as
- * follows (TO BE ADAPTED TO NEW REALSTATE).
+ * Inter-block control flow and consequent mapping dependence between blocks
+ * is not implemented. TO BE DONE. The design of mapping multiple blocks is as
+ * follows:
*
* - Initially the program wide initial mapping is a 1 to 1 mapping of virtual
* to real qubits.
- * - When starting to map a kernel, there is a set of already mapped kernels,
- * and a set of not yet mapped kernels. Of each mapped kernel, there is an
+ * - When starting to map a block, there is a set of already mapped blocks,
+ * and a set of not yet mapped blocks. Of each mapped block, there is an
* output mapping, i.e. the mapping of virts to reals with the rs per
- * virtual. The current kernel has a set of kernels which are direct
+ * virtual. The current block has a set of blocks which are direct
* predecessor in the program's control flow, a subset of those direct
* predecessors thus has been mapped and another subset not mapped; the
- * output mappings of the mapped predecessor kernels are input.
+ * output mappings of the mapped predecessor blocks are input.
* - Unify these multiple input mappings to a single one; this may introduce
* swaps on the control flow edges. The result is the input mapping of the
- * current kernel; keep it for later reference.
+ * current block; keep it for later reference.
* - Use heuristics to map the input (or what initial placement left to do).
- * - When done, keep the output mapping as the kernel's output mapping. For all
- * mapped successor kernels, compute a transition from output to their input,
+ * - When done, keep the output mapping as the block's output mapping. For all
+ * mapped successor blocks, compute a transition from output to their input,
* and add it to the edge; the edge code must be optimized for:
* - being empty: nothing needs to be done
* - having a source with one succ; the edge code can be appended to that
* succ
* - having a target with one pred; the edge code can be prepended to that
* pred
- * - otherwise, a separate intermediate kernel for the transition code must
+ * - otherwise, a separate intermediate block for the transition code must
* be created, and added.
*
- * THE ABOVE INTER-KERNEL MAPPING IS NOT IMPLEMENTED.
- *
- * The Mapper's main entry is map_kernel which manages the input and output
- * streams of QASM instructions. It selects the
- * quantum gates from it, and maps these in the context of what was mapped
- * before (the Past). Each gate is separately mapped in MapGate in the main
- * Past's context.
+ * THE ABOVE INTER-BLOCK MAPPING IS NOT IMPLEMENTED.
*/
class Mapper {
-private:
+public:
+ Mapper(const ir::PlatformRef &p, const OptionsRef &o) :
+ platform(p), options(o) {
+ static constexpr utils::UInt seed = 123;
+ rng.seed(seed);
+ }
/**
- * Current platform: topology and gate definitions.
+ * Routes and maps the given program. A single block is supported.
+ * If the program consists of multiple blocks, a fatal error is raised.
*/
- ir::compat::PlatformRef platform;
+ void map(ir::ProgramRef program);
- /**
- * (copy of) current kernel (class) with free private circuit and methods.
- * Primarily used to create gates in Past; Past is part of Mapper and of
- * each Alter.
- */
- ir::compat::KernelRef kernel;
+private:
+ struct RoutingStatistics {
+ utils::UInt num_swaps_added = 0;
+ utils::UInt num_moves_added = 0;
+ };
- /**
- * Parsed mapper pass options structure.
- */
+ ir::PlatformRef platform;
+ ir::BlockBaseRef block;
OptionsRef options;
- /**
- * Number of qubits in the platform, i.e. the number of real qubits.
- */
- utils::UInt nq;
-
- /**
- * Number of cregs in the platform (classical registers).
- */
- utils::UInt nc;
-
- /**
- * Number of bregs in the platform (number of bit registers).
- */
- utils::UInt nb;
-
- /**
- * Length in ns of a single cycle of the platform. This is the divisor of
- * duration in ns to convert it to cycles.
- */
- utils::UInt cycle_time;
-
/**
* Random-number generator for the "random" tie-breaking option.
+ * The seed is constant so that the output of OpenQL is deterministic.
*/
std::mt19937 rng;
@@ -245,32 +164,15 @@ class Mapper {
utils::Progress routing_progress;
/**
- * Number of swaps added (including moves) to the most recently mapped
- * kernel, set by map_kernel().
- */
- utils::UInt num_swaps_added;
-
- /**
- * Number of moves added to the most recently mapped kernel, set by
- * map_kernel().
- */
- utils::UInt num_moves_added;
-
- /**
- * Qubit mapping before mapping, set by map_kernel().
+ * Qubit mapping before mapping, set by map_block().
*/
com::map::QubitMapping v2r_in;
/**
- * Qubit mapping after mapping, set by map_kernel().
+ * Qubit mapping after mapping, set by route().
*/
com::map::QubitMapping v2r_out;
- struct Path {
- utils::UInt qubit;
- utils::RawPtr prev;
- };
-
/**
* Find shortest paths between src and tgt in the grid, bounded by a
* particular strategy. path is a linked-list node representing the complete
@@ -287,13 +189,12 @@ class Mapper {
* total number of entries in alters reaches or surpasses the limit (it may
* surpass due to the checks only happening before splitting).
*/
- void gen_shortest_paths(
- const ir::compat::GateRef &gate,
- utils::RawPtr path,
+ utils::List gen_shortest_paths(
+ const ir::CustomInstructionRef &gate,
+ std::list path,
utils::UInt src,
utils::UInt tgt,
utils::UInt budget,
- utils::List &alters,
utils::UInt max_alters,
PathStrategy strategy
);
@@ -312,50 +213,33 @@ class Mapper {
* - When not all shortest paths found are valid, take these out.
* - Paths are further split because each split may give rise to a separate
* alternative. A split is a hop where the two-qubit gate is assumed to
- * be done. After splitting each alternative contains two lists, one
- * before and one after (reversed) the envisioned two-qubit gate; all
- * result alternatives are such that a two-qubit gate can be placed at
- * the split
+ * be done.
*
- * The end result is a list of alternatives (in alters) suitable for being
+ * The result is a list of alternatives suitable for being
* evaluated for any routing metric.
*/
- void gen_shortest_paths(
- const ir::compat::GateRef &gate,
+ utils::List gen_shortest_paths(
+ const ir::CustomInstructionRef &gate,
utils::UInt src,
- utils::UInt tgt,
- utils::List &alters
+ utils::UInt tgt
);
/**
- * Generates all possible variations of making the given gate
- * nearest-neighbor, starting from given past (with its mappings), and
- * return the found variations by appending them to the given list of
- * Alters.
+ * Return all possible alternatives for making the given gate
+ * nearest-neighbor, starting from given past (with its mappings).
*/
- void gen_alters_gate(
- const ir::compat::GateRef &gate,
- utils::List &alters,
+ utils::List gen_alters_gate(
+ const ir::CustomInstructionRef &gate,
Past &past
);
/**
* Generates all possible variations of making the given gates
- * nearest-neighbor, starting from given past (with its mappings), and
- * return the found variations by appending them to the given list of
- * Alters. Depending on the lookahead option, only take the first (most
- * critical) gate, or take all gates.
- */
- void gen_alters(
- const utils::List &gates,
- utils::List &alters,
- Past &past
- );
-
- /**
- * Seeds the random number generator with the current time in microseconds.
+ * nearest-neighbor, starting from given past (with its mappings).
+ * Depending on the lookahead option, only take the first (most
+ * critical) gate, or take all gates and concatenate alternatives.
*/
- void random_init();
+ utils::List gen_alters(const utils::List &gates, Past &past);
/**
* Chooses an Alter from the list based on the configured tie-breaking
@@ -364,14 +248,14 @@ class Mapper {
Alter tie_break_alter(utils::List &alters, Future &future);
/**
- * Map the gate/operands of a gate that has been routed or doesn't require
- * routing.
+ * Map gate virtual operands wrt past's mapping, and add the gate to past
+ * and its free cycle map.
*/
- void map_routed_gate(const ir::compat::GateRef &gate, Past &past);
+ void map_routed_gate(const ir::CustomInstructionRef &gate, Past &past);
/**
* Commit the given Alter, generating swaps in the past and taking it out
- * of future when done with it. Depending on configuration, this might not
+ * of future. Depending on configuration, this might not
* actually place the target gate for the given alternative yet, because
* only part of the swap chain is generated; in this case, swaps are added
* to past, but future is not updated.
@@ -379,45 +263,21 @@ class Mapper {
void commit_alter(Alter &alter, Future &future, Past &past);
/**
- * Find gates available for scheduling that do not require routing, take
- * them out, and map them. Ultimately, either no available gates remain, or
- * only gates that require routing remain. Return false when no gates remain
- * at all, and true when any gates remain; those gates are returned in
- * the gates list.
- *
- * Behavior depends on the value of the lookahead_mode option and on
- * also_nn_two_qubit_gate. When the latter is true, and lookahead_mode is...
- * - DISABLED: while next in circuit is non-quantum or single-qubit, map
- * that gate. Return when we find a two-qubit gate (nearest-neighbor or
- * not). In this case, get_non_quantum_gates only returns a non-quantum
- * gate when it is next in circuit.
- * - ONE_QUBIT_GATE_FIRST: while next in circuit is non-quantum or
- * single-qubit, map that gate. Return the most critical two-qubit gate
- * (nearest-neighbor or not).
- * - NO_ROUTING_FIRST: while next in circuit is non-quantum, single-qubit,
- * or nearest-neighbor two-qubit, map that gate. Return the most critical
- * non-nearest-neighbor two-qubit gate.
- * - ALL: while next in circuit is non-quantum, single-qubit, or
- * nearest-neighbor two-qubit, map that gate. Return ALL
- * non-nearest-neighbor two-qubit gates.
- *
- * When also_nn_two_qubit_gate is false, behavior is the same, except
- * nearest-neighbor two-qubit gates behave as if they're not
- * nearest-neighbor.
+ * Find gates available for scheduling that do not require routing and map them.
+ * Returns remaining gates that require routing.
*/
- utils::Bool map_mappable_gates(
+ utils::List map_mappable_gates(
Future &future,
Past &past,
- utils::List &gates,
utils::Bool also_nn_two_qubit_gates
);
/**
* Select an Alter based on the selected heuristic.
*
- * - If BASE[_RC], consider all Alters as equivalent, and thus apply the
- * tie-breaking strategy to all.
- * - If MIN_EXTEND[_RC], consider Alters with the minimal cycle extension
+ * - If route_heuristic == "base", consider all alternatives as equivalent,
+ * and thus apply the tie-breaking strategy to all.
+ * - If route_heuristic == "minextend", prefer alternatives with the minimal cycle extension
* of the given past (or some factor of that amount, ordered by
* increasing cycle extension) and recurse. When the recursion depth
* limit is reached, apply the tie-breaking strategy.
@@ -425,9 +285,8 @@ class Mapper {
* For recursion, past is the speculative past, and base_past is the past
* we've already committed to, and should thus measure fitness against.
*/
- void select_alter(
+ Alter select_alter(
utils::List &alters,
- Alter &result,
Future &future,
Past &past,
Past &base_past,
@@ -435,51 +294,19 @@ class Mapper {
);
/**
- * Given the states of past and future, map all mappable gates and find the
- * non-mappable ones. For those, evaluate what to do next and do it. During
- * recursion, comparison is done with the base past (bottom of recursion
- * stack), and past is the last past (top of recursion stack) relative to
- * which the mapping is done.
+ * Process all gates in future and update past with routing result.
*/
- void map_gates(Future &future, Past &past, Past &base_past);
+ utils::Any route_gates(Future &future, Past &past);
/**
- * Map the kernel's circuit's gates in the provided context (v2r maps),
- * updating circuit and v2r maps.
- */
- void route(const ir::compat::KernelRef &k, com::map::QubitMapping &v2r);
-
- /**
- * Decomposes all gates in the circuit that have a definition with _prim
- * appended to its name. The mapper does this after routing.
+ * Map/route the block wrt the virtual-to-real v2r qubit mapping.
*/
- void decompose_to_primitives(const ir::compat::KernelRef &k);
+ RoutingStatistics route(ir::BlockBaseRef block, com::map::QubitMapping &v2r);
/**
- * Initialize the data structures in this class that don't change from
- * kernel to kernel.
- */
- void initialize(const ir::compat::PlatformRef &p, const OptionsRef &opt);
-
- /**
- * Runs routing and decomposition to primitives for
- * the given kernel.
- *
- * TODO: split off the decomposition to primitives.
+ * Runs routing for the given block.
*/
- void map_kernel(const ir::compat::KernelRef &k);
-
-public:
-
- /**
- * Runs mapping for the given program.
- *
- * TODO: inter-kernel mapping is NOT SUPPORTED; each kernel is mapped
- * individually. That means that the resulting program is garbage if any
- * quantum state was originally maintained from kernel to kernel!
- */
- void map(const ir::compat::ProgramRef &prog, const OptionsRef &opt);
-
+ RoutingStatistics map_block(ir::BlockBaseRef block);
};
} // namespace detail
diff --git a/src/ql/pass/map/qubits/map/detail/options.cc b/src/ql/pass/map/qubits/map/detail/options.cc
index e4b421908..000fa68f0 100644
--- a/src/ql/pass/map/qubits/map/detail/options.cc
+++ b/src/ql/pass/map/qubits/map/detail/options.cc
@@ -1,7 +1,3 @@
-/** \file
- * Defines the parsed options structures for the mapper.
- */
-
#include "options.h"
namespace ql {
@@ -11,36 +7,23 @@ namespace qubits {
namespace map {
namespace detail {
-/**
- * String conversion for Heuristic.
- */
std::ostream &operator<<(std::ostream &os, Heuristic h) {
switch (h) {
case Heuristic::BASE: os << "base"; break;
- case Heuristic::BASE_RC: os << "base_rc"; break;
case Heuristic::MIN_EXTEND: os << "min_extend"; break;
- case Heuristic::MIN_EXTEND_RC: os << "min_extend_rc"; break;
- case Heuristic::MAX_FIDELITY: os << "max_fidelity"; break;
}
return os;
}
-/**
- * String conversion for LookaheadMode.
- */
std::ostream &operator<<(std::ostream &os, LookaheadMode lm) {
switch (lm) {
case LookaheadMode::DISABLED: os << "disabled"; break;
- case LookaheadMode::ONE_QUBIT_GATE_FIRST: os << "one_qubit_gate_first"; break;
case LookaheadMode::NO_ROUTING_FIRST: os << "no_routing_first"; break;
case LookaheadMode::ALL: os << "all"; break;
}
return os;
}
-/**
- * String conversion for PathSelectionMode.
- */
std::ostream &operator<<(std::ostream &os, PathSelectionMode psm) {
switch (psm) {
case PathSelectionMode::ALL: os << "all"; break;
@@ -50,9 +33,6 @@ std::ostream &operator<<(std::ostream &os, PathSelectionMode psm) {
return os;
}
-/**
- * String conversion for SwapSelectionMode.
- */
std::ostream &operator<<(std::ostream &os, SwapSelectionMode ssm) {
switch (ssm) {
case SwapSelectionMode::ONE: os << "one"; break;
@@ -62,9 +42,6 @@ std::ostream &operator<<(std::ostream &os, SwapSelectionMode ssm) {
return os;
}
-/**
- * String conversion for TieBreakMethod.
- */
std::ostream &operator<<(std::ostream &os, TieBreakMethod tbm) {
switch (tbm) {
case TieBreakMethod::FIRST: os << "first"; break;
diff --git a/src/ql/pass/map/qubits/map/detail/options.h b/src/ql/pass/map/qubits/map/detail/options.h
index 991edde55..92438ce7e 100644
--- a/src/ql/pass/map/qubits/map/detail/options.h
+++ b/src/ql/pass/map/qubits/map/detail/options.h
@@ -1,7 +1,3 @@
-/** \file
- * Defines the parsed options structures for the mapper.
- */
-
#pragma once
#include "ql/utils/num.h"
@@ -29,11 +25,6 @@ enum class Heuristic {
*/
BASE,
- /**
- * Same as BASE, but with resource constraints.
- */
- BASE_RC,
-
/**
* Favor alternatives with minimal cycle time extension when using
* non-resource-constrained scheduling. When multiple (good) alternatives
@@ -44,21 +35,8 @@ enum class Heuristic {
*/
MIN_EXTEND,
- /**
- * Same as MIN_EXTEND, but using resource-constrained scheduling.
- */
- MIN_EXTEND_RC,
-
- /**
- * No longer supported?
- */
- MAX_FIDELITY
-
};
-/**
- * String conversion for Heuristic.
- */
std::ostream &operator<<(std::ostream &os, Heuristic h);
/**
@@ -68,14 +46,10 @@ std::ostream &operator<<(std::ostream &os, Heuristic h);
*/
enum class LookaheadMode {
DISABLED,
- ONE_QUBIT_GATE_FIRST,
NO_ROUTING_FIRST,
ALL
};
-/**
- * String conversion for LookaheadMode.
- */
std::ostream &operator<<(std::ostream &os, LookaheadMode lm);
/**
@@ -86,8 +60,8 @@ enum class PathSelectionMode {
/**
* Consider all possible paths.
*/
- ALL,
+ ALL,
/**
* Favor routing along the borders of the rectangle defined by the source
* and target qubit. Only supported when the qubits are given coordinates in
@@ -104,9 +78,6 @@ enum class PathSelectionMode {
};
-/**
- * String conversion for PathSelectionMode.
- */
std::ostream &operator<<(std::ostream &os, PathSelectionMode psm);
/**
@@ -133,9 +104,6 @@ enum class SwapSelectionMode {
};
-/**
- * String conversion for SwapSelectionMode.
- */
std::ostream &operator<<(std::ostream &os, SwapSelectionMode ssm);
/**
@@ -166,9 +134,6 @@ enum class TieBreakMethod {
};
-/**
- * String conversion for TieBreakMethod.
- */
std::ostream &operator<<(std::ostream &os, TieBreakMethod tbm);
/**
@@ -183,7 +148,7 @@ struct Options {
/**
* Controls whether the mapper should assume that each qubit starts out
- * as zero at the start of each kernel, rather than with an undefined
+ * as zero at the start of each block, rather than with an undefined
* state.
*/
utils::Bool assume_initialized = false;
@@ -278,12 +243,6 @@ struct Options {
*/
utils::Bool commute_single_qubit = false;
- /**
- * Whether the critical path selection logic of the embedded scheduler is
- * enabled.
- */
- utils::Bool enable_criticality = true;
-
/**
* Whether to print dot graphs of the schedules created using the embedded
* scheduler.
diff --git a/src/ql/pass/map/qubits/map/detail/past.cc b/src/ql/pass/map/qubits/map/detail/past.cc
index 780b0f8b6..10cc35e48 100644
--- a/src/ql/pass/map/qubits/map/detail/past.cc
+++ b/src/ql/pass/map/qubits/map/detail/past.cc
@@ -1,13 +1,10 @@
-/** \file
- * Past implementation.
- */
-
#include "past.h"
#include "ql/utils/filesystem.h"
-
-// uncomment next line to enable multi-line dumping
-// #define MULTI_LINE_LOG_DEBUG
+#include "ql/ir/describe.h"
+#include "ql/ir/ops.h"
+#include "ql/ir/swap_parameters.h"
+#include "ql/com/map/reference_updater.h"
namespace ql {
namespace pass {
@@ -16,278 +13,54 @@ namespace qubits {
namespace map {
namespace detail {
-/**
- * Past initializer.
- */
-void Past::initialize(const ir::compat::KernelRef &k, const OptionsRef &opt) {
- QL_DOUT("Past::initialize");
- platform = k->platform;
- kernel = k;
+Past::Past(ir::PlatformRef p, const OptionsRef &opt) {
+ platform = p;
options = opt;
- nq = platform->qubit_count;
- nb = platform->breg_count;
- ct = platform->cycle_time;
-
- QL_ASSERT(kernel->gates.empty()); // kernelp->c will be used by new_gate to return newly created gates into
- v2r.resize( // v2r initializtion until v2r is imported from context
- nq,
+ v2r.resize(
+ platform->qubits->shape[0],
true,
options->assume_initialized ? com::map::QubitState::INITIALIZED : com::map::QubitState::NONE
);
- fc.initialize(platform, options); // fc starts off with all qubits free, is updated after schedule of each gate
- waiting_gates.clear(); // no gates pending to be scheduled in; Add of gate to past entered here
- gates.clear(); // no gates scheduled yet in this past; after schedule of gate, it gets here
- output_gates.clear(); // no gates output yet by flushing from or bypassing this past
- num_swaps_added = 0; // no swaps or moves added yet to this past; AddSwap adds one here
- num_moves_added = 0; // no moves added yet to this past; AddSwap may add one here
- cycle.clear(); // no gates have cycles assigned in this past; scheduling gate updates this
+ fc.initialize(platform, options);
}
-/**
- * Copies the given qubit mapping into our mapping.
- */
void Past::import_mapping(const com::map::QubitMapping &v2r_value) {
v2r = v2r_value;
}
-/**
- * Copies our qubit mapping into the given mapping.
- */
void Past::export_mapping(com::map::QubitMapping &v2r_destination) const {
v2r_destination = v2r;
}
-/**
- * Prints the state of the embedded FreeCycle object.
- */
-void Past::print_fc() const{
- fc.print("");
-}
-
-/**
- * Prints the state of the embedded FreeCycle object only when verbosity
- * is at least debug.
- */
-void Past::debug_print_fc() const {
-#ifdef MULTI_LINE_LOG_DEBUG
- QL_IF_LOG_DEBUG {
- QL_DOUT("FreeCycle dump:");
- fc.print("");
- }
-#else
- QL_DOUT("FreeCycle dump (disabled)");
-#endif
-}
-
-/**
- * Prints the state of this object along with the given string.
- */
-void Past::print(const utils::Str &s) const {
- std::cout << "... Past " << s << ":";
- v2r.dump_state();
- fc.print("");
- // QL_DOUT("... list of gates in past");
- for (auto &gp : gates) {
- QL_DOUT("[" << cycle.at(gp) << "] " << gp->qasm());
- }
-}
-
-/**
- * Schedules all waiting gates into the main gates list. Note that these
- * gates all are mapped and so have real operand qubit indices. The
- * FreeCycle map reflects for each qubit the first free cycle. All new
- * gates, now in waitinglist, get such a cycle assigned below, increased
- * gradually, until definitive.
- */
-void Past::schedule() {
- // the copy includes the resource manager.
- // QL_DOUT("Schedule ...");
-
- while (!waiting_gates.empty()) {
- utils::UInt start_cycle = ir::compat::MAX_CYCLE;
- utils::List::iterator gate_it;
-
- // Find the gate with the minimum start cycle.
- //
- // IMPORTANT: this assumes that the waiting gates list is in topological
- // order, which is ok because the pair of swap lists use distinct qubits
- // and the gates of each are added to the back of the list in the order
- // of execution. Using tryfc.add, the tryfc (try FreeCycle map) reflects
- // the earliest start cycle per qubit, and so dependencies are
- // respected, so we can find the gate that can start first...
- //
- // Note that tryfc includes the free cycle vector AND the resource map,
- // so using tryfc.StartCycle/tryfc.Add we get a realistic ASAP rc
- // schedule. We use a copy of fc and not fc itself, since the latter
- // reflects the really scheduled gates and that shouldn't be changed.
- //
- // This search is really a hack to avoid the construction of a
- // dependency graph and a set of schedulable gates.
- FreeCycle tryfc = fc;
- for (auto try_gate_it = waiting_gates.begin(); try_gate_it != waiting_gates.end(); ++try_gate_it) {
- utils::UInt try_start_cycle = tryfc.get_start_cycle(*try_gate_it);
- tryfc.add(*try_gate_it, try_start_cycle);
-
- if (try_start_cycle < start_cycle) {
- start_cycle = try_start_cycle;
- gate_it = try_gate_it;
- }
- }
-
- auto gate = *gate_it;
-
- // Add this gate to the maps, scheduling the gate (doing the cycle
- // assignment).
- // QL_DOUT("... add " << gp->qasm() << " startcycle=" << startCycle << " cycles=" << ((gp->duration+ct-1)/ct) );
- fc.add(gate, start_cycle);
- cycle.set(gate) = start_cycle; // cycle[gp] is private to this past but gp->cycle is private to gp
- gate->cycle = start_cycle; // so gp->cycle gets assigned for each alter' Past and finally definitively for mainPast
- // QL_DOUT("... set " << gp->qasm() << " at cycle " << startCycle);
-
- // Insert gate into the list of gates, in cycle[gp] order, and inside
- // this order, as late as possible.
- //
- // Reverse iterate because the insertion is near the end of the list.
- // Insert so that cycle values are in order afterwards and the new one
- // is nearest to the end.
- auto rigp = gates.rbegin();
- utils::Bool inserted = false;
- for (; rigp != gates.rend(); rigp++) {
- if (cycle.at(*rigp) <= start_cycle) {
-
- // rigp.base() because insert doesn't work with reverse iteration
- // rigp.base points after the element that rigp is pointing at
- // which is lucky because insert only inserts before the given
- // element. The net result is inserting after rigp.
- gates.insert(rigp.base(), gate);
-
- inserted = true;
- break;
- }
- }
-
- // When list was empty or no element was found, just put it in front.
- if (!inserted) {
- gates.push_front(gate);
- }
-
- // Having added it to the main list, remove it from the waiting list.
- waiting_gates.erase(gate_it);
- }
-
- // DPRINT("Schedule:");
-}
-
-/**
- * Computes the costs in cycle extension of optionally scheduling
- * init_circuit before the inevitable circuit.
- */
-utils::Int Past::get_insertion_cost(
- const ir::compat::GateRefs &init_circuit,
- const ir::compat::GateRefs &circuit
-) const {
-
- // First fake-schedule init_circuit followed by circuit in a private
- // FreeCycle map.
- FreeCycle fc_with_init = fc;
- for (const auto &gate : init_circuit) {
- utils::UInt try_start_cycle = fc_with_init.get_start_cycle_no_rc(gate);
- fc_with_init.add_no_rc(gate, try_start_cycle);
- }
- for (const auto &gate : circuit) {
- utils::UInt tryStartCycle = fc_with_init.get_start_cycle_no_rc(gate);
- fc_with_init.add_no_rc(gate, tryStartCycle);
- }
- utils::UInt max_with_init = fc_with_init.get_max(); // this reflects the depth afterwards
-
- // Then fake-schedule circuit alone in a private FreeCycle map.
- FreeCycle fc_without_init = fc;
- for (auto &gate : circuit) {
- utils::UInt try_start_cycle = fc_without_init.get_start_cycle_no_rc(gate);
- fc_without_init.add_no_rc(gate, try_start_cycle);
- }
- utils::UInt max_without_init = fc_without_init.get_max(); // this reflects the depth afterwards
-
- QL_DOUT("... scheduling init+circ => depth " << max_with_init << ", scheduling circ => depth " << max_without_init << ", init insertion cost " << (max_with_init - max_without_init));
- QL_ASSERT(max_with_init >= max_without_init);
- // scheduling initcirc would be for free when initmax == max, so the cost is (initmax - max)
- return max_with_init - max_without_init;
-}
+void Past::add(const ir::CustomInstructionRef &gate) {
+ auto start_cycle = fc.get_start_cycle(gate);
+ fc.add(gate, start_cycle);
-/**
- * Adds the given mapped gate to the current past. This means adding it to
- * the current past's waiting list, waiting for it to be scheduled later.
- */
-void Past::add(const ir::compat::GateRef &gate) {
- waiting_gates.push_back(gate);
+ output_gates.push_back(gate);
}
-/**
- * Creates a new gate with given name and qubits, returning whether this was
- * successful. Return the created gate(s) in circ, which is supposed to be
- * empty on entry.
- *
- * Since kernel.h only provides a gate interface as method of class
- * Kernel, that adds the gate to kernel.c, and we want the gate (or its
- * decomposed sequence) here to be added to circ, the kludge is implemented
- * to make sure that kernel.c (the current kernel's mapper input/output
- * circuit) is available for this. In class Future, kernel.c is copied into
- * the dependence graph or copied to a local circuit, and in
- * Mapper::route, a temporary local output circuit is used, which is
- * written to kernel.c only at the very end.
- */
-utils::Bool Past::new_gate(
- ir::compat::GateRefs &circ,
+ir::Maybe Past::new_gate(
const utils::Str &gname,
- const utils::Vec &qubits,
- const utils::Vec &cregs,
- utils::UInt duration,
- utils::Real angle,
- const utils::Vec &bregs,
- ir::compat::ConditionType gcond,
- const utils::Vec &gcondregs
+ const utils::Vec &qubits
) const {
- utils::Bool added;
- QL_ASSERT(circ.empty());
- QL_ASSERT(kernel->gates.empty());
- // create gate(s) in kernelp->c
- added = kernel->gate_nonfatal(gname, qubits, cregs, duration, angle, bregs, gcond, gcondregs);
- circ = kernel->gates;
- kernel->gates.reset();
- for (const auto &gate : circ) {
- QL_DOUT("new_gate added: " << gate->qasm());
+ utils::Any operands;
+ for (auto q: qubits) {
+ operands.add(make_qubit_ref(platform, q));
}
- QL_ASSERT(!(added && circ.empty()));
- return added;
+
+ auto insn = make_instruction(platform, gname, operands); // FIXME: return {} if instr does not exist? Currently throws
+ return insn.as();
}
-/**
- * Returns the number of swaps added to this past.
- */
utils::UInt Past::get_num_swaps_added() const {
return num_swaps_added;
}
-/**
- * Returns the number of moves added to this past.
- */
utils::UInt Past::get_num_moves_added() const {
return num_moves_added;
}
-/**
- * Shorthand for throwing an exception for a non-existant gate.
- */
-static void new_gate_exception(const utils::Str &s) {
- QL_FATAL("gate is not supported by the target platform: '" << s << "'");
-}
-
-/**
- * Returns whether swap(fr0,fr1) starts earlier than swap(sr0,sr1). This is
- * really a short-cut ignoring config file and perhaps several other
- * details.
- */
utils::Bool Past::is_first_swap_earliest(
utils::UInt fr0,
utils::UInt fr1,
@@ -297,410 +70,115 @@ utils::Bool Past::is_first_swap_earliest(
return fc.is_first_swap_earliest(fr0, fr1, sr0, sr1);
}
-/**
- * Generates a move into circ with parameters r0 and r1 (which
- * generate_move() may reverse). Whether this was successfully done can be
- * seen from whether circ was extended. Please note that the reversal of
- * operands may have been done also when generate_move() was not successful.
- */
-void Past::generate_move(ir::compat::GateRefs &circuit, utils::UInt &r0, utils::UInt &r1) {
+bool Past::add_move(utils::UInt &r0, utils::UInt &r1, const ir::SwapParameters& swap_params) {
if (v2r.get_state(r0) != com::map::QubitState::LIVE) {
QL_ASSERT(
v2r.get_state(r0) == com::map::QubitState::NONE ||
v2r.get_state(r0) == com::map::QubitState::INITIALIZED
);
+
// Interchange r0 and r1, so that r1 (right-hand operand of move) will
// be the state-less one.
- utils::UInt tmp = r1; r1 = r0; r0 = tmp;
- // QL_DOUT("... reversed operands for move to become move(q" << r0 << ",q" << r1 << ") ...");
+ std::swap(r0, r1);
}
+
QL_ASSERT(v2r.get_state(r0) == com::map::QubitState::LIVE); // and r0 will be the one with state
QL_ASSERT(v2r.get_state(r1) != com::map::QubitState::LIVE); // and r1 will be the one without state (QubitState::NONE || com::QubitState::INITIALIZED)
- // First (optimistically) create the move circuit and add it to circuit.
- utils::Bool created;
- if (platform->topology->is_inter_core_hop(r0, r1)) {
- if (options->heuristic == Heuristic::MAX_FIDELITY) {
- created = new_gate(circuit, "tmove_prim", {r0, r1}); // gates implementing tmove returned in circ
- } else {
- created = new_gate(circuit, "tmove_real", {r0, r1}); // gates implementing tmove returned in circ
- }
- if (!created) {
- created = new_gate(circuit, "tmove", {r0, r1});
- if (!created) {
- new_gate_exception("tmove or tmove_real");
- }
- }
- } else {
- if (options->heuristic == Heuristic::MAX_FIDELITY) {
- created = new_gate(circuit, "move_prim", {r0, r1}); // gates implementing move returned in circ
- } else {
- created = new_gate(circuit, "move_real", {r0, r1}); // gates implementing move returned in circ
- }
- if (!created) {
- created = new_gate(circuit, "move", {r0, r1});
- if (!created) {
- new_gate_exception("move or move_real");
- }
- }
- }
-
if (v2r.get_state(r1) == com::map::QubitState::NONE) {
- // r1 is not in inited state, generate in initcirc the circuit to do so
- // QL_DOUT("... initializing non-inited " << r1 << " to |0> (inited) state preferably using move_init ...");
- ir::compat::GateRefs init_circuit;
-
- created = new_gate(init_circuit, "move_init", {r1});
- if (!created) {
- created = new_gate(init_circuit, "prepz", {r1});
- // if (created)
- // {
- // created = new_gate(initcirc, "h", {r1});
- // if (!created) new_gate_exception("h");
- // }
- if (!created) {
- new_gate_exception("move_init or prepz");
- }
- }
-
- // When difference in extending circuit after scheduling
- // init_circuit+circuit or just circuit is less equal than threshold
- // cycles (0 would mean scheduling initcirc was for free), commit to it,
- // otherwise abort.
- if (get_insertion_cost(init_circuit, circuit) <= static_cast(options->max_move_penalty)) {
-
- // So we go for it! circuit contains move; it must get the
- // init_circuit before it. Do this by appending circ's gates to
- // init_circuit, and then swapping circuit and init_circuit's
- // contents.
- QL_DOUT("... initialization is for free, do it ...");
- for (auto &gp : circuit) {
- init_circuit.add(gp);
- }
- circuit.get_vec().swap(init_circuit.get_vec());
- v2r.set_state(r1, com::map::QubitState::INITIALIZED);
-
+ auto prepz = new_gate("prepz", {r1});
+ prepz->set_annotation(swap_params);
+
+ if (fc.cycle_extension(prepz) <= options->max_move_penalty) {
+ add(prepz);
} else {
-
- // Undo damage done, will not do move but swap, i.e. nothing created
- // thus far.
- QL_DOUT("... initialization extends circuit, don't do it ...");
- circuit.reset(); // circ being cleared also indicates creation wasn't successful
-
+ return false;
}
- // initcirc going out of scope here so it gets destroyed.
}
+
+ auto gname = platform->topology->is_inter_core_hop(r0, r1) ? "tmove" : "move";
+ auto move_gate = new_gate(gname, {r0, r1});
+ add(move_gate);
+ return true;
}
-/**
- * Generates a single swap/move with real operands and adds it to the
- * current past's waiting list. Note that the swap/move may be implemented
- * by a series of gates (circuit circ below), and that a swap/move
- * essentially is a commutative operation, interchanging the states of the
- * two qubits.
- *
- * A move is implemented by 2 CNOTs, while a swap is 3 CNOTs, provided the
- * target qubit is in |0> (inited) state. So, when one of the operands is
- * the current location of an unused virtual qubit, use a move with that
- * location as 2nd operand, after first having initialized the target qubit
- * in |0> (inited) state when that has not been done already. However, this
- * initialization must not extend the depth (beyond the configured limit),
- * so this can only be done when cycles for it are for free.
- */
void Past::add_swap(utils::UInt r0, utils::UInt r1) {
- utils::Bool created = false;
-
- QL_DOUT("... extending with swap(q" << r0 << ",q" << r1 << ") ...");
- QL_DOUT("... adding swap/move: " << v2r.real_to_string(r0) << ", " << v2r.real_to_string(r1));
-
- QL_ASSERT(v2r.get_state(r0) == com::map::QubitState::INITIALIZED ||
- v2r.get_state(r0) == com::map::QubitState::NONE ||
- v2r.get_state(r0) == com::map::QubitState::LIVE);
- QL_ASSERT(v2r.get_state(r1) == com::map::QubitState::INITIALIZED ||
- v2r.get_state(r1) == com::map::QubitState::NONE ||
- v2r.get_state(r1) == com::map::QubitState::LIVE);
-
if (v2r.get_state(r0) != com::map::QubitState::LIVE &&
v2r.get_state(r1) != com::map::QubitState::LIVE) {
- QL_DOUT("... no state in both operand of intended swap/move; don't add swap/move gates");
+ // No state in both operand of intended swap/move; no gate needed.
v2r.swap(r0, r1);
return;
}
- // Store the virtual qubits corresponding to each real qubit.
utils::UInt v0 = v2r.get_virtual(r0);
utils::UInt v1 = v2r.get_virtual(r1);
- ir::compat::GateRefs circuit; // current kernel copy, clear circuit
- if (options->use_move_gates && (v2r.get_state(r0) != com::map::QubitState::LIVE ||
- v2r.get_state(r1) != com::map::QubitState::LIVE)) {
- generate_move(circuit, r0, r1);
- created = circuit.size() != 0;
- if (created) {
- // generated move
- // move is in circ, optionally with initialization in front of it
- // also rs of its 2nd operand is 'QubitState::INITIALIZED'
- // note that after swap/move, r0 will be in this state then
- num_moves_added++; // for reporting at the end
- QL_DOUT("... move(q" << r0 << ",q" << r1 << ") ...");
- } else {
- QL_DOUT("... move(q" << r0 << ",q" << r1 << ") cancelled, go for swap");
- }
- }
- if (!created) {
- // no move generated so do swap
- if (options->reverse_swap_if_better) {
- // swap(r0,r1) is about to be generated
- // it is functionally symmetrical,
- // but in the implementation r1 starts 1 cycle earlier than r0 (we should derive this from json file ...)
- // so swap(r0,r1) with interchanged operands might get scheduled 1 cycle earlier;
- // when fcv[r0] < fcv[r1], r0 is free for use 1 cycle earlier than r1, so a reversal will help
- if (fc.is_first_operand_earlier(r0, r1)) {
- utils::UInt tmp = r1; r1 = r0; r0 = tmp;
- QL_DOUT("... reversed swap to become swap(q" << r0 << ",q" << r1 << ") ...");
- }
- }
- if (platform->topology->is_inter_core_hop(r0, r1)) {
- if (options->heuristic == Heuristic::MAX_FIDELITY) {
- created = new_gate(circuit, "tswap_prim", {r0, r1}); // gates implementing tswap returned in circ
- } else {
- created = new_gate(circuit, "tswap_real", {r0, r1}); // gates implementing tswap returned in circ
- }
- if (!created) {
- created = new_gate(circuit, "tswap", {r0, r1});
- if (!created) {
- new_gate_exception("tswap or tswap_real");
- }
- }
- QL_DOUT("... tswap(q" << r0 << ",q" << r1 << ") ...");
- } else {
- if (options->heuristic == Heuristic::MAX_FIDELITY) {
- created = new_gate(circuit, "swap_prim", {r0, r1}); // gates implementing swap returned in circ
- } else {
- created = new_gate(circuit, "swap_real", {r0, r1}); // gates implementing swap returned in circ
- }
- if (!created) {
- created = new_gate(circuit, "swap", {r0, r1});
- if (!created) {
- new_gate_exception("swap or swap_real");
- }
- }
- QL_DOUT("... swap(q" << r0 << ",q" << r1 << ") ...");
- }
+ const ir::SwapParameters swap_params{true, (utils::Int) r0, (utils::Int) r1, (utils::Int) v1, (utils::Int) v0};
+
+ if (options->use_move_gates &&
+ (v2r.get_state(r0) != com::map::QubitState::LIVE ||
+ v2r.get_state(r1) != com::map::QubitState::LIVE)) {
+ if (add_move(r0, r1, swap_params)) {
+ num_moves_added++;
+ v2r.swap(r0, r1);
+ return;
+ };
}
- num_swaps_added++; // for reporting at the end
-
- // Add each gate in the resulting circuit.
- for (auto &gp : circuit) {
- add(gp);
- // each gate in circ is part of a swap or move, so add the parameters
- //TODO: uint to int conversion
- const ir::compat::SwapParamaters swap_params {true, (utils::Int) r0, (utils::Int) r1, (utils::Int) v1, (utils::Int) v0};
- gp->swap_params = swap_params;
+
+ if (options->reverse_swap_if_better && fc.is_qubit_free_before(r0, r1)) {
+ std::swap(r0, r1);
}
+ auto gname = platform->topology->is_inter_core_hop(r0, r1) ? "tswap" : "swap";
+ auto newg = new_gate(gname, {r0, r1});
+ newg->set_annotation(swap_params);
+ add(newg);
+
+ num_swaps_added++;
+
// Reflect in v2r that r0 and r1 interchanged state, i.e. update the map to
// reflect the swap.
v2r.swap(r0, r1);
}
-/**
- * Adds the mapped gate (with real qubit indices as operands) to the past
- * by adding it to the waiting list and scheduling it into the past.
- */
-void Past::add_and_schedule(const ir::compat::GateRef &gate) {
- add(gate);
- schedule();
-}
-
-/**
- * Returns the real qubit index implementing virtual qubit index.
- */
utils::UInt Past::get_real_qubit(utils::UInt virt) {
utils::UInt r = v2r[virt];
QL_ASSERT(r != com::map::UNDEFINED_QUBIT);
- QL_DOUT("get_real_qubit(virt=" << virt << " mapped to real=" << r);
return r;
}
-/**
- * Strips the fixed qubit operands (if any) from the given gate name.
- */
-static void strip_name(utils::Str &name) {
- QL_DOUT("strip_name(name=" << name << ")");
- utils::UInt p = name.find(' ');
- if (p != utils::Str::npos) {
- name = name.substr(0,p);
- }
- QL_DOUT("... after strip_name name='" << name << "'");
-}
-
-/**
- * Turns the given gate into a "real" gate.
- *
- * See header file for more information.
- */
-void Past::make_real(const ir::compat::GateRef &gate, ir::compat::GateRefs &circuit) {
- QL_DOUT("make_real: " << gate->qasm());
-
- utils::Str gname = gate->name;
- strip_name(gname);
-
- utils::Vec real_qubits = gate->operands; // starts off as copy of virtual qubits!
- for (auto &qi : real_qubits) {
- qi = get_real_qubit(qi); // and now they are real
+void Past::make_real(const ir::CustomInstructionRef &gate) {
+ const auto &gname = gate->instruction_type->name;
+ com::map::ReferenceUpdater::Callback cb = [this, gname](utils::UInt virtual_qubit) {
if (options->assume_prep_only_initializes && (gname == "prepz" || gname == "Prepz")) {
- v2r.set_state(qi, com::map::QubitState::INITIALIZED);
+ v2r.set_state(virtual_qubit, com::map::QubitState::INITIALIZED);
} else {
- v2r.set_state(qi, com::map::QubitState::LIVE);
- }
- }
-
- utils::Str real_gname = gname;
- if (options->heuristic == Heuristic::MAX_FIDELITY) {
- QL_DOUT("make_real: with mapper==maxfidelity generate _prim");
- real_gname.append("_prim");
- } else {
- real_gname.append("_real");
- }
-
- utils::Bool created = new_gate(
- circuit,
- real_gname,
- real_qubits,
- gate->creg_operands,
- gate->duration,
- gate->angle,
- gate->breg_operands,
- gate->condition,
- gate->cond_operands
- );
- if (!created) {
- created = new_gate(
- circuit,
- gname,
- real_qubits,
- gate->creg_operands,
- gate->duration,
- gate->angle,
- gate->breg_operands,
- gate->condition,
- gate->cond_operands
- );
- if (!created) {
- QL_FATAL("make_real: failed creating gate " << real_gname << " or " << gname);
- }
- }
- QL_DOUT("... make_real: new gate created for: " << real_gname << " or " << gname);
-
- if (gate->swap_params.part_of_swap) {
- QL_DOUT("original gate was swap/move, adding swap/move parameters for gates in decomposed circuit");
- for (ir::compat::GateRef &it : circuit) {
- it->swap_params = gate->swap_params;
- }
- }
-}
-
-/**
- * Mapper after-burner. Used to make primitives of all gates that also have
- * a config file entry with _prim appended to their name, decomposing it
- * according to the config file gate decomposition.
- */
-void Past::make_primitive(const ir::compat::GateRef &gate, ir::compat::GateRefs &circuit) const {
- utils::Str gname = gate->name;
- strip_name(gname);
- utils::Str prim_gname = gname;
- prim_gname.append("_prim");
- utils::Bool created = new_gate(
- circuit,
- prim_gname,
- gate->operands,
- gate->creg_operands,
- gate->duration,
- gate->angle,
- gate->breg_operands,
- gate->condition,
- gate->cond_operands
- );
- if (!created) {
- created = new_gate(
- circuit,
- gname,
- gate->operands,
- gate->creg_operands,
- gate->duration,
- gate->angle,
- gate->breg_operands,
- gate->condition,
- gate->cond_operands
- );
- if (!created) {
- QL_FATAL("make_primitive: failed creating gate " << prim_gname << " or " << gname);
+ v2r.set_state(virtual_qubit, com::map::QubitState::LIVE);
}
- QL_DOUT("... make_primitive: new gate created for: " << gname);
- } else {
- QL_DOUT("... make_primitive: new gate created for: " << prim_gname);
- }
+ };
- if (gate->swap_params.part_of_swap) {
- QL_DOUT("original gate was swap/move, adding swap/move parameters for gates in decomposed circuit");
- for (const ir::compat::GateRef &it : circuit) {
- it->swap_params = gate->swap_params;
- }
- }
+ com::map::mapInstruction(platform, v2r.get_virt_to_real(), gate, cb);
}
-/**
- * Returns the first completely free cycle.
- */
utils::UInt Past::get_max_free_cycle() const {
return fc.get_max();
}
-/**
- * Non-quantum and quantum gates follow separate flows through Past:
- *
- * - Quantum gates are put in the waiting gate list when added, are then
- * scheduled, and are finally ordered by cycle into the main gate list.
- * They wait there to be inspected and scheduled, until there are too
- * many, a non-quantum gate comes by, or the end of the circuit is
- * reached.
- * - Non-quantum nonq gates first cause the main gate list to be
- * flushed/cleared to output before the non-quantum gate is output.
- *
- * All gates in the output gate list are out of view for scheduling/mapping
- * optimization and can be taken out to someplace else.
- */
-void Past::flush_all() {
- for (const auto &gate : gates) {
- output_gates.push_back(gate);
- }
- gates.clear(); // so effectively, lg's content was moved to outlg
-
- // fc.Init(platformp, nb); // needed?
- // cycle.clear(); // needed?
- // cycle is initialized to empty map
- // is ok without windowing, but with window, just delete the ones outside the window
-}
+utils::Any Past::flush_to_circuit() {
+ utils::UInt cycle = 0;
+ utils::Any output_circuit;
+ for (const auto &gate : output_gates) {
+ gate->cycle = cycle++;
-/**
- * Add the given non-qubit gate directly to the output list.
- */
-void Past::bypass(const ir::compat::GateRef &gate) {
- if (!gates.empty()) {
- flush_all();
- }
- output_gates.push_back(gate);
-}
+ ir::OperandsHelper ops(platform, *gate);
+ if (ops.numberOfQubitOperands() == 2) {
+ auto qs = ops.get2QGateOperands();
+ QL_ASSERT(platform->topology->get_distance(qs.first, qs.second) == 1 && "Mapper/router has not done its job properly");
+ }
-/**
- * Flushes the output gate list to the given circuit.
- */
-void Past::flush_to_circuit(ir::compat::GateRefs &output_circuit) {
- for (const auto &gate : output_gates) {
output_circuit.add(gate);
}
- output_gates.clear();
+ return output_circuit;
}
} // namespace detail
diff --git a/src/ql/pass/map/qubits/map/detail/past.h b/src/ql/pass/map/qubits/map/detail/past.h
index a71466486..937d87915 100644
--- a/src/ql/pass/map/qubits/map/detail/past.h
+++ b/src/ql/pass/map/qubits/map/detail/past.h
@@ -1,7 +1,3 @@
-/** \file
- * Past implementation.
- */
-
#pragma once
#include "ql/utils/num.h"
@@ -9,7 +5,6 @@
#include "ql/utils/list.h"
#include "ql/utils/map.h"
#include "ql/utils/vec.h"
-#include "ql/ir/compat/compat.h"
#include "ql/com/map/qubit_mapping.h"
#include "options.h"
#include "free_cycle.h"
@@ -26,136 +21,23 @@ namespace detail {
*
* There is a Past attached to the output stream, that is a kind of window with
* a list of gates in it, to which gates are added after mapping. This is called
- * the 'main' Past. While mapping, several alternatives are evaluated, each of
+ * the 'main' Past. While mapping, several alternatives might be evaluated, each of
* which also has a Past attached, and each of which for most of the parts
* start off as a copy of the 'main' Past. But it is in fact a temporary
* extension of this main Past.
*
- * Past contains gates of which the schedule might influence a future path
- * selected for mapping binary gates. It maintains for each qubit from which
- * cycle on it is free, so that swap insertion can exploit this to hide its
- * overall circuit latency overhead by increasing ILP. Also it maintains the 1
- * to 1 (reversible) virtual to real qubit map: all gates in past and beyond are
- * mapped and have real qubits as operands. While experimenting with path
- * alternatives, a clone is made of the main past, to insert swaps and evaluate
- * the latency effects; note that inserting swaps changes the mapping.
- *
- * On arrival of a quantum gate(s):
- * - [isempty(waiting_gates)]
- * - if 2q nonNN clone mult. pasts, in each clone add swap/move gates,
- * schedule, evaluate clones, select, add swaps to mainPast
- * - add(), add(), ...: add quantum gates to waiting_gates, waiting to be
- * scheduled in [!isempty(waiting_gates)]
- * - schedule(): schedules all quantum gates of waiting_gates into gates
- * [isempty(waiting_gates) && !isempty(gates)]
- *
- * On arrival of a classical gate:
- * - flush_all: gates flushed to output_gates
- * [isempty(waiting_gates) && isempty(gates) && !isempty(output_gates)]
- * - bypass: classical gate added to output_gates
- * [isempty(waiting_gates) && isempty(gates) && !isempty(output_gates)]
- *
- * On no gates:
- * - [isempty(waiting_gates)]
- * - flush_all: lg flushed to output_gates
- * [isempty(waiting_gates) && isempty(gates) && !isempty(output_gates)]
- *
- * On end:
- * - flush_to_circuit: output_gates flushed to output circuit
- * [isempty(waiting_gates) && isempty(gates) && isempty(output_gates)]
+ * Past contains:
+ * - the list of gates that are already mapped (this should be at all times a valid circuit
+ * with routed gates),
+ * - the virtual to real qubit mapping after execution of above gates (i.e. swaps and moves added for routing),
+ * - the free cycle map, which is a scheduling heuristic telling which qubits/references are free
+ * at which cycle. This allows routing to use paths that extend the overall circuit depth
+ * as little as possible.
*/
-class Past {
-private:
-
- /**
- * Number of qubits.
- */
- utils::UInt nq;
-
- /**
- * Number of bregs.
- */
- utils::UInt nb;
-
- /**
- * Cycle time, multiplier from cycles to nanoseconds.
- */
- utils::UInt ct;
-
- /**
- * Platform describing resources for scheduling.
- */
- ir::compat::PlatformRef platform;
-
- /**
- * Current kernel for creating gates.
- */
- ir::compat::KernelRef kernel;
-
- /**
- * Parsed options record for the whole mapper pass.
- */
- OptionsRef options;
-
- /**
- * State: current virtual to real qubit map, imported/exported to kernel.
- */
- com::map::QubitMapping v2r;
-
- /**
- * State: FreeCycle map (including resource_manager) of this Past.
- */
- FreeCycle fc;
-
- /**
- * List of quantum gates in this Past, topological order, waiting to be
- * scheduled. This only contains gates from add() and the final schedule()
- * call. When evaluating alternatives, it is empty when Past is cloned; so
- * no state.
- */
- utils::List waiting_gates;
-
-public:
-
- /**
- * State: list of q gates in this Past, scheduled by their (start) cycle
- * values. So this is the result list of this Past, to compare with other
- * Alters.
- */
- utils::List gates;
-
-private:
-
- /**
- * List of gates flushed out of this Past, not yet put in outCirc when
- * evaluating alternatives. output_gates stays constant; so no state.
- */
- utils::List output_gates;
-
- /**
- * State: gate to cycle map, startCycle value of each past gatecycle[gp].
- * cycle[gp] can be different for each gp for each past. gp->cycle is not
- * used by map_gates, although updated by set_cycle called from
- * MakeAvailable/TakeAvailable.
- */
- utils::Map