QSharp: the Register

19 May 2015

C#, F#, Maths, QSharp, Quantum Computing


The Register type represents a quantum state vector, which can be viewed as analogous to a memory register. The register contains a collection of computational basis states, which represent elements that may be manipulated via gates using matrix algebra.

The register may be constructed by either specifying the number of random qubits to automatically create, or by passing in an array of qubits. Constructing a register with randomly generated qubits is guaranteed to produce a normalised state vector, constructing the register with custom generated qubits may result in the state vector not normalising correctly. The state vector should be normalised before processing any instructions (read more on normalisation).

When the register is constructed, the number of qubits governs the size of the state vector using the formula 2n where n is equal to the number of qubits within the system. Constructing a register with 2 qubits results in a state vector containing 4 elements, constructing a register with 3 qubits results in a state vector containing 8 elements, and so forth (read more about the qubit). During constructing, the amplitude of each state vector element is produced from a multiplication product of the corresponding qubits (more on this feature below).

The Register type contains much of the collection manipulation functionality that you would expect of a standard FCL collection, however due to the nature of the underlying mathematics, ad-hoc element addition and deletion is not supported. From 1.0.0.3, it is not possible to add or remove any state vector elements.

Consider the following C# code, which demonstrates various register functionality:


public static void Main(string[] Arguments) 
{ 
    int iQubits = 3; 

    //  Construct the register with 3 random qubits
    Register oRegister = new Register(iQubits); 

    //  Write qubit information to the console
    WriteQubits(oRegister); 

    //  Write state vector information to the console
    WriteStateVector(oRegister); 

    //  Ensure that the register is normalised
    if (oRegister.IsNormalised() == false) 
    { 
        Console.WriteLine(); 
        Console.WriteLine("Normalising the state vector..."); 

        oRegister.Normalise(); 

        //  Write the updated information to the console
        WriteStateVector(oRegister); 

        Console.WriteLine(); 
    } 

    //  Perform a Z measurement on qubit 1
    string sBasis = "Z"; 
    int iQubitToMeasure = 1; 

    //  The measure result value will be either a 0 or a 1
    int iMeasurementResult = oRegister.Measure(sBasis, iQubitToMeasure); 

    Console.WriteLine(); 
    Console.WriteLine("{0} measurement of qubit {1} result was: {2}", sBasis, iQubitToMeasure, iMeasurementResult); 

    //  Write out the final state
    WriteQubits(oRegister); 
    WriteStateVector(oRegister); 
} 

private static void WriteQubits(Register Register) 
{ 
    Console.WriteLine(); 
    Console.WriteLine("Qubits:"); 

    for (int i = 0; i < Register.Qubits.Count; i++) 
    { 
        Console.Write("({0}|0> + {1}|1>)", Register.Qubits[i].AlphaLabel, Register.Qubits[i].BetaLabel); 

        if (i < (Register.Qubits.Count - 1)) 
        { 
            Console.Write(" (x) "); 
        } 
    } 

    Console.WriteLine(); 
} 

private static void WriteStateVector(Register Register) 
{ 
    Console.WriteLine(); 
    Console.WriteLine("State Vector:"); 
    Console.Write("|v> = ("); 

    for (int i = 0; i < Register.StateVector.Length; i++) 
    { 
        //  Using the overload to control the output
        Console.Write(Register.StateVector[i].ToString(true, true)); 

        if (i < (Register.StateVector.Length - 1)) 
        { 
            Console.Write(" + "); 
        } 
    } 

    Console.WriteLine(")"); 
} 

And again, this time in F#:


type Program() = 

    [<EntryPoint>] 
    static let main argv =  

        let iQubits = 3 

        //  Construct a register, prepared with 3 random qubits
        let oRegister = new Register(iQubits) 

        //  Write qubit information to the console
        Program.WriteQubits(oRegister) 

        //  Write the state vector to the console
        Program.WriteStateVector(oRegister) 

        //  Ensure the register is normalised
        //  A register prepared with random qubits is guaranteed to be normalised
        if oRegister.IsNormalised() = false then 
            printfn "" 
            printfn "Normalising the state vector..." 

            oRegister.Normalise() 

            //  Write the updated information to the console
            Program.WriteStateVector(oRegister); 
            printfn "" 

        //  Perform a Z measurement on qubit 1
        let sBasis = "Z";  
        let iQubitToMeasure = 1;  

        //  The measure result value will be either a 0 or a 1
        let iMeasurementResult = oRegister.Measure(sBasis, iQubitToMeasure) 

        printfn "" 
        printfn "%s measurement of qubit %d result was: %d" sBasis iQubitToMeasure iMeasurementResult 

        //  Write out the final state
        Program.WriteQubits(oRegister) 
        Program.WriteStateVector(oRegister) 

        0 // return an integer exit code

    static member WriteQubits(register: Register) = 

        printfn "" 
        printfn "Qubits:" 

        for i in 0 .. (register.Qubits.Length - 1) do 
            let oQubit = register.Qubits.[i] 
            printf "(%s|0> + %s|1>)" oQubit.AlphaLabel oQubit.BetaLabel 

            if i < (register.Qubits.Length - 1) then 
                printf " (x) " 

        printfn "" 

    static member WriteStateVector(register: Register) =  

        printfn "" 
        printfn "State Vector:" 

        printf "|v> = (" 

        for i in 0 .. (register.StateVector.Length - 1) do 
            //  Using the overload to control the output
            let sState = register.StateVector.[i].ToString(true, true)  
            printf "%s" sState 

            if i < (register.StateVector.Length - 1) then 
                printf " + " 
             
        printfn ")" 

This small program produces output similar to:

Qubits:
(A|0› + B|1›) (x) (C|0› + D|1›) (x) (E|0› + F|1›)

State Vector:
|v› = (0.012 ACE|000› + 0.164 ACF|001› + 0.053 ADE|010› + 0.721 ADF|011› + 0.011 BCE|100› + 0.148 BCF|101› + 0.048 BDE|110› + 0.652 BDF|111›)

Note that here both the coefficient and the algebraic values have been written to the console. At this point in the program, the coefficient is indeed a product of its constituent terms, however as soon as manipulation takes place, this property may no longer valid. At this time, you should consider the algebraic value as simply a way of tracking how a value is moved through the system.

A better way to view these elements is with either the coefficient or the algebraic values displayed:

State Vector:
//    Algebraic values only
|v› = (  ACE|000› +   ACF|001› +   ADE|010› +   ADF|011› +   BCE|100› +   BCF|101› +   BDE|110› +   BDF|111›)
//    Coefficients only
|v› = (0.012|000› + 0.164|001› + 0.053|010› + 0.721|011› + 0.011|100› + 0.148|101› + 0.048|110› + 0.652|111›)

The important thing to remember from this output above is that ACE = 0.012 (etc.) is only a property of the state vector when it is first prepared.

Z measurement of qubit 1 result was: 1

Qubits:
(A|0› + B|1›) (x) (C|0› + D|1›) (x) (E|0› + F|1›)

State Vector:
|v› = ( ACE|000› +  ACF|001› + 0.054 ADE|010› + 0.740 ADF|011› +  BCE|100› +  BCF|101› + 0.049 BDE|110› + 0.669 BDF|111›)

It may not be obvious at first just how 3 qubits turn into 8 computational basis states, however looking closer at the qubit and state vector definitions should help.

The computational basis state ACE|000› is a superposition of A|0›, C|0›, and E|0›. In a similar fashion, the computational basis state BCF|101› is a superposition of B|1›, C|0›, and F|1›. You could actually ignore the algebraic values out the front of the computational basis state completely such that |000› is a superposition of |0›, |0›, and |0›. In a similar fashion, the computational basis state |101› is a superposition of |1›, |0›, and |1›. There will always be some kind of coefficient value however, so it would be more correct to write these values as α|000› or β|101›.

The algebraic value ACE suggests that the coefficient of the |000› qubit is derived as a product of (A|0› * C|0› * E|0›), and it is - at least when the register is initially created, these values are guaranteed to be a mathematical product derived from their constituent parts. Once manipulation of the state vector commences, it may be no longer possible to decompose a coefficient value back to its constituent terms. At this point, the algebraic value should be considered a label useful for only for tracking computations.


 

Copyright © 2024 carlbelle.com