Skip to content

Instantly share code, notes, and snippets.

@jond01
Last active April 12, 2023 15:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jond01/278be4d105835afdc7d2c69523cf6493 to your computer and use it in GitHub Desktop.
Save jond01/278be4d105835afdc7d2c69523cf6493 to your computer and use it in GitHub Desktop.

Note - the latest version of the post is available on:
https://jond01.github.io/blog/expressing-quantum-programs-in-qsharp-and-openqasm

Expressing Quantum Programs in Q# and OpenQASM

Jonathan Daniel, 5 December 2021.

With the rising popularity of quantum computing over the past years, open-source frameworks for developing quantum programs mature and form substantial ecosystems around them. Microsoft's Quantum Development Kit (QDK) and Qiskit by IBM are two major open-source frameworks. They present different approaches and tools for developing quantum algorithms and executing them on simulators or quantum hardware. However, since their goal and audience overlap, the two frameworks share many similarities. This blog post focuses on the main quantum programming languages of the two frameworks: Q# (QDK) and OpenQASM (Qiskit).

The Q# language is a part of the .NET family (C# and F#) and is a domain-specific language for programming on quantum computers. It is a strongly typed language that includes particular quantum types, statements, and operations, such as Qubit and Measurement, or specifying whether an operation can be inverted or controlled by other qubits. The OpenQASM language is often described as the open quantum assembly language (comparable to the classical Verilog) and is the primary exportable format from Qiskit's code in Python. Typically, the users of the Qiskit framework do not write OpenQASM code themselves (although it is possible) but use the Python packages that implement some of the logic in OpenQASM. OpenQASM 2.0 was announced in 2017, and since then, it has been adopted and developed. The pre-release of OpenQASM 3.0 is already available and is being incorporated into the Qiskit framework.

In this post, we highlight some aspects of expressing quantum programs and present how they are implemented in Q# versus OpenQASM programs. Effectively, the full expressiveness power of a language is limited by its grammar and additional rules. The grammar specifications of OpenQASM 3.0 and Q# are both defined with the ANTLR v4 format, which is the first similarity between the two languages. A central difference is that OpenQASM is a low-level language intended to be an intermediate representation, while Q# is a high-level language for developing quantum algorithms. Note that unless stated otherwise, "OpenQASM" means here OpenQASM 3.0, as many of the discussed points below have been added to OpenQASM only in version 3.0.

1. Basis gates

A cornerstone in quantum computing is the possibility to build every unitary gate from a predefined set of gates (a universal gate set). OpenQASM 2.0 defines a minimalistic universal quantum gate set of two gates: the generic single-qubit unitary U(θ, φ, λ) and the two-qubit controlled X gate CX. These are the only predefined gates in OpenQASM 2.0. Every other gate in the language is explicitly derived from these two. For example, consider the following snippet from a .qasm file, in which the custom (but widely used) ry and swap gates are defined:

// Defining gates in OpenQASM
gate ry(theta) a {
  U(theta, 0, 0) a;
}

gate swap a, b {
  CX a, b;
  CX b, a;
  CX a, b;
}

Many OpenQASM programs use standard gates from common extension libraries. The addition of these gates is directed by include "qelib1.inc" in the .qasm file, for example.

In Q#, the standard library includes the "Intrinsic" (open Microsoft.Quantum.Intrinsic) and "Canonical" (open Microsoft.Quantum.Canon) namespaces, which define many basic and advanced quantum operations. Some basic operations are the standard Pauli gates (X, Y, Z), conditional Paulis (CNOT, CCNOT, CY, CZ), rotations (R), whereas the more advanced operations contain QFT, GreaterThan, and ApplyCNOTChain. In OpenQASM, a lower-level language, the extension libraries usually do not include high-level gates like QFT. These gates and circuits are implemented by higher-level Python wrappers that export the implementation into OpenQASM.

2. Gate modifiers

Suppose you have an operation/gate that you want to apply to some target qubits conditioned on the values of other qubits. This composite gate is the quantum analog of the classical "if" statement. Another prevalent scenario is performing the inverse of a quantum gate (the "adjoint" U† of a unitary operation U). Specifically, this is useful in uncomputation techniques. The two mentioned examples create a new gate from a given one - "gate modifiers". Luckily, both Q# and OpenQASM 3.0 allow users to elegantly extend existing gates into new ones.

Q# defines the Adjoint and Controlled functors, which can extend gates. Developers can apply the functors only to operations with the Adj and Ctl characteristics, respectively. If a Q# operation's signature includes is Adj - it means that the operation is adjointable - one can invert it with the Adjoint functor. The same applies to is Ctl and Controlled. These characteristics are a vital part of the Q# typing system. Note that an operation can be both adjointable and controllable (is Adj + Ctl). OpenQASM 3.0 introduces the equivalent gate modifiers concept: inv @, ctrl @, negctrl @, and pow @. Each modifier can be applied to a gate, and one can even use multiple modifiers in the same statement. Some examples are given in the table below:

Modifier OpenQASM3 Q#
Inverse inv @ op q[0], q[1] Adjoint op(q[0], q[1])
Controlled gate ctrl(2) @ op cont[0], cont[1], q[0], q[1] Controlled op([cont[0], cont[1]], q)
Negative control negctrl(2) @ op cont[0], cont[1], q[0], q[1] (*)
Power pow(k) @ op q[0], q[1] OperationPow(op, k)(q[0], q[1]) (**)

op is an OpenQASM gate or a Q# operation (op : 'T => Unit is Adj + Ctl). The input of op is two qubits.

For example, the statement inv @ op q[0], q[1] applies the inverse gate of op (inv @ op) to the qubits q[0] and q[1].

* A possible implmentation of negative control is (snippet from a .qs file):

// Negatively controlled operation in Q#
use q = Qubit[2];
use t = Qubit();
within {
    ApplyToEachA(X, q);
}
apply {
    Controlled op(q, t);
}

The expression in the within block is applied before the apply block, and its Adjoint is applied afterward. Note that there are more concise options for expressing the same program, such as using the ApplyControlledOnBitString operation.

** In Q#, the OperationPow function supports only integer powers.

3. Parametrized programs

The main operation of a Q# project is marked by the @EntryPoint() attribute before its signature. Consider the following parametrized Q# program (.qs file):

// An example for a Q# program that accepts parameters
namespace Quantum.Parametrized {

    open Microsoft.Quantum.Canon;
    open Microsoft.Quantum.Intrinsic;
    open Microsoft.Quantum.Measurement;  // for MultiM

    @EntryPoint()
    operation QuantumProg(numQubits : Int, angles : Double[]) : Result[] {
        use qubits = Qubit[numQubits];
        // Implement the actual logic
        
        
        let res = MultiM(qubits);
        return res;
    }
}

The quantum program can be called from a classical Python or C# host program. The host program can send the input parameters and obtain the outputs. The host program can optimize the parameters and change the quantum program via its interface, allowing a specific scheme of hybrid quantum-classical computation.

OpenQASM 3.0 as well defines an I/O interface for parameters via the input and output modifiers:

// An OpenQASM 3.0 program with input and output
OPENQASM 3;
input int num_repetitions;
output bit result;
qubit q;

// Implement the logic here


result = measure q;

The OpenQASM code can be wrapped in Python, useful for variational algorithms.

4. Classical logic

OpenQASM3 and Q# support necessary statements for classical computation. for and while loops are supported in both, along with proper classical typing and functions, and enable the composition of sophisticated quantum programs. Without classical logic inside the quantum code, the quantum program is static and described as a quantum circuit. On the contrary, dynamic quantum programs change the execution on the quantum processor in real-time. The classical control opens the way to implementing concurrent quantum-classical programs.

5. Quantum memory management

One of the most powerful features of the Q# language is allocating auxiliary qubits inside an operation and other block statements. One can explicitly allocate a clean qubit (initialized to 0) via the use statement:

// Allocate a clean qubit in Q#
use qubit = Qubit();

Or a "dirty" qubit via the borrow statement:

// Allocate a dirty qubit in Q#
borrow qubit = Qubit();

Either way, the Q# developer must release allocated qubits as they were given. Namely, clean qubits must be in the state 0 at the end of their scope (by measurement or a reset, for instance), and dirty qubits must be in the same state as when borrowed.

In OpenQASM (2.0 and 3.0), qubits are declared globally. All the qubits have to be allocated in the main OpenQASM program rather than in gates. A breaking change between the versions of OpenQASM is to which state qubits are initialized. In OpenQASM 2.0, all the qubits of a quantum register are initialized to the 0 state. In OpenQASM 3.0, allocated qubits are initialized to an undefined state. One should use the reset statement to assure a 0 state of freshly allocated qubits in OpenQASM 3.0.

// Allocate and initialize qubits to 0 in OpenQASM 3.0
qubit[3] q;
reset q;

Note that there are some use cases for "dirty" qubits so that the reset is not a completely boilerplate code in OpenQASM 3.0.

Focusing on clean qubits, the reset requirement in Q# and OpenQASM3 is genuinely very similar, although distinctive and quite subtle. The qubits are reset at the end of their scope (Q#) or the beginning (OpenQASM3). It may seem that the choice where the reset takes place is an arbitrary peculiarity of each language. However, it is essential to remember that in Q#, the same qubit can be reused (twice or more) throughout the program with a different name. Therefore, if the qubits were reset in Q# just after their allocation, the reset could obliviously affect the state in the other qubits. It stems from the entanglement concept, one of the profound subtleties of quantum computing. Therefore, it is mandatory to reset Q# qubits before their automatic deallocation at the end of their scope.

A distinctive feature of OpenQASM 3.0 is the access to the physical qubits with $i, where $i is the i + 1's physical qubit (from 0 to n - 1, where n is the number of physical qubits).

Summary

In this post, we focused on the similarities between the Q# and OpenQASM programming languages, and we left aside lower-level hardware control and some more advanced concepts. It is also vital to remember that most of the ideas here entered OpenQASM only in version 3.0, which is still being adopted, but the future looks bright. We hope that this post will assist you in your quantum development journey.


References:

  1. OpenQASM 2: https://arxiv.org/abs/1707.03429
  2. OpenQASM 3: https://arxiv.org/abs/2104.14722
  3. Q#: https://arxiv.org/abs/1803.00652
  4. OpenQASM 3 specification: https://openqasm.com/
  5. Q# API reference: https://docs.microsoft.com/en-us/qsharp/api/qsharp/

This post was written as a part of Q# Advent Calendar 2021.

@jwoehr
Copy link

jwoehr commented Dec 15, 2021

You state above:

The OpenQASM code can be wrapped in Python, useful for variational algorithms.

OpenQASM 3 is a full-featured language that also allows externs written in other languages such as C. The OpenQASM Live Specification states, in part:

We envision two levels of classical control: simple, low-level instructions embedded as part of a quantum circuit and high-level external functions which perform more complex classical computations. The low-level functions allow basic computations on lower-level parallel control processors. These instructions are likely to have known durations and many such instructions might be executed within the qubit coherence time. The external, or extern, functions execute complex blocks of classical code that may be neither fast nor guaranteed to return. In order to connect with classical compilation infrastucture, extern functions are defined outside of OpenQASM. The compiler toolchain is expected to link extern functions when building an executable. This strategy allows the programmer to use existing libraries without porting them into OpenQASM. extern functions run on a global processor concurrently with operations on local processors, if possible. extern functions can write to the global controller’s memory, which may not be directly accessible by the local controllers.

This corresponds to the goals stated in section 2.1 Versatility in describing logical circuits of the original ArXiv paper OpenQASM 3: A broader and deeper quantum assembly language:

For instance, some applications require pre-processing of problem data, such as choosing a co-prime in Shor’s algorithm, post-processing to compute expectation values of Pauli operators, or further generation of circuits like in the outer loop of many variational algorithms. However, these classical computations do not have tight deadlines requiring execution within the coherence time of the quantum hardware. Consequently, we think of their execution in a near-time context which is more conveniently expressed in existing classical programming frameworks. Our classical computation extensions in OpenQASM 3 instead focus on the real-time domain which must be tightly coupled to quantum hardware. Even in the real-time context we enable re-use of existing tooling by allowing references to externally specified real-time functions defined outside of OpenQASM itself.

@jond01
Copy link
Author

jond01 commented Dec 16, 2021

Right, thank you for the addition, @jwoehr.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment