Working with Qubits

Having now seen a variety of different parts of the Q# language, let us get into the thick of it and see how to use qubits themselves.

Allocating Qubits

First, to obtain a qubit that we can use in Q#, we allocate qubits within a using block:

using (register = Qubit[5]) {
    // Do stuff...
}

Any qubits allocated in this way start off in the $\ket{0}$ state; in the example above, register is thus in the state $\ket{00000} = \ket{0} \otimes \ket{0} \otimes \cdots \otimes \ket{0}$. At the end of the using block, any qubits allocated by that block are immediately deallocated and cannot be used further.

Warning

Target machines expect that qubits are in the $\ket{0}$ state immediately before deallocation, so that they can be reused and offered to other using blocks for allocation. Whenever possible, use unitary operations to return any allocated qubits to $\ket{0}$. If need be, the Reset operation can be used to measure a qubit instead, and to use that measurement result to ensure that the measured qubit is returned to $\ket{0}$. Such a measurement will destroy any entanglement with the remaining qubits and can thus impact the computation.

Intrinsic Operations

Once allocated, a qubit can then be passed to functions and operations. In some sense, this is all that a Q# program can do with a qubit, as the actions that can be taken are all defined as operations. We will see these operations in more detail in Intrinsic Operations and Functions, but for now, we mention a few useful operations that can be used to interact with qubits.

First, the single-qubit Pauli operators $X$, $Y$, and $Z$ are represented in Q# by the intrinsic operations X, Y, and Z, each of which has type (Qubit => Unit is Adj + Ctl). As described in Intrinsic Operations and Functions, we can think of $X$ and hence of X as a bit-flip operation or NOT gate. This lets us prepare states of the form $\ket{s_0 s_1 \dots s_n}$ for some classical bit string $s$:

operation PrepareBitString(bitstring : Bool[], register : Qubit[]) : Unit 
is Adj + Ctl {

    let nQubits = Length(register);
    for (idxQubit in 0..nQubits - 1) {
        if (bitstring[idxQubit]) {
            X(register[idxQubit]);
        }
    }
}

operation Example() : Unit {

    using (register = Qubit[8]) {
        PrepareBitString(
            [true, true, false, false, true, false, false, true],
            register
        );
        // At this point, register now has the state |11001001〉.
    }
}

Tip

Later, we will see more compact ways of writing this operation that do not require manual flow control.

We can also prepare states such as $\ket{+} = \left(\ket{0} + \ket{1}\right) / \sqrt{2}$ and $\ket{-} = \left(\ket{0} - \ket{1}\right) / \sqrt{2}$ by using the Hadamard transform $H$, which is represented in Q# by the intrinsic operation H : (Qubit => Unit is Adj + Ctl):

operation PreparePlusMinusState(bitstring : Bool[], register : Qubit[]) : Unit {

    // First, get a computational basis state of the form
    // |s_0 s_1 ... s_n〉 by using PrepareBitString, above.
    PrepareBitString(bitstring, register);
    // Next, we use that |+〉 = H|0〉 and |-〉 = H|1〉 to
    // prepare the state we want.
    for (idxQubit in IndexRange(register)) {
        H(register[idxQubit]);
    }
}

Measurements

Using the Measure operation, which is a built in intrinsic non-unitary operation, we can extract classical information from an object of type Qubit and assign a classical value as a result, which has a reserved type Result, indicating that the result is no longer a quantum state. The input to Measure is a Pauli axis on the Bloch sphere, represented by an object of type Pauli (i.e., for instance PauliX) and an object of type Qubit.

A simple example is the following operation which creates one qubit in the $\ket{0}$ state, then applies a Hadamard gate H to it and then measures the result in the PauliZ basis.

operation MeasurementOneQubit () : Result {

    // The following using block creates a fresh qubit and initializes it 
    // in the |0〉 state.
    using (qubit = Qubit()) {
        // We apply a Hadamard operation H to the state, thereby creating the 
        // state 1/sqrt(2)(|0〉+|1〉). 
        H(qubit); 
        // Now we measure the qubit in Z-basis.
        let result = M(qubit);
        // As the qubit is now in an eigenstate of the measurement operator, 
        // we reset the qubit before releasing it. 
        if (result == One) { X(qubit); }   
        // Finally, we return the result of the measurement. 
        return result;
    }
}

A slightly more complicated example is given by the following operation which returns the Boolean value true if all qubits in a register of type Qubit[] are in the state zero, when measured in a specified Pauli basis and false otherwise.

operation AllMeasurementsZero (qs : Qubit[], pauli : Pauli) : Bool {

    mutable value = true;
    for (q in qs) {
        if ( Measure([pauli], [q]) == One ) {
            set value = false;
        }
    }
    return value;
}

The Q# language allows dependencies of classical control flow on measurement results of qubits. This in turn enables to implement powerful probabilistic gadgets that can reduce the computational cost for implementing unitaries. As an example, it is easy to implement so-called Repeat-Until-Success in Q# which are probabilistic circuits that have an expected low cost in terms of elementary gates, but for which the true cost depends on an actual run and an actual interleaving of various possible branchings.

To facilitate Repeat-Until-Success (RUS) patterns, Q# supports the construct

repeat {
    statementBlock1 
}
until (expression)
fixup {
    statementBlock2
}

where statementBlock1 and statementBlock2 are zero or more Q# statements, and expression any valid expression that evaluates to a value of type Bool. In a typical use case, the following circuit implements a rotation around an irrational axis of $(I + 2i Z)/\sqrt{5}$ on the Bloch sphere. This is accomplished by using a known RUS pattern:

operation RUScircuit (qubit : Qubit) : Unit {

    using(ancillas = Qubit[2]) {
        ApplyToEachA(H, ancillas);
        mutable finished = false;
        repeat {
            Controlled X(ancillas, qubit);
            S(qubit);
            Controlled X(ancillas, qubit);
            Z(qubit);
        }
        until(finished)
        fixup {
            if AllMeasurementsZero(ancillas, Xpauli) {
                set finished = true;
            }
        }
    }
}

This example shows the use of a mutable variable finished which is in scope of the entire repeat-until-fixup loop and which gets initialized before the loop and updated in the fixup step.

Finally, we show an example of a RUS pattern to prepare a quantum state $\frac{1}{\sqrt{3}}\left(\sqrt{2}\ket{0}+\ket{1}\right)$, starting from the $\ket{+}$ state. See also the unit testing sample provided with the standard library:

operation RepeatUntilSuccessStatePreparation( target : Qubit ) : Unit {

    using( ancilla = Qubit() ) {
        H(ancilla);
        repeat {
            // We expect target and ancilla qubit to be in |+⟩ state.
            AssertProb( 
                [PauliX], [target], Zero, 1.0, 
                "target qubit should be in the |+⟩ state", 1e-10 );
            AssertProb( 
                [PauliX], [ancilla], Zero, 1.0,
                "ancilla qubit should be in the |+⟩ state", 1e-10 );
                
            Adjoint T(ancilla);
            CNOT(target, ancilla);
            T(ancilla);

            // The probability of measuring |+⟩ state on ancilla is 3/4.
            AssertProb( 
                [PauliX], [ancilla], Zero, 3. / 4., 
                "Error: the probability to measure |+⟩ in the first 
                ancilla must be 3/4",
                1e-10);

            // If we get measurement outcome Zero, we prepare the required state 
            let outcome = Measure([PauliX], [ancilla]);
        }
        until( outcome == Zero )
        fixup {
            // Bring ancilla and target back to |+⟩ state
            if( outcome == One ) {
                Z(ancilla);
                X(target);
                H(target);
            }
        }
        // Return ancilla back to Zero state
        H(ancilla);
    }
}

Notable programmatic features shown in this operation are a more complex fixup part of the loop which involves quantum operations, and the use of AssertProb statements to ascertain the probability of measuring the quantum state at certain specified points in the program. See also Testing and debugging for more information about Assert and AssertProb statements.