P.3 Let's be rational

In Codercise P.3.1 of this node, we have to find the phase angle between the two values of the estimation window. As I understand, the solution steps are:
1- from qpe function, we find the largest two probablity values.
2- convert the two values to fractional binary.
3- convert the fractional binary values in step2 to decimal.
Using these steps , I got (0.0625, 0.03125)

as largest estimation values of U = np.array([[1, 0], [0, np.exp((2*np.pi*1j/7))]])

But this solution is not correct:

dev = qml.device("default.qubit", wires=10)

def fractional_binary_to_decimal(binary_fraction, wires):
    return float(binary_fraction/ 2 ** len(wires))

def phase_window(probs, estimation_wires):
    """ Given an array of probabilities, return the phase window of the 
    unitary's eigenvalue
    
    Args: 
        probs (array[float]): Probabilities on the estimation wires.
        estimation_wires (list[int]): List of estimation wires
    
    Returns:
        (float, float): the lower and upper bound of the phase
    """

    ##################
    # YOUR CODE HERE #
    ################## 
    keys = [-1,-1]
    values = [np.nextafter(0, 1) , np.nextafter(0, 1)]
    
    def swap(values , keys):
        tempv = values[1]
        tempk = keys[1]
        values[1] = values[0]
        keys[1] =  keys[0]
        values[0] = tempv
        keys[0] = tempk
     #Step 1: find the two largest prob values
    for i , item in zip(range(len(probs)) , probs):
        if(item > values[0]):
            values[0] = item
            keys[0]  = i
            if( values[0] >  values[1]):
                swap(values , keys)
    bounds = []

    for j in range(len(keys)):
        sum1 = 1/(2**( keys[j])) # step 2: convert them to fract binaries
        bounds.append(fractional_binary_to_decimal(sum1, estimation_wires)) # step 3
    
    bound_1 = bounds[1] # MOST LIKELY OUTCOME
    bound_2 = bounds[0] # SECOND MOST LIKELY OUTCOME
    return (bound_1, bound_2)


# Test your solution

# You can increase the number of estimation wires to a maximum of range(0, 9)
estimation_wires = range(0, 3)

# The target is set to the last qubit
target_wires = [9]

# Define the unitary
U = np.array([[1, 0], [0, np.exp((2*np.pi*1j/7))]])

probs = qpe(U, estimation_wires, target_wires)
print(probs)
print(phase_window(probs, estimation_wires))

# MODIFY TO TRUE AFTER TESTING YOUR SOLUTION
done = True

Hey @Afrah! Thanks for your question :grin:!

Within the phase_window function, bound_1 and bound_2 are the fractional binary representations of the index belonging to the largest and second largest elements in probs (as measured on the estimation_wires). Once you obtain the 2 largest entries in probs, you just need to use fractional_binary_to_decimal to convert them to the correct representation.

Your listed solution steps seem completely fine to me, but your solution leads me to believe that you’re over-complicating the problem! I’ll give you a hint:

Let’s say I have a list a = [4, 1, 3, 6, 5, 2]. Clearly, its entries are 1, 2, 3, 4, 5, 6, but they’re jumbled up. numpy.argsort is a function that provides a list of indices of a given array such that when the array is re-indexed with those indices, the array is sorted from smallest to largest. Here’s the example:

import numpy as np

a =  [4, 1, 3, 6, 5, 2]
print(np.argsort(a))

# array([1, 5, 2, 0, 4, 3])

for sorted_index in np.argsort(a):
    print(a[sorted_index])

'''
1
2
3
4
5
6
'''

I hope that steers you in a better direction! Let me know if you’re still stuck :smile:.

Thank you for your reply. I realized my error. It is here:

for j in range(len(keys)):
        sum1 = 1/(2**( keys[j])) # step 2: convert them to fract binaries
        bounds.append(fractional_binary_to_decimal(sum1, estimation_wires)) # step 3
    

should be:

for j in range(len(keys)):
        sum1 = keys[j] # step 2: convert them to fract binaries
        bounds.append(fractional_binary_to_decimal(sum1, estimation_wires)) # step 3

i.e index of the largest elements is the solution as you said.
Can I ask about Codercise P.4.3. here. I can not pass the exercise which is similar to the above one. I can not figure out why the anwer is wrong:

dev = qml.device("default.qubit", wires=6)
estimation_wires = [0, 1, 2, 3]
target_wires = [4, 5]

def prepare_eigenvector_superposition(alpha, beta, gamma,
delta):
    # Normalize alpha, beta, gamma, and delta
    norm_squared = np.abs(alpha) ** 2 + np.abs(beta) ** 2 +\
    np.abs(gamma) ** 2 + np.abs(delta) ** 2 
    norm = np.sqrt(norm_squared)
    state = np.array([alpha/norm, beta/norm, gamma/norm,
    delta/norm])
    
    # Prepare the state
    qml.MottonenStatePreparation(state, wires=target_wires)


@qml.qnode(dev)
def qpe(unitary):
    """Estimate the phase for a given unitary.
    
    Args:
        unitary (array[complex]): A unitary matrix.

    Returns:
        probs (array[float]): Probabilities on the estimation
        wires.
    """
    
    # MODIFY ALPHA, BETA, GAMMA, DELTA TO PREPARE 
    #EIGENVECTOR 
    prepare_eigenvector_superposition(0.5, 0.5,0.5, 0.5)
    #prepare_eigenvector_superposition(0.25 , 0.25 , 0.25 , 0.25)
    #prepare_eigenvector_superposition(0, 1, 0, 0)
    # OR UNCOMMENT LINES ABOVE TO PREPARE THE STATE OF 
    #YOUR CHOICE
    
    qml.QuantumPhaseEstimation(
        unitary,
        target_wires=target_wires,
        estimation_wires=estimation_wires,
    )
    return qml.probs(wires=estimation_wires)
    
def phase_windowProb(probs, estimation_wires):
    """ Given an array of probabilities, return the phase window of the 
    unitary's eigenvalue
    
    Args: 
        probs (array[float]): Probabilities on the estimation wires.
        estimation_wires (list[int]): List of estimation wires
    
    Returns:
        (float, float): the lower and upper bound of the phase
    """
    keys = [-1,-1]
    values = [np.nextafter(0, 1) , np.nextafter(0, 1)]
    def fractional_binary_to_decimal(binary_fraction, wires):
        return float(binary_fraction/ 2 ** len(wires))

    def swap(values , keys):
        tempv = values[1]
        tempk = keys[1]
        values[1] = values[0]
        keys[1] =  keys[0]
        values[0] = tempv
        keys[0] = tempk

    for i , item in zip(range(len(probs)) , probs):
        if(item > values[0]):
            values[0] = item
            keys[0]  = i
            if( values[0] >  values[1]):
                swap(values , keys)
    bounds = []

    for j in range(len(keys)):
        #sum1 = 1/(2**( keys[j]))
        bounds.append(fractional_binary_to_decimal(keys[j], 
        estimation_wires))
    
    bound_1 = values[1] # MOST LIKELY OUTCOME
    bound_2 = values[0] # SECOND MOST LIKELY OUTCOME
    return (bound_1, bound_2)
    

# UNCOMMENT THE LINE CORRESPONDING TO THE MATRIX YOU'D LIKE 
# TO ESTIMATE PHASES OF
#U = qml.CZ.compute_matrix()
# U = qml.CRZ.compute_matrix(0.4)
U = qml.CRX.compute_matrix(1.0/3.0)
#U = qml.CRot.compute_matrix(0.9, 0.7, 0.4)

probs = qpe(U)
print(probs)
p1 , p2 = phase_windowProb(probs, estimation_wires)
mystery_phase = p2 # MODIFY THIS  ... ## I got 0.1451 which is wrong!

I think you might be over-complicating it again! Try working things out a bit on pen and paper, then do some trial and error to modify the values and uncomment lines :). You shouldn’t need to define extra functions.