Skip to content

Commit

Permalink
✨ OpenQASM 3.0 support (#309)
Browse files Browse the repository at this point in the history
This PR replaces the existing OpenQASM 2.0 parser with a new OpenQASM
3.0 parser.

The new parser now builds a syntax tree, where type checking, constant
evaluation, and translation to the Quantum circuit.

The parser can handle the following new features:

### New Syntax

New syntax for declaring bits, qubit, measure operations. The old syntax
(`creg`, `qreg`) is still supported.

```qasm
qubit[8] q;
bit[8] c;

c[0] = measure q[0];
measure q[1] -> c[1];

if (c[0] == 1) {
  x q[0];
}
```

### Gate modifiers

Gate modifiers (`inv`, `ctrl`, and `negctrl`) are now supported. This
replaces the `c` prefix.
See the OpenQASM 3.0 specification for more information:
https://openqasm.com/language/gates.html#quantum-gate-modifiers

```qasm
ctrl @ x q[0], q[1]; // Equivalent to cx q
ctrl(2) @ x q[0], q[1], q[2]; // Equivalent to ccx q;
```

### Classical constant values

The parser now supports classical computation with constant values. This
can be used to e.g. define the number of quantum registers.

```qasm
const uint N = 4;
qubit[N * 2];
x qubit[N * 2 - 1];
```

Additionally, all features of the previous parser are still supported.


The big features from OpenQASM 3.0 still missing are:

- classical computational features such as loops, functions, etc. (see
#33)
- types such as bools, floats, angles, complex types, etc. (see #30,
#32)
- `pow` modifier (#27)

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Lukas Burgholzer <[email protected]>
  • Loading branch information
3 people authored Dec 13, 2023
1 parent d181d45 commit e193ddb
Show file tree
Hide file tree
Showing 54 changed files with 7,999 additions and 2,059 deletions.
8 changes: 7 additions & 1 deletion include/Definitions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,18 @@ static constexpr fp PI_2 = static_cast<fp>(
1.570796326794896619231321691639751442098584699687552910487L);
static constexpr fp PI_4 = static_cast<fp>(
0.785398163397448309615660845819875721049292349843776455243L);
static constexpr fp TAU = static_cast<fp>(
6.283185307179586476925286766559005768394338798750211641950L);
static constexpr fp E = static_cast<fp>(
2.718281828459045235360287471352662497757247093699959574967L);

static constexpr size_t OUTPUT_INDENT_SIZE = 2;

// forward declaration
class Operation;

// supported file formats
enum class Format { Real, OpenQASM, GRCS, TFC, QC, Tensor };
enum class Format { Real, OpenQASM, OpenQASM3, GRCS, TFC, QC, Tensor };

using DAG = std::vector<std::deque<std::unique_ptr<Operation>*>>;
using DAGIterator = std::deque<std::unique_ptr<Operation>*>::iterator;
Expand Down
31 changes: 16 additions & 15 deletions include/QuantumComputation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

#include "Definitions.hpp"
#include "operations/ClassicControlledOperation.hpp"
#include "operations/CompoundOperation.hpp"
#include "operations/NonUnitaryOperation.hpp"
#include "operations/StandardOperation.hpp"
#include "operations/SymbolicOperation.hpp"
#include "parsers/qasm_parser/Parser.hpp"

#include <algorithm>
#include <fstream>
Expand Down Expand Up @@ -37,7 +37,6 @@ class QuantumComputation {
std::size_t nqubits = 0;
std::size_t nclassics = 0;
std::size_t nancillae = 0;
std::size_t maxControls = 0;
std::string name;

// register names are used as keys, while the values are `{startIndex,
Expand All @@ -53,7 +52,7 @@ class QuantumComputation {

std::unordered_set<sym::Variable> occuringVariables;

void importOpenQASM(std::istream& is);
void importOpenQASM3(std::istream& is);
void importReal(std::istream& is);
int readRealHeader(std::istream& is);
void readRealGateDescriptions(std::istream& is, int line);
Expand All @@ -70,7 +69,7 @@ class QuantumComputation {
template <class RegisterType>
static void printSortedRegisters(const RegisterMap<RegisterType>& regmap,
const std::string& identifier,
std::ostream& of) {
std::ostream& of, bool openQASM3 = false) {
// sort regs by start index
std::map<decltype(RegisterType::first),
std::pair<std::string, RegisterType>>
Expand All @@ -80,8 +79,13 @@ class QuantumComputation {
}

for (const auto& reg : sortedRegs) {
of << identifier << " " << reg.second.first << "["
<< reg.second.second.second << "];" << std::endl;
if (openQASM3) {
of << identifier << "[" << reg.second.second.second << "] "
<< reg.second.first << ";" << std::endl;
} else {
of << identifier << " " << reg.second.first << "["
<< reg.second.second.second << "];" << std::endl;
}
}
}
template <class RegisterType>
Expand Down Expand Up @@ -243,9 +247,9 @@ class QuantumComputation {
QuantumComputation& operator=(QuantumComputation&& qc) noexcept = default;
QuantumComputation(const QuantumComputation& qc)
: nqubits(qc.nqubits), nclassics(qc.nclassics), nancillae(qc.nancillae),
maxControls(qc.maxControls), name(qc.name), qregs(qc.qregs),
cregs(qc.cregs), ancregs(qc.ancregs), mt(qc.mt), seed(qc.seed),
globalPhase(qc.globalPhase), occuringVariables(qc.occuringVariables),
name(qc.name), qregs(qc.qregs), cregs(qc.cregs), ancregs(qc.ancregs),
mt(qc.mt), seed(qc.seed), globalPhase(qc.globalPhase),
occuringVariables(qc.occuringVariables),
initialLayout(qc.initialLayout),
outputPermutation(qc.outputPermutation), ancillary(qc.ancillary),
garbage(qc.garbage) {
Expand All @@ -259,7 +263,6 @@ class QuantumComputation {
nqubits = qc.nqubits;
nclassics = qc.nclassics;
nancillae = qc.nancillae;
maxControls = qc.maxControls;
name = qc.name;
qregs = qc.qregs;
cregs = qc.cregs;
Expand Down Expand Up @@ -712,10 +715,6 @@ class QuantumComputation {
void addQubit(Qubit logicalQubitIndex, Qubit physicalQubitIndex,
std::optional<Qubit> outputQubitIndex);

void updateMaxControls(const std::size_t ncontrols) {
maxControls = std::max(ncontrols, maxControls);
}

void instantiate(const VariableAssignment& assignment);

void addVariable(const SymbolOrNumber& expr);
Expand Down Expand Up @@ -783,7 +782,9 @@ class QuantumComputation {
dump(std::move(of), format);
}
virtual void dump(std::ostream&& of, Format format);
virtual void dumpOpenQASM(std::ostream& of);
void dumpOpenQASM2(std::ostream& of) { dumpOpenQASM(of, false); }
void dumpOpenQASM3(std::ostream& of) { dumpOpenQASM(of, true); }
virtual void dumpOpenQASM(std::ostream& of, bool openQasm3);

// this convenience method allows to turn a circuit into a compound operation.
std::unique_ptr<CompoundOperation> asCompoundOperation() {
Expand Down
36 changes: 29 additions & 7 deletions include/operations/ClassicControlledOperation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,33 @@

namespace qc {

enum ComparisonKind {
Eq,
Neq,
Lt,
Leq,
Gt,
Geq,
};

std::ostream& operator<<(std::ostream& os, const ComparisonKind& kind);

class ClassicControlledOperation final : public Operation {
private:
std::unique_ptr<Operation> op;
ClassicalRegister controlRegister{};
std::uint64_t expectedValue = 1U;
ComparisonKind comparisonKind = ComparisonKind::Eq;

public:
// 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)
std::uint64_t expectedVal = 1U,
ComparisonKind kind = ComparisonKind::Eq)
: op(std::move(operation)), controlRegister(std::move(controlReg)),
expectedValue(expectedVal) {
expectedValue(expectedVal), comparisonKind(kind) {
nqubits = op->getNqubits();
name = "c_" + shortName(op->getType());
parameter.reserve(3);
Expand Down Expand Up @@ -108,7 +121,8 @@ class ClassicControlledOperation final : public Operation {
return false;
}

if (expectedValue != classic->expectedValue) {
if (expectedValue != classic->expectedValue ||
comparisonKind != classic->comparisonKind) {
return false;
}

Expand All @@ -121,11 +135,19 @@ class ClassicControlledOperation final : public Operation {
}

void dumpOpenQASM(std::ostream& of, const RegisterNames& qreg,
const RegisterNames& creg) const override {
of << "if(";
const RegisterNames& creg, size_t indent,
bool openQASM3) const override {
of << std::string(indent * OUTPUT_INDENT_SIZE, ' ');
of << "if (";
of << creg[controlRegister.first].first;
of << " == " << expectedValue << ") ";
op->dumpOpenQASM(of, qreg, creg);
of << " " << comparisonKind << " " << expectedValue << ") ";
if (openQASM3) {
of << "{\n";
}
op->dumpOpenQASM(of, qreg, creg, indent + 1, openQASM3);
if (openQASM3) {
of << "}\n";
}
}

void invert() override { op->invert(); }
Expand Down
5 changes: 3 additions & 2 deletions include/operations/CompoundOperation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,10 @@ class CompoundOperation final : public Operation {
}

void dumpOpenQASM(std::ostream& of, const RegisterNames& qreg,
const RegisterNames& creg) const override {
const RegisterNames& creg, size_t indent,
bool openQASM3) const override {
for (const auto& op : ops) {
op->dumpOpenQASM(of, qreg, creg);
op->dumpOpenQASM(of, qreg, creg, indent, openQASM3);
}
}

Expand Down
3 changes: 2 additions & 1 deletion include/operations/NonUnitaryOperation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ class NonUnitaryOperation final : public Operation {
std::size_t prefixWidth) const override;

void dumpOpenQASM(std::ostream& of, const RegisterNames& qreg,
const RegisterNames& creg) const override;
const RegisterNames& creg, size_t indent,
bool openQASM3) const override;

void invert() override {
throw QFRException("Inverting a non-unitary operation is not supported.");
Expand Down
11 changes: 10 additions & 1 deletion include/operations/Operation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,17 @@ class Operation {
return op.print(os);
}

void dumpOpenQASM2(std::ostream& of, const RegisterNames& qreg,
const RegisterNames& creg) const {
dumpOpenQASM(of, qreg, creg, 0, false);
}
void dumpOpenQASM3(std::ostream& of, const RegisterNames& qreg,
const RegisterNames& creg) const {
dumpOpenQASM(of, qreg, creg, 0, true);
}
virtual void dumpOpenQASM(std::ostream& of, const RegisterNames& qreg,
const RegisterNames& creg) const = 0;
const RegisterNames& creg, size_t indent,
bool openQASM3) const = 0;

virtual void invert() = 0;

Expand Down
12 changes: 11 additions & 1 deletion include/operations/StandardOperation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,19 @@ class StandardOperation : public Operation {
}

void dumpOpenQASM(std::ostream& of, const RegisterNames& qreg,
const RegisterNames& creg) const override;
const RegisterNames& creg, size_t indent,
bool openQASM3) const override;

void invert() override;

protected:
void dumpOpenQASM2(std::ostream& of, std::ostringstream& op,
const RegisterNames& qreg) const;
void dumpOpenQASM3(std::ostream& of, std::ostringstream& op,
const RegisterNames& qreg) const;

void dumpGateType(std::ostream& of, std::ostringstream& op,
const RegisterNames& qreg) const;
};

} // namespace qc
3 changes: 2 additions & 1 deletion include/operations/SymbolicOperation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,8 @@ class SymbolicOperation final : public StandardOperation {
}

[[noreturn]] void dumpOpenQASM(std::ostream& of, const RegisterNames& qreg,
const RegisterNames& creg) const override;
const RegisterNames& creg, size_t indent,
bool openQASM3) const override;

[[nodiscard]] StandardOperation
getInstantiatedOperation(const VariableAssignment& assignment) const;
Expand Down
51 changes: 51 additions & 0 deletions include/parsers/qasm3_parser/Exception.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#pragma once

#include "Statement.hpp"

namespace qasm3 {
class CompilerError final : std::exception {
public:
std::string message{};
std::shared_ptr<DebugInfo> debugInfo{};

CompilerError(std::string msg, std::shared_ptr<DebugInfo> debug)
: message(std::move(msg)), debugInfo(std::move(debug)) {}

[[nodiscard]] std::string toString() const {
std::stringstream ss{};
ss << debugInfo->toString();

auto parentDebugInfo = debugInfo->parent;
while (parentDebugInfo != nullptr) {
ss << "\n (included from " << parentDebugInfo->toString() << ")";
parentDebugInfo = parentDebugInfo->parent;
}

ss << ":\n" << message;

return ss.str();
}
};
} // namespace qasm3

class ConstEvalError final : std::exception {
public:
std::string message{};

explicit ConstEvalError(std::string msg) : message(std::move(msg)) {}

[[nodiscard]] std::string toString() const {
return "Constant Evaluation: " + message;
}
};

class TypeCheckError final : std::exception {
public:
std::string message{};

explicit TypeCheckError(std::string msg) : message(std::move(msg)) {}

[[nodiscard]] std::string toString() const {
return "Type Check Error: " + message;
}
};
50 changes: 50 additions & 0 deletions include/parsers/qasm3_parser/Gate.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#pragma once

#include "NestedEnvironment.hpp"
#include "QuantumComputation.hpp"
#include "Statement.hpp"

namespace qasm3 {
struct GateInfo {
size_t nControls;
size_t nTargets;
size_t nParameters;
qc::OpType type;
};

struct Gate {
virtual ~Gate() = default;

virtual size_t getNControls() = 0;
virtual size_t getNTargets() = 0;
virtual size_t getNParameters() = 0;
};

struct StandardGate : Gate {
GateInfo info;

explicit StandardGate(const GateInfo& gateInfo) : info(gateInfo) {}

size_t getNControls() override { return info.nControls; }

size_t getNTargets() override { return info.nTargets; }
size_t getNParameters() override { return info.nParameters; }
};

struct CompoundGate : Gate {
std::vector<std::string> parameterNames;
std::vector<std::string> targetNames;
std::vector<std::shared_ptr<QuantumStatement>> body;

explicit CompoundGate(
std::vector<std::string> parameters, std::vector<std::string> targets,
std::vector<std::shared_ptr<QuantumStatement>> bodyStatements)
: parameterNames(std::move(parameters)), targetNames(std::move(targets)),
body(std::move(bodyStatements)) {}

size_t getNControls() override { return 0; }

size_t getNTargets() override { return targetNames.size(); }
size_t getNParameters() override { return parameterNames.size(); }
};
} // namespace qasm3
Loading

0 comments on commit e193ddb

Please sign in to comment.