From 55752ce318cfd083194e27db1ff07c125379f869 Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Thu, 8 Feb 2024 16:51:57 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20ensure=20typeinfo=20and=20vtable?= =?UTF-8?q?=20information=20is=20present=20for=20`CompoundOperation`=20(#5?= =?UTF-8?q?48)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description This PR ensures that all the different `Operation` sub-classes get a corresponding `typeinfo` and `vtable` symbol in the compiled binaries. This is important for RTTI when using shared libraries and ensured that `dynamic_cast`s across library boundaries work as intended. Specifically, the `CompoundOperation` class was only defined in a header, which led to the omission of said information. This is fixed by creating a dedicated `.cpp` file and moving at least one definition there. This was discovered while working on #538 in combination with mqt-ddsim. ## Checklist: - [x] The pull request only contains commits that are related to it. - [x] I have added appropriate tests and documentation. - [x] I have made sure that all CI jobs on GitHub pass. - [x] The pull request introduces no new warnings and follows the project's style guidelines. --- .../mqt-core/operations/CompoundOperation.hpp | 177 +++-------------- src/CMakeLists.txt | 1 + src/operations/CompoundOperation.cpp | 179 ++++++++++++++++++ 3 files changed, 207 insertions(+), 150 deletions(-) create mode 100644 src/operations/CompoundOperation.cpp diff --git a/include/mqt-core/operations/CompoundOperation.hpp b/include/mqt-core/operations/CompoundOperation.hpp index 3b07f1019..f8764fe82 100644 --- a/include/mqt-core/operations/CompoundOperation.hpp +++ b/include/mqt-core/operations/CompoundOperation.hpp @@ -2,8 +2,6 @@ #include "Operation.hpp" -#include - namespace qc { class CompoundOperation final : public Operation { @@ -11,151 +9,53 @@ class CompoundOperation final : public Operation { std::vector> ops{}; public: - explicit CompoundOperation(const std::size_t nq) { - name = "Compound operation:"; - nqubits = nq; - type = Compound; - } - - explicit CompoundOperation( - const std::size_t nq, - std::vector>&& operations) - : CompoundOperation(nq) { - // NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer) - ops = std::move(operations); - } + explicit CompoundOperation(std::size_t nq); - CompoundOperation(const CompoundOperation& co) - : Operation(co), ops(co.ops.size()) { - for (std::size_t i = 0; i < co.ops.size(); ++i) { - ops[i] = co.ops[i]->clone(); - } - } + CompoundOperation(std::size_t nq, + std::vector>&& operations); - CompoundOperation& operator=(const CompoundOperation& co) { - if (this != &co) { - Operation::operator=(co); - ops.resize(co.ops.size()); - for (std::size_t i = 0; i < co.ops.size(); ++i) { - ops[i] = co.ops[i]->clone(); - } - } - return *this; - } + CompoundOperation(const CompoundOperation& co); - [[nodiscard]] std::unique_ptr clone() const override { - return std::make_unique(*this); - } + CompoundOperation& operator=(const CompoundOperation& co); - void setNqubits(const std::size_t nq) override { - nqubits = nq; - for (auto& op : ops) { - op->setNqubits(nq); - } - } + [[nodiscard]] std::unique_ptr clone() const override; - [[nodiscard]] bool isCompoundOperation() const override { return true; } + void setNqubits(std::size_t nq) override; - [[nodiscard]] bool isNonUnitaryOperation() const override { - return std::any_of(ops.cbegin(), ops.cend(), [](const auto& op) { - return op->isNonUnitaryOperation(); - }); - } + [[nodiscard]] bool isCompoundOperation() const override; - [[nodiscard]] inline bool isSymbolicOperation() const override { - return std::any_of(ops.begin(), ops.end(), [](const auto& op) { - return op->isSymbolicOperation(); - }); - } + [[nodiscard]] bool isNonUnitaryOperation() const override; - void addControl(const Control c) override { - controls.insert(c); - // we can just add the controls to each operation, as the operations will - // check if they already act on the control qubits. - for (auto& op : ops) { - op->addControl(c); - } - } + [[nodiscard]] inline bool isSymbolicOperation() const override; - void clearControls() override { - // we remove just our controls from nested operations - removeControls(controls); - } + void addControl(Control c) override; - void removeControl(const Control c) override { - // first we iterate over our controls and check if we are actually allowed - // to remove them - if (controls.erase(c) == 0) { - throw QFRException("Cannot remove control from compound operation as it " - "is not a control."); - } - - for (auto& op : ops) { - op->removeControl(c); - } - } + void clearControls() override; - Controls::iterator removeControl(const Controls::iterator it) override { - for (auto& op : ops) { - op->removeControl(*it); - } + void removeControl(Control c) override; - return controls.erase(it); - } + Controls::iterator removeControl(Controls::iterator it) override; [[nodiscard]] bool equals(const Operation& op, const Permutation& perm1, - const Permutation& perm2) const override { - if (const auto* comp = dynamic_cast(&op)) { - if (comp->ops.size() != ops.size()) { - return false; - } - - auto it = comp->ops.cbegin(); - for (const auto& operation : ops) { - if (!operation->equals(**it, perm1, perm2)) { - return false; - } - ++it; - } - return true; - } - return false; - } - [[nodiscard]] bool equals(const Operation& operation) const override { - return equals(operation, {}, {}); - } + const Permutation& perm2) const override; + [[nodiscard]] bool equals(const Operation& operation) const override; std::ostream& print(std::ostream& os, const Permutation& permutation, - const std::size_t prefixWidth) const override { - const auto prefix = std::string(prefixWidth - 1, ' '); - os << std::string(4 * nqubits, '-') << "\n"; - for (const auto& op : ops) { - os << prefix << ":"; - op->print(os, permutation, prefixWidth); - os << "\n"; - } - os << prefix << std::string(4 * nqubits + 1, '-'); - return os; - } + std::size_t prefixWidth) const override; - [[nodiscard]] bool actsOn(const Qubit i) const override { - return std::any_of(ops.cbegin(), ops.cend(), - [&i](const auto& op) { return op->actsOn(i); }); - } + [[nodiscard]] bool actsOn(Qubit i) const override; - void addDepthContribution(std::vector& depths) const override { - for (const auto& op : ops) { - op->addDepthContribution(depths); - } - } + void addDepthContribution(std::vector& depths) const override; void dumpOpenQASM(std::ostream& of, const RegisterNames& qreg, const RegisterNames& creg, size_t indent, - bool openQASM3) const override { - for (const auto& op : ops) { - op->dumpOpenQASM(of, qreg, creg, indent, openQASM3); - } - } + bool openQASM3) const override; + + std::vector>& getOps() { return ops; } + + [[nodiscard]] std::set getUsedQubits() const override; + + void invert() override; /** * Pass-Through @@ -230,34 +130,11 @@ class CompoundOperation final : public Operation { } [[nodiscard]] const auto& at(std::size_t i) const { return ops.at(i); } - - std::vector>& getOps() { return ops; } - - [[nodiscard]] std::set getUsedQubits() const override { - std::set usedQubits{}; - for (const auto& op : ops) { - usedQubits.merge(op->getUsedQubits()); - } - return usedQubits; - } - - void invert() override { - for (auto& op : ops) { - op->invert(); - } - std::reverse(ops.begin(), ops.end()); - } }; } // namespace qc namespace std { template <> struct hash { - std::size_t operator()(const qc::CompoundOperation& co) const noexcept { - std::size_t seed = 0U; - for (const auto& op : co) { - qc::hashCombine(seed, std::hash{}(*op)); - } - return seed; - } + std::size_t operator()(const qc::CompoundOperation& co) const noexcept; }; } // namespace std diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e5aba42d6..4b74ac95e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -99,6 +99,7 @@ if(NOT TARGET ${MQT_CORE_TARGET_NAME}) algorithms/WState.cpp CircuitOptimizer.cpp operations/ClassicControlledOperation.cpp + operations/CompoundOperation.cpp operations/Expression.cpp operations/NonUnitaryOperation.cpp operations/Operation.cpp diff --git a/src/operations/CompoundOperation.cpp b/src/operations/CompoundOperation.cpp new file mode 100644 index 000000000..e313624c2 --- /dev/null +++ b/src/operations/CompoundOperation.cpp @@ -0,0 +1,179 @@ +#include "operations/CompoundOperation.hpp" + +#include + +namespace qc { +CompoundOperation::CompoundOperation(const std::size_t nq) { + name = "Compound operation:"; + nqubits = nq; + type = Compound; +} + +CompoundOperation::CompoundOperation( + const std::size_t nq, std::vector>&& operations) + : CompoundOperation(nq) { + // NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer) + ops = std::move(operations); +} + +CompoundOperation::CompoundOperation(const CompoundOperation& co) + : Operation(co), ops(co.ops.size()) { + for (std::size_t i = 0; i < co.ops.size(); ++i) { + ops[i] = co.ops[i]->clone(); + } +} + +CompoundOperation& CompoundOperation::operator=(const CompoundOperation& co) { + if (this != &co) { + Operation::operator=(co); + ops.resize(co.ops.size()); + for (std::size_t i = 0; i < co.ops.size(); ++i) { + ops[i] = co.ops[i]->clone(); + } + } + return *this; +} + +std::unique_ptr CompoundOperation::clone() const { + return std::make_unique(*this); +} + +void CompoundOperation::setNqubits(const std::size_t nq) { + nqubits = nq; + for (auto& op : ops) { + op->setNqubits(nq); + } +} + +bool CompoundOperation::isNonUnitaryOperation() const { + return std::any_of(ops.cbegin(), ops.cend(), [](const auto& op) { + return op->isNonUnitaryOperation(); + }); +} + +bool CompoundOperation::isCompoundOperation() const { return true; } + +bool CompoundOperation::isSymbolicOperation() const { + return std::any_of(ops.begin(), ops.end(), + [](const auto& op) { return op->isSymbolicOperation(); }); +} + +void CompoundOperation::addControl(const Control c) { + controls.insert(c); + // we can just add the controls to each operation, as the operations will + // check if they already act on the control qubits. + for (auto& op : ops) { + op->addControl(c); + } +} + +void CompoundOperation::clearControls() { + // we remove just our controls from nested operations + removeControls(controls); +} + +void CompoundOperation::removeControl(const Control c) { + // first we iterate over our controls and check if we are actually allowed + // to remove them + if (controls.erase(c) == 0) { + throw QFRException("Cannot remove control from compound operation as it " + "is not a control."); + } + + for (auto& op : ops) { + op->removeControl(c); + } +} + +Controls::iterator +CompoundOperation::removeControl(const Controls::iterator it) { + for (auto& op : ops) { + op->removeControl(*it); + } + + return controls.erase(it); +} +bool CompoundOperation::equals(const Operation& op, const Permutation& perm1, + const Permutation& perm2) const { + if (const auto* comp = dynamic_cast(&op)) { + if (comp->ops.size() != ops.size()) { + return false; + } + + auto it = comp->ops.cbegin(); + for (const auto& operation : ops) { + if (!operation->equals(**it, perm1, perm2)) { + return false; + } + ++it; + } + return true; + } + return false; +} + +bool CompoundOperation::equals(const Operation& operation) const { + return equals(operation, {}, {}); +} + +std::ostream& CompoundOperation::print(std::ostream& os, + const Permutation& permutation, + const std::size_t prefixWidth) const { + const auto prefix = std::string(prefixWidth - 1, ' '); + os << std::string(4 * nqubits, '-') << "\n"; + for (const auto& op : ops) { + os << prefix << ":"; + op->print(os, permutation, prefixWidth); + os << "\n"; + } + os << prefix << std::string(4 * nqubits + 1, '-'); + return os; +} + +bool CompoundOperation::actsOn(const Qubit i) const { + return std::any_of(ops.cbegin(), ops.cend(), + [&i](const auto& op) { return op->actsOn(i); }); +} + +void CompoundOperation::addDepthContribution( + std::vector& depths) const { + for (const auto& op : ops) { + op->addDepthContribution(depths); + } +} + +void CompoundOperation::dumpOpenQASM(std::ostream& of, + const RegisterNames& qreg, + const RegisterNames& creg, size_t indent, + bool openQASM3) const { + for (const auto& op : ops) { + op->dumpOpenQASM(of, qreg, creg, indent, openQASM3); + } +} + +std::set CompoundOperation::getUsedQubits() const { + std::set usedQubits{}; + for (const auto& op : ops) { + usedQubits.merge(op->getUsedQubits()); + } + return usedQubits; +} +void CompoundOperation::invert() { + for (auto& op : ops) { + op->invert(); + } + std::reverse(ops.begin(), ops.end()); +} + +} // namespace qc + +namespace std { +std::size_t std::hash::operator()( + const qc::CompoundOperation& co) const noexcept { + std::size_t seed = 0U; + for (const auto& op : co) { + qc::hashCombine(seed, std::hash{}(*op)); + } + return seed; +} +} // namespace std