Skip to content

Commit

Permalink
✨ Gate Extensions and ♻️ QASM Parser Refactor (#280)
Browse files Browse the repository at this point in the history
This (rather large) PR builds on cda-tum/dd_package#144 and extends the
capabilities of the QFR by **_eight_** more gates:
 - `DCX` (double CNOT)
 - `ECR` (cross-resonance)
 - `RZZ(\theta)` (ZZ rotation gate)
 - `RXX(\theta)` (XX rotation gate)
 - `RYY(\theta)` (YY rotation gate)
 - `RZX(\theta)` (ZX rotation gate)
 - `XXMinusYY(\theta, \beta)` (XX-YY rotation gate)
 - `XXPlusYY(\theta, \beta)` (XX+YY rotation gate)
It also includes initial support for the OpenQASM 3.0 `gphase` global
phase gate.

The addition of these new gates made it clear, that our QASM parser
needs some major refactors to remain extendable and performant. This PR
performs such a refactoring, with the following key take-aways:
- 🐛 All gates that are natively supported by the QFR are now directly
read in instead of recursing down until `U` and `CX`. This fixes the
long-outstanding bug outlined in #275.
- 🚸 Handling of gates and definitions is unified much more than before.
The number of tokens has been reduced and lots of duplicate code and
unnecessary special case handling has been removed.
- ⚡ The default `qelib1.inc` has been reduced to a minimum due to most
gates being natively supported now. This should speed up parsing of
every single QASM file.
- 🔥 The number of gate parameters is no longer fixed to a maximum of
three. Over the course of this refactor, a breaking change was
introduced regarding the parameter order in `U3` and `U2` gates, in
order to align our definition with the one Qiskit uses. This requires
calls to `.u3(...)`, `.u2(...)`, and constructions of the respective
gates to be updated correspondingly.
 - 🚸 Implements #276
  • Loading branch information
burgholzer authored Mar 14, 2023
2 parents 6fc0527 + be65509 commit 412a185
Show file tree
Hide file tree
Showing 33 changed files with 1,715 additions and 1,826 deletions.
559 changes: 359 additions & 200 deletions include/QuantumComputation.hpp

Large diffs are not rendered by default.

104 changes: 91 additions & 13 deletions include/dd/Operations.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ namespace dd {
case qc::Tdag: gm = inverse ? dd::Tmat : dd::Tdagmat; break;
case qc::V: gm = inverse ? dd::Vdagmat : dd::Vmat; break;
case qc::Vdag: gm = inverse ? dd::Vmat : dd::Vdagmat; break;
case qc::U3: gm = inverse ? dd::U3mat(-parameter[1U], -parameter[0U], -parameter[2U]) : dd::U3mat(parameter[0U], parameter[1U], parameter[2U]); break;
case qc::U2: gm = inverse ? dd::U2mat(-parameter[1U] + dd::PI, -parameter[0U] - dd::PI) : dd::U2mat(parameter[0U], parameter[1U]); break;
case qc::U3: gm = inverse ? dd::U3mat(-parameter[1U], -parameter[2U], -parameter[0U]) : dd::U3mat(parameter[2U], parameter[1U], parameter[0U]); break;
case qc::U2: gm = inverse ? dd::U2mat(-parameter[0U] + dd::PI, -parameter[1U] - dd::PI) : dd::U2mat(parameter[1U], parameter[0U]); break;
case qc::Phase: gm = inverse ? dd::Phasemat(-parameter[0U]) : dd::Phasemat(parameter[0U]); break;
case qc::SX: gm = inverse ? dd::SXdagmat : dd::SXmat; break;
case qc::SXdag: gm = inverse ? dd::SXmat : dd::SXdagmat; break;
Expand All @@ -75,31 +75,99 @@ namespace dd {
// two-target Operations
template<class Config>
qc::MatrixDD getStandardOperationDD(const qc::StandardOperation* op, std::unique_ptr<dd::Package<Config>>& dd, const dd::Controls& controls, dd::Qubit target0, dd::Qubit target1, bool inverse) {
const auto type = op->getType();
const auto nqubits = static_cast<dd::QubitCount>(op->getNqubits());
const auto startQubit = static_cast<std::size_t>(op->getStartingQubit());
const auto type = op->getType();
const auto nqubits = static_cast<dd::QubitCount>(op->getNqubits());
const auto startQubit = static_cast<std::size_t>(op->getStartingQubit());
const auto& parameter = op->getParameter();

if (type == qc::DCX && inverse) {
// DCX is not self-inverse, but the inverse is just swapping the targets
std::swap(target0, target1);
}

if (controls.empty()) {
// the DD creation without controls is faster, so we use it if possible
// and only use the DD creation with controls if necessary
TwoQubitGateMatrix gm;
bool definitionFound = true;
switch (type) {
case qc::SWAP: gm = dd::SWAPmat; break;
case qc::iSWAP: gm = inverse ? dd::iSWAPinvmat : dd::iSWAPmat; break;
case qc::DCX: gm = dd::DCXmat; break;
case qc::ECR: gm = dd::ECRmat; break;
case qc::RXX: gm = inverse ? dd::RXXmat(-parameter[0U]) : dd::RXXmat(parameter[0U]); break;
case qc::RYY: gm = inverse ? dd::RYYmat(-parameter[0U]) : dd::RYYmat(parameter[0U]); break;
case qc::RZZ: gm = inverse ? dd::RZZmat(-parameter[0U]) : dd::RZZmat(parameter[0U]); break;
case qc::RZX: gm = inverse ? dd::RZXmat(-parameter[0U]) : dd::RZXmat(parameter[0U]); break;
case qc::XXminusYY: gm = inverse ? dd::XXMinusYYmat(-parameter[0U], parameter[1U]) : dd::XXMinusYYmat(parameter[0U], parameter[1U]); break;
case qc::XXplusYY: gm = inverse ? dd::XXPlusYYmat(-parameter[0U], parameter[1U]) : dd::XXPlusYYmat(parameter[0U], parameter[1U]); break;
default: definitionFound = false;
}
if (definitionFound) {
return dd->makeTwoQubitGateDD(gm, nqubits, target0, target1, startQubit);
}
}

switch (type) {
case qc::SWAP:
// SWAP is self-inverse
return dd->makeSWAPDD(nqubits, controls, target0, target1, startQubit);
case qc::iSWAP:
if (inverse) {
return dd->makeiSWAPinvDD(nqubits, controls, target0, target1, startQubit);
} else {
return dd->makeiSWAPDD(nqubits, controls, target0, target1, startQubit);
}
return dd->makeiSWAPDD(nqubits, controls, target0, target1, startQubit);
case qc::Peres:
if (inverse) {
return dd->makePeresdagDD(nqubits, controls, target0, target1, startQubit);
} else {
return dd->makePeresDD(nqubits, controls, target0, target1, startQubit);
}
return dd->makePeresDD(nqubits, controls, target0, target1, startQubit);
case qc::Peresdag:
if (inverse) {
return dd->makePeresDD(nqubits, controls, target0, target1, startQubit);
} else {
return dd->makePeresdagDD(nqubits, controls, target0, target1, startQubit);
}
return dd->makePeresdagDD(nqubits, controls, target0, target1, startQubit);
case qc::DCX:
return dd->makeDCXDD(nqubits, controls, target0, target1, startQubit);
case qc::ECR:
// ECR is self-inverse
return dd->makeECRDD(nqubits, controls, target0, target1, startQubit);
case qc::RXX: {
if (inverse) {
return dd->makeRXXDD(nqubits, controls, target0, target1, -parameter[0U], startQubit);
}
return dd->makeRXXDD(nqubits, controls, target0, target1, parameter[0U], startQubit);
}
case qc::RYY: {
if (inverse) {
return dd->makeRYYDD(nqubits, controls, target0, target1, -parameter[0U], startQubit);
}
return dd->makeRYYDD(nqubits, controls, target0, target1, parameter[0U], startQubit);
}
case qc::RZZ: {
if (inverse) {
return dd->makeRZZDD(nqubits, controls, target0, target1, -parameter[0U], startQubit);
}
return dd->makeRZZDD(nqubits, controls, target0, target1, parameter[0U], startQubit);
}
case qc::RZX: {
if (inverse) {
return dd->makeRZXDD(nqubits, controls, target0, target1, -parameter[0U], startQubit);
}
return dd->makeRZXDD(nqubits, controls, target0, target1, parameter[0U], startQubit);
}
case qc::XXminusYY: {
if (inverse) {
return dd->makeXXMinusYYDD(nqubits, controls, target0, target1, -parameter[0U], parameter[1U], startQubit);
}
return dd->makeXXMinusYYDD(nqubits, controls, target0, target1, parameter[0U], parameter[1U], startQubit);
}
case qc::XXplusYY: {
if (inverse) {
return dd->makeXXPlusYYDD(nqubits, controls, target0, target1, -parameter[0U], parameter[1U], startQubit);
}
return dd->makeXXPlusYYDD(nqubits, controls, target0, target1, parameter[0U], parameter[1U], startQubit);
}
default:
std::ostringstream oss{};
oss << "DD for gate" << op->getName() << " not available!";
Expand Down Expand Up @@ -139,6 +207,16 @@ namespace dd {
return dd->makeIdent(static_cast<dd::QubitCount>(nqubits));
}

if (type == qc::GPhase) {
auto phase = op->getParameter()[0U];
if (inverse) {
phase = -phase;
}
auto id = dd->makeIdent(static_cast<dd::QubitCount>(nqubits));
id.w = dd->cn.lookup(std::cos(phase), std::sin(phase));
return id;
}

if (const auto* standardOp = dynamic_cast<const qc::StandardOperation*>(op)) {
auto targets = op->getTargets();
auto controls = op->getControls();
Expand All @@ -158,7 +236,7 @@ namespace dd {
}
}

if (op->getType() == qc::SWAP || op->getType() == qc::iSWAP || op->getType() == qc::Peres || op->getType() == qc::Peresdag) {
if (qc::isTwoQubitGate(type)) {
assert(targets.size() == 2);
const auto target0 = static_cast<dd::Qubit>(targets[0U]);
const auto target1 = static_cast<dd::Qubit>(targets[1U]);
Expand Down Expand Up @@ -241,7 +319,7 @@ namespace dd {

// swap i and j
auto saved = on;
const auto swapDD = dd->makeSWAPDD(static_cast<dd::QubitCount>(on.p->v + 1), {}, static_cast<dd::Qubit>(from.at(i)), static_cast<dd::Qubit>(from.at(j)));
const auto swapDD = dd->makeSWAPDD(static_cast<dd::QubitCount>(on.p->v + 1), dd::Controls{}, static_cast<dd::Qubit>(from.at(i)), static_cast<dd::Qubit>(from.at(j)));
if constexpr (std::is_same_v<DDType, qc::VectorDD>) {
on = dd->multiply(swapDD, on);
} else {
Expand Down
13 changes: 7 additions & 6 deletions include/operations/ClassicControlledOperation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ namespace qc {
// Applies operation `_op` if the creg starting at index `control` has the expected value
ClassicControlledOperation(std::unique_ptr<qc::Operation>& operation, ClassicalRegister controlReg, std::uint64_t expectedVal = 1U):
op(std::move(operation)), controlRegister(std::move(controlReg)), expectedValue(expectedVal) {
nqubits = op->getNqubits();
name = "c_" + op->getName();
parameter[0] = static_cast<fp>(controlRegister.first);
parameter[1] = static_cast<fp>(controlRegister.second);
parameter[2] = static_cast<fp>(expectedValue);
type = ClassicControlled;
nqubits = op->getNqubits();
name = "c_" + op->getName();
parameter.reserve(3);
parameter.emplace_back(static_cast<fp>(controlRegister.first));
parameter.emplace_back(static_cast<fp>(controlRegister.second));
parameter.emplace_back(static_cast<fp>(expectedValue));
type = ClassicControlled;
}

[[nodiscard]] std::unique_ptr<Operation> clone() const override {
Expand Down
145 changes: 35 additions & 110 deletions include/operations/OpType.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ namespace qc {
enum OpType : std::uint8_t {
None,
// Standard Operations
GPhase,
I,
H,
X,
Expand All @@ -38,6 +39,14 @@ namespace qc {
iSWAP, // NOLINT (readability-identifier-naming)
Peres,
Peresdag,
DCX,
ECR,
RXX,
RYY,
RZZ,
RZX,
XXminusYY,
XXplusYY,
// Compound Operation
Compound,
// Non Unitary Operations
Expand All @@ -61,6 +70,7 @@ namespace qc {
inline std::string toString(const OpType& opType) {
switch (opType) {
case None: return "none";
case GPhase: return "gphase";
case I: return "i";
case H: return "h";
case X: return "x";
Expand All @@ -84,6 +94,14 @@ namespace qc {
case iSWAP: return "iswap";
case Peres: return "peres";
case Peresdag: return "peresdg";
case DCX: return "dcx";
case ECR: return "ecr";
case RXX: return "rxx";
case RYY: return "ryy";
case RZZ: return "rzz";
case RZX: return "rzx";
case XXminusYY: return "xx_minus_yy";
case XXplusYY: return "xx_plus_yy";
case Compound: return "compound";
case Measure: return "measure";
case Reset: return "reset";
Expand All @@ -97,117 +115,24 @@ namespace qc {
}
}

inline OpType opTypeFromString(const std::string& opType) {
if (opType == "none" || opType == "0") {
return OpType::None;
}
if (opType == "i" || opType == "id" || opType == "1") {
return OpType::I;
}
if (opType == "h" || opType == "ch" || opType == "2") {
return OpType::H;
}
if (opType == "x" || opType == "cx" || opType == "cnot" || opType == "3") {
return OpType::X;
}
if (opType == "y" || opType == "cy" || opType == "4") {
return OpType::Y;
}
if (opType == "z" || opType == "cz" || opType == "5") {
return OpType::Z;
}
if (opType == "s" || opType == "cs" || opType == "6") {
return OpType::S;
}
if (opType == "sdg" || opType == "csdg" || opType == "7") {
return OpType::Sdag;
}
if (opType == "t" || opType == "ct" || opType == "8") {
return OpType::T;
}
if (opType == "tdg" || opType == "ctdg" || opType == "9") {
return OpType::Tdag;
}
if (opType == "v" || opType == "10") {
return OpType::V;
}
if (opType == "vdg" || opType == "11") {
return OpType::Vdag;
}
if (opType == "u3" || opType == "cu3" || opType == "u" || opType == "cu" || opType == "12") {
return OpType::U3;
}
if (opType == "u2" || opType == "cu2" || opType == "13") {
return OpType::U2;
}
if (opType == "u1" || opType == "cu1" || opType == "p" || opType == "cp" || opType == "14") {
return OpType::Phase;
}
if (opType == "sx" || opType == "csx" || opType == "15") {
return OpType::SX;
}
if (opType == "sxdg" || opType == "csxdg" || opType == "16") {
return OpType::SXdag;
}
if (opType == "rx" || opType == "crx" || opType == "17") {
return OpType::RX;
}
if (opType == "ry" || opType == "cry" || opType == "18") {
return OpType::RY;
}
if (opType == "rz" || opType == "crz" || opType == "19") {
return OpType::RZ;
}
if (opType == "swap" || opType == "cswap" || opType == "20") {
return OpType::SWAP;
}
if (opType == "iswap" || opType == "21") {
return OpType::iSWAP;
}
if (opType == "peres" || opType == "22") {
return OpType::Peres;
}
if (opType == "peresdg" || opType == "23") {
return OpType::Peresdag;
}
if (opType == "compound" || opType == "24") {
return OpType::Compound;
}
if (opType == "measure" || opType == "25") {
return OpType::Measure;
}
if (opType == "reset" || opType == "26") {
return OpType::Reset;
}
if (opType == "snapshot" || opType == "27") {
return OpType::Snapshot;
}
if (opType == "show probabilities" || opType == "28") {
return OpType::ShowProbabilities;
}
if (opType == "barrier" || opType == "29") {
return OpType::Barrier;
}
if (opType == "teleportation" || opType == "30") {
return OpType::Teleportation;
}
if (opType == "classic controlled" || opType == "31") {
return OpType::ClassicControlled;
}
throw std::invalid_argument("Unknown operation type: " + opType);
}

inline std::istream& operator>>(std::istream& in, OpType& opType) {
std::string token;
in >> token;

if (token.empty()) {
in.setstate(std::istream::failbit);
return in;
inline bool isTwoQubitGate(const OpType& opType) {
switch (opType) {
case SWAP:
case iSWAP:
case Peres:
case Peresdag:
case DCX:
case ECR:
case RXX:
case RYY:
case RZZ:
case RZX:
case XXminusYY:
case XXplusYY:
return true;
default:
return false;
}

opType = opTypeFromString(token);
return in;
}

inline std::ostream& operator<<(std::ostream& out, OpType& opType) {
Expand Down
Loading

0 comments on commit 412a185

Please sign in to comment.