Skip to content

Commit

Permalink
♻️ optimize isDynamic check and move to QuantumComputation class (#…
Browse files Browse the repository at this point in the history
…689)

## Description

This PR optimizes the `isDynamic`(Circuit) check by simplifying the
overall implementation and making the function `const`.
Furthermore, the function is moved from the `CircuitOptimizer` to the
`QuantumComputation` class itself.

Last, but not least, this PR fixes an issue in the CMake configuration
that would prevent the algorithm tests to run properly.
## 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 Sep 11, 2024
1 parent 8ee8fdb commit bcb8371
Show file tree
Hide file tree
Showing 8 changed files with 99 additions and 106 deletions.
2 changes: 0 additions & 2 deletions include/mqt-core/circuit_optimizer/CircuitOptimizer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ class CircuitOptimizer {

static void deferMeasurements(QuantumComputation& qc);

static bool isDynamicCircuit(QuantumComputation& qc);

static void flattenOperations(QuantumComputation& qc,
bool customGatesOnly = false);

Expand Down
18 changes: 17 additions & 1 deletion include/mqt-core/ir/QuantumComputation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -344,10 +344,16 @@ class QuantumComputation {

[[nodiscard]] std::string getQubitRegister(Qubit physicalQubitIndex) const;
[[nodiscard]] std::string getClassicalRegister(Bit classicalIndex) const;
static Qubit getHighestLogicalQubitIndex(const Permutation& permutation);
[[nodiscard]] static Qubit
getHighestLogicalQubitIndex(const Permutation& permutation);
[[nodiscard]] Qubit getHighestLogicalQubitIndex() const {
return getHighestLogicalQubitIndex(initialLayout);
};
[[nodiscard]] static Qubit
getHighestPhysicalQubitIndex(const Permutation& permutation);
[[nodiscard]] Qubit getHighestPhysicalQubitIndex() const {
return getHighestPhysicalQubitIndex(initialLayout);
};
[[nodiscard]] std::pair<std::string, Qubit>
getQubitRegisterAndIndex(Qubit physicalQubitIndex) const;
[[nodiscard]] std::pair<std::string, Bit>
Expand Down Expand Up @@ -880,6 +886,16 @@ class QuantumComputation {
*/
void reorderOperations();

/**
* @brief Check whether the quantum computation contains dynamic circuit
* primitives
* @details Dynamic circuit primitives are mid-circuit measurements, resets,
* or classical control flow operations. This method traverses the whole
* circuit once until it finds a dynamic operation.
* @return Whether the quantum computation contains dynamic circuit primitives
*/
[[nodiscard]] bool isDynamic() const;

/**
* Pass-Through
*/
Expand Down
88 changes: 4 additions & 84 deletions src/circuit_optimizer/CircuitOptimizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,7 @@ void CircuitOptimizer::removeOperation(
}

void CircuitOptimizer::swapReconstruction(QuantumComputation& qc) {
Qubit highestPhysicalQubit = 0;
for (const auto& q : qc.initialLayout) {
highestPhysicalQubit = std::max(q.first, highestPhysicalQubit);
}

auto dag = DAG(highestPhysicalQubit + 1);
auto dag = DAG(qc.getHighestPhysicalQubitIndex() + 1);

for (auto& it : qc) {
if (!it->isStandardOperation()) {
Expand Down Expand Up @@ -161,12 +156,7 @@ void CircuitOptimizer::swapReconstruction(QuantumComputation& qc) {
}

DAG CircuitOptimizer::constructDAG(QuantumComputation& qc) {
Qubit highestPhysicalQubit = 0;
for (const auto& q : qc.initialLayout) {
highestPhysicalQubit = std::max(q.first, highestPhysicalQubit);
}

auto dag = DAG(highestPhysicalQubit + 1);
auto dag = DAG(qc.getHighestPhysicalQubitIndex() + 1);

for (auto& op : qc) {
addToDag(dag, &op);
Expand All @@ -181,12 +171,7 @@ void CircuitOptimizer::singleQubitGateFusion(QuantumComputation& qc) {
{qc::Sdg, qc::S}, {qc::T, qc::Tdg}, {qc::Tdg, qc::T},
{qc::SX, qc::SXdg}, {qc::SXdg, qc::SX}, {qc::Barrier, qc::Barrier}};

Qubit highestPhysicalQubit = 0;
for (const auto& q : qc.initialLayout) {
highestPhysicalQubit = std::max(q.first, highestPhysicalQubit);
}

auto dag = DAG(highestPhysicalQubit + 1);
auto dag = DAG(qc.getHighestPhysicalQubitIndex() + 1);

for (auto& it : qc) {
// not a single-qubit operation
Expand Down Expand Up @@ -928,66 +913,6 @@ void CircuitOptimizer::deferMeasurements(QuantumComputation& qc) {
qc.initializeIOMapping();
}

bool isDynamicCircuit(std::unique_ptr<Operation>* op,
std::vector<bool>& measured, DAG& dag) {
assert(op != nullptr);
auto& it = *op;
// whenever a classic-controlled or a reset operation are encountered
// the circuit has to be dynamic.
if (it->getType() == Reset || it->isClassicControlledOperation()) {
return true;
}

if (it->isStandardOperation()) {
// Whenever a qubit has already been measured, the circuit is dynamic
const auto& usedQubits = it->getUsedQubits();
for (const auto& q : usedQubits) {
if (measured[q]) {
return true;
}
}
addToDag(dag, op);
return false;
}

if (it->isNonUnitaryOperation()) {
assert(it->getType() == qc::Measure);
for (const auto& b : it->getTargets()) {
dag.at(b).push_back(op);
measured[b] = true;
}
return false;
}

assert(it->isCompoundOperation());
auto* compOp = dynamic_cast<CompoundOperation*>(it.get());
for (auto& g : *compOp) {
if (isDynamicCircuit(&g, measured, dag)) {
return true;
}
}
return false;
}

bool CircuitOptimizer::isDynamicCircuit(QuantumComputation& qc) {
Qubit highestPhysicalQubit = 0;
for (const auto& q : qc.initialLayout) {
highestPhysicalQubit = std::max(q.first, highestPhysicalQubit);
}

auto dag = DAG(highestPhysicalQubit + 1);

// marks whether a qubit in the DAG has been measured
std::vector<bool> measured(highestPhysicalQubit + 1, false);

for (auto& it : qc) {
if (::qc::isDynamicCircuit(&it, measured, dag)) {
return true;
}
}
return false;
}

void CircuitOptimizer::printDAG(const DAG& dag) {
for (const auto& qubitDag : dag) {
std::cout << " - ";
Expand Down Expand Up @@ -1054,12 +979,7 @@ void CircuitOptimizer::flattenOperations(QuantumComputation& qc,
}

void CircuitOptimizer::cancelCNOTs(QuantumComputation& qc) {
Qubit highestPhysicalQubit = 0;
for (const auto& q : qc.initialLayout) {
highestPhysicalQubit = std::max(q.first, highestPhysicalQubit);
}

auto dag = DAG(highestPhysicalQubit + 1U);
auto dag = DAG(qc.getHighestPhysicalQubitIndex() + 1U);

for (auto& it : qc) {
if (!it->isStandardOperation()) {
Expand Down
48 changes: 48 additions & 0 deletions src/ir/QuantumComputation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -870,6 +870,15 @@ Qubit QuantumComputation::getHighestLogicalQubitIndex(
return maxIndex;
}

Qubit QuantumComputation::getHighestPhysicalQubitIndex(
const Permutation& permutation) {
Qubit maxIndex = 0;
for (const auto& [physical, logical] : permutation) {
maxIndex = std::max(maxIndex, physical);
}
return maxIndex;
}

bool QuantumComputation::physicalQubitIsAncillary(
const Qubit physicalQubitIndex) const {
return std::any_of(ancregs.cbegin(), ancregs.cend(),
Expand Down Expand Up @@ -1220,4 +1229,43 @@ void QuantumComputation::reorderOperations() {
std::move(newOps.begin(), newOps.end(), std::back_inserter(ops));
}

bool isDynamicCircuit(const std::unique_ptr<Operation>* op,
std::vector<bool>& measured) {
assert(op != nullptr);
const auto& it = *op;
// whenever a classic-controlled or a reset operation are encountered
// the circuit has to be dynamic.
if (it->getType() == Reset || it->isClassicControlledOperation()) {
return true;
}

if (it->isStandardOperation()) {
// Whenever a qubit has already been measured, the circuit is dynamic
const auto& usedQubits = it->getUsedQubits();
return std::any_of(usedQubits.cbegin(), usedQubits.cend(),
[&measured](const auto& q) { return measured[q]; });
}

if (it->getType() == qc::Measure) {
for (const auto& b : it->getTargets()) {
measured[b] = true;
}
return false;
}

assert(it->isCompoundOperation());
auto* compOp = dynamic_cast<CompoundOperation*>(it.get());
return std::any_of(
compOp->cbegin(), compOp->cend(),
[&measured](const auto& g) { return isDynamicCircuit(&g, measured); });
}

bool QuantumComputation::isDynamic() const {
// marks whether a qubit in the DAG has been measured
std::vector<bool> measured(getHighestPhysicalQubitIndex() + 1, false);
return std::any_of(cbegin(), cend(), [&measured](const auto& op) {
return ::qc::isDynamicCircuit(&op, measured);
});
}

} // namespace qc
6 changes: 3 additions & 3 deletions test/algorithms/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
if(TARGET MQT::CoreAlgo)
if(TARGET MQT::CoreAlgorithms)
file(GLOB_RECURSE ALGO_TEST_SOURCES *.cpp)
package_add_test(mqt-core-algo-test MQT::CoreAlgo ${ALGO_TEST_SOURCES})
target_link_libraries(mqt-core-algo-test PRIVATE MQT::CoreDD MQT::CoreCircuitOptimizer)
package_add_test(mqt-core-algorithms-test MQT::CoreAlgorithms ${ALGO_TEST_SOURCES})
target_link_libraries(mqt-core-algorithms-test PRIVATE MQT::CoreDD MQT::CoreCircuitOptimizer)
endif()
24 changes: 12 additions & 12 deletions test/circuit_optimizer/test_defer_measurements.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ TEST(DeferMeasurements, basicTest) {
qc.classicControlled(qc::X, 1, {0, 1U}, 1U);
std::cout << qc << "\n";

EXPECT_TRUE(CircuitOptimizer::isDynamicCircuit(qc));
EXPECT_TRUE(qc.isDynamic());

EXPECT_NO_THROW(CircuitOptimizer::deferMeasurements(qc););

std::cout << qc << "\n";

EXPECT_FALSE(CircuitOptimizer::isDynamicCircuit(qc));
EXPECT_FALSE(qc.isDynamic());

ASSERT_EQ(qc.getNqubits(), 2);
ASSERT_EQ(qc.getNindividualOps(), 3);
Expand Down Expand Up @@ -96,13 +96,13 @@ TEST(DeferMeasurements, measurementBetweenMeasurementAndClassic) {
qc.classicControlled(qc::X, 1, {0, 1U}, 1U);
std::cout << qc << "\n";

EXPECT_TRUE(CircuitOptimizer::isDynamicCircuit(qc));
EXPECT_TRUE(qc.isDynamic());

EXPECT_NO_THROW(CircuitOptimizer::deferMeasurements(qc););

std::cout << qc << "\n";

EXPECT_FALSE(CircuitOptimizer::isDynamicCircuit(qc));
EXPECT_FALSE(qc.isDynamic());

ASSERT_EQ(qc.getNqubits(), 2);
ASSERT_EQ(qc.getNindividualOps(), 4);
Expand Down Expand Up @@ -172,13 +172,13 @@ TEST(DeferMeasurements, twoClassic) {

std::cout << qc << "\n";

EXPECT_TRUE(CircuitOptimizer::isDynamicCircuit(qc));
EXPECT_TRUE(qc.isDynamic());

EXPECT_NO_THROW(CircuitOptimizer::deferMeasurements(qc););

std::cout << qc << "\n";

EXPECT_FALSE(CircuitOptimizer::isDynamicCircuit(qc));
EXPECT_FALSE(qc.isDynamic());

ASSERT_EQ(qc.getNqubits(), 2);
ASSERT_EQ(qc.getNindividualOps(), 5);
Expand Down Expand Up @@ -253,13 +253,13 @@ TEST(DeferMeasurements, correctOrder) {
qc.classicControlled(qc::X, 1, {0, 1U}, 1U);
std::cout << qc << "\n";

EXPECT_TRUE(CircuitOptimizer::isDynamicCircuit(qc));
EXPECT_TRUE(qc.isDynamic());

EXPECT_NO_THROW(CircuitOptimizer::deferMeasurements(qc););

std::cout << qc << "\n";

EXPECT_FALSE(CircuitOptimizer::isDynamicCircuit(qc));
EXPECT_FALSE(qc.isDynamic());

ASSERT_EQ(qc.getNqubits(), 2);
ASSERT_EQ(qc.getNindividualOps(), 4);
Expand Down Expand Up @@ -328,13 +328,13 @@ TEST(DeferMeasurements, twoClassicCorrectOrder) {
qc.classicControlled(qc::Z, 1, {0, 1U}, 1U);
std::cout << qc << "\n";

EXPECT_TRUE(CircuitOptimizer::isDynamicCircuit(qc));
EXPECT_TRUE(qc.isDynamic());

EXPECT_NO_THROW(CircuitOptimizer::deferMeasurements(qc););

std::cout << qc << "\n";

EXPECT_FALSE(CircuitOptimizer::isDynamicCircuit(qc));
EXPECT_FALSE(qc.isDynamic());

ASSERT_EQ(qc.getNqubits(), 2);
ASSERT_EQ(qc.getNindividualOps(), 5);
Expand Down Expand Up @@ -401,7 +401,7 @@ TEST(DeferMeasurements, errorOnImplicitReset) {
qc.classicControlled(qc::X, 0, {0, 1U}, 1U);
std::cout << qc << "\n";

EXPECT_TRUE(CircuitOptimizer::isDynamicCircuit(qc));
EXPECT_TRUE(qc.isDynamic());

EXPECT_THROW(CircuitOptimizer::deferMeasurements(qc), qc::QFRException);
}
Expand Down Expand Up @@ -429,7 +429,7 @@ TEST(DeferMeasurements, isDynamicOnRepeatedMeasurements) {
qc.h(0);
qc.measure(0, 1);

EXPECT_TRUE(CircuitOptimizer::isDynamicCircuit(qc));
EXPECT_TRUE(qc.isDynamic());
}

} // namespace qc
8 changes: 4 additions & 4 deletions test/circuit_optimizer/test_eliminate_resets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ TEST(EliminateResets, eliminateResetsBasicTest) {

std::cout << qc << "\n";

EXPECT_TRUE(CircuitOptimizer::isDynamicCircuit(qc));
EXPECT_TRUE(qc.isDynamic());

EXPECT_NO_THROW(CircuitOptimizer::eliminateResets(qc););

Expand Down Expand Up @@ -78,7 +78,7 @@ TEST(EliminateResets, eliminateResetsClassicControlled) {
qc.classicControlled(qc::X, 0, {0, 1U}, 1U);
std::cout << qc << "\n";

EXPECT_TRUE(CircuitOptimizer::isDynamicCircuit(qc));
EXPECT_TRUE(qc.isDynamic());

EXPECT_NO_THROW(CircuitOptimizer::eliminateResets(qc););

Expand Down Expand Up @@ -128,7 +128,7 @@ TEST(EliminateResets, eliminateResetsMultipleTargetReset) {

std::cout << qc << "\n";

EXPECT_TRUE(CircuitOptimizer::isDynamicCircuit(qc));
EXPECT_TRUE(qc.isDynamic());

EXPECT_NO_THROW(CircuitOptimizer::eliminateResets(qc););

Expand Down Expand Up @@ -176,7 +176,7 @@ TEST(EliminateResets, eliminateResetsCompoundOperation) {

std::cout << qc << "\n";

EXPECT_TRUE(CircuitOptimizer::isDynamicCircuit(qc));
EXPECT_TRUE(qc.isDynamic());

EXPECT_NO_THROW(CircuitOptimizer::eliminateResets(qc););

Expand Down
11 changes: 11 additions & 0 deletions test/ir/test_qfr_functionality.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1131,3 +1131,14 @@ TEST_F(QFRFunctionality, OperationReorderingBarrier) {
const auto target2 = (*it)->getTargets().at(0);
EXPECT_EQ(target2, 1);
}

TEST_F(QFRFunctionality, isDynamicCompoundOperation) {
QuantumComputation qc(1, 1);
QuantumComputation compound(1, 1);
compound.measure(0, 0);
compound.x(0);
compound.measure(0, 0);
qc.emplace_back(compound.asCompoundOperation());
std::cout << qc << "\n";
EXPECT_TRUE(qc.isDynamic());
}

0 comments on commit bcb8371

Please sign in to comment.