Skip to content

Commit

Permalink
♻️ Refactor handling of Barrier operations (#394)
Browse files Browse the repository at this point in the history
## Description

This commit refactors how the `Barrier` operations are handled across
the operations, moving it from non-unitary operations to the standard
operations. The changes include updates in the display of the barriers
and how they are parsed in the QASMParser, along with modifications in
the operation files and test files. This change improves code structure
allowing a more consistent handling of operations. Barriers are
essentially No-Ops, so there is no reason for them to be non-unitary.

## 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: Lukas Burgholzer <[email protected]>
  • Loading branch information
burgholzer authored Aug 10, 2023
1 parent 5982ba7 commit f36e71c
Show file tree
Hide file tree
Showing 12 changed files with 76 additions and 103 deletions.
5 changes: 3 additions & 2 deletions include/CircuitOptimizer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
#include <memory>

namespace qc {
static constexpr std::array<qc::OpType, 8> DIAGONAL_GATES = {
qc::I, qc::Z, qc::S, qc::Sdag, qc::T, qc::Tdag, qc::Phase, qc::RZ};
static constexpr std::array<qc::OpType, 10> DIAGONAL_GATES = {
qc::Barrier, qc::I, qc::Z, qc::S, qc::Sdag,
qc::T, qc::Tdag, qc::Phase, qc::RZ, qc::RZZ};

class CircuitOptimizer {
protected:
Expand Down
5 changes: 2 additions & 3 deletions include/QuantumComputation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -915,12 +915,11 @@ class QuantumComputation {

void barrier(const Qubit target) {
checkQubitRange(target);
emplace_back<NonUnitaryOperation>(getNqubits(), std::vector<Qubit>{target},
qc::Barrier);
emplace_back<StandardOperation>(getNqubits(), target, qc::Barrier);
}
void barrier(const std::vector<Qubit>& targets) {
checkQubitRange(targets);
emplace_back<NonUnitaryOperation>(getNqubits(), targets, qc::Barrier);
emplace_back<StandardOperation>(getNqubits(), targets, qc::Barrier);
}

void classicControlled(const OpType op, const Qubit target,
Expand Down
18 changes: 6 additions & 12 deletions include/operations/NonUnitaryOperation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ class NonUnitaryOperation final : public Operation {
void printMeasurement(std::ostream& os, const std::vector<Qubit>& q,
const std::vector<Bit>& c,
const Permutation& permutation) const;
void printResetOrBarrier(std::ostream& os, const std::vector<Qubit>& q,
const Permutation& permutation) const;
void printReset(std::ostream& os, const std::vector<Qubit>& q,
const Permutation& permutation) const;

public:
// Measurement constructor
Expand Down Expand Up @@ -65,26 +65,20 @@ class NonUnitaryOperation final : public Operation {

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

void addDepthContribution(std::vector<std::size_t>& depths) const override;

[[nodiscard]] bool equals(const Operation& op, const Permutation& perm1,
const Permutation& perm2) const override;
[[nodiscard]] bool equals(const Operation& operation) const override {
return equals(operation, {}, {});
}

std::ostream& print(std::ostream& os) const override {
if (type == Measure) {
return printNonUnitary(os, qubits, classics);
}
return printNonUnitary(os, targets);
const auto& qubitArgs = getTargets();
return printNonUnitary(os, qubitArgs, classics);
}
std::ostream& print(std::ostream& os,
const Permutation& permutation) const override {
if (type == Measure) {
return printNonUnitary(os, qubits, classics, permutation);
}
return printNonUnitary(os, targets, {}, permutation);
const auto& qubitArgs = getTargets();
return printNonUnitary(os, qubitArgs, classics, permutation);
}

void dumpOpenQASM(std::ostream& of, const RegisterNames& qreg,
Expand Down
2 changes: 1 addition & 1 deletion include/operations/OpType.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ enum OpType : std::uint8_t {
// Standard Operations
GPhase,
I,
Barrier,
H,
X,
Y,
Expand Down Expand Up @@ -47,7 +48,6 @@ enum OpType : std::uint8_t {
// Non Unitary Operations
Measure,
Reset,
Barrier,
Teleportation,
// Classically-controlled Operation
ClassicControlled,
Expand Down
4 changes: 4 additions & 0 deletions include/operations/Operation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ class Operation {
}

[[nodiscard]] inline virtual bool actsOn(const Qubit i) const {
if (type == Barrier) {
return false;
}

for (const auto& t : targets) {
if (t == i) {
return true;
Expand Down
45 changes: 18 additions & 27 deletions src/CircuitOptimizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ void CircuitOptimizer::removeIdentities(QuantumComputation& qc) {
// delete the identities from circuit
auto it = qc.ops.begin();
while (it != qc.ops.end()) {
if ((*it)->getType() == I || (*it)->getType() == Barrier) {
if ((*it)->getType() == I) {
it = qc.ops.erase(it);
} else if ((*it)->isCompoundOperation()) {
auto* compOp = dynamic_cast<qc::CompoundOperation*>((*it).get());
auto cit = compOp->cbegin();
while (cit != compOp->cend()) {
const auto* cop = cit->get();
if (cop->getType() == qc::I || cop->getType() == Barrier) {
if (cop->getType() == qc::I) {
cit = compOp->erase(cit);
} else {
++cit;
Expand Down Expand Up @@ -163,11 +163,6 @@ void CircuitOptimizer::addNonStandardOperationToDag(
}
}
} else if (gate->isNonUnitaryOperation()) {
// barriers are not added to a circuit DAG
if (gate->getType() == Barrier) {
return;
}

for (const auto& b : gate->getTargets()) {
dag.at(b).push_back(op);
}
Expand All @@ -187,10 +182,10 @@ void CircuitOptimizer::addNonStandardOperationToDag(

void CircuitOptimizer::singleQubitGateFusion(QuantumComputation& qc) {
static const std::map<qc::OpType, qc::OpType> INVERSE_MAP = {
{qc::I, qc::I}, {qc::X, qc::X}, {qc::Y, qc::Y},
{qc::Z, qc::Z}, {qc::H, qc::H}, {qc::S, qc::Sdag},
{qc::Sdag, qc::S}, {qc::T, qc::Tdag}, {qc::Tdag, qc::T},
{qc::SX, qc::SXdag}, {qc::SXdag, qc::SX}};
{qc::I, qc::I}, {qc::X, qc::X}, {qc::Y, qc::Y},
{qc::Z, qc::Z}, {qc::H, qc::H}, {qc::S, qc::Sdag},
{qc::Sdag, qc::S}, {qc::T, qc::Tdag}, {qc::Tdag, qc::T},
{qc::SX, qc::SXdag}, {qc::SXdag, qc::SX}, {qc::Barrier, qc::Barrier}};

Qubit highestPhysicalQubit = 0;
for (const auto& q : qc.initialLayout) {
Expand Down Expand Up @@ -387,9 +382,7 @@ void CircuitOptimizer::removeDiagonalGatesBeforeMeasureRecursive(
}
}
auto* op = (*it)->get();
if (op->getType() == Barrier) {
++it;
} else if (op->isStandardOperation()) {
if (op->isStandardOperation()) {
// try removing gate and upon success increase all corresponding iterators
auto onlyDiagonalGates =
removeDiagonalGate(dag, dagIterators, idx, it, op);
Expand Down Expand Up @@ -482,33 +475,34 @@ bool CircuitOptimizer::removeFinalMeasurement(DAG& dag,
qc::Operation* op) {
if (op->getNtargets() != 0) {
// need to check all targets
bool onlyMeasurments = true;
bool onlyMeasurements = true;
for (const auto& target : op->getTargets()) {
if (target == idx) {
continue;
}
if (dagIterators.at(target) == dag.at(target).rend()) {
onlyMeasurments = false;
onlyMeasurements = false;
break;
}
// recursive call at target with this operation as goal
removeFinalMeasurementsRecursive(dag, dagIterators, target, (*it)->get());
// check if iteration of target qubit was successful
if (dagIterators.at(target) == dag.at(target).rend() ||
*dagIterators.at(target) != *it) {
onlyMeasurments = false;
onlyMeasurements = false;
break;
}
}
if (!onlyMeasurments) {
if (!onlyMeasurements) {
// end qubit
dagIterators.at(idx) = dag.at(idx).rend();
} else {
// set operation to identity so that it can be collected by the
// removeIdentities pass
op->setTargets(op->getTargets()); // work-around to properly set targets
op->setGate(qc::I);
}
return onlyMeasurments;
return onlyMeasurements;
}
return false;
}
Expand Down Expand Up @@ -537,9 +531,9 @@ void CircuitOptimizer::removeFinalMeasurementsRecursive(
}
auto* op = (*it)->get();
if (op->getType() == Measure) {
const bool onlyMeasurment =
const bool onlyMeasurement =
removeFinalMeasurement(dag, dagIterators, idx, it, op);
if (onlyMeasurment) {
if (onlyMeasurement) {
for (const auto& target : op->getTargets()) {
if (dagIterators.at(target) == dag.at(target).rend()) {
break;
Expand All @@ -554,6 +548,8 @@ void CircuitOptimizer::removeFinalMeasurementsRecursive(
}
++(dagIterators.at(target));
}
// Barriers at the end of the circuit can be removed
op->setGate(OpType::I);
} else if (op->isCompoundOperation() && op->isNonUnitaryOperation()) {
// iterate over all gates of compound operation and upon success increase
// all corresponding iterators
Expand Down Expand Up @@ -829,7 +825,7 @@ void CircuitOptimizer::deferMeasurements(QuantumComputation& qc) {
// iterate over all subsequent operations
while (opIt != qc.end()) {
const auto* operation = opIt->get();
if (operation->isUnitary() || operation->getType() == qc::Barrier) {
if (operation->isUnitary()) {
// if an operation does not act on the measured qubit, the insert
// location for potential operations has to be updated
if (!operation->actsOn(measurementQubit)) {
Expand Down Expand Up @@ -1097,11 +1093,6 @@ void CircuitOptimizer::reorderOperations(QuantumComputation& qc) {
"circuit contains classically controlled operations\n";
}

if (op->getType() == Barrier) {
++it;
continue;
}

// check whether the gate can be scheduled, i.e. whether all qubits it
// acts on are at this operation
bool executable = true;
Expand Down
16 changes: 8 additions & 8 deletions src/QuantumComputation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -573,12 +573,12 @@ std::ostream& QuantumComputation::print(std::ostream& os) const {
os << logical << "\t";
}
}
os << std::endl;
os << "\n";
size_t i = 0U;
for (const auto& op : ops) {
os << std::setw(width) << ++i << ": \t";
op->print(os, initialLayout);
os << std::endl;
os << "\n";
}
if (!ops.empty()) {
os << std::setw(width) << "o"
Expand All @@ -598,7 +598,7 @@ std::ostream& QuantumComputation::print(std::ostream& os) const {
os << it->second << "\t";
}
}
os << std::endl;
os << "\n";
return os;
}

Expand All @@ -610,11 +610,11 @@ void QuantumComputation::printBin(std::size_t n, std::stringstream& ss) {
}

std::ostream& QuantumComputation::printStatistics(std::ostream& os) const {
os << "QC Statistics:\n";
os << "\tn: " << static_cast<std::size_t>(nqubits) << std::endl;
os << "\tanc: " << static_cast<std::size_t>(nancillae) << std::endl;
os << "\tm: " << ops.size() << std::endl;
os << "--------------" << std::endl;
os << "QC Statistics:";
os << "\n\tn: " << static_cast<std::size_t>(nqubits);
os << "\n\tanc: " << static_cast<std::size_t>(nancillae);
os << "\n\tm: " << ops.size();
os << "\n--------------\n";
return os;
}

Expand Down
49 changes: 9 additions & 40 deletions src/operations/NonUnitaryOperation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,7 @@ std::ostream& NonUnitaryOperation::printNonUnitary(
printMeasurement(os, q, c, permutation);
break;
case Reset:
case Barrier:
printResetOrBarrier(os, q, permutation);
printReset(os, q, permutation);
break;
default:
break;
Expand Down Expand Up @@ -87,16 +86,9 @@ void NonUnitaryOperation::dumpOpenQASM(std::ostream& of,
}

bool NonUnitaryOperation::actsOn(Qubit i) const {
if (type == Measure) {
return std::any_of(qubits.cbegin(), qubits.cend(),
[&i](const auto& q) { return q == i; });
}
if (type == Reset) {
return std::any_of(targets.cbegin(), targets.cend(),
[&i](const auto& t) { return t == i; });
}
// other non-unitary operations (e.g., barrier statements) may be ignored
return false;
const auto& qubitArgs = getTargets();
return std::any_of(qubitArgs.cbegin(), qubitArgs.cend(),
[&i](const auto& q) { return q == i; });
}

bool NonUnitaryOperation::equals(const Operation& op, const Permutation& perm1,
Expand Down Expand Up @@ -151,13 +143,6 @@ bool NonUnitaryOperation::equals(const Operation& op, const Permutation& perm1,
return false;
}

void NonUnitaryOperation::addDepthContribution(
std::vector<std::size_t>& depths) const {
if (type == Measure || type == Reset) {
Operation::addDepthContribution(depths);
}
}

void NonUnitaryOperation::printMeasurement(
std::ostream& os, const std::vector<Qubit>& q, const std::vector<Bit>& c,
const Permutation& permutation) const {
Expand Down Expand Up @@ -189,23 +174,15 @@ void NonUnitaryOperation::printMeasurement(
}
}

void NonUnitaryOperation::printResetOrBarrier(
std::ostream& os, const std::vector<Qubit>& q,
const Permutation& permutation) const {
void NonUnitaryOperation::printReset(std::ostream& os,
const std::vector<Qubit>& q,
const Permutation& permutation) const {
auto qubitIt = q.cbegin();
os << name << "\t";
if (permutation.empty()) {
for (std::size_t i = 0; i < nqubits; ++i) {
if (qubitIt != q.cend() && *qubitIt == i) {
if (type == Reset) {
os << "\033[31m"
<< "r";
} else {
assert(type == Barrier);
os << "\033[32m"
<< "b";
}
os << "\t\033[0m";
os << "\033[31mr\t\033[0m";
++qubitIt;
} else {
os << "|\t";
Expand All @@ -214,15 +191,7 @@ void NonUnitaryOperation::printResetOrBarrier(
} else {
for (const auto& [physical, logical] : permutation) {
if (qubitIt != q.cend() && *qubitIt == physical) {
if (type == Reset) {
os << "\033[31m"
<< "r";
} else {
assert(type == Barrier);
os << "\033[32m"
<< "b";
}
os << "\t\033[0m";
os << "\033[31mr\t\033[0m";
++qubitIt;
} else {
os << "|\t";
Expand Down
8 changes: 8 additions & 0 deletions src/operations/Operation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ std::ostream& Operation::print(std::ostream& os) const {
if (targetIt != targets.end() && *targetIt == i) {
if (type == ClassicControlled) {
os << "\033[1m\033[35m" << name[2] << name[3];
} else if (type == Barrier) {
os << "\033[1m\033[32mb";
} else {
os << "\033[1m\033[36m" << name[0] << name[1];
}
Expand Down Expand Up @@ -129,6 +131,8 @@ std::ostream& Operation::print(std::ostream& os,
if (targetIt != actualTargets.cend() && *targetIt == physical) {
if (type == ClassicControlled) {
os << "\033[1m\033[35m" << name[2] << name[3];
} else if (type == Barrier) {
os << "\033[1m\033[32mb";
} else {
os << "\033[1m\033[36m" << name[0] << name[1];
}
Expand Down Expand Up @@ -225,6 +229,10 @@ bool Operation::equals(const Operation& op, const Permutation& perm1,
}

void Operation::addDepthContribution(std::vector<std::size_t>& depths) const {
if (type == Barrier) {
return;
}

std::size_t maxDepth = 0;
for (const auto& target : getTargets()) {
maxDepth = std::max(maxDepth, depths[target]);
Expand Down
Loading

1 comment on commit f36e71c

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cpp-Linter Report ✔️

No problems need attention.

Have any feedback or feature suggestions? Share it here.

Please sign in to comment.