Chapter 4. Statements
This chapter offers an overview of the statements defined in Q#. A statement is a program instruction that describes an action to be performed. Q# is an imperative programming language, so its operations and functions are sequences of statements.
I’ll start with a review of “classical” statements: variable declaration and reassignment, conditional execution, loops, calling other routines, and halting routine execution. Most other programming languages have some variants of these statements, and you’re probably familiar with them conceptually, so this part focuses mostly on the syntax and any language-specific nuances.
The second part of the chapter covers statements specific to quantum programs: qubit allocation, a quantum variant of a conditional loop, and a quantum-specific flow-control construct called conjugation.
Example: Calculate Euler’s Totient Function
Let’s take a look at Example 4-1, a Q# program that calculates Euler’s totient function of the given integer n
.
The value of this function equals the number of positive numbers less than or equal to n
that are coprime to it—that is, they don’t have common divisors greater than 1.
This example implements the most straightforward approach to computing this function: it iterates over all numbers from 1
to n
and checks whether each of them is coprime to n
by calculating their greatest common divisor (GCD).
This code is designed to showcase the main “classical” statements we will be considering later in this chapter.
Example 4-1. Calculating Euler’s totient function in Q#
namespace
EulersTotientFunction
{
open
Microsoft
.
Quantum
.
Intrinsic
;
// Message
/// # Summary
/// Calculates Euler's totient function.
function
TotientFunction
(
n
:
Int
)
:
Int
{
// Conditional statement checks input validity.
if
n
<=
0
{
// Fail statement throws an exception.
fail
"The argument n should be positive"
;
}
// Declare mutable variable to count coprimes.
mutable
nCoprimes
=
0
;
// For loop runs through numbers from 1 to n.
for
i
in
1
..
n
{
// Declare mutable variables to use when
// computing GCD of i and n.
mutable
(
x
,
y
)
=
(
n
,
i
);
// While loop runs while y is positive.
while
y
>
0
{
// Reassign both mutable variables.
set
(
x
,
y
)
=
(
y
,
x
%
y
);
}
// At this point x = GCD(i, n).
// Conditional statement checks whether
// the current number is coprime with n.
if
x
==
1
{
// Evaluate-and-reassign statement.
set
nCoprimes
+=
1
;
}
}
// Return statement.
return
nCoprimes
;
}
@EntryPoint
()
function
PrintEulersTotientFunction
()
:
Unit
{
// Declare immutable variable - the argument
// to use when computing the function.
let
n
=
4
;
// Call statement prints the function value.
Message
(
$
"φ({n}) = {TotientFunction(n)}"
);
}
}
Indentation in Q# is not significant: blocks are marked explicitly using curly brackets {
and }
, similarly to C++ and C#, rather than using indentation, like in Python.
However, the Q# style guide recommends indenting blocks to improve code readability.
To compute the value of the function for other inputs, you’ll need to modify this program to define a different value of n
.
Q# doesn’t support user input during program runtime.
In Chapter 6, I’ll show you how to define programs that take input from the user upon their invocation, and how to pass input to them.
The part of the for
loop body that calculates the greatest common divisor of two integers could be replaced with
a single call to the GreatestCommonDivisorI
library function from the Microsoft.Quantum.Math
namespace (see Chapter 7).
However, I chose to spell it out in order to include more statements in this example.
Let’s take a closer look at the various statements used in this program.
Working with Variables
Q# offers multiple statements for declaring and modifying Q# variables. Before we dive into the statements themselves, I’ll make some general notes about variable handling in Q# that will be important for the rest of the chapter.
Variable Scope
In general the scope of a variable defined within a statement block, such as a body of a callable or a loop body, is that statement block. Variables defined in an outer block will be visible in an inner block, but not vice versa. You cannot define a variable with a name that has already been defined earlier in the block or in an outer block; Q# prohibits this practice, which is known as variable shadowing.
All Q# variables are local; an attempt to define a global variable will yield a compilation error. You might have noticed that our discussion of top-level Q# elements in Chapter 1 did not include global variable declarations; this is why.
Several statements have slightly different rules that define visibility of the variables defined in them. In these cases, I’ll make a separate note when discussing the corresponding statement.
Mutable and Immutable Variables
Q# has two types of variables: mutable and immutable. An immutable variable is effectively a constant—it’s a variable that is always bound to the same value and cannot be re-bound to a different one. A mutable variable is a “normal” variable that can take on different values during the program execution.
Q# differentiates between mutable and immutable variables on the language level to simplify the analysis of the program behavior, so you have to declare whether the variable will be reassigned later up front. In Chapter 5, we’ll see how variable mutability affects code generation performed by the Q# compiler.
Declaring Immutable Variables: let Statements
A let
statement allows you to declare an immutable variable (that is, a constant) and to bind it to its value.
The let
statement consists of the following parts:
-
A
let
keyword -
A variable name or a tuple of names
-
An equal sign
-
The expression that is being bound to the variable
-
A semicolon
let
n
=
10
;
let
doubleArray
=
[
1.
,
2.
,
3.
];
let
name
=
"Mariia"
;
let
gate
=
H
;
The type of the variable assigned can be any type supported by Q#.
Note that the let
statement doesn’t specify the type of the variable it defines explicitly.
The type of the variable is inferred from the expression on the right side of the equal sign.
We will discuss assigning tuples separately a bit later in the chapter.
Declaring Mutable Variables: mutable Statements
A mutable
statement allows you to declare a mutable variable (a variable that can take different values during the program execution) and bind it to its initial value.
The mutable
statement consists of the following parts:
-
A
mutable
keyword -
A variable name or a tuple of names
-
An equal sign
-
The expression that is being bound to the variable
-
A semicolon
mutable
m
=
10
;
mutable
intArray
=
[
1
,
2
,
3
];
As with the let
statement, the type of the variable defined using the mutable
statement is inferred from the expression on the right side, and can be any type supported by Q#.
The difference between the mutable
and let
statements comes in play with the next statement we’ll look at, which allows you to reassign mutable variables.
Reassigning Mutable Variables: set Statements
A set
statement allows you to reassign the value of a mutable variable to a different value.
The set
statement consists of the following parts:
-
A
set
keyword -
A variable name or a tuple of names
-
An equal sign
-
The expression that is being bound to the variable
-
A semicolon
set
m
=
m
+
32
;
set
intArray
=
intArray
+
[
4
];
You cannot use the set
statement with an immutable variable defined using a let
statement; an attempt to do this will cause a compilation error.
Note that since Q# is a statically typed language, a set
statement cannot change the type of the variable that was defined by the mutable
statement.
An attempt to assign a value to a variable of a different type without an explicit type cast will cause a compilation error.
Evaluate-and-reassign statements are shorthand for a special type of set
statements.
If the expression on the right side of the equal sign in a set
statement is an application of a binary operator to the variable on the left side and another expression, you can use an evaluate-and-reassign statement to write it more concisely.
Evaluate-and-reassign statements are defined for arithmetic and bitwise binary operators, as well as string and array concatenation operators and copy-and-update expressions for arrays and UDTs.
The following code snippet shows several pairs of equivalent set
statements, in which the second variant is shorthand for the first variant:
// Increment a number by 32.
set
m
=
m
+
32
;
set
m
+=
32
;
// Multiply a number by 2 using bit shift.
set
m
=
m
<<<
1
;
set
m
<<<=
1
;
// Append an element to the current array.
set
intArray
=
intArray
+
[
4
];
set
intArray
+=
[
4
];
// Replace the first element of an array with 5
// using a copy-and-update expression.
set
intArray
=
intArray
w
/
0
<-
5
;
set
intArray
w
/=
0
<-
5
;
// Replace an element of a UDT defined as
// newtype NamedPair = (Fst : Int, Snd : Bool);
// using a copy-and-update expression.
set
np
=
np
w
/
Fst
<-
3
;
set
np
w
/=
Fst
<-
3
;
In order to expand an evaluate-and-reassign statement into a full set
statement, the Q# compiler uses the mutable variable on the left side of the equal sign as the left-hand argument of the binary operator, and the expression on the right side of the equal sign as the right-hand argument of the binary operator.
This means, for example, that you cannot use the mutable
variable in an evaluate-and-reassign statement as the divisor in the division expression or the shift amount for the left-shift operator, since they are right-hand arguments of the corresponding operators.
Assigning Tuples: Tuple Deconstruction
Before moving on to different statements, let’s take a closer look at assigning tuples.
let
, mutable
, and set
statements can all be applied to tuples as well as other data types.
Conveniently, they are not limited to dealing with the tuple being assigned as a whole;
tuple deconstruction allows you to deconstruct the tuple into its components and assign them to different variables separately.
Let’s look at some examples that illustrate tuple deconstruction and its applications:
// Access tuple components separately.
let
tuple
=
(
5
,
false
);
let
(
first
,
second
)
=
tuple
;
// first = 5, second = false
// Access tuple components that are tuples.
let
nestedTuple
=
(
42
,
(
PauliX
,
2.0
));
let
(
n
,
pair
)
=
nestedTuple
;
// n = 42, pair = (PauliX, 2.0)
// Access nested tuple components.
let
(
n
,
(
pauli
,
angle
))
=
nestedTuple
;
// n = 42, pauli = PauliX, angle = 2.0
// Access some, but not all, components.
let
(
_
,
(
pauli
,
angle
))
=
nestedTuple
;
// Update two variables simultaneously.
set
(
x
,
y
)
=
(
y
,
x
%
y
);
// Use library function that returns a tuple.
let
(
u
,
v
)
=
ExtendedGreatestCommonDivisorI
(
a
,
b
);
Note the fourth snippet that shows that you don’t need to assign all components of the tuple to some variables when using tuple decomposition; you can use underscore (_
) in lieu of any tuple components you don’t need.
Tuple deconstruction is the only way to access unnamed tuple elements individually.
Besides, it can be very convenient when working with more complicated code,
since it allows you to use functions or operations that return tuples of values, arrays of tuples, and so on.
The last snippet shows accessing the return value of a library function that computes the greatest common divisor of two integers a
and b
, represents it as a linear combination a ⋅ u + b ⋅ v = GCD(a, b)
, and returns a tuple (u, v)
.
Conditional Execution: if Statements
A conditional statement allows you to execute different statements depending on the values of one or several expressions. Q# offers a single if
statement that implements three variants of conditional execution.
The if
statement consists of the following parts:
-
An
if
keyword -
A block of statements that are executed if the expression evaluates to
true
For example:
if
n
<=
0
{
fail
"The argument n should be positive"
;
}
The if-else
statement expands the if
statement, following it with an else
keyword and a block of statements that are executed if the expression in the if
statement evaluates to false
:
if
n
%
2
==
0
{
Message
(
"The number is even"
);
}
else
{
Message
(
"The number is odd"
);
}
Finally, the if-elif-else
statement allows you to check several conditions in a row, executing the first block for which its matching condition evaluates to true
, and executing the optional else
block if all conditions evaluate to false
.
The following code snippet implements the decision-making fragment of the FooBar task, which, given an integer number, prints “Foo” if it is divisible by 3
, “Bar” if it is divisible by 5
, “Foobar” if it is divisible by both, and the number itself if it is divisible by neither 3
nor 5
:
if
i
%
15
==
0
{
Message
(
"Foobar"
);
}
elif
i
%
3
==
0
{
Message
(
"Foo"
);
}
elif
i
%
5
==
0
{
Message
(
"Bar"
);
}
else
{
Message
(
$
"{i}"
);
}
Note that the statement blocks in each scenario must be enclosed in curly brackets, even if some of the blocks contain only a single statement. Q# doesn’t allow you to omit curly brackets around the statement blocks; you’ll see other examples of this later in this chapter. Enclosing the conditional expressions in round brackets is optional, though.
Remember that Q# doesn’t support implicit type casts; all expressions used after if
and elif
keywords must be Boolean.
Loops
A loop allows you to execute a fragment of code repeatedly. Q# offers three types of loops; we will start with the first two that were featured in Example 4-1 and discuss the third one later in the chapter.
Iterate Over a Sequence: for Loops
A for
loop allows you to iterate over the elements of a sequence—a range or an array—and to perform certain actions for each element.
(You cannot iterate over a tuple, since a tuple represents a group of items of different types rather than a sequence of items of the same type.)
The for
statement consists of the following parts:
-
A
for
keyword -
A variable or a tuple of variables
-
An expression that evaluates to a range or an array
-
The block of statements that make up the loop body
Let’s take a look at several examples of for
loops and the variations in their functionality.
The simplest variant of the for
loop iterates over a range of integers.
The loop variable is an integer that takes on each value in the range in turn.
Here, for example, is the Q# code for calculating the factorial of the number n
.
The for
loop is used to multiply the mutable variable fact
by each of the integers from 2
to n
, inclusive:
mutable
fact
=
1
;
for
i
in
2
..
n
{
set
fact
*=
i
;
}
If you don’t need to use the loop variable in the loop (that is, you just need to execute the loop body a certain number of times), you can replace the loop variable with an underscore (_
).
For example, the following code snippet calculates the n
th power of 2
by multiplying the mutable variable power
by 2
n
times.
mutable
power
=
1
;
for
_
in
1
..
n
{
set
power
*=
2
;
}
The second variant of the for
loop iterates over the elements of an array.
In this case the loop variable is of the same type as the array elements, which can be any of the types supported by Q#, from integers to data structures or even callables.
This code snippet applies the Hadamard gate to each of the qubits in the array:
use
qs
=
Qubit
[
n
];
for
q
in
qs
{
H
(
q
);
}
For a more interesting example, let’s apply Hadamard gates followed by different single-qubit gates with different parameters to different qubits in the array.
Here, the type of the array in the for
loop is ((Double, Qubit) => Unit, Double, Qubit)[]
, and each of its elements is a tuple of three values: a gate of type (Double, Qubit) => Unit
that acts on a qubit and takes an extra floating-point parameter (see Chapter 5), a floating-point parameter to use, and a qubit to apply this gate to.
Note that using tuple deconstruction when defining the loop variable allows you to define individual variables in the loop header instead of defining a tuple loop variable and deconstructing it in the loop body:
let
gates
=
[
Rx
,
Ry
,
Rz
];
let
angles
=
[
1.
,
2.
,
3.
];
use
qs
=
Qubit
[
3
];
let
array
=
Zipped3
(
gates
,
angles
,
qs
);
for
(
gate
,
angle
,
qubit
)
in
array
{
H
(
qubit
);
gate
(
angle
,
qubit
);
}
Note
This example uses a library function Zipped3
, which “zips” three arrays into an array of tuples that combine respective elements of each array.
Microsoft.Quantum.Arrays
namespace contains a lot of very convenient functions for working with arrays (see Chapter 7).
You can replace the loop variable with an underscore when iterating over the array as well, but it is less common than when iterating over a range. In this variant you’re likely to use the array elements in the loop body, rather than just use the length of the array to define the number of loop iterations. However, if your code iterates over an array of tuples, it can be convenient to replace some of the variables with underscores in tuple deconstruction if your loop body needs only part of the information stored in each tuple. For example, you could split the loop from the previous code snippet into two, applying the Hadamard gates in the first loop and the rotation gates in the second one. In this case the first loop doesn’t need the information about the rotation gates you’ll use in the second loop, so it can be ignored:
for
(
_
,
_
,
qubit
)
in
array
{
H
(
qubit
);
}
for
(
gate
,
angle
,
qubit
)
in
array
{
gate
(
angle
,
qubit
);
}
Loop variables are immutable; they are bound to each element of the sequence for the matching iteration, and go out of scope at the end of the loop body.
Q# for
loops don’t support transfer of control statements such as break
or continue
.
If you anticipate the need to stop iteration early depending on some condition, use the while
loop or the repeat-until
loop that we’ll cover later in this chapter.
Alternatively, if your for
loop doesn’t involve allocated qubits, you can use a return
statement to leave the loop and the operation/function in which it is defined.
Classical Conditional Loop: while Loops
A while
loop allows you to evaluate an expression before each iteration and execute the loop body only if that expression evaluates to true
.
Note that in Q# this loop is purely classical: you can only use it within functions (recall that Q# functions can contain only classical computations).
For similar functionality in operations, use the repeat-until
loop we’ll discuss later in this chapter.
The while
statement consists of the following parts:
-
A
while
keyword -
A block of statements that are executed as long as the expression evaluates to
true
For example, the following code snippet implements a function that finds the index of a negative element in the given array, and it returns that index or -1
if all array elements
are nonnegative:
function
FirstNegativeIndex
(
a
:
Int
[])
:
Int
{
mutable
ind
=
0
;
while
ind
<
Length
(
a
)
and
a
[
ind
]
>=
0
{
set
ind
+=
1
;
}
return
ind
==
Length
(
a
)
?
-
1
|
ind
;
}
Note that, unlike in the repeat-until
loop you’ll see later, the condition of the while
loop must be defined using variables declared outside the loop body that are updated in the loop body.
Call an Operation or a Function: Call Statements
A call statement allows you to call an operation or a function that returns a Unit
value.
(As you learned in Chapter 3, calling an operation or a function that returns any other type of value is a kind of expression.) For example:
// Calling an operation.
X
(
q
);
// Calling a built-in function.
Message
(
"Hello, quantum world!"
);
Note
Most call statements will involve calling operations.
In most cases, functions are used to perform classical computations and to return the result of these computations,
and thus can be used only as expressions.
The few functions that return a Unit
type and thus can be called as statements are special functions that rely on their side effects.
The examples include the Message
function you’ve seen in Example 4-1 and the debugging functions you’ll see in Chapter 8.
We will discuss the behavior of operations and functions and calling operations in more detail in Chapter 5.
Stop Execution: return and fail Statements
Q# offers two ways to stop execution of the current operation/function, depending on whether it completed successfully or a fatal error occurred.
Note
Q# does not support other types of transfer-of-control statements offered by many general-purpose languages, such as break
, continue
, or goto
.
Finish Execution: return Statements
A return
statement signals successful completion of the current operation/function.
If the signature of the callable specifies that it has a return value of type other than Unit
, a return
statement also returns the result of the computation.
This statement stops execution of the current operation/function and returns control to the caller. If the current callable was the entry point of Q# code, the return
statement returns control to the classical host program or the execution environment.
A return
statement consists of the following parts:
-
A
return
keyword -
An expression of the type that matches the return type of the current operation/function
-
A semicolon
For example:
function
FortyTwo
()
:
Int
{
return
42
;
}
If the callable return type is Unit
, the return
statement has to return the value ()
:
/// # Summary
/// Prepares the qubit in the basis state given
/// by the measurement result in computational
/// basis. The qubit starts in the |0⟩ state.
operation
PrepareBasisState
(
q
:
Qubit
,
state
:
Result
)
:
Unit
{
if
state
==
Zero
{
// The qubit is already in the Zero state.
return
();
}
X
(
q
);
}
A callable can have multiple execution paths; each must end in a return
statement,
except for the path that ends at the end of a callable that returns Unit
, as you can see in the preceding example.
Q# imposes some restrictions on the use of return
statements.
You cannot use a return
statement in the middle of the block that allocated qubits, but you can use it as the last statement of such a block.
As long as no allocated qubits are involved, you can use a return
statement at any point in the code, though you might get a “This statement will never be executed” warning for any statements after the return
statement.
Throw an Exception: fail Statements
A fail
statement signals a fatal error from which the program cannot recover.
This statement halts Q# program execution completely and returns control to the classical host program or the execution environment, depending on how you run this Q# program.
A fail
statement consists of the following parts:
-
A
fail
keyword -
A string—the message that will accompany the exception thrown by the statement
-
A semicolon
The error message can be any expression of type String
, including interpolated strings:
if
n
<=
0
{
fail
"The argument n should be positive"
;
}
You can use the fail
statement at any point inside a function or an operation, including the places where you cannot use a return
statement.
Note
fail
statements are broadly used by Q# library operations and functions to ensure the constraints on their expected inputs, and in the implementation of unit tests that cover Q# libraries.
They also power a lot of functions from the Microsoft.Quantum.Diagnostics
namespace (please see Chapter 8).
Example: Prepare a Quantum State
Now let’s consider Example 4-2, a Q# program that prepares a quantum state (|00⟩ + |10⟩ + |11⟩). It showcases all the remaining statements we will cover in this chapter, including all “quantum” statements.
Example 4-2. Preparing superposition state (|00⟩ + |10⟩ + |11⟩)
namespace
StatePreparation
{
open
Microsoft
.
Quantum
.
Canon
;
// ApplyToEach
open
Microsoft
.
Quantum
.
Diagnostics
;
// DumpRegister
open
Microsoft
.
Quantum
.
Intrinsic
;
// Message
open
Microsoft
.
Quantum
.
Measurement
;
// MResetZ
@EntryPoint
()
/// # Summary
/// Prepares state (|00⟩ + |10⟩ + |11⟩) / √3.
operation
PrepareSuperpositionState
()
:
Unit
{
// Use statement allocates the qubits.
use
(
qs
,
aux
)
=
(
Qubit
[
2
],
Qubit
());
// Repeat-until loop runs until the loop body
// succeeds at preparing the right state.
repeat
{
// Call statement applies H gates to qubits
// to prep (|00⟩ + |01⟩ + |10⟩ + |11⟩) / 2.
ApplyToEach
(
H
,
qs
);
// Conjugation statement marks the basis
// state we need to discard (|10⟩)
// with |1⟩ in qubit aux.
within
{
// Call statement applies X gate
// to the second qubit.
X
(
qs
[
1
]);
}
apply
{
// Call statement applies controlled
// variant of X gate to the qubits.
Controlled
X
(
qs
,
aux
);
}
// Variable assignment statement measures
// qubit aux and stores the result.
let
res
=
MResetZ
(
aux
);
}
until
res
==
Zero
fixup
{
// Call statement resets qubits to |0⟩.
ResetAll
(
qs
);
}
// Call statement prints the qubits' state.
DumpRegister
((),
qs
);
// Call statement resets qubits to |0⟩.
ResetAll
(
qs
);
}
}
Note
As in Example 4-1, this program can be implemented more efficiently using
the ControlledOnBitString
library function from the Microsoft.Quantum.Canon
namespace
to replace the within-apply
statement (see Chapter 7).
However, it is spelled out here in order to include more statements in this example.
Let’s take a closer look at the new Q# statements you see in this program.
Allocate Qubits: use and borrow Statements
The key “quantum” statement in Q# is qubit allocation. Any Q# program always starts with no qubits and has to allocate and release any qubits it needs.
There are two statements you can use to allocate qubits: the use
statement allocates fresh qubits in the |0⟩ state, and the borrow
statement allocates qubits that are in some other state.
These statements can appear only in operations, never in functions, due to their inherently quantum nature.
The two statements have identical syntax. The vast majority of qubit allocations in Q# programs rely on the use
statement, so in this section I’ll focus mostly on that, and note the subtleties of the borrow
statement afterward.
The use
statement consists of the following parts:
-
A
use
keyword -
A variable name or a tuple of names
-
An equal sign
-
The initializer that is being bound to the variable
-
A semicolon or an optional block of statements
The right side of the equal sign can have one of the limited set of initializers: Qubit()
to allocate a single qubit, Qubit[n]
to allocate an array of n
qubits, or tuples thereof to allocate several qubits and/or qubit arrays in a single statement:
use
q
=
Qubit
();
...
use
qs
=
Qubit
[
n
];
...
use
(
input
,
output
)
=
(
Qubit
[
3
],
Qubit
())
{
// ...
}
The optional block of statements after the use
keyword defines the scope of the allocated qubits: the section of the program in which they will be available.
Q# doesn’t have a separate “release” statement to accompany the use
and borrow
statements.
Instead, the allocated qubits are released automatically at the end of the block in which they were allocated (in case of the simple form of the use
statement) or at the end of the block that is part of the use
statement, if such a block is present.
The qubits allocated with the use
statement must be returned to the |0⟩ state or measured before the release.
The compiler does not enforce this requirement. The qubits can be returned to the |0⟩ state without measurement using uncomputation: undoing the part of the computation in which they were involved. This means that verifying their state upon release would effectively require the compiler to simulate the program during its compilation, which is not possible for programs larger than a certain (rather small) threshold. However, quantum simulators offer this verification at runtime as part of their functionality; if your code performs neither uncomputation nor measurement before returning the qubits, running it on a quantum simulator will throw a runtime error (see Chapter 6).
The borrow
statement allocates qubits in an arbitrary unknown state and expects your program to return them to the same state before releasing them:
borrow
q
=
Qubit
();
// ...
// Remember to return q to its original state!
Under the hood, the borrow
statement literally borrows temporarily unused qubits from other parts of the program.
These qubits are not actively being used by the code that allocated them originally, so the current operation can borrow them for a bit and use them, as long as it returns the borrowed qubits to their starting state before releasing them.
This means that, for example, you cannot measure the borrowed qubits—this would destroy the information about their original state and ruin the computation from which they were borrowed.
Note
The scenarios in which borrowed qubits can be useful are pretty advanced, and out of scope for this book. You can see the paper “Factoring using 2n+2 qubits with Toffoli based modular multiplication” by Thomas Häner et al. for an example of an algorithm that uses borrowed qubits to implement multicontrolled CNOT gates with few qubits.
As discussed in Chapter 2, you can define variables of Qubit
data type using let
and mutable
statements to copy allocated qubits to new variables.
However, this does not create or allocate new qubits, nor does it violate the no-cloning theorem.
Instead, copying a qubit to a different variable creates another copy of the reference to the same qubit:
// Allocate a qubit in an unknown state.
borrow
q
=
Qubit
();
// Create another variable pointing to it.
let
qCopy
=
q
;
// Measure the copied qubit and reset it to |0⟩.
let
res
=
MResetZ
(
qCopy
);
// q points to the same qubit in the |0⟩ state!
Copying qubit variables is typically used to combine qubits from several sources—for example, combining qubits passed as an argument to an operation and auxiliary qubits allocated within that operation into a single array to be used as an argument for another operation.
Quantum Conditional Loops: repeat-until Loops
A repeat-until
loop allows you to repeat iterations until a certain condition is satisfied.
This statement can appear only in operations, and never in functions, since it is designed to work with quantum logic and conditions based on measurement results.
For similar functionality in functions, use the while
loop discussed earlier in this chapter.
The repeat-until
statement consists of the following parts:
-
A
repeat
keyword -
A block of statements that represents the loop body
-
A semicolon or an optional
fixup
keyword followed by a block of statements that is executed after each iteration if the expression evaluated tofalse
and the loop is going to restart
Here is a short example of using a repeat-until
loop to prepare the |1⟩ state without using an X gate.
In it, the loop body puts a qubit in a superposition using the Hadamard gate and measures it; if the measurement yielded Zero
, the loop repeats, but if it yielded One
, we know that the qubit ended up in the |1⟩ state:
use
q
=
Qubit
();
repeat
{
H
(
q
);
let
res
=
M
(
q
);
}
until
res
==
One
;
In Example 4-2, you saw a more interesting example of a repeat-until
loop in action:
-
The loop body attempts to prepare the required state by preparing a different state using an extra qubit and measuring that extra qubit. This will prepare the required state with 75% probability and a different, incorrect state with 25% probability.
-
The exit condition
res == Zero
is based on the outcome of the measurement, which tells us whether we got the required state or a different one. -
If we got a different state, the loop repeats itself after performing the
fixup
block, which resets the qubits to their starting state so that the loop body can start from a clean slate (ResetAll(qs)
).
All parts of the loop form a single scope, so any variables defined in the loop body are visible to the expression that defines the exit condition and to the fixup
block for the same iteration.
This means, for example, that the exit condition can be defined using variables declared in the loop body, rather than mutable variables.
A repeat-until
loop can also be used to implement a purely classical computation, as long as it happens in an operation and not in a function. But usually it’s better to separate a classical computation that requires conditional iterating into a function, and to use a while
loop instead.
Conjugation: within-apply Statements
The conjugation statement is also known as a within-apply
statement.
This statement can appear only in operations, and never in functions, due to its inherently quantum nature.
It implements the pattern of applying the unitary transformations U†VU, where U and V are unitary transformations, and U† is an adjoint of the transformation U: a transformation that undoes the effects of U.
In terms of Q# code, the conjugation statement applies two blocks of code, U and V, followed by an automatically generated adjoint of the first block U†.
Unlike other language statements, conjugation is not fundamentally necessary to a quantum programming language; in fact, it was introduced to Q# only in version 0.9, almost two years after the first release! However, conjugations are ubiquitous in quantum programs, expressing concepts such as uncomputation, which returns temporarily allocated qubits to their starting states before releasing them by undoing the part of the computation in which they were involved.
Seeing how often this code pattern occurred in Q# programs, it made sense to add a special statement to the language to make writing them more convenient and less error-prone. Indeed, spelling out the adjoint transformation U† manually means either forfeiting the advantages of automatic adjoint generation provided by the Q# compiler (and potentially introducing errors in the process) or having to define a separate operation for the transformation U and calling its adjoint (leading to bulkier and harder-to-read code). I will talk more about adjoints in Chapter 5.
The conjugation statement consists of the following parts:
-
A
within
keyword -
The first block of code (transformation U)
-
The second block of code (transformation V)
The following code snippet shows two equivalent fragments of code, both of which implement a controlled-on-zero variant of the X gate—a gate that flips the state of the target qubit if both control qubits are in the |0⟩ state:
within
{
ApplyToEachA
(
X
,
qs
);
}
apply
{
Controlled
X
(
qs
,
aux
);
}
ApplyToEachA
(
X
,
qs
);
Controlled
X
(
qs
,
aux
);
Adjoint
ApplyToEachA
(
X
,
qs
);
This example is too small to illustrate the advantages of using a conjugation statement instead of writing out the adjoint of the first block manually.
But, as you can imagine, real-world quantum programs tend to have much more complex code in both within
and apply
blocks, so a conjugation statement can be really handy.
The code in the within
block of a conjugation statement must be adjointable, i.e., it must be written in a way that allows the Q# compiler to generate its adjoint. (Note the use of library operation ApplyToEachA
in the last code snippet instead of ApplyToEach
; the only difference between these two operations is that ApplyToEachA
has an adjoint variant defined and ApplyToEach
does not.)
If the within
block includes any statements that don’t have an adjoint specialization, the compiler will throw an error pointing to them and explaining why the adjoint cannot be generated.
Get Q# Pocket Guide now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.