def main(encoding='denseangle_param'):

    qc_name = '1q-qvm'
    qc = get_qc(qc_name)
    num_shots = 1024
    device_qubits = qc.qubits()
    classifier_qubits = device_qubits
    if encoding.lower() == 'wavefunction_param':
        params = np.array([0.45811744, 0.2575122, 0.52902198])
    else:
        params = np.random.rand(3)
    n_layers = 1
    if encoding.lower() == 'denseangle_param':
        encoding_choice = 'denseangle_param'
        init_encoding_params = [np.pi, 2*np.pi]
    elif encoding.lower() == 'wavefunction_param':
        encoding_choice = 'wavefunction_param'
        init_encoding_params = [0]
    elif encoding.lower() == 'superdenseangle_param':
        encoding_choice = 'superdenseangle_param'
        init_encoding_params = [np.pi, 2*np.pi]
    else: raise NotImplementedError
    '''
        # Generate Grid of datapoints to determine and visualise ideal decision boundary
    '''
    data_choice = 'full_vertical_boundary'
    num_grid_points = 2000
    data_grid, grid_true_labels = generate_data(data_choice, num_grid_points)
    data_grid, grid_true_labels = remove_zeros(data_grid, grid_true_labels)

    predicted_labels_grid = ClassificationCircuit(classifier_qubits, data_grid).make_predictions(params, n_layers, encoding_choice, init_encoding_params, \
                                                                num_shots, qc)
    plot_params = {'colors': ['blue', 'orange'], 'alpha': 1, 'size': 70}
    scatter(data_grid, predicted_labels_grid, **plot_params)
    plt.show()
def main(train=False,
         retrain=False,
         data_choice='moons',
         noise_choice='amp_damp_before_measurement',
         noise_values=0.3):

    ### Firstly, generate the dataset:

    data_train, data_test, true_labels_train, true_labels_test = generate_data(
        data_choice, num_points=500, split=True)

    data_train, true_labels_train = remove_zeros(data_train, true_labels_train)
    data_test, true_labels_test = remove_zeros(data_test, true_labels_test)

    encodings = [
        'denseangle_param', 'wavefunction_param', 'superdenseangle_param'
    ]

    minimal_costs, ideal_costs, noisy_costs, noisy_costs_uncorrected = [
        np.ones(len(encodings)) for _ in range(4)
    ]

    qc_name = '1q-qvm'
    n_layers = 1
    qc = get_qc(qc_name)
    num_shots = 1024
    classifier_qubits = qc.qubits()
    init_params = np.random.rand(3)
    ideal_params = []
    ideal_encoding_params = []
    init_encoding_params = []

    for ii, encoding_choice in enumerate(encodings):

        print('\n**********************************')
        print('\nThe encoding is:', encoding_choice)
        print('\n**********************************')

        if encoding_choice.lower() == 'wavefunction_param':
            init_encoding_params.append(
                [0]
            )  # Generalized Wavefunction Encoding initialised to Wavefunction encoding
        else:
            init_encoding_params.append([np.pi, 2 * np.pi])

        if train:

            optimiser = 'Powell'
            params, result_unitary_param = train_classifier(
                qc, num_shots, init_params, encoding_choice,
                init_encoding_params[ii], optimiser, data_train,
                true_labels_train)

            print('The optimised parameters are:', result_unitary_param.x)
            print(
                'These give a cost of:',
                ClassificationCircuit(classifier_qubits,
                                      data_train).build_classifier(
                                          result_unitary_param.x,
                                          encoding_choice,
                                          init_encoding_params[ii], num_shots,
                                          qc, true_labels_train))
            ideal_params.append(result_unitary_param.x)
        else:

            if data_choice.lower() == 'moons':
                if encoding_choice.lower() == 'denseangle_param':
                    ideal_params.append([2.19342064, 1.32972029, -0.18308298])
                elif encoding_choice.lower() == 'superdenseangle_param':
                    ideal_params.append([-0.27365492, 0.83278854, 3.00092961])
                elif encoding_choice.lower() == 'wavefunction':
                    ideal_params.append([0.81647273, 0.41996708, 2.20603541])
                elif encoding_choice.lower() == 'wavefunction_param':
                    ideal_params.append([0.81647273, 0.41996708, 2.20603541])

            elif data_choice.lower() == 'random_vertical_boundary':
                if encoding_choice.lower() == 'denseangle_param':
                    ideal_params.append([1.67814786, 1.56516469, 1.77820848])
                elif encoding_choice.lower() == 'superdenseangle_param':
                    ideal_params.append([1.60642225, 0.23401504, 5.69422628])
                elif encoding_choice.lower() == 'wavefunction':
                    ideal_params.append([0.96291484, 0.18133714, 0.35436732])
                elif encoding_choice.lower() == 'wavefunction_param':
                    ideal_params.append([0.96291484, 0.18133714, 0.35436732])

            elif data_choice.lower() == 'random_diagonal_boundary':
                if encoding_choice.lower() == 'denseangle_param':
                    ideal_params.append([0.8579214, 1.22952647, 4.99408074])
                elif encoding_choice.lower() == 'superdenseangle_param':
                    ideal_params.append([2.0101407, 1.05916291, 1.14570489])
                elif encoding_choice.lower() == 'wavefunction':
                    ideal_params.append([0.69409285, 0.0862859, 0.42872711])
                elif encoding_choice.lower() == 'wavefunction_param':
                    ideal_params.append([0.69409285, 0.0862859, 0.42872711])

            else:
                print('THIS DATASET HAS NOT BEEN TRAINED FOR')
                if encoding_choice.lower() == 'denseangle_param':
                    ideal_params.append(init_params)
                elif encoding_choice.lower() == 'superdenseangle_param':
                    ideal_params.append(init_params)
                elif encoding_choice.lower() == 'wavefunction':
                    ideal_params.append(init_params)
                elif encoding_choice.lower() == 'wavefunction_param':
                    ideal_params.append(init_params)

        ideal_costs[ii] = ClassificationCircuit(
            classifier_qubits, data_test,
            qc).build_classifier(ideal_params[ii], n_layers, encoding_choice,
                                 init_encoding_params[ii], num_shots,
                                 true_labels_test)

        print('In the ideal case, the cost is:', ideal_costs[ii])

        predicted_labels_ideal = ClassificationCircuit(
            classifier_qubits, data_test,
            qc).make_predictions(ideal_params[ii], n_layers, encoding_choice,
                                 init_encoding_params[ii], num_shots)

        noisy_costs_uncorrected[ii] = ClassificationCircuit(classifier_qubits, data_test, qc, noise_choice, noise_values).build_classifier(ideal_params[ii], n_layers,\
                                                                                                                            encoding_choice, init_encoding_params[ii],\
                                                                                                                            num_shots, true_labels_test)
        print('\nWithout encoding training, the noisy cost is:',
              noisy_costs_uncorrected[ii])


        noisy_predictions, number_classified_same = generate_noisy_classification(ideal_params[ii], n_layers, noise_choice, noise_values, \
                                                                                    encoding_choice, init_encoding_params[ii],\
                                                                                    qc, classifier_qubits, num_shots, data_test, predicted_labels_ideal)

        print('The proportion classified differently after noise is:',
              1 - number_classified_same)

        if retrain:
            if encoding_choice.lower() == 'wavefunction_param':
                optimiser = 'L-BFGS-B'
            else:
                optimiser = 'Powell'

            encoding_params, result_encoding_param = train_classifier_encoding(
                qc, noise_choice, noise_values, num_shots, ideal_params[ii],
                encoding_choice, init_encoding_params[ii], optimiser,
                data_train, true_labels_train)
            print('The optimised encoding parameters with noise are:',
                  result_encoding_param.x)
            ideal_encoding_params.append(result_encoding_param.x)

        else:
            if data_choice.lower() == 'moons' and noise_choice.lower(
            ) == 'amp_damp_before_measurement' and isclose(
                    noise_values, 0.3, abs_tol=1e-8):
                if encoding_choice.lower() == 'denseangle_param':
                    ideal_encoding_params.append([2.23855329, 7.57781576])
                elif encoding_choice.lower() == 'superdenseangle_param':
                    ideal_encoding_params.append([3.31296568, 6.34142188])
                elif encoding_choice.lower() == 'wavefunction_param':
                    ideal_encoding_params.append([0.02884417])

            elif data_choice.lower() == 'random_vertical_boundary':
                if encoding_choice.lower() == 'denseangle_param':
                    ideal_encoding_params.append([2.26042559, 8.99138928])
                elif encoding_choice.lower() == 'superdenseangle_param':
                    ideal_encoding_params.append([3.1786475, 8.36712745])
                elif encoding_choice.lower() == 'wavefunction_param':
                    ideal_encoding_params.append([0.01503151])

            elif data_choice.lower() == 'random_diagonal_boundary':
                if encoding_choice.lower() == 'denseangle_param':
                    ideal_encoding_params.append([2.11708966, 5.69354627])
                elif encoding_choice.lower() == 'superdenseangle_param':
                    ideal_encoding_params.append([0.08689283, 6.21166815])
                elif encoding_choice.lower() == 'wavefunction_param':
                    ideal_encoding_params.append([0.0])

            else:
                print('THIS DATASET HAS NOT BEEN TRAINED FOR')
                if encoding_choice.lower() == 'denseangle_param':
                    ideal_encoding_params.append(init_encoding_params[ii])
                elif encoding_choice.lower() == 'superdenseangle_param':
                    ideal_encoding_params.append(init_encoding_params[ii])
                elif encoding_choice.lower() == 'wavefunction_param':
                    ideal_encoding_params.append(init_encoding_params[ii])

        noisy_costs[ii] = ClassificationCircuit(classifier_qubits, data_test, qc, noise_choice, noise_values).build_classifier(ideal_params[ii], n_layers,\
                                                                                                                encoding_choice, ideal_encoding_params[ii],\
                                                                                                                num_shots, true_labels_test)

        print('\nWith encoding training, the noisy cost is:', noisy_costs[ii])

        noisy_predictions, number_classified_same = generate_noisy_classification(ideal_params[ii], n_layers, noise_choice, noise_values, \
                                                                                encoding_choice, ideal_encoding_params[ii], qc, classifier_qubits, num_shots,\
                                                                                data_test, predicted_labels_ideal)
        print(
            'The proportion classified differently after noise with learned encoding is:',
            1 - number_classified_same)

    for ii, encoding_choice in enumerate(encodings):
        print('\nThe encoding is:', encodings[ii])
        print('The ideal params are:', ideal_params[ii])
        print('The ideal encoding params are', ideal_encoding_params[ii])

        print('The ideal cost for encoding', ideal_costs[ii])
        print('The noisy cost with untrained encoding',
              noisy_costs_uncorrected[ii])
        print('The noisy cost with trained encoding', noisy_costs[ii])

    return encodings, ideal_params, init_encoding_params, ideal_encoding_params, ideal_costs, noisy_costs_uncorrected, noisy_costs
예제 #3
0
def main(train=False, encoding_choice='denseangle_param', retrain=False, data_choice='moons', noise=False):
    
    ### Firstly, generate for dataset:
    '''
    # We use the transpose of the (scaled to unit square) Moons dataset in order to see a non-linear decision boundary
    '''
    data_train, data_test, true_labels_train, true_labels_test = generate_data(data_choice, num_points=500, split=True)

    # data_train, true_labels_train   = remove_zeros(data_train, true_labels_train)
    # data_test, true_labels_test     = remove_zeros(data_test, true_labels_test)

    ### Next, generate correct classification parameters for dataset (perfect classification):
    '''
    # Define parameters of model. Start with DenseAngle encoding with fixed parameters.
    '''

    qc_name = '1q-qvm'
    qc = get_qc(qc_name)
    num_shots = 1024
    qubits = qc.qubits()
    init_params = np.random.rand(3)
    if encoding_choice.lower() == 'wavefunction_param':
        init_encoding_params = [ 0 ] # Generalized Wavefunction Encoding initialised to Wavefunction encoding 
    else: 
        init_encoding_params = [np.pi, 2*np.pi]

    if train:
         
        optimiser = 'Powell' 
        params, result_unitary_param = train_classifier(qc, num_shots, init_params, encoding_choice, init_encoding_params, optimiser, data_train, true_labels_train)

        print('The optimised parameters are:', result_unitary_param.x)
        print('These give a cost of:', ClassificationCircuit(qubits, data_train).build_classifier(result_unitary_param.x, encoding_choice, init_encoding_params, num_shots, qc, true_labels_train))
        ideal_params =  result_unitary_param.x
    else:
        if data_choice.lower() == 'moons':
            ### Define Ideal parameters for trained model. Simple model can acheieve classification of about 90 %
            '''
            # 90% Classification parameters for dense angle encoding
            '''
            if encoding_choice.lower() == 'denseangle_param': ideal_params= [ 2.19342064 , 1.32972029, -0.18308298]

            ### Define Ideal parameters for trained model. Simple model can acheieve classification of about 75 %
            '''
            # 73% Classification parameters for superdense angle encoding
            '''
            if encoding_choice.lower() == 'superdenseangle_param': ideal_params =  [-0.27365492,  0.83278854,  3.00092961]

            ### Define Ideal parameters for trained model. Simple model can acheieve classification of about  %
            '''
            # 85% Classification parameters for wavefunction encoding
            '''
            if encoding_choice.lower() == 'wavefunction': ideal_params = [0.81647273, 0.41996708, 2.20603541]
            if encoding_choice.lower() == 'wavefunction_param': ideal_params = [0.81647273, 0.41996708, 2.20603541]
        
        elif data_choice.lower() == 'random_vertical_boundary':
            if encoding_choice.lower() == 'superdenseangle_param': ideal_params = [1.606422245361118, 0.23401504261014927, 5.694226283697996]
        
        elif data_choice.lower() == 'random_diagonal_boundary':

            ### Define Ideal parameters for trained model. Simple model can acheieve classification of about 90 %
            '''
            # 90% Classification parameters for dense angle encoding
            '''
            if encoding_choice.lower() == 'denseangle_param':   ideal_params = [0.8579214,  1.22952647, 4.99408074]
            
            ### Define Ideal parameters for trained model. Simple model can acheieve classification of about  %
            '''
            # % Classification parameters for superdense angle encoding
            '''
            if encoding_choice.lower() == 'superdenseangle_param': ideal_params = [2.0101407,  1.05916291, 1.14570489]
            
            ### Define Ideal parameters for trained model. Simple model can acheieve classification of about  97%
            '''
            # 97% Classification parameters for wavefunction encoding
            '''
            if encoding_choice.lower() == 'wavefunction':           ideal_params = [0.69409285 0.0862859  0.42872711]
            if encoding_choice.lower() == 'wavefunction_param':     ideal_params =  [0.69409285 0.0862859  0.42872711]

        
    print('These give a cost of:', ClassificationCircuit(qubits, data_test).build_classifier(ideal_params, encoding_choice, init_encoding_params, num_shots, qc, true_labels_test))
    predicted_labels_ideal = ClassificationCircuit(qubits, data_test).make_predictions(ideal_params, encoding_choice, init_encoding_params, num_shots, qc)

    # nisqai.visual.scatter(data_test, true_labels_test, predicted_labels)


    ### Overlay decision bounday
    '''
    # Generate Grid of datapoints to determine and visualise ideal decision boundary
    '''
    num_points = 400
    data_grid, grid_true_labels = generate_data('full_vertical_boundary', num_points)
    data_grid, grid_true_labels = remove_zeros(data_grid, grid_true_labels)

    predicted_labels = ClassificationCircuit(qubits, data_test).make_predictions(ideal_params, encoding_choice, init_encoding_params, num_shots, qc)
    plot_params = {'colors': ['blue', 'orange'], 'alpha': 1}
    scatter(data_test, true_labels_test, predicted_labels, **plot_params)

    predicted_labels_grid = ClassificationCircuit(qubits, data_grid).make_predictions(ideal_params, encoding_choice, init_encoding_params, num_shots, qc)

    plot_params = {'colors': ['red', 'green'], 'alpha': 0.2}

    scatter(data_grid, predicted_labels_grid, **plot_params)
    plt.show()


    ## Define noise parameters
    '''
    # Define noise parameters to add to model to determine how classification is affected.
    '''
    if noise: 
        noise_choice = 'amp_damp_before_measurement'
        noise_values = 0.3

    ### Add noise to circuit and classify
    '''
    # Add noise to circuit, and determine number of points classified differently (not mis-classified since we can't achieve perfect classification)
    '''
    if noise:
        noisy_predictions, number_classified_same = generate_noisy_classification(ideal_params, noise_choice, noise_values, encoding_choice, init_encoding_params, qc, num_shots, data_test, predicted_labels_ideal)
        print('The proportion classified differently after noise is:', 1- number_classified_same)

    ## Overlay decision boundary
    '''
    # Generate Grid of datapoints to determine and visualise ideal decision boundary WITH noise added
    '''

    if noise:
        print(noise_choice)
        predicted_labels = ClassificationCircuit(qubits, data_test, noise_choice, noise_values).make_predictions(ideal_params, encoding_choice, init_encoding_params, num_shots, qc)
        plot_params = {'colors': ['blue', 'orange'], 'alpha': 1}
        scatter(data_test, true_labels_test, predicted_labels, **plot_params)

        predicted_labels_grid = ClassificationCircuit(qubits, data_grid, noise_choice, noise_values).make_predictions(ideal_params, encoding_choice, init_encoding_params, num_shots, qc)
        plot_params = {'colors': ['red', 'green'], 'alpha': 0.2}
        scatter(data_grid, predicted_labels_grid, **plot_params)

        plt.show()

    ### Retrain circuit with noise
    '''
    # Given the noise in the circuit, train the parameters of encoding unitary to account for noise. Parameterised unitary parameters are fixed as the ideal ones learned.
    '''
    if retrain:

        if encoding_choice.lower() == 'wavefunction_param': optimiser = 'L-BFGS-B' 
        else:                                               optimiser = 'Powell' 

        if noise:
            encoding_params, result_encoding_param = train_classifier_encoding(qc, noise_choice, noise_values, num_shots, ideal_params, encoding_choice, init_encoding_params, optimiser, data_train, true_labels_train)
            print('The optimised encoding parameters with noise are:', result_encoding_param.x)
            ideal_encoding_params = result_encoding_param.x
        else:
            encoding_params, result_encoding_param = train_classifier_encoding(qc, None, None, num_shots, ideal_params, encoding_choice, init_encoding_params, optimiser, data_train, true_labels_train)
            print('The optimised encoding parameters without noise are:', result_encoding_param.x)
            ideal_encoding_params = result_encoding_param.x
    else:
        ### Define Ideal ENCODING parameters for trained model. Simple model can acheieve classification of about 90 with noise, 93% without noise %
        '''
        # 90% Classification parameters for dense angle encoding
        '''
        if data_choice.lower() == 'moons' and encoding_choice.lower() == 'denseangle_param' and noise:  
            ideal_encoding_params = [2.23855329, 7.57781576]
            '''
            # 93% Classification parameters for dense angle encoding without noise
            '''
        elif data_choice.lower() == 'moons' and encoding_choice.lower() == 'denseangle_param':
            ideal_encoding_params =  [3.05615259, 7.61215138]  # No noise

        ### Define Ideal ENCODING parameters for trained model. Simple model can acheieve classification of about 90 %
        '''
        # NO NOISE  - 74-77% Classification parameters with training for superdense angle encoding  
        # NOISE     - Classification parameters for superdense angle encoding (0.3 amp damp = 20% different classification - 69% accuracy with noise before encoding training)
        #             With learned encoding - 
        '''
        if data_choice.lower() == 'moons' and encoding_choice.lower() == 'superdenseangle_param' and noise: 
            ideal_encoding_params =  [3.31296568, 6.34142188]

        elif data_choice.lower() == 'moons' and encoding_choice.lower() == 'superdenseangle_param':
            ideal_encoding_params = [2.86603822, 6.14328274] # No noise
        
        ### Define Ideal ENCODING parameters for trained model. Simple model can acheieve classification of about 90 %
        '''
        # NO NOISE  - 82-84% Classification parameters with training for generalised wavefunction encoding  
        # NOISE     - Classification parameters for superdense angle encoding (0.3 amp damp = 20% different classification - 78% accuracy with noise before encoding training)
        #             With learned encoding - 
        '''
        print(data_choice.lower(), encoding_choice.lower())
        if data_choice.lower() == 'moons' and encoding_choice.lower() == 'wavefunction_param' and noise: 
            ideal_encoding_params =  [0.02884417]
        elif data_choice.lower() == 'moons' and encoding_choice.lower() == 'wavefunction_param':
            ideal_encoding_params = [0.01582773] # No noise
            

    if noise:
        print('These give a cost with the noisy circuit of:',\
            ClassificationCircuit(qubits, data_test, noise_choice, noise_values).build_classifier(ideal_params, encoding_choice, ideal_encoding_params , num_shots, qc, true_labels_test) )
    else:       
        print('These give a cost with the ideal circuit of:',\
            ClassificationCircuit(qubits, data_test).build_classifier(ideal_params, encoding_choice, ideal_encoding_params , num_shots, qc, true_labels_test) )

    ### Add noise to circuit and classify
    '''
    # Using learned encoding parameters, check again proportion misclassified
    '''
    if noise:
        noisy_predictions, number_classified_same = generate_noisy_classification(ideal_params, noise_choice, noise_values, encoding_choice, ideal_encoding_params, qc, num_shots, data_test, predicted_labels)
        print('The proportion classified differently after noise with learned encoding is:', 1 - number_classified_same)

    ## Overlay decision boundary
    '''
    # Generate Grid of datapoints to determine and visualise ideal decision boundary WITH/WITHOUT noise added
    '''
    if noise:
        predicted_labels = ClassificationCircuit(qubits, data_test, noise_choice, noise_values).make_predictions(ideal_params, encoding_choice, ideal_encoding_params, num_shots, qc)

        plot_params = {'colors': ['blue', 'orange'], 'alpha': 1}
        scatter(data_test,  true_labels_test, predicted_labels, **plot_params)

        predicted_labels_grid = ClassificationCircuit(qubits, data_grid, noise_choice, noise_values).make_predictions(ideal_params, encoding_choice, ideal_encoding_params, num_shots, qc)
        
        plot_params = {'colors': ['red', 'green'], 'alpha': 0.2}
        scatter(data_grid, predicted_labels_grid, **plot_params)
        plt.show()
    else:
        predicted_labels = ClassificationCircuit(qubits, data_test).make_predictions(ideal_params, encoding_choice, ideal_encoding_params, num_shots, qc)
        
        plot_params = {'colors': ['blue', 'orange'], 'alpha': 1}
        scatter(data_test, true_labels_test,  predicted_labels, **plot_params)
        
        predicted_labels_grid = ClassificationCircuit(qubits, data_grid).make_predictions(ideal_params, encoding_choice, ideal_encoding_params, num_shots, qc)
        
        plot_params = {'colors': ['red', 'green'], 'alpha': 0.2}
        scatter(data_grid, predicted_labels_grid, **plot_params)
        plt.show()
def main(train=False,
         encoding='denseangle_param',
         ideal=False,
         noise=False,
         analytic=False,
         compare=False):
    """
    # Find optimal parameters for linear decision boundary and add noise
    """

    ### Firstly, generate for dataset:
    '''
    # We use the transpose of the (scaled to unit square) Moons dataset in order to see a non-linear decision boundary
    '''
    data_vertical_train, data_vertical_test, true_labels_train, true_labels_test = generate_data(
        'random_vertical_boundary', num_points=500, split=True)

    ### Next, generate correct classification parameters for dataset (perfect classification):
    '''
    # Define parameters of model. Start with DenseAngle encoding with fixed parameters.
    '''

    qc_name = '1q-qvm'
    qc = get_qc(qc_name)
    num_shots = 1024
    device_qubits = qc.qubits()
    classifier_qubits = device_qubits
    n_layers = 1
    init_params = np.random.rand(3)
    if encoding.lower() == 'denseangle_param':
        encoding_choice = 'denseangle_param'
        # init_encoding_params = [np.pi, 2*np.pi]
        init_encoding_params = [np.pi, 2 * np.pi]

    elif encoding.lower() == 'wavefunction' or encoding.lower(
    ) == 'wavefunction_param':
        encoding_choice = 'wavefunction_param'
        init_encoding_params = [0]

    optimiser = 'Powell'

    if train:
        ### Train model, and check classification result of ideal parameters found
        '''
        # Train model using scipy.optimize
        '''
        params, result_unitary_param = train_classifier(
            qc, num_shots, init_params, encoding_choice, init_encoding_params,
            optimiser, data_vertical_train, true_labels_train)
        print('The optimised parameters are:', result_unitary_param.x)
        print('These give a cost of:', ClassificationCircuit(classifier_qubits, data_vertical_train).build_classifier(result_unitary_param.x, n_layers, \
                                                                            encoding_choice, init_encoding_params, num_shots, qc, true_labels_train))
        ideal_params_vertical = result_unitary_param.x
    else:
        ### Define Ideal parameters for trained model learned from previous. Simple model can acheieve classification of about 90 %

        if encoding_choice.lower() == 'denseangle_param':
            '''
            # 100% Classification parameters (modulo points on the boundary)
            '''
            # ideal_params_vertical = [3.8208,1.525,0.0808]
            ideal_params_vertical = [1.67814786, 1.56516469, 1.77820848]
        elif encoding_choice.lower() == 'wavefunction_param':
            '''
            # 78% Classification parameters (modulo points on the boundary)
            '''
            ideal_params_vertical = [2.2921198, 0.61375299, -5.15252796]

    plt.rcParams.update({
        "font.size": 20,
        "font.serif": "Computer Modern Roman"
    })

    ### Overlay decision bounday
    '''
    # Generate Grid of datapoints to determine and visualise ideal decision boundary
    '''
    data_choice = 'full_vertical_boundary'
    num__grid_points = 1000
    data_grid, grid_true_labels = generate_data(data_choice, num__grid_points)
    data_grid, grid_true_labels = remove_zeros(data_grid, grid_true_labels)

    if ideal:

        predicted_labels_test = ClassificationCircuit(classifier_qubits, data_vertical_test, qc).make_predictions(ideal_params_vertical,  n_layers, \
                                                                                        encoding_choice, init_encoding_params, num_shots)
        plot_params = {'colors': ['blue', 'orange'], 'alpha': 1}
        scatter(data_vertical_test, true_labels_test, predicted_labels_test,
                **plot_params)

        predicted_labels_grid = ClassificationCircuit(classifier_qubits, data_grid, qc).make_predictions(ideal_params_vertical, n_layers,\
                                                                             encoding_choice, init_encoding_params, num_shots)
        plot_params = {'colors': ['red', 'green'], 'alpha': 0.2}
        scatter(data_grid, predicted_labels_grid, **plot_params)
        plt.show()

    ### Define noise parameters
    '''
        # Define noise parameters to add to model to determine how classification is affected.
    '''

    noise_choice = 'amp_damp_before_measurement'
    noise_values = 0.4

    ### Add noise to circuit and classify
    '''
        # Add noise to circuit, and determine number of points classified differently (not mis-classified since we can't achieve perfect classification)
    '''

    if noise:
        ## Overlay decision boundary
        '''
        # Generate Grid of datapoints to determine and visualise ideal decision boundary WITH noise added
        '''
        predicted_labels_test_noise = ClassificationCircuit(classifier_qubits, data_vertical_test, qc,\
                     noise_choice, noise_values).make_predictions(ideal_params_vertical, n_layers, encoding_choice, init_encoding_params, num_shots)
        plot_params = {'colors': ['blue', 'orange'], 'alpha': 1}
        scatter(data_vertical_test, true_labels_test,
                predicted_labels_test_noise, **plot_params)

        predicted_labels_grid_noise = ClassificationCircuit(classifier_qubits, data_grid, qc,\
                                                            noise_choice, noise_values).make_predictions(ideal_params_vertical, n_layers, \
                                                            encoding_choice, init_encoding_params, num_shots)

        plot_params = {'colors': ['red', 'green'], 'alpha': 0.2}
        scatter(data_grid, predicted_labels_grid_noise, **plot_params)
        plt.show()
    '''
    # Define function to compute points which will remian correctly classified after noise is added
    '''
    def correct_function(data_point, params, encoding_choice, encoding_params):
        [alpha_1, alpha_2, alpha_3] = params
        [x_1, x_2] = data_point

        if encoding_choice.lower() == 'denseangle_param':
            [theta, phi] = encoding_params
            function = (np.sin(alpha_2) )**2 * ( np.cos(theta * x_1) )**2  + (np.cos(alpha_2))**2 * (np.sin(theta * x_1))**2 \
                        + ((1/2)*(np.sin(2 * alpha_2) * np.sin(2 * theta * x_1) * np.exp(-1j*(2 * alpha_3 + phi * x_2)))).real
        elif encoding_choice.lower() == 'wavefunction_param':
            [theta] = encoding_params
            l2_norm = np.linalg.norm(np.array([x_1, x_2]))**2
            function = (np.sin(alpha_2)**2 ) * ( x_1**2/(l2_norm) )  + (np.cos(alpha_2)**2) * (x_2**2/(l2_norm)) \
                        + ((1/(2*l2_norm))*(np.sin(2 * alpha_2) * (x_1) * (x_2) * np.exp(-1j*(2 * alpha_3)))).real

        return function

    def compute_analytic_misclassifed_condition(data, params, encoding_choice,
                                                encoding_params,
                                                noise_strength, true_labels):
        correct_classification_labels = []
        for ii, data_point in enumerate(data):

            function = correct_function(data_point, params, encoding_choice,
                                        encoding_params)
            if true_labels[ii] == 0:
                correct_classification_labels.append(
                    0
                )  # If datapoint was zero originally, it will be correctly classified regardless of noise

            else:
                if function > 1 / (
                        2 * (1 - noise_strength)
                ):  # If data point was classified as 1, it will be correctly classified if condition is met.
                    correct_classification_labels.append(0)

                else:
                    correct_classification_labels.append(1)
        number_robust = 1 - sum(correct_classification_labels) / len(
            correct_classification_labels)  # percentage of misclassified points
        return np.array(correct_classification_labels), number_robust

    def plot_number_misclassified_amp_damp(ideal_params, num_shots, num_points,
                                           qc, noise_values):

        points_noise_inc = []

        data_vertical_train, data_vertical_test, true_labels_train, true_labels_test = generate_data('random_vertical_boundary',\
                                                                                                     num_points=num_points, split=True)
        interval = 0.2
        encoding_choice = 'denseangle_param'
        theta = np.arange(0, 2 * np.pi, interval)
        phi = np.arange(0, 2 * np.pi, interval)
        X, Y = np.meshgrid(theta, phi)
        noise_choice = 'amp_damp_before_measurement'
        test_acc_ideal = np.zeros((theta.shape[0], phi.shape[0]), dtype=float)

        test_acc_noise = np.zeros((theta.shape[0], phi.shape[0]), dtype=float)
        number_robust = np.zeros((theta.shape[0], phi.shape[0]), dtype=float)

        for ii, t in enumerate(theta):
            for jj, p in enumerate(phi):
                temp_encoding_params = [t, p]

                # Classification of encoding parameters *without* noise
                ideal_predictions, test_acc_ideal[ii,jj]  = generate_noisy_classification(ideal_params, 1, None, None,\
                                                                                                    encoding_choice, temp_encoding_params, qc,\
                                                                                                    classifier_qubits, num_shots, data_vertical_test, true_labels_test)

                # Learned encoding parameters *with* noise
                noisy_predictions, test_acc_noise[ii,jj]  = generate_noisy_classification(ideal_params, 1, noise_choice, noise_values,\
                                                                                                    encoding_choice, temp_encoding_params, qc,\
                                                                                                    classifier_qubits, num_shots, data_vertical_test, true_labels_test)
                # Number expected to be robust under analytic condition
                correct_classification_labels, number_robust[ii, jj] = compute_analytic_misclassifed_condition(data_vertical_test, ideal_params_vertical,\
                                                                                                                encoding_choice, temp_encoding_params,\
                                                                                                                noise_values, true_labels_test)

                print('Theta, Phi is:', t, p)
                print('Test accuracy ideal:', test_acc_ideal[ii, jj])
                print('Test accuracy with noise:', test_acc_noise[ii, jj])
                print('Proportion robust:', number_robust[ii, jj])

        max_acc_indices_ideal = np.unravel_index(
            np.argmax(test_acc_ideal, axis=None), test_acc_ideal.shape)
        max_acc_indices = np.unravel_index(
            np.argmax(test_acc_noise, axis=None), test_acc_noise.shape)
        max_robust_indices = np.unravel_index(
            np.argmax(number_robust, axis=None), number_robust.shape)

        plt.rcParams.update({"font.size": 14, "font.family": "serif"})

        # ----------------------
        # Uncomment below for 3d plots
        # ----------------------

        # fig = plt.figure(figsize=plt.figaspect(0.33))
        # ax1 = fig.add_subplot(1, 3, 1, projection='3d')
        # surf1 = ax1.plot_surface(X, Y, test_acc_ideal, cmap=cm.coolwarm_r,linewidth=0, antialiased=False)
        # # ax1.set_zlim(0.45, 1.01)
        # cbar1 =fig.colorbar(surf1)
        # cbar1.ax.set_ylabel('Test Accuracy')

        # ax2 = fig.add_subplot(1, 3, 2, projection='3d')
        # surf2 = ax2.plot_surface(X, Y, test_acc_noise, cmap=cm.coolwarm_r, linewidth=0, antialiased=False)
        # # ax2.set_zlim(0.45, 1.01)
        # cbar2 = fig.colorbar(surf2)
        # cbar2.ax.set_ylabel('Test Accuracy')

        # ax3 = fig.add_subplot(1, 3, 3, projection='3d')

        # surf3 = ax3.plot_surface(X, Y, number_robust, cmap=cm.PuOr, linewidth=0, antialiased=False)
        # cbar3 = fig.colorbar(surf3)
        # cbar3.ax.set_ylabel('Proportion robust')

        # ax1.set_ylabel(r'$\theta (rads)$')
        # ax1.set_xlabel(r'$\phi (rads)$' )
        # ax1.set_title(  'Best accuracy ideal: '             + str( round( test_acc_ideal[max_acc_indices_ideal] , 2) ) \
        #                 + '\nBest accuracy with noise: '    + str( round( test_acc_noise[max_acc_indices_ideal] , 2) ) \
        #                 + '\nRobustness: '                  + str( round( number_robust[max_acc_indices_ideal]  , 2) ) + '\n' \
        #                 + r'$[\theta, \phi]$ = '            + '['+str(round(theta [ max_acc_indices_ideal[0] ], 2) )+ ', ' + str( round( phi [ max_acc_indices_ideal[1] ] , 2) ) + ']' )

        # ax2.set_ylabel(r'$\theta (rads)$')
        # ax2.set_xlabel(r'$\phi (rads)$' )
        # ax2.set_title(  'Best accuracy with noise: '    + str( round( test_acc_noise[max_acc_indices]   , 2) ) \
        #                 + '\nBest accuracy ideal: '     + str( round( test_acc_ideal[max_acc_indices]   , 2) ) \
        #                 + '\nRobustness: '               + str( round( number_robust[max_acc_indices]    , 2) ) + '\n' \
        #                 + r'$[\theta, \phi]$ = '        + '['+str(theta [ max_acc_indices[0] ])+ ', ' + str(round( phi [ max_acc_indices[1] ], 2) ) + ']' )

        # ax3.set_ylabel(r'$\theta (rads)$')
        # ax3.set_xlabel(r'$\phi (rads)$' )
        # ax3.set_title('Max. robustness: '               + str( round( number_robust[max_robust_indices]  , 2) ) \
        #                 +'\nBest accuracy with noise: ' + str( round( test_acc_noise[max_robust_indices] , 2) ) \
        #                 +'\nBest accuracy ideal: '      + str( round( test_acc_ideal[max_robust_indices] , 2) ) + '\n'\
        #                 +r'$[\theta, \phi]$ = '         + '[' + str(theta [ max_robust_indices[0] ]) \
        #                                                 + ', ' + str(phi [ max_robust_indices[1] ] ) + ']' )

        ## 2D PLOTS
        fig, ax = plt.subplots(1, 3)
        im0 = ax[0].imshow(test_acc_ideal,
                           cmap=cm.coolwarm_r,
                           extent=[0, 2 * np.pi, 2 * np.pi, 0])
        divider = make_axes_locatable(ax[0])
        cax = divider.append_axes('right', size='5%', pad=0.1)
        cbar0 = fig.colorbar(im0, cax=cax, orientation='vertical')

        cbar0.ax.set_ylabel('Test Accuracy')

        im1 = ax[1].imshow(test_acc_noise,
                           cmap=cm.coolwarm_r,
                           extent=[0, 2 * np.pi, 2 * np.pi, 0])
        divider = make_axes_locatable(ax[1])
        cax = divider.append_axes('right', size='5%', pad=0.1)
        cbar1 = fig.colorbar(im1, cax=cax, orientation='vertical')

        cbar1.ax.set_ylabel('Test Accuracy')

        im2 = ax[2].imshow(number_robust,
                           cmap=cm.PuOr,
                           extent=[0, 2 * np.pi, 2 * np.pi, 0])
        divider = make_axes_locatable(ax[2])
        cax = divider.append_axes('right', size='5%', pad=0.1)
        cbar2 = fig.colorbar(im2, cax=cax, orientation='vertical')

        cbar2.ax.set_ylabel('Proportion robust')

        ax[0].set_title(  'Best accuracy ideal: '             + str( round( test_acc_ideal[max_acc_indices_ideal] , 2) ) \
                        + '\nBest accuracy with noise: '    + str( round( test_acc_noise[max_acc_indices_ideal] , 2) ) \
                        + '\nRobustness: '                  + str( round( number_robust[max_acc_indices_ideal]  , 2) ) + '\n' \
                        + r'$[\theta, \phi]$ = '            + '['+str(round(theta [ max_acc_indices_ideal[0] ], 2) )+ ', ' + str( round( phi [ max_acc_indices_ideal[1] ] , 2) ) + ']' )

        ax[1].set_title(  'Best accuracy with noise: '    + str( round( test_acc_noise[max_acc_indices]   , 2) ) \
                        + '\nBest accuracy ideal: '     + str( round( test_acc_ideal[max_acc_indices]   , 2) ) \
                        + '\nRobustness: '               + str( round( number_robust[max_acc_indices]    , 2) ) + '\n' \
                        + r'$[\theta, \phi]$ = '        + '['+str(theta [ max_acc_indices[0] ])+ ', ' + str(round( phi [ max_acc_indices[1] ], 2) ) + ']' )

        ax[2].set_title('Max. robustness: '               + str( round( number_robust[max_robust_indices]  , 2) ) \
                        +'\nBest accuracy with noise: ' + str( round( test_acc_noise[max_robust_indices] , 2) ) \
                        +'\nBest accuracy ideal: '      + str( round( test_acc_ideal[max_robust_indices] , 2) ) + '\n'\
                        +r'$[\theta, \phi]$ = '         + '[' + str(theta [ max_robust_indices[0] ]) \
                                                        + ', ' + str(phi [ max_robust_indices[1] ] ) + ']' )

        return

    if analytic:
        correct_classification_labels, number_robust = compute_analytic_misclassifed_condition(data_grid, ideal_params_vertical,\
                                                                                                encoding_choice, init_encoding_params,\
                                                                                                noise_values, grid_true_labels)
        plot_params = {'colors': ['blue', 'black'], 'alpha': 0.3}

        scatter(data_grid, correct_classification_labels, **plot_params)
        plt.show()

    if compare:
        plot_number_misclassified_amp_damp(ideal_params_vertical, num_shots,
                                           500, qc, noise_values)
        plt.show()
def main(train=False,
         retrain=False,
         qc_name='2q-qvm',
         data_choice='iris',
         noise_choice='decoherence_symmetric_ro',
         noise_values=None):

    ### Firstly, generate for dataset:

    data_train, data_test, true_labels_train, true_labels_test = generate_data(
        data_choice, num_points=500, split=True)

    data_train, true_labels_train = remove_zeros(data_train, true_labels_train)
    data_test, true_labels_test = remove_zeros(data_test, true_labels_test)

    # encodings = [ 'denseangle_param','superdenseangle_param', 'wavefunction_param' ]
    encodings = ['superdenseangle_param']
    minimal_costs, ideal_costs, noisy_costs, noisy_costs_uncorrected = [
        np.ones(len(encodings)) for _ in range(4)
    ]

    # qc_name = '2q-qvm'
    qc = get_qc(qc_name)
    num_shots = 1024
    device_qubits = qc.qubits()
    classifier_qubits = device_qubits
    n_layers = 1
    # init_params = np.random.rand(len(qubits),n_layers,  3)
    # init_params = np.random.rand(len(qubits),n_layers,  3)
    # init_params = np.random.rand((7)) # TTN
    init_params = np.random.rand((12))  # General 2 qubit unitary

    ideal_params = []
    ideal_encoding_params = []
    init_encoding_params = []

    for ii, encoding_choice in enumerate(encodings):

        print('\n**********************************')
        print('\nThe encoding is:', encoding_choice)
        print('\n**********************************')

        if encoding_choice.lower() == 'wavefunction_param':
            init_encoding_params.append(
                [0]
            )  # Generalized Wavefunction Encoding initialised to Wavefunction encoding
        else:
            init_encoding_params.append([np.pi, 2 * np.pi])

        if train:

            optimiser = 'Powell'
            params, result_unitary_param = train_classifier(qc, classifier_qubits, num_shots, init_params, n_layers,\
                                                            encoding_choice, init_encoding_params[ii],\
                                                            optimiser, data_train, true_labels_train)

            print('The optimised parameters are:', result_unitary_param.x)
            print('These give a cost of:', ClassificationCircuit(classifier_qubits, data_train, qc).build_classifier(result_unitary_param.x, n_layers,\
                                                                                                        encoding_choice, init_encoding_params[ii],\
                                                                                                        num_shots, true_labels_train))
            ideal_params.append(result_unitary_param.x)
        else:

            if data_choice.lower() == 'iris':
                if encoding_choice.lower() == 'denseangle_param':                    ideal_params.append([ 2.02589489, 1.24358318, -0.6929718,\
                                             0.85764484, 2.7572075, 1.12317156, \
                                             4.01974889, 0.30921738, 0.88106973,\
                                             1.461694, 0.367226, 5.01508911 ])

                    # elif    encoding_choice.lower() == 'superdenseangle_param':         ideal_params.append([1.1617383, -0.05837820, -0.7216498,\
                    #                                                                                         1.3195103, 0.52933357, 1.2854939,
                    #                                                                                         1.2097700, 0.26920745, 0.4239539,
                    #                                                                                         1.2999367, 0.37921617, 0.790320211])

                elif encoding_choice.lower() == 'superdenseangle_param':                    ideal_params.append([ 2.91988765,  1.85012634,  1.75234515,  0.81420946,\
                                        1.286217,0.62223565, 0.8356422, 1.36681893,  0.58494563,\
                                         -0.02031075,  0.80183355,  6.92525521])

                elif encoding_choice.lower() == 'wavefunction':                    ideal_params.append([ 2.37732073, 1.01449711, 1.12025344,\
                                -0.087440021, 0.46937127, 2.14387135, \
                                0.4696964, 1.444409282, 0.14412614,\
                                1.4825742, 1.0817654, 6.30943537 ])

                elif encoding_choice.lower() == 'wavefunction_param':                    ideal_params.append([ 2.37732073, 1.01449711, 1.12025344,\
                                -0.087440021, 0.46937127, 2.14387135, \
                                0.4696964, 1.444409282, 0.14412614,\
                                1.4825742, 1.0817654, 6.30943537 ])

        ideal_costs[ii] = ClassificationCircuit(classifier_qubits, data_test, qc).build_classifier(ideal_params[ii], n_layers, \
                                                                                                encoding_choice, init_encoding_params[ii], \
                                                                                                num_shots, true_labels_test)

        print('In the ideal case, the cost is:', ideal_costs[ii])

        predicted_labels_ideal = ClassificationCircuit(classifier_qubits, data_test, qc).make_predictions(ideal_params[ii], n_layers,\
                                                                                                    encoding_choice, init_encoding_params[ii],\
                                                                                                    num_shots)

        if noise_choice is not None:

            noisy_costs_uncorrected[ii] = ClassificationCircuit(classifier_qubits, data_test, qc, \
                                                            noise_choice, noise_values).build_classifier(ideal_params[ii], n_layers,\
                                                                                                        encoding_choice, init_encoding_params[ii],\
                                                                                                        num_shots, true_labels_test)
            print('\nWithout encoding training, the noisy cost is:',
                  noisy_costs_uncorrected[ii])

            noisy_predictions, number_classified_same = generate_noisy_classification(ideal_params[ii], n_layers,\
                                                                                    noise_choice, noise_values,\
                                                                                    encoding_choice, init_encoding_params[ii],\
                                                                                    qc, classifier_qubits, num_shots, data_test, predicted_labels_ideal)

            print('The proportion classified differently after noise is:',
                  1 - number_classified_same)

            if retrain:
                if encoding_choice.lower() == 'wavefunction_param':
                    optimiser = 'L-BFGS-B'
                else:
                    optimiser = 'Powell'

                encoding_params, result_encoding_param = train_classifier_encoding(
                    qc, noise_choice, noise_values, num_shots,
                    ideal_params[ii], encoding_choice,
                    init_encoding_params[ii], optimiser, data_train,
                    true_labels_train)
                print('The optimised encoding parameters with noise are:',
                      result_encoding_param.x)
                ideal_encoding_params.append(result_encoding_param.x)

            else:
                if data_choice.lower() == 'iris' and noise_choice.lower(
                ) == 'decoherence_symmetric_ro':

                    if encoding_choice.lower() == 'denseangle_param':
                        ideal_encoding_params.append(init_encoding_params[ii])
                    elif encoding_choice.lower() == 'superdenseangle_param':
                        ideal_encoding_params.append(init_encoding_params[ii])
                    elif encoding_choice.lower() == 'wavefunction_param':
                        ideal_encoding_params.append(init_encoding_params[ii])

                else:
                    print('THIS DATASET HAS NOT BEEN TRAINED FOR')
                    if encoding_choice.lower() == 'denseangle_param':
                        ideal_encoding_params.append(init_encoding_params[ii])
                    elif encoding_choice.lower() == 'superdenseangle_param':
                        ideal_encoding_params.append(init_encoding_params[ii])
                    elif encoding_choice.lower() == 'wavefunction_param':
                        ideal_encoding_params.append(init_encoding_params[ii])

            noisy_costs[ii] = ClassificationCircuit(classifier_qubits, data_test, qc,  \
                                                    noise_choice, noise_values).build_classifier(ideal_params[ii], n_layers, \
                                                                                                encoding_choice, ideal_encoding_params[ii],\
                                                                                                num_shots, true_labels_test)

            print('\nWith encoding training, the noisy cost is:',
                  noisy_costs[ii])

            noisy_predictions, number_classified_same = generate_noisy_classification(ideal_params[ii], n_layers, \
                                                                                        noise_choice, noise_values,\
                                                                                        encoding_choice, ideal_encoding_params[ii],\
                                                                                        qc, classifier_qubits, num_shots, data_test, predicted_labels_ideal)

            print(
                'The proportion classified differently after noise with learned encoding is:',
                1 - number_classified_same)

    # for ii, encoding_choice in enumerate(encodings):
    # print('\nThe encoding is:'                      , encodings[ii]                 )
    # print('The ideal params are:'                   , ideal_params[ii]              )
    # print('The ideal encoding params are'           , ideal_encoding_params[ii]     )

    # print('The ideal cost for encoding'             , ideal_costs[ii]               )
    # print('The noisy cost with untrained encoding'  , noisy_costs_uncorrected[ii]   )
    # print('The noisy cost with trained encoding'    , noisy_costs[ii]               )

    return encodings, ideal_params, init_encoding_params, ideal_encoding_params, ideal_costs, noisy_costs_uncorrected, noisy_costs