Code samples from the Introduction to Quantum Computing with Q# and QDK book, published by Springer Nature on 7 May 2022.
This book introduces the fundamentals of the theory of quantum computing, illustrated with code samples written in Q#, a quantum-specific programming language, and its related Quantum Development Kit. Quantum computing (QC) is a multidisciplinary field that sits at the intersection of quantum physics, quantum information theory, computer science and mathematics, and which may revolutionize the world of computing and software engineering.
The book begins by covering historical aspects of quantum theory and quantum computing, as well as offers a gentle, algebra-based, introduction to quantum mechanics, specifically focusing on concepts essential for the field of quantum programming. Quantum state description, state evolution, quantum measurement and the Bell’s theorem are among the topics covered. The readers also get a tour of the features of Q# and familiarize themselves with the QDK.
Next, the core QC topics are discussed, complete with the necessary mathematical formalism. This includes the notions of qubit, quantum gates and quantum circuits. In addition to that, the book provides a detailed treatment of a series of important concepts from quantum information theory, in particular entanglement and the no-cloning theorem, followed by discussion about quantum key distribution and its various protocols. Finally, the canon of most important QC algorithms and algorithmic techniques is covered in-depth - from the Deutsch-Jozsa algorithm, through Grover’s search, to Quantum Fourier Transform, quantum phase estimation and Shor’s algorithm.
The book is an accessible introduction into the vibrant and fascinating field of quantum computing, offering a blend of academic diligence with pragmatism that is so central to software development world. All of the discussed theoretical aspects of QC are accompanied by runnable code examples, providing the reader with two different angles - mathematical and programmatic - of looking at the same problem space.
The main branch in this repository contains the sample code that matches the content of the book as it was published. The code was written using the version 0.21.2112180703 of the QDK and the Q# language, which was released on 14th December 2021. The code should also work fine with all the newer versions of QDK lower than 1.0 (the last pre-1.0 release was 0.28.302812 on 15 September 2023).
QDK 1.0 was released on 12 January 2024 and contains numerous breaking changes and feature gaps in the libraries and in the language itself. The code samples ported to that release can be found on the qdk-1.0 branch.
You can also use the table below to navigate the samples, and switch between the versions in the book (QDK 0.2x) and QDK 1.0.
Name | Chapter | Description | QDK 0.2x | QDK 1.0 |
---|---|---|---|---|
basic | 3 | Basic Q# language examples | link | link |
basic-qubits | 4 | Basic qubit examples | link | link |
superposition | 4 | Superposition examples | link | link |
single qubit gates | 4 | Single qubit gates examples | link | link |
multi qubit gates | 4 | Multi qubit gates examples | link | link |
entanglement | 5 | Basic Bell states examples | link | link |
bell | 5 | Bell's inequality examples | link | link |
chsh | 5 | CHSH game example | link | link |
ghz | 5 | GHZ game example | link | link |
teleportation | 5 | Teleportation example | link | link |
entanglement swapping | 5 | Entanglement swapping example | link | link |
superdense | 5 | Superdense coding example | link | link |
bb84 | 6 | BB84 protocol example | link | link |
b92 | 6 | B92 protocol example | link | link |
eprqkd | 6 | EPR QDK protocol example | link | link |
deutsch-jozsa | 7 | Deutsch-Jozsa algorithm | link | link |
bernstein-vazirani | 7 | Bernstein-Vazirani algorithm | link | link |
grover | 7 | Grover algorithm | link | link |
qft | 7 | Quantum Fourier Transform example | link | n/a |
qpe | 7 | Quantum phase estimation example | link | link |
shor | 7 | Shor's algorithm | link | n/a |
Below is the list of breaking changes that occur in QDK 1.0, compared to QDK 0.2x. Note that this is not a comprehensive list, but one that focuses on the content and the code used in this book. It effectively summarizes that changes that have been applied between the main
and qdk-1.0
branches.
The grouping into chapters helps identify when a given braking change can be first encountered in the book. However, once it has been mentioned, it is not repeated again.
The explicit release of qubits is now mandatory (it used to happen implicitly).
Use Reset
, ResetAll
or measurement with a built-in reset (for example MResetZ
instead of M
).
The following namespaces no longer exist:
- Microsoft.Quantum.Logical
- Microsoft.Quantum.Arithmetic
- Microsoft.Quantum.Preparation
- Microsoft.Quantum.Oracles
- Microsoft.Quantum.Characterization
This is not a breaking change, but an alternative new syntax. A return statement on the last line of a function or operation may be omitted for brevity. In such cases the trailing semicolon is also omitted. For example:
function Hello() : String {
return "Hello, world!";
}
Can be simplified to:
function Hello() : String {
"Hello, world!"
}
When the book was written, the QDK did not support lambdas yet. Those are now supported. As a result, helper functions are not needed where lambdas can be used. For example:
let allZeroes = All(ResultIsZero, MeasureEachZ(x));
function ResultIsZero(result : Result) : Bool {
return result == Zero;
}
Can become:
let allZeroes = All(result -> result == Zero, MeasureEachZ(x));
This cannot be ported as attributes are no longer supported in Q#.
Can be replaced with simple string interpolation
i.e. BoolAsString(flag)
-> $“{flag}”
Can be replaced with Truncate
function from the Microsoft.Quantum.Math
namespace.
operation MultiM
is missing.
However, internally it was really just a loop of measurements.
Can be replaced with MeasureEachZ
or a manual measurement loop.
Wherever needed, we can now just pass qubit array in little-endian format instead.
MeasureInteger
takes a qubit array now, instead of a LittleEndian
register
It is newly in the Microsoft.Quantum.Measurement
namespace.
This can be replaced with:
function OperationPow<'T> (op : ('T => Unit), power : Int) : ('T => Unit) {
return ApplyOperationRepeatedly(op, power, _);
}
internal operation ApplyOperationRepeatedly<'T> (op : ('T => Unit), power : Int, target : 'T)
: Unit {
for idxApplication in 0 .. power - 1 {
op(target);
}
}
or simply by calling the operation the required (N) amount of times.
These can be replaced with simple checks: result == Zero
or result == One
.
Still open how to do formatting in QDK 1.0.
- for cases where we used
DrawRandomBool(0.5)
- it can be replaced withDrawRandomInt(0, 1) == 1
- for cases where we used
DrawRandomBool(successProbability)
- it can be replaced withDrawRandomDouble(0.0, 1.0) < successProbability
A polyfill looks like this:
operation DrawRandomBool(successProbability: Double) : Bool {
let randomValue = DrawRandomDouble(0.0, 1.0);
return randomValue < successProbability;
}
This was already re-introduced and is availble in QDK 1.1+
This was just a shorthand for creating entanglement using H + CNOT - can be added manually wherever needed.
This is not necessary as most types are now comparable to each other.
For example: if (EqualA(EqualB, aliceBits, bobBits))
can become if aliceBits == bobBits
This was already re-introduced and is available in QDK 1.1+
Not needed, can be solved with just interpolation or concatenation to a string.
For example simple string interpolation
i.e. IntAsString(number)
-> $“{number}”
Can be re-added by hand. Here is the polyfill:
function CControlledCA<'T> (op : ('T => Unit is Ctl + Adj)) : ((Bool, 'T) => Unit is Ctl + Adj) {
return ApplyIfCA(_, op, _);
}
operation ApplyIfCA<'T> (bit : Bool, op : ('T => Unit is Ctl + Adj), target : 'T) : Unit is Ctl + Adj {
if (bit) {
op(target);
}
}
This was already re-introduced and is availble in QDK 1.2+
Phase estimation has to be done manually. See the sample code in this repo.
There is now ApplyQFT
but it behaves differently (no swap) and is little endian by default.
It’s very easy to polyfill though:
operation QFTLE(qs : Qubit[]) : Unit is Adj + Ctl {
// reversal needed since we want to use little endian order
ApproximateQFT(Length(qs), Reversed(qs));
}
// original QDK QFT implmentation for big-endian
operation ApproximateQFT (a : Int, qs : Qubit[]) : Unit is Adj + Ctl {
let nQubits = Length(qs);
Fact(nQubits > 0, "`Length(qs)` must be least 1");
Fact(a > 0 and a <= nQubits, "`a` must be positive and less than `Length(qs)`");
for i in 0 .. nQubits - 1 {
for j in 0 .. i - 1 {
if i - j < a {
Controlled R1Frac([qs[i]], (1, i - j, (qs)[j]));
}
}
H(qs[i]);
}
SwapReverseRegister(qs);
}