Skip to content

Commit

Permalink
šŸ› ensure typeinfo and vtable information is present for `CompoundOperā€¦
Browse files Browse the repository at this point in the history
ā€¦ation` (#548)

## 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:

<!---
This checklist serves as a reminder of a couple of things that ensure
your pull request will be merged swiftly.
-->

- [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.
  • Loading branch information
burgholzer authored Feb 8, 2024
1 parent 2141558 commit 55752ce
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 150 deletions.
177 changes: 27 additions & 150 deletions include/mqt-core/operations/CompoundOperation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,160 +2,60 @@

#include "Operation.hpp"

#include <algorithm>

namespace qc {

class CompoundOperation final : public Operation {
private:
std::vector<std::unique_ptr<Operation>> 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<std::unique_ptr<Operation>>&& 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<std::unique_ptr<Operation>>&& 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<Operation> clone() const override {
return std::make_unique<CompoundOperation>(*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<Operation> 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<const CompoundOperation*>(&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<std::size_t>& depths) const override {
for (const auto& op : ops) {
op->addDepthContribution(depths);
}
}
void addDepthContribution(std::vector<std::size_t>& 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<std::unique_ptr<Operation>>& getOps() { return ops; }

[[nodiscard]] std::set<Qubit> getUsedQubits() const override;

void invert() override;

/**
* Pass-Through
Expand Down Expand Up @@ -230,34 +130,11 @@ class CompoundOperation final : public Operation {
}

[[nodiscard]] const auto& at(std::size_t i) const { return ops.at(i); }

std::vector<std::unique_ptr<Operation>>& getOps() { return ops; }

[[nodiscard]] std::set<Qubit> getUsedQubits() const override {
std::set<Qubit> 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<qc::CompoundOperation> {
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<qc::Operation>{}(*op));
}
return seed;
}
std::size_t operator()(const qc::CompoundOperation& co) const noexcept;
};
} // namespace std
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 55752ce

Please sign in to comment.