Skip to content

Commit

Permalink
♻️ Eliminate nqubits and startingQubit from Operation class hie…
Browse files Browse the repository at this point in the history
…rarchy (#574)

## Description

The third follow-up PRs to #358, which tries to simplify the `Operation`
class hierarchy by eliminating the `nqubits` and `startingQubit`
members, which were purely in place for managing the corresponding
decision diagrams.

This also practically fixes #345. Only state creation routines now have
the additional starting qubit parameter.

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

---------

Signed-off-by: burgholzer <[email protected]>
  • Loading branch information
burgholzer authored Mar 26, 2024
1 parent 16ae2b7 commit 3c07991
Show file tree
Hide file tree
Showing 44 changed files with 438 additions and 734 deletions.
20 changes: 10 additions & 10 deletions docs/quickstart.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -209,13 +209,13 @@
"nqubits = 3\n",
"\n",
"# u3 gate on qubit 0 in a 3-qubit circuit\n",
"u_gate = StandardOperation(nqubits, target=0, params=[pi / 4, pi, -pi / 2], op_type=OpType.u)\n",
"u_gate = StandardOperation(target=0, params=[pi / 4, pi, -pi / 2], op_type=OpType.u)\n",
"\n",
"# controlled x-rotation\n",
"crx = StandardOperation(nqubits, control=Control(0), target=1, params=[pi], op_type=OpType.rx)\n",
"crx = StandardOperation(control=Control(0), target=1, params=[pi], op_type=OpType.rx)\n",
"\n",
"# multi-controlled x-gate\n",
"mcx = StandardOperation(nqubits, controls={Control(0), Control(1)}, target=2, op_type=OpType.x)\n",
"mcx = StandardOperation(controls={Control(0), Control(1)}, target=2, op_type=OpType.x)\n",
"\n",
"# add operations to a quantum computation\n",
"qc = QuantumComputation(nqubits)\n",
Expand Down Expand Up @@ -250,10 +250,10 @@
"qc.h(0)\n",
"\n",
"# measure qubit 0 on classical bit 0\n",
"meas_0 = NonUnitaryOperation(nqubits, target=0, classic=0)\n",
"meas_0 = NonUnitaryOperation(target=0, classic=0)\n",
"\n",
"# reset all qubits\n",
"reset = NonUnitaryOperation(nqubits, targets=[0, 1], op_type=OpType.reset)\n",
"reset = NonUnitaryOperation(targets=[0, 1], op_type=OpType.reset)\n",
"\n",
"qc.append(meas_0)\n",
"qc.append(reset)\n",
Expand Down Expand Up @@ -293,10 +293,10 @@
"print(sym)\n",
"\n",
"# Create symbolic gate\n",
"u1_symb = SymbolicOperation(nqubits, target=0, params=[sym], op_type=OpType.p)\n",
"u1_symb = SymbolicOperation(target=0, params=[sym], op_type=OpType.p)\n",
"\n",
"# Mixed symbolic and instantiated parameters\n",
"u2_symb = SymbolicOperation(nqubits, target=0, params=[sym, 2.0], op_type=OpType.u2)"
"u2_symb = SymbolicOperation(target=0, params=[sym, 2.0], op_type=OpType.u2)"
]
},
{
Expand All @@ -319,11 +319,11 @@
"from mqt.core.operations import CompoundOperation\n",
"\n",
"nqubits = 2\n",
"comp_op = CompoundOperation(nqubits)\n",
"comp_op = CompoundOperation()\n",
"\n",
"# create bell pair circuit\n",
"comp_op.append(StandardOperation(nqubits, 0, op_type=OpType.h))\n",
"comp_op.append(StandardOperation(nqubits, target=0, control=Control(1), op_type=OpType.x))\n",
"comp_op.append(StandardOperation(0, op_type=OpType.h))\n",
"comp_op.append(StandardOperation(target=0, control=Control(1), op_type=OpType.x))\n",
"\n",
"qc = QuantumComputation(nqubits)\n",
"qc.append(comp_op)\n",
Expand Down
4 changes: 2 additions & 2 deletions eval/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
add_executable(${PROJECT_NAME}-dd-eval eval_dd_package.cpp)
target_link_libraries(${PROJECT_NAME}-dd-eval ${PROJECT_NAME}-dd)
add_executable(mqt-core-dd-eval eval_dd_package.cpp)
target_link_libraries(mqt-core-dd-eval PRIVATE MQT::CoreDD MQT::ProjectOptions MQT::ProjectWarnings)
54 changes: 26 additions & 28 deletions include/mqt-core/QuantumComputation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@ class QuantumComputation {
} \
void mc##op(const Controls& controls, const Qubit target) { \
checkQubitRange(target, controls); \
emplace_back<StandardOperation>(getNqubits(), controls, target, \
emplace_back<StandardOperation>(controls, target, \
OP_NAME_TO_TYPE.at(#op)); \
}

Expand Down Expand Up @@ -464,14 +464,13 @@ class QuantumComputation {
const Qubit target) { \
checkQubitRange(target, controls); \
if (std::holds_alternative<fp>(param)) { \
emplace_back<StandardOperation>(getNqubits(), controls, target, \
emplace_back<StandardOperation>(controls, target, \
OP_NAME_TO_TYPE.at(#op), \
std::vector{std::get<fp>(param)}); \
} else { \
addVariables(param); \
emplace_back<SymbolicOperation>(getNqubits(), controls, target, \
OP_NAME_TO_TYPE.at(#op), \
std::vector{param}); \
emplace_back<SymbolicOperation>( \
controls, target, OP_NAME_TO_TYPE.at(#op), std::vector{param}); \
} \
}

Expand All @@ -495,11 +494,11 @@ class QuantumComputation {
if (std::holds_alternative<fp>(param0) && \
std::holds_alternative<fp>(param1)) { \
emplace_back<StandardOperation>( \
getNqubits(), controls, target, OP_NAME_TO_TYPE.at(#op), \
controls, target, OP_NAME_TO_TYPE.at(#op), \
std::vector{std::get<fp>(param0), std::get<fp>(param1)}); \
} else { \
addVariables(param0, param1); \
emplace_back<SymbolicOperation>(getNqubits(), controls, target, \
emplace_back<SymbolicOperation>(controls, target, \
OP_NAME_TO_TYPE.at(#op), \
std::vector{param0, param1}); \
} \
Expand All @@ -526,12 +525,12 @@ class QuantumComputation {
std::holds_alternative<fp>(param1) && \
std::holds_alternative<fp>(param2)) { \
emplace_back<StandardOperation>( \
getNqubits(), controls, target, OP_NAME_TO_TYPE.at(#op), \
controls, target, OP_NAME_TO_TYPE.at(#op), \
std::vector{std::get<fp>(param0), std::get<fp>(param1), \
std::get<fp>(param2)}); \
} else { \
addVariables(param0, param1, param2); \
emplace_back<SymbolicOperation>(getNqubits(), controls, target, \
emplace_back<SymbolicOperation>(controls, target, \
OP_NAME_TO_TYPE.at(#op), \
std::vector{param0, param1, param2}); \
} \
Expand All @@ -550,7 +549,7 @@ class QuantumComputation {
void mc##op(const Controls& controls, const Qubit target0, \
const Qubit target1) { \
checkQubitRange(target0, target1, controls); \
emplace_back<StandardOperation>(getNqubits(), controls, target0, target1, \
emplace_back<StandardOperation>(controls, target0, target1, \
OP_NAME_TO_TYPE.at(#op)); \
}

Expand All @@ -575,13 +574,13 @@ class QuantumComputation {
const Qubit target0, const Qubit target1) { \
checkQubitRange(target0, target1, controls); \
if (std::holds_alternative<fp>(param)) { \
emplace_back<StandardOperation>(getNqubits(), controls, target0, \
target1, OP_NAME_TO_TYPE.at(#op), \
emplace_back<StandardOperation>(controls, target0, target1, \
OP_NAME_TO_TYPE.at(#op), \
std::vector{std::get<fp>(param)}); \
} else { \
addVariables(param); \
emplace_back<SymbolicOperation>(getNqubits(), controls, target0, \
target1, OP_NAME_TO_TYPE.at(#op), \
emplace_back<SymbolicOperation>(controls, target0, target1, \
OP_NAME_TO_TYPE.at(#op), \
std::vector{param}); \
} \
}
Expand All @@ -608,12 +607,12 @@ class QuantumComputation {
if (std::holds_alternative<fp>(param0) && \
std::holds_alternative<fp>(param1)) { \
emplace_back<StandardOperation>( \
getNqubits(), controls, target0, target1, OP_NAME_TO_TYPE.at(#op), \
controls, target0, target1, OP_NAME_TO_TYPE.at(#op), \
std::vector{std::get<fp>(param0), std::get<fp>(param1)}); \
} else { \
addVariables(param0, param1); \
emplace_back<SymbolicOperation>(getNqubits(), controls, target0, \
target1, OP_NAME_TO_TYPE.at(#op), \
emplace_back<SymbolicOperation>(controls, target0, target1, \
OP_NAME_TO_TYPE.at(#op), \
std::vector{param0, param1}); \
} \
}
Expand All @@ -632,15 +631,15 @@ class QuantumComputation {
void measure(const Qubit qubit, const std::size_t bit) {
checkQubitRange(qubit);
checkBitRange(bit);
emplace_back<NonUnitaryOperation>(getNqubits(), qubit, bit);
emplace_back<NonUnitaryOperation>(qubit, bit);
}

void measure(Qubit qubit, const std::pair<std::string, Bit>& registerBit);

void measure(const Targets& qubits, const std::vector<Bit>& bits) {
checkQubitRange(qubits);
checkBitRange(bits);
emplace_back<NonUnitaryOperation>(getNqubits(), qubits, bits);
emplace_back<NonUnitaryOperation>(qubits, bits);
}

/**
Expand All @@ -654,26 +653,25 @@ class QuantumComputation {

void reset(const Qubit target) {
checkQubitRange(target);
emplace_back<NonUnitaryOperation>(getNqubits(), std::vector<Qubit>{target},
qc::Reset);
emplace_back<NonUnitaryOperation>(std::vector<Qubit>{target}, qc::Reset);
}
void reset(const Targets& targets) {
checkQubitRange(targets);
emplace_back<NonUnitaryOperation>(getNqubits(), targets, qc::Reset);
emplace_back<NonUnitaryOperation>(targets, qc::Reset);
}

void barrier() {
std::vector<Qubit> targets(getNqubits());
std::iota(targets.begin(), targets.end(), 0);
emplace_back<StandardOperation>(getNqubits(), targets, qc::Barrier);
emplace_back<StandardOperation>(targets, qc::Barrier);
}
void barrier(const Qubit target) {
checkQubitRange(target);
emplace_back<StandardOperation>(getNqubits(), target, qc::Barrier);
emplace_back<StandardOperation>(target, qc::Barrier);
}
void barrier(const Targets& targets) {
checkQubitRange(targets);
emplace_back<StandardOperation>(getNqubits(), targets, qc::Barrier);
emplace_back<StandardOperation>(targets, qc::Barrier);
}

void classicControlled(const OpType op, const Qubit target,
Expand All @@ -698,8 +696,8 @@ class QuantumComputation {
const std::vector<fp>& params = {}) {
checkQubitRange(target, controls);
checkClassicalRegister(controlRegister);
std::unique_ptr<Operation> gate = std::make_unique<StandardOperation>(
getNqubits(), controls, target, op, params);
std::unique_ptr<Operation> gate =
std::make_unique<StandardOperation>(controls, target, op, params);
emplace_back<ClassicControlledOperation>(std::move(gate), controlRegister,
expectedValue);
}
Expand Down Expand Up @@ -844,7 +842,7 @@ class QuantumComputation {

// this convenience method allows to turn a circuit into a compound operation.
std::unique_ptr<CompoundOperation> asCompoundOperation() {
return std::make_unique<CompoundOperation>(getNqubits(), std::move(ops));
return std::make_unique<CompoundOperation>(std::move(ops));
}

// this convenience method allows to turn a circuit into an operation.
Expand Down
14 changes: 0 additions & 14 deletions include/mqt-core/dd/Operations.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -164,20 +164,6 @@ template <class Config>
qc::MatrixDD getDD(const qc::Operation* op, Package<Config>& dd,
qc::Permutation& permutation, const bool inverse = false) {
const auto type = op->getType();
const auto nqubits = op->getNqubits();

// check whether the operation can be handled by the underlying DD package
if (nqubits > Package<Config>::MAX_POSSIBLE_QUBITS) {
throw qc::QFRException(
"Requested too many qubits to be handled by the DD package. Qubit "
"datatype only allows up to " +
std::to_string(Package<Config>::MAX_POSSIBLE_QUBITS) +
" qubits, while " + std::to_string(nqubits) +
" were requested. If you want to use more than " +
std::to_string(Package<Config>::MAX_POSSIBLE_QUBITS) +
" qubits, you have to recompile the package with a wider Qubit type in "
"`include/dd/DDDefinitions.hpp!`");
}

// if a permutation is provided and the current operation is a SWAP, this
// routine just updates the permutation
Expand Down
4 changes: 2 additions & 2 deletions include/mqt-core/ecc/Ecc.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ class Ecc {

void gateNotAvailableError(const qc::Operation& gate) const {
std::stringstream stream;
stream << "Gate " << gate << " not supported to encode in error code "
<< ecc.name << "!";
stream << "Gate " << toString(gate.getType())
<< " not supported to encode in error code " << ecc.name << "!";
throw qc::QFRException(stream.str());
}
};
Expand Down
6 changes: 0 additions & 6 deletions include/mqt-core/operations/ClassicControlledOperation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ class ClassicControlledOperation final : public Operation {
ComparisonKind kind = ComparisonKind::Eq)
: op(std::move(operation)), controlRegister(std::move(controlReg)),
expectedValue(expectedVal), comparisonKind(kind) {
nqubits = op->getNqubits();
name = "c_" + shortName(op->getType());
parameter.reserve(3);
parameter.emplace_back(static_cast<fp>(controlRegister.first));
Expand Down Expand Up @@ -73,11 +72,6 @@ class ClassicControlledOperation final : public Operation {

[[nodiscard]] auto getOperation() const { return op.get(); }

void setNqubits(std::size_t nq) override {
nqubits = nq;
op->setNqubits(nq);
}

[[nodiscard]] const Targets& getTargets() const override {
return op->getTargets();
}
Expand Down
11 changes: 5 additions & 6 deletions include/mqt-core/operations/CompoundOperation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,17 @@ class CompoundOperation final : public Operation {
std::vector<std::unique_ptr<Operation>> ops{};

public:
explicit CompoundOperation(std::size_t nq);
explicit CompoundOperation();

CompoundOperation(std::size_t nq,
std::vector<std::unique_ptr<Operation>>&& operations);
explicit CompoundOperation(
std::vector<std::unique_ptr<Operation>>&& operations);

CompoundOperation(const CompoundOperation& co);

CompoundOperation& operator=(const CompoundOperation& co);

[[nodiscard]] std::unique_ptr<Operation> clone() const override;

void setNqubits(std::size_t nq) override;

[[nodiscard]] bool isCompoundOperation() const override;

[[nodiscard]] bool isNonUnitaryOperation() const override;
Expand All @@ -41,7 +39,8 @@ class CompoundOperation final : public Operation {
[[nodiscard]] bool equals(const Operation& operation) const override;

std::ostream& print(std::ostream& os, const Permutation& permutation,
std::size_t prefixWidth) const override;
std::size_t prefixWidth,
std::size_t nqubits) const override;

[[nodiscard]] bool actsOn(Qubit i) const override;

Expand Down
18 changes: 10 additions & 8 deletions include/mqt-core/operations/NonUnitaryOperation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,21 @@ class NonUnitaryOperation final : public Operation {
protected:
std::vector<Bit> classics{}; // vector for the classical bits to measure into

void printMeasurement(std::ostream& os, const std::vector<Qubit>& q,
const std::vector<Bit>& c,
const Permutation& permutation) const;
static void printMeasurement(std::ostream& os, const std::vector<Qubit>& q,
const std::vector<Bit>& c,
const Permutation& permutation,
std::size_t nqubits);
void printReset(std::ostream& os, const std::vector<Qubit>& q,
const Permutation& permutation) const;
const Permutation& permutation, std::size_t nqubits) const;

public:
// Measurement constructor
NonUnitaryOperation(std::size_t nq, std::vector<Qubit> qubitRegister,
NonUnitaryOperation(std::vector<Qubit> qubitRegister,
std::vector<Bit> classicalRegister);
NonUnitaryOperation(std::size_t nq, Qubit qubit, Bit cbit);
NonUnitaryOperation(Qubit qubit, Bit cbit);

// General constructor
NonUnitaryOperation(std::size_t nq, Targets qubits, OpType op = Reset);
explicit NonUnitaryOperation(Targets qubits, OpType op = Reset);

[[nodiscard]] std::unique_ptr<Operation> clone() const override {
return std::make_unique<NonUnitaryOperation>(*this);
Expand Down Expand Up @@ -73,7 +74,8 @@ class NonUnitaryOperation final : public Operation {
}

std::ostream& print(std::ostream& os, const Permutation& permutation,
std::size_t prefixWidth) const override;
std::size_t prefixWidth,
std::size_t nqubits) const override;

void dumpOpenQASM(std::ostream& of, const RegisterNames& qreg,
const RegisterNames& creg, size_t indent,
Expand Down
Loading

0 comments on commit 3c07991

Please sign in to comment.