├── Doxyfile ├── LICENSE ├── README.md ├── analysis.py ├── auxiliary_functions.py ├── classical_kernel.py ├── cost_function_train.py ├── cost_functions.py ├── feydy_sinkhorn.py ├── file_operations_in.py ├── file_operations_out.py ├── kernel_functions.py ├── param_init.py ├── plots.py ├── quantum_kernel.py ├── run_and_compare.py ├── sample_gen.py ├── sinkhorn_functions.py ├── stein_functions.py ├── stein_score.py ├── train_generation.py ├── train_plot.py └── unit_test.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 BrianCoyle 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IsingBornMachine 2 | Implementation of Quantum Ising Born Machine using Rigetti Forest Platform, with two approaches: 3 | 1. Training using MMD as a Cost function. 4 | 2. Training using the Stein Discrepancy as a Cost Function 5 | 3. Traing using the Sinkhorn Divergence as a Cost Function. 6 | Both of the above have the option to run using a 'Quantum-Hard' Kernel function 7 | 8 | To run the, PyQuil is needed, plus a user code which can be gotten by signing up to use the Rigetti simulator online: 9 | 10 | Follow instructions online: 11 | http://docs.rigetti.com/en/stable/start.html 12 | This will allow you to download the Rigetti SDK which includes a compiler and a Quantum Virtual Machine 13 | 14 | Also numpy, matplotlib are required etc. 15 | 16 | Finally, pytorch will need to be installed to run the tensor operations in feydy_sink.py 17 | 18 | --------------------------------------------------------------------------------------------- 19 | Maximum Mean Discrepancy (MMD) 20 | ---------------------------------------------------------------------------------------------- 21 | The Maximum Mean Discrepancy is a cost function used for two-sample testing. It compares a measure 22 | of 'Discrepancy' between two distributions P and Q. If and only if the distributions are idential, 23 | the MMD = 0. 24 | 25 | Here we wish to compare between the empirical distribution outputted by the Born Machine, and 26 | some data distribution, both of which are given as binary samples. 27 | 28 | --------------------------------------------------------------------------------------------- 29 | Stein Discrepancy 30 | ---------------------------------------------------------------------------------------------- 31 | The Stein Discrepancy is an alternative cost function which may be used to compare distributions. 32 | Typically, it is used as a 'goodness-of-fit' test, to determine whether samples come from a 33 | particular distribution or not. This is due to its asymmetry, whereas the MMD is symmetric in 34 | the distributions. 35 | 36 | --------------------------------------------------------------------------------------------- 37 | Sinkhorn Divergence 38 | ---------------------------------------------------------------------------------------------- 39 | 40 | Finally, the Sinkhorn Divergence is a third cost function which leverages both the favourable 41 | qualities of the MMD, with the so-called Wasserstein Distance. The Wasserstein Distance is 42 | strongly related to the notion of 'Optimal Transport', or a means of moving between two 43 | distributions by minimising some 'cost'. The Wasserstein has the capability of taking into 44 | account the different between points in the distributions due to the use of this cost, which 45 | is taken to be a metric on the sample space. However, it is notoriously hard to estimate from 46 | samples, i.e. it has a sample complexity which scales exponentially with the size of the space. 47 | 48 | The Sinkhorn Divergence is an attempt to call on the ease of computability of the MMD, but the 49 | favourable properties of the Wasserstein Distance. In fact, it is a version of Wasserstein which 50 | is regularized by an entropy term, which makes the problem strongly convex. 51 | 52 | --------------------------------------------------------------------------------------------- 53 | INSTRUCTIONS FOR USE 54 | --------------------------------------------------------------------------------------------- 55 | 56 | run using: 57 | 58 | ```shell 59 | python3 run_and_compare.py inputs.txt 60 | ``` 61 | 62 | Where input.txt should look like: 63 | 64 | N_epochs 65 | 66 | learning_rate 67 | 68 | data_type 69 | 70 | N_data_samples 71 | 72 | N_born_samples 73 | 74 | N_kernel_samples 75 | 76 | batch_size 77 | 78 | kernel_type 79 | 80 | cost_func 81 | 82 | device_name 83 | 84 | as_qvm_value (1) = True, (0) = False 85 | 86 | stein_score 87 | 88 | stein_eigvecs 89 | 90 | stein_eta 91 | 92 | sinkhorn_eps 93 | 94 | Where: 95 | 96 | 1. N_epochs is the number of epochs the training will run for. 97 | 2. data_type will define the type of data which is to be learned either: 98 | 'Classical_Data', which is generated by a simple function or 99 | 'Quantum_Data', which is outputted by a quantum circuit 100 | 3. N_data_samples defines the number of data samples to be used from the data distribution 101 | to learn from 102 | 4. N_born_samples defines the number of born samples to be used from the Ising Born Machine 103 | to determine the instantaneous distribution and to assess learning progress 104 | 5. N_kernel_samples defines the number of samples to be used when computing a single kernel element 105 | If a Quantum kernel is chosen, the kernel will be the overlap between two states 106 | which is determined as a result of measurements 107 | 6. batch_size is the number of samples to be used in each mini-batch during training 108 | 7. kernel_type is either 'Quantum' or 'Gaussian', depending on whether a mixture of Gaussians kernel or a 109 | quantum kernel is used in the cost_function 110 | 8. cost_func is the choice of cost function used for training, either 'MMD' or 'Stein' 111 | 9. device_name is the Rigetti chip to be used, it also determines the number of qubits to be used: 112 | e.g. Aspen-1-2Q-B uses a particular two qubits from the Aspen chip 113 | 10. as_qvm_value determines whether to run on the Rigetti simulator, or the actual Quantum chip 114 | 11. stein_score is the choice of method to compute the Stein Score function, either 'Exact_Score', 'Identity_Score' or 'Spectral_Score', to compute using exact probabilities, inverting Stein's identity, or the spectral method respectively 115 | 12. stein_eigvecs is the number of Nystrom eigenvectors required to compute the Stein Score using the Spectral Method, an integer. 116 | 13. stein_eta is the regularisation parameter required in the Identity Score method, a small number, typically 0.01 117 | 14. sinkhorn_eps is the regularisation parameter used to compute the Sinkhorn Divergence, between (0, infinity) 118 | -------------------------------------------------------------------------------------------- 119 | Generate Data & Kernels 120 | -------------------------------------------------------------------------------------------- 121 | To Generate Classical (Mixture of Gaussians) kernels for all qubits up to 8: 122 | 123 | ```shell 124 | python3 file_operations_out.py None Gaussian 8 125 | ``` 126 | To Generate Quantum kernels for all qubits up to 8: 127 | 128 | ```shell 129 | python3 file_operations_out.py None Quantum 8 130 | ``` 131 | To Generate Classical Data (Mixture of Bernoulli Modes) for all qubits up to 8: 132 | 133 | ```shell 134 | python3 file_operations_out.py Bernoulli None 8 135 | ``` 136 | 137 | To Generate Quantum Data (from a fully connected IQP circuit particularly) for all qubits up to 8: 138 | 139 | ```shell 140 | python3 file_operations_out.py Quantum None 8 141 | ``` 142 | -------------------------------------------------------------------------------- /analysis.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | os.system("python -m cProfile -o profile_data.pyprof run_and_compare.py inputs.txt") 4 | 5 | os.system("pyprof2calltree -i profile_data.pyprof") 6 | 7 | os.system("kcachegrind profile_data.pyprof.log") 8 | -------------------------------------------------------------------------------- /auxiliary_functions.py: -------------------------------------------------------------------------------- 1 | ## @package auxiliary_functions some additional useful functions 2 | # 3 | # A collection of sever additional function useful during the running of the code. 4 | 5 | import numpy as np 6 | import matplotlib.pyplot as plt 7 | 8 | from collections import Counter 9 | from pyquil.api import get_qc 10 | import torch 11 | import sys 12 | 13 | def AllBinaryStrings(N_qubits): 14 | #Generate Array of all binary strings of length N_qubits 15 | binary_strings_array = np.zeros((2**N_qubits, N_qubits)) 16 | for integer in range(0, 2**N_qubits): 17 | qubit_string = IntegerToString(integer, N_qubits) 18 | for qubit in range(0, N_qubits): 19 | binary_strings_array[integer][qubit] = float(qubit_string[qubit]) 20 | 21 | return binary_strings_array 22 | 23 | 24 | def IntegerToString(integer, N_qubits): 25 | '''This function converts a integer to a binary string''' 26 | if type(integer) is not int: 27 | raise TypeError('\'index\' must be an integer') 28 | if type(N_qubits) is not int: 29 | raise TypeError('\'N_qubits\' must be an integer') 30 | 31 | return "0" * (N_qubits-len(format(integer,'b'))) + format(integer,'b') 32 | 33 | 34 | def StringToList(string): 35 | '''This kernel converts a binary string to a list of bits''' 36 | if type(string) is not str: 37 | raise TypeError('\'string\' must be a str') 38 | string_list = [] 39 | 40 | for element in range(len(string)): 41 | string_list.append(int(string[element])) 42 | return string_list 43 | 44 | def ShiftString(string, shift_index): 45 | ''' 46 | This function shifts the (shift_index)th element of a string by 1 (mod 2). 47 | This is the shift operator ¬ on a binary space. Returns a binary 1D array 48 | ''' 49 | if type(string) is np.ndarray: 50 | if string.ndim != 1: 51 | raise IOError('If \'string\' is a numpy array, it must be one dimensional.') 52 | shifted_string = string 53 | 54 | for i in range(len(string)): 55 | if i is shift_index: 56 | shifted_string[i] = (string[i]+1)%2 57 | elif type(string) is str: 58 | string_list = StringToList(string) 59 | shifted_string = np.ndarray((len(string_list)), dtype = int) 60 | for i in range(len(string_list)): 61 | if i is shift_index: 62 | shifted_string[i] = (string_list[i]+1)%2 63 | else: 64 | shifted_string[i] = string_list[i] 65 | 66 | return shifted_string 67 | 68 | ## Convert string to 1D numpy array 69 | # 70 | # @param[in] input_object list or 1D numpy array 71 | # 72 | # @param[out] input_as_string converted array/list to string 73 | # 74 | # return Converted String 75 | 76 | def ToString(input_object): 77 | '''This converts an input (integer, list, numpy array) to string''' 78 | if type(input_object) is np.ndarray: 79 | if input_object.ndim != 1: 80 | raise IOError('If \'input\' is numpy array it must be 1D') 81 | else: 82 | input_as_string = ''.join([str(bit) for bit in list(input_object)]) 83 | elif type(input_object) is list: 84 | input_as_string = ''.join([str(bit) for bit in input_object]) 85 | elif type(input_object) is str: 86 | input_as_string = input_object 87 | elif type(input_object) is int: 88 | input_as_string = bin(input_object)[2:] 89 | return input_as_string 90 | 91 | ## Convert 1D numpy array to string 92 | # 93 | # @param[in] string original string 94 | # 95 | # @param[out] string_array converted string to array 96 | # 97 | # return Converted Array 98 | 99 | def StringToArray(string): 100 | '''This breaks a string into a numpy array''' 101 | string_array = np.zeros((len(string)), dtype = int) 102 | for bit in range(0, len(string)): 103 | string_array[bit] = int(string[bit]) 104 | return string_array 105 | 106 | ## Convert list to array 107 | # 108 | # @param[in] original_samples_list The original list 109 | # @param[in] N_qubits The number of qubits 110 | # 111 | # @param[out] sample_array The list converted into an array 112 | # 113 | # return Converted list 114 | 115 | 116 | def SampleListToArray(original_samples_list, N_qubits, array_type): 117 | ''' 118 | This function converts a list of strings, into a numpy array, where 119 | each [i,j] element of the array_type (float/int) new array is the jth bit of the ith string 120 | ''' 121 | N_data_samples = len(original_samples_list) 122 | if array_type == 'float': 123 | sample_array = np.zeros((N_data_samples, N_qubits), dtype = float) 124 | for sample in range(0, N_data_samples): 125 | for outcome in range(0, N_qubits): 126 | sample_array[sample][outcome] = float(original_samples_list[sample][outcome]) 127 | elif array_type == 'int': 128 | sample_array = np.zeros((N_data_samples, N_qubits), dtype = int) 129 | for sample in range(0, N_data_samples): 130 | for outcome in range(0, N_qubits): 131 | sample_array[sample][outcome] = int(original_samples_list[sample][outcome]) 132 | 133 | return sample_array 134 | 135 | 136 | def SampleArrayToList(sample_array): 137 | '''This function converts a np.array where rows are samples 138 | into a list of length N_samples''' 139 | #if number of samples in array is just one, handle separately 140 | if sample_array.ndim == 1: 141 | sample_list = [] 142 | sample_list.append(''.join(str(e) for e in (sample_array.tolist()))) 143 | else: 144 | N_samples = sample_array.shape[0] 145 | sample_list = [] 146 | 147 | for sample in range(0, N_samples): 148 | sample_list.append(''.join(str(int(e)) for e in (sample_array[sample][:].tolist()))) 149 | 150 | return sample_list 151 | 152 | def EmpiricalDist(samples, N_qubits, *arg): 153 | '''This method outputs the empirical probability distribution given samples in a numpy array 154 | as a dictionary, with keys as outcomes, and values as probabilities''' 155 | 156 | if type(samples) is not np.ndarray and type(samples) is not list: 157 | raise TypeError('The samples must be either a numpy array, or list') 158 | 159 | if type(samples) is np.ndarray: 160 | N_samples = samples.shape[0] 161 | string_list = [] 162 | for sample in range(0, N_samples): 163 | '''Convert numpy array of samples, to a list of strings of the samples to put in dict''' 164 | string_list.append(''.join(map(str, samples[sample].tolist()))) 165 | 166 | elif type(samples) is list: 167 | if type(samples[0]) is not str: 168 | samples_new = [] 169 | for sample in samples: 170 | samples_new.append(str(samples[sample])) 171 | samples = samples_new 172 | N_samples = len(samples) 173 | string_list = samples 174 | 175 | counts = Counter(string_list) 176 | 177 | for element in counts: 178 | '''Convert occurances to relative frequencies of binary string''' 179 | counts[element] = counts[element]/(N_samples) 180 | if 'full_dist' in arg: 181 | for index in range(0, 2**N_qubits): 182 | '''If a binary string has not been seen in samples, set its value to zero''' 183 | if IntegerToString(index, N_qubits) not in counts: 184 | counts[IntegerToString(index, N_qubits)] = 0 185 | 186 | sorted_samples_dict = {} 187 | 188 | keylist = sorted(counts) 189 | for key in keylist: 190 | sorted_samples_dict[key] = counts[key] 191 | 192 | return sorted_samples_dict 193 | 194 | def ExtractSampleInformation(samples): 195 | ''' 196 | Converts an array of samples into the empirical distribution, and extracts empirical probabilities and 197 | corresponding sample values, and convert to pytorch tensors 198 | ''' 199 | 200 | if type(samples) is np.ndarray: 201 | if samples.ndim == 1: 202 | N_qubits = len(samples) 203 | else: 204 | N_qubits = len(samples[0]) 205 | else: N_qubits = len(samples[0]) 206 | 207 | emp_dist_dict = EmpiricalDist(samples, N_qubits) 208 | samples =SampleListToArray(list(emp_dist_dict.keys()), N_qubits, 'float') 209 | probs = np.asarray(list(emp_dist_dict.values())) #Empirical probabilities of samples 210 | 211 | #convert to numpy tensors if necessary 212 | # pylint: disable=E1101 213 | samples_tens = torch.from_numpy(samples).view(len(samples), -1) 214 | probs_tens = torch.from_numpy(probs).view(len(probs), -1) 215 | # pylint: enable=E1101 216 | 217 | samples_int =SampleListToArray(list(emp_dist_dict.keys()), N_qubits, 'int') 218 | probs_int = np.asarray(list(emp_dist_dict.values())) #Empirical probabilities of samples 219 | 220 | return samples_int, probs_int, samples_tens, probs_tens 221 | 222 | 223 | 224 | def TotalVariationCost(dict_one, dict_two): 225 | '''This Function computes the variation distace between two distributions''' 226 | if dict_one.keys() != dict_two.keys(): 227 | raise ValueError('Keys are not the same') 228 | dict_abs_diff = {} 229 | for variable in dict_one.keys(): 230 | dict_abs_diff[variable] = abs(dict_one[variable] - dict_two[variable]) 231 | 232 | variation_distance = (1/4)*sum(dict_abs_diff.values())**2 233 | 234 | return variation_distance 235 | 236 | def ConvertStringToVector(string): 237 | '''This function converts a string to a np array''' 238 | string_len = len(string) 239 | string_vector = np.zeros(string_len, dtype = int) 240 | for bit in range(string_len): 241 | if (string[bit] == '0' or string[bit] =='1'): 242 | string_vector[bit] = int(string[bit]) 243 | else: raise IOError('Please enter a binary string') 244 | 245 | return string_vector 246 | 247 | def L2Norm(input1, input2): 248 | '''This function computes the squared L2 norm between two binary vectors''' 249 | if (type(input1) is str) and (type(input2) is str): 250 | l2norm = (np.linalg.norm(np.abs(ConvertStringToVector(input1) - ConvertStringToVector(input2)), 2))**2 251 | elif (type(input1) is np.ndarray) and (type(input2) is np.ndarray): 252 | if input1.ndim != 1 or input2.ndim != 1: 253 | raise IOError('Input vector arrays do not consist of single samples') 254 | else: 255 | l2norm = (np.linalg.norm(np.abs(input1 - input2), 2))**2 256 | else: raise IOError('The inputs must be 1D numpy arrays, or strings') 257 | return l2norm 258 | 259 | 260 | ## This function partitions an array of samples into a training array and a test array. 261 | # 262 | # The last 20% of the original set is used for testing 263 | # 264 | # @param[in] samples A list of samples 265 | # 266 | # @param[out] train_test array of lists 267 | # 268 | # return Split array 269 | def TrainTestPartition(samples): 270 | train_test = np.split(samples, [round(len(samples)*0.8), len(samples)], axis = 0) 271 | 272 | return train_test 273 | 274 | def MiniBatchSplit(samples, batch_size): 275 | '''This function takes the first \'batch_size\' samples out of the full sample set ''' 276 | if (type(samples) is not np.ndarray): 277 | raise TypeError('The input \'samples\' must be a numpy ndarray') 278 | batches = np.split(samples, [batch_size, len(samples)], axis = 0) 279 | 280 | return batches[0] 281 | 282 | def num_bytes_needed(num_bits): 283 | 284 | num_bytes = num_bits // 8 285 | 286 | if num_bits % 8 != 0: 287 | num_bytes += 1 288 | 289 | return num_bytes 290 | 291 | -------------------------------------------------------------------------------- /classical_kernel.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from auxiliary_functions import SampleArrayToList, L2Norm 3 | 4 | def GaussianKernel(sample1, sample2, sigma): 5 | '''This function computes a single Gaussian kernel value between two samples''' 6 | c = len(sigma) 7 | kernel = np.zeros((c)) 8 | for k in range(0, c): 9 | kernel[k] = (1/c)*np.exp(-1/(2*sigma[k])*(L2Norm(sample1, sample2))) 10 | 11 | return kernel.sum() 12 | 13 | def GaussianKernelArray(samples1, samples2, sigma): 14 | '''This function computes a kernel matrix for all pairs of samples, for a chosen kernel''' 15 | #sigma[i] are bandwidth parameters 16 | # if (type(samples1) is not np.ndarray or not list) or (type(samples2) is not np.array or not list): 17 | # raise IOError('The input samples must be a numpy array or list') 18 | if type(samples1) is np.ndarray: 19 | if samples1.ndim == 1: #Check if there is only a single sample in the array of samples 20 | N_samples1 = 1 21 | else: 22 | N_samples1 = samples1.shape[0] 23 | else: N_samples1 = len(samples1) 24 | 25 | if type(samples2) is np.ndarray: 26 | if samples2.ndim == 1: 27 | N_samples2 = 1 28 | else: 29 | N_samples2 = samples2.shape[0] 30 | else: N_samples2 = len(samples2) 31 | gauss_kernel_array = np.zeros((N_samples1, N_samples2)) 32 | 33 | for sample1_index in range(0, N_samples1): 34 | for sample2_index in range(0, N_samples2): 35 | if N_samples1 == 1: 36 | gauss_kernel_array[sample1_index][sample2_index] =\ 37 | GaussianKernel(samples1, samples2[sample2_index], sigma) 38 | elif N_samples2 == 1: 39 | gauss_kernel_array[sample1_index][sample2_index] =\ 40 | GaussianKernel(samples1[sample1_index], samples2, sigma) 41 | else: 42 | gauss_kernel_array[sample1_index][sample2_index] =\ 43 | GaussianKernel(samples1[sample1_index], samples2[sample2_index], sigma) 44 | 45 | return gauss_kernel_array 46 | 47 | 48 | def GaussianKernelDict(samples1, samples2, sigma): 49 | '''This function computes a kernel matrix for all pairs of samples, for a chosen kernel''' 50 | #sigma[i] are bandwidth parameters 51 | 52 | if type(samples1) is not np.ndarray and type(samples2) is not np.array: 53 | raise IOError('The input samples must be a numpy array') 54 | 55 | if samples1.ndim == 1: #Check if there is only a single sample in the array of samples 56 | N_samples1 = 1 57 | else: 58 | N_samples1 = samples1.shape[0] 59 | 60 | if samples2.ndim == 1: 61 | N_samples2 = 1 62 | else: 63 | N_samples2 = samples2.shape[0] 64 | 65 | samples_list_1 = SampleArrayToList(samples1) 66 | samples_list_2 = SampleArrayToList(samples2) 67 | gauss_kernel_dict = {} 68 | 69 | for sample1_index in range(0, N_samples1): 70 | for sample2_index in range(0, N_samples2): 71 | 72 | gauss_kernel_dict[(samples_list_1[sample1_index], samples_list_2[sample2_index])] =\ 73 | GaussianKernel(samples_list_1[sample1_index], samples_list_2[sample2_index], sigma) 74 | 75 | return gauss_kernel_dict -------------------------------------------------------------------------------- /cost_function_train.py: -------------------------------------------------------------------------------- 1 | from pyquil.quil import Program 2 | import numpy as np 3 | 4 | from sample_gen import BornSampler, PlusMinusSampleGen 5 | from cost_functions import CostFunction, CostGrad 6 | from auxiliary_functions import EmpiricalDist, TotalVariationCost, MiniBatchSplit 7 | 8 | ################################################################################################################ 9 | #Train Model Using Stein Discrepancy with either exact kernel and gradient or approximate one using samples 10 | ################################################################################################################ 11 | def TrainBorn(qc, cost_func,initial_params, 12 | N_epochs, N_samples, 13 | data_train_test, data_exact_dict, 14 | k_choice, flag, learning_rate_init, 15 | stein_params, 16 | sinkhorn_eps): 17 | 18 | N_qubits = len(qc.qubits()) 19 | #Import initial parameter values 20 | circuit_params = {} 21 | circuit_params[('J', 0)] = initial_params['J'] 22 | circuit_params[('b', 0)] = initial_params['b'] 23 | circuit_params[('gamma', 0)] = initial_params['gamma'] 24 | circuit_params[('delta', 0)] = initial_params['delta'] 25 | circuit_params[('sigma', 0)] = initial_params['sigma'] 26 | 27 | batch_size = N_samples[2] 28 | #Initialise the gradient arrays, each element is one parameter 29 | weight_grad = np.zeros((N_qubits, N_qubits)) 30 | bias_grad = np.zeros((N_qubits)) 31 | 32 | loss = {('MMD', 'Train'): [], ('MMD', 'Test'): [],\ 33 | ('Stein', 'Train'): [], ('Stein', 'Test'): [], \ 34 | ('Sinkhorn', 'Train'): [], ('Sinkhorn', 'Test'): [], \ 35 | 'TV': []} 36 | 37 | born_probs_list = [] 38 | empirical_probs_list = [] 39 | 40 | circuit_choice ='QAOA' 41 | print('The lattice is:', qc.name) 42 | #Initialize momentum vectors at 0 for Adam optimiser 43 | [m_bias, v_bias] = [np.zeros((N_qubits)) for _ in range(2)] 44 | [m_weights, v_weights] = [np.zeros((N_qubits, N_qubits)) for _ in range(2)] 45 | 46 | for epoch in range(0, N_epochs-1): 47 | 48 | #gamma/delta is not to be trained, set gamma values to be constant at each epoch 49 | circuit_params[('gamma', epoch+1)] = circuit_params[('gamma', epoch)] 50 | circuit_params[('delta', epoch+1)] = circuit_params[('delta', epoch)] 51 | 52 | print("\nThis is Epoch number: ", epoch) 53 | 54 | circuit_params_per_epoch = {} 55 | 56 | circuit_params_per_epoch['J'] = circuit_params[('J', epoch)] 57 | circuit_params_per_epoch['b'] = circuit_params[('b', epoch)] 58 | circuit_params_per_epoch['gamma'] = circuit_params[('gamma', epoch)] 59 | circuit_params_per_epoch['delta'] = circuit_params[('delta', epoch)] 60 | 61 | #generate samples, and exact probabilities for current set of parameters 62 | born_samples, born_probs_approx_dict, born_probs_exact_dict = BornSampler(qc, N_samples, circuit_params_per_epoch, circuit_choice) 63 | # print(born_samples) 64 | 65 | born_probs_list.append(born_probs_approx_dict) 66 | empirical_probs_list.append(born_probs_approx_dict) 67 | # print('The Empirical Data is:', EmpiricalDist(data_train_test[0], N_qubits )) 68 | print('The Born Machine Outputs Probabilites\n', born_probs_approx_dict) 69 | print('The Data is\n,', data_exact_dict) 70 | 71 | loss[(cost_func, 'Train')].append(CostFunction(qc, cost_func, data_train_test[0], data_exact_dict, born_samples,\ 72 | born_probs_approx_dict, N_samples, k_choice, stein_params, flag, sinkhorn_eps)) 73 | loss[(cost_func, 'Test')].append(CostFunction(qc, cost_func, data_train_test[1], data_exact_dict, born_samples,\ 74 | born_probs_approx_dict, N_samples, k_choice, stein_params, flag, sinkhorn_eps)) 75 | 76 | print("The %s Loss for epoch " %cost_func, epoch, "is", loss[(cost_func, 'Train')][epoch]) 77 | 78 | #Check Total Variation Distribution using the exact output probabilities 79 | loss[('TV')].append(TotalVariationCost(data_exact_dict, born_probs_exact_dict)) 80 | 81 | print("The Variation Distance for epoch ", epoch, "is", loss['TV'][epoch]) 82 | '''Updating bias b[r], control set to 'BIAS' ''' 83 | for bias_index in range(0, N_qubits): 84 | born_samples_pm = PlusMinusSampleGen(qc, circuit_params_per_epoch,\ 85 | 0,0, bias_index, 0, \ 86 | circuit_choice, 'BIAS', N_samples) 87 | 88 | #Shuffle all samples to avoid bias in Minibatch Training 89 | np.random.shuffle(data_train_test[0]) 90 | np.random.shuffle(born_samples) 91 | 92 | #Use only first 'batch_size' number of samples for each update 93 | if batch_size > len(data_train_test[0]) or batch_size > len(born_samples): 94 | raise IOError('The batch size is too large') 95 | else: 96 | data_batch = MiniBatchSplit(data_train_test[0], batch_size) 97 | born_batch = MiniBatchSplit(born_samples, batch_size) 98 | 99 | 100 | bias_grad[bias_index] = CostGrad(qc, cost_func, data_batch, data_exact_dict, 101 | born_batch, born_probs_approx_dict, 102 | born_samples_pm, 103 | N_samples, k_choice, stein_params, flag, sinkhorn_eps) 104 | #Update biases for next epoch 105 | 106 | '''Updating weight J[p,q], control set to 'WEIGHTS' ''' 107 | for q in range(0, N_qubits): 108 | for p in range(0, N_qubits): 109 | if (p < q): 110 | ## Draw samples from +/- pi/2 shifted circuits for each weight update, J_{p, q} 111 | born_samples_pm = PlusMinusSampleGen(qc, circuit_params_per_epoch, \ 112 | p, q, 0, 0,\ 113 | circuit_choice, 'WEIGHTS', N_samples) 114 | 115 | #Shuffle all samples to avoid bias in Minibatch Training 116 | np.random.shuffle(data_train_test[0]) 117 | np.random.shuffle(born_samples) 118 | 119 | #Use only first 'batch_size' number of samples for each update 120 | if (batch_size > len(data_train_test[0]) or batch_size > len(born_samples)): 121 | raise IOError('The batch size is too large') 122 | else: 123 | data_batch = MiniBatchSplit(data_train_test[0], batch_size) 124 | born_batch = MiniBatchSplit(born_samples, batch_size) 125 | 126 | 127 | weight_grad[p,q] = CostGrad(qc, cost_func, data_batch, data_exact_dict, 128 | born_batch, born_probs_approx_dict, 129 | born_samples_pm, 130 | N_samples, k_choice, stein_params, flag, sinkhorn_eps) 131 | 132 | #Update Weights for next epoch 133 | learning_rate_bias, m_bias, v_bias = AdamLR(learning_rate_init, epoch, bias_grad, m_bias, v_bias) 134 | learning_rate_weights, m_weights, v_weights = AdamLR(learning_rate_init, epoch, weight_grad + np.transpose(weight_grad), m_weights, v_weights) 135 | 136 | # circuit_params[('b', epoch+1)] = circuit_params[('b', epoch)] - learning_rate_bias*bias_grad 137 | # circuit_params[('J', epoch+1)] = circuit_params[('J', epoch)] - learning_rate_weights*(weight_grad + np.transpose(weight_grad)) 138 | 139 | circuit_params[('b', epoch+1)] = circuit_params[('b', epoch)] - learning_rate_bias 140 | circuit_params[('J', epoch+1)] = circuit_params[('J', epoch)] - learning_rate_weights 141 | 142 | 143 | return loss, circuit_params, born_probs_list, empirical_probs_list 144 | 145 | 146 | def AdamLR(learning_rate_init, timestep, gradient, m, v, beta1 = 0.9, beta2 = 0.999, epsilon = 1e-8): 147 | ''' 148 | Method to compute Adam learning rate which includes momentum 149 | Parameters, beta1, beta2, epsilon are as recommended in orginal Adam paper 150 | ''' 151 | timestep = timestep +1 152 | m = np.multiply(beta1, m) + np.multiply((1-beta1), gradient) 153 | v = np.multiply(beta2, v) + np.multiply((1-beta2) , gradient**2) 154 | corrected_m = np.divide(m , (1- beta1**timestep)) 155 | corrected_v = np.divide(v, (1- beta2**timestep)) 156 | 157 | return learning_rate_init*(np.divide(corrected_m, np.sqrt(corrected_v)+ epsilon)), m, v 158 | -------------------------------------------------------------------------------- /cost_functions.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from random import * 3 | from classical_kernel import GaussianKernelArray 4 | from quantum_kernel import QuantumKernelArray 5 | from numpy import linalg as LA 6 | from file_operations_in import KernelDictFromFile 7 | import stein_functions as sf 8 | import sinkhorn_functions as shornfun 9 | import auxiliary_functions as aux 10 | import sys 11 | import json 12 | import time 13 | 14 | def KernelSum(samplearray1, samplearray2, kernel_dict): 15 | ''' 16 | This function computes the contribution to the MMD from the empirical distibutions 17 | from two sets of samples. 18 | kernel_dict contains the kernel values for all pairs of binary strings 19 | ''' 20 | if type(samplearray1) is not np.ndarray or type(samplearray2) is not np.ndarray: 21 | raise TypeError('The input samples must be in numpy arrays') 22 | N_samples1 = samplearray1.shape[0] 23 | N_samples2 = samplearray2.shape[0] 24 | kernel_array = np.zeros((N_samples1, N_samples2)) 25 | 26 | for sample1_index in range(0, N_samples1): 27 | for sample2_index in range(0, N_samples2): 28 | sample1 = aux.ToString(samplearray1[sample1_index]) 29 | sample2 = aux.ToString(samplearray2[sample2_index]) 30 | kernel_array[sample1_index, sample2_index] = kernel_dict[(sample1, sample2)] 31 | 32 | return kernel_array 33 | 34 | 35 | def CostFunction(qc, cost_func, data_samples, data_exact_dict, born_samples, born_probs_dict, 36 | N_samples, kernel_choice, stein_params, flag, sinkhorn_eps): 37 | ''' 38 | This function computes the cost function between two distributions P and Q from samples from P and Q 39 | ''' 40 | 41 | #Extract unique samples and corresponding empirical probabilities from set of samples 42 | born_emp_samples, born_emp_probs, _, _ = aux.ExtractSampleInformation(born_samples) 43 | data_emp_samples, data_emp_probs, _, _ = aux.ExtractSampleInformation(data_samples) 44 | if cost_func.lower() == 'mmd': 45 | score_choice = stein_params[0] 46 | 47 | if score_choice.lower() == 'approx': 48 | if (flag.lower() == 'onfly'): 49 | if (kernel_choice.lower() == 'gaussian'): 50 | sigma = np.array([0.25, 10, 1000]) 51 | #Compute the Gaussian kernel on the fly for all samples in the sample space 52 | kernel_born_born_emp = GaussianKernelArray(born_emp_samples, born_emp_samples, sigma) 53 | kernel_born_data_emp = GaussianKernelArray(born_emp_samples, data_emp_samples, sigma) 54 | kernel_data_data_emp = GaussianKernelArray(data_emp_samples, data_emp_samples, sigma) 55 | 56 | elif kernel_choice.lower() == 'quantum': 57 | N_kernel_samples = N_samples[-1] #Number of kernel samples is the last element of N_samples 58 | #Compute the Quantum kernel on the fly for all pairs of samples required 59 | 60 | kernel_born_born_emp ,_,_,_ = QuantumKernelArray(qc, N_kernel_samples, born_emp_samples, born_emp_samples) 61 | kernel_born_data_emp ,_,_,_ = QuantumKernelArray(qc, N_kernel_samples, born_emp_samples, data_emp_samples) 62 | kernel_data_data_emp ,_,_,_ = QuantumKernelArray(qc, N_kernel_samples, data_emp_samples, data_emp_samples) 63 | 64 | elif (flag.lower() == 'precompute'): 65 | #Compute the empirical data distibution given samples 66 | 67 | kernel_dict = KernelDictFromFile(qc, N_samples, kernel_choice) 68 | 69 | kernel_born_born_emp = KernelSum(born_emp_samples, born_emp_samples, kernel_dict) 70 | kernel_born_data_emp = KernelSum(born_emp_samples, data_emp_samples, kernel_dict) 71 | kernel_data_data_emp = KernelSum(data_emp_samples, data_emp_samples, kernel_dict) 72 | 73 | else: raise ValueError('\'flag\' must be either \'Onfly\' or \'Precompute\'') 74 | 75 | 76 | loss = np.dot(np.dot(born_emp_probs, kernel_born_born_emp), born_emp_probs) \ 77 | - 2*np.dot(np.dot(born_emp_probs, kernel_born_data_emp), data_emp_probs) \ 78 | + np.dot(np.dot(data_emp_probs, kernel_data_data_emp), data_emp_probs) 79 | elif score_choice.lower() == 'exact': 80 | #Compute MMD using exact data probabilities if score is exact 81 | data_exact_samples = aux.SampleListToArray(list(data_exact_dict.keys()), len(qc.qubits()), 'int') 82 | data_exact_probs = np.asarray(list(data_exact_dict.values())) 83 | 84 | if (flag.lower() == 'onfly'): 85 | if (kernel_choice.lower() == 'gaussian'): 86 | sigma = np.array([0.25, 10, 1000]) 87 | #Compute the Gaussian kernel on the fly for all samples in the sample space 88 | kernel_born_born_emp = GaussianKernelArray(born_emp_samples, born_emp_samples, sigma) 89 | kernel_born_data_emp = GaussianKernelArray(born_emp_samples, data_exact_samples, sigma) 90 | kernel_data_data_emp = GaussianKernelArray(data_exact_samples, data_exact_samples, sigma) 91 | 92 | elif kernel_choice.lower() == 'quantum': 93 | N_kernel_samples = N_samples[-1] #Number of kernel samples is the last element of N_samples 94 | #Compute the Quantum kernel on the fly for all pairs of samples required 95 | 96 | kernel_born_born_emp ,_,_,_ = QuantumKernelArray(qc, N_kernel_samples, born_emp_samples, born_emp_samples) 97 | kernel_born_data_emp ,_,_,_ = QuantumKernelArray(qc, N_kernel_samples, born_emp_samples, data_exact_samples) 98 | kernel_data_data_emp ,_,_,_ = QuantumKernelArray(qc, N_kernel_samples, data_exact_samples, data_exact_samples) 99 | 100 | elif (flag.lower() == 'precompute'): 101 | #Compute the empirical data distibution given samples 102 | 103 | kernel_dict = KernelDictFromFile(qc, N_samples, kernel_choice) 104 | 105 | kernel_born_born_emp = KernelSum(born_emp_samples, born_emp_samples, kernel_dict) 106 | kernel_born_data_emp = KernelSum(born_emp_samples, data_exact_samples, kernel_dict) 107 | kernel_data_data_emp = KernelSum(data_exact_samples, data_exact_samples, kernel_dict) 108 | 109 | else: raise ValueError('\'flag\' must be either \'Onfly\' or \'Precompute\'') 110 | 111 | 112 | loss = np.dot(np.dot(born_emp_probs, kernel_born_born_emp), born_emp_probs) \ 113 | - 2*np.dot(np.dot(born_emp_probs, kernel_born_data_emp), data_emp_probs) \ 114 | + np.dot(np.dot(data_exact_probs, kernel_data_data_emp), data_emp_probs) 115 | 116 | elif cost_func.lower() == 'stein': 117 | 118 | if flag.lower() == 'onfly': 119 | if (kernel_choice.lower() == 'gaussian'): 120 | 121 | sigma = np.array([0.25, 10, 1000]) 122 | kernel_array = GaussianKernelArray(born_emp_samples, born_emp_samples, sigma) 123 | 124 | elif kernel_choice.lower() == 'quantum': 125 | kernel_array ,_,_,_ = QuantumKernelArray(qc, N_kernel_samples, born_samples, born_samples) 126 | 127 | else: raise ValueError('Stein only supports Gaussian kernel currently') 128 | elif flag.lower() == 'precompute': 129 | 130 | kernel_dict = KernelDictFromFile(qc, N_samples, kernel_choice) 131 | 132 | kernel_array = KernelSum(born_emp_samples, born_emp_samples, kernel_dict) 133 | 134 | else: raise ValueError('\'flag\' must be either \'Onfly\' or \'Precompute\'') 135 | 136 | stein_flag = 'Precompute' 137 | kernel_stein_weighted = sf.WeightedKernel(qc,kernel_choice, kernel_array, N_samples, \ 138 | data_samples, data_exact_dict, \ 139 | born_emp_samples, born_emp_samples, \ 140 | stein_params, stein_flag) 141 | 142 | 143 | loss = np.dot(np.dot(born_emp_probs, kernel_stein_weighted), born_emp_probs) 144 | 145 | elif cost_func.lower() == 'sinkhorn': 146 | #If Sinkhorn cost function to be used 147 | loss = shornfun.FeydySink(born_samples, data_samples, sinkhorn_eps).item() 148 | 149 | else: raise ValueError('\'cost_func\' must be either \'MMD\', \'Stein\', or \'Sinkhorn\' ') 150 | 151 | return loss 152 | 153 | 154 | def CostGrad(qc, cost_func, data_samples, data_exact_dict, 155 | born_samples, born_probs_dict, born_samples_pm, 156 | N_samples, kernel_choice, stein_params, flag, sinkhorn_eps): 157 | ''' 158 | This function computes the gradient of the desired cost function, cost_func, using the various parameters 159 | ''' 160 | [born_samples_plus, born_samples_minus] = born_samples_pm 161 | 162 | #extract unique samples, and corresponding probabilities from a list of samples 163 | born_emp_samples, born_emp_probs, _, _ = aux.ExtractSampleInformation(born_samples) 164 | data_emp_samples, data_emp_probs, _, _ = aux.ExtractSampleInformation(data_samples) 165 | born_plus_emp_samples, born_plus_emp_probs, _, _ = aux.ExtractSampleInformation(born_samples_plus) 166 | born_minus_emp_samples, born_minus_emp_probs, _, _ = aux.ExtractSampleInformation(born_samples_minus) 167 | 168 | if cost_func.lower() == 'mmd': 169 | score_choice = stein_params[0] 170 | if score_choice.lower() == 'approx': 171 | if flag.lower() == 'onfly': 172 | if kernel_choice.lower() == 'gaussian': 173 | sigma = np.array([0.25, 10, 1000]) 174 | #Compute the Gaussian kernel on the fly for all pairs of samples required 175 | kernel_born_plus_emp = GaussianKernelArray(born_emp_samples, born_plus_emp_samples, sigma) 176 | kernel_born_minus_emp = GaussianKernelArray(born_emp_samples, born_minus_emp_probs, sigma) 177 | kernel_data_plus_emp = GaussianKernelArray(data_emp_samples, born_plus_emp_samples, sigma) 178 | kernel_data_minus_emp = GaussianKernelArray(data_emp_samples, born_minus_emp_probs, sigma) 179 | 180 | elif kernel_choice.lower() == 'quantum': 181 | N_kernel_samples = N_samples[-1] #Number of kernel samples is the last element of N_samples 182 | #Compute the Quantum kernel on the fly for all pairs of samples required 183 | 184 | kernel_born_plus_emp ,_,_,_ = QuantumKernelArray(qc, N_kernel_samples, born_emp_samples, born_plus_emp_samples) 185 | kernel_born_minus_emp,_,_,_ = QuantumKernelArray(qc, N_kernel_samples, born_emp_samples, born_minus_emp_probs) 186 | kernel_data_plus_emp ,_,_,_ = QuantumKernelArray(qc, N_kernel_samples, data_emp_samples, born_plus_emp_samples) 187 | kernel_data_minus_emp,_,_,_ = QuantumKernelArray(qc, N_kernel_samples, data_emp_samples, born_minus_emp_probs) 188 | 189 | 190 | elif flag.lower() == 'precompute': 191 | #To speed up computation, read in precomputed kernel dicrionary from a file. 192 | kernel_dict = KernelDictFromFile(qc, N_samples, kernel_choice) 193 | 194 | kernel_born_plus_emp = KernelSum(born_emp_samples, born_plus_emp_samples, kernel_dict) 195 | kernel_born_minus_emp = KernelSum(born_emp_samples, born_minus_emp_samples, kernel_dict) 196 | kernel_data_plus_emp = KernelSum(data_emp_samples, born_plus_emp_samples, kernel_dict) 197 | kernel_data_minus_emp = KernelSum(data_emp_samples, born_minus_emp_samples, kernel_dict) 198 | 199 | 200 | else: raise ValueError('\'flag\' must be either \'Onfly\' or \'Precompute\'') 201 | 202 | loss_grad = 2*( np.dot(np.dot(born_emp_probs, kernel_born_minus_emp), born_minus_emp_probs) \ 203 | - np.dot(np.dot(born_emp_probs, kernel_born_plus_emp), born_plus_emp_probs) \ 204 | - np.dot(np.dot(data_emp_probs, kernel_data_minus_emp), born_minus_emp_probs) \ 205 | + np.dot(np.dot(data_emp_probs, kernel_data_plus_emp), born_plus_emp_probs) ) 206 | 207 | elif score_choice.lower() == 'exact': 208 | #Compute MMD using exact data probabilities if score is exact 209 | data_exact_samples = aux.SampleListToArray(list(data_exact_dict.keys()), len(qc.qubits()), 'int') 210 | 211 | data_exact_probs = np.asarray(list(data_exact_dict.values())) 212 | 213 | if flag.lower() == 'onfly': 214 | if kernel_choice.lower() == 'gaussian': 215 | sigma = np.array([0.25, 10, 1000]) 216 | #Compute the Gaussian kernel on the fly for all pairs of samples required 217 | kernel_born_plus_emp = GaussianKernelArray(born_emp_samples, born_plus_emp_samples, sigma) 218 | kernel_born_minus_emp = GaussianKernelArray(born_emp_samples, born_minus_emp_probs, sigma) 219 | kernel_data_plus_emp = GaussianKernelArray(data_exact_samples, born_plus_emp_samples, sigma) 220 | kernel_data_minus_emp = GaussianKernelArray(data_exact_samples, born_minus_emp_probs, sigma) 221 | 222 | elif kernel_choice.lower() == 'quantum': 223 | N_kernel_samples = N_samples[-1] #Number of kernel samples is the last element of N_samples 224 | #Compute the Quantum kernel on the fly for all pairs of samples required 225 | 226 | kernel_born_plus_emp ,_,_,_ = QuantumKernelArray(qc, N_kernel_samples, born_emp_samples, born_plus_emp_samples) 227 | kernel_born_minus_emp,_,_,_ = QuantumKernelArray(qc, N_kernel_samples, born_emp_samples, born_minus_emp_probs) 228 | kernel_data_plus_emp ,_,_,_ = QuantumKernelArray(qc, N_kernel_samples, data_exact_samples, born_plus_emp_samples) 229 | kernel_data_minus_emp,_,_,_ = QuantumKernelArray(qc, N_kernel_samples, data_exact_samples, born_minus_emp_probs) 230 | 231 | 232 | elif flag.lower() == 'precompute': 233 | #To speed up computation, read in precomputed kernel dicrionary from a file. 234 | kernel_dict = KernelDictFromFile(qc, N_samples, kernel_choice) 235 | 236 | kernel_born_plus_emp = KernelSum(born_emp_samples, born_plus_emp_samples, kernel_dict) 237 | kernel_born_minus_emp = KernelSum(born_emp_samples, born_minus_emp_samples, kernel_dict) 238 | kernel_data_plus_emp = KernelSum(data_exact_samples, born_plus_emp_samples, kernel_dict) 239 | kernel_data_minus_emp = KernelSum(data_exact_samples, born_minus_emp_samples, kernel_dict) 240 | 241 | 242 | else: raise ValueError('\'flag\' must be either \'Onfly\' or \'Precompute\'') 243 | 244 | loss_grad = 2*( np.dot(np.dot(born_emp_probs, kernel_born_minus_emp), born_minus_emp_probs) \ 245 | - np.dot(np.dot(born_emp_probs, kernel_born_plus_emp), born_plus_emp_probs) \ 246 | - np.dot(np.dot(data_exact_probs, kernel_data_minus_emp), born_minus_emp_probs) \ 247 | + np.dot(np.dot(data_exact_probs, kernel_data_plus_emp), born_plus_emp_probs) ) 248 | 249 | elif cost_func.lower() == 'stein': 250 | 251 | sigma = np.array([0.25, 10, 1000]) 252 | [born_samples_plus, born_samples_minus] = born_samples_pm 253 | 254 | 255 | if flag.lower() == 'onfly': 256 | if kernel_choice.lower() == 'gaussian': 257 | sigma = np.array([0.25, 10, 1000]) 258 | #Compute the Gaussian kernel on the fly for all pairs of samples required 259 | kernel_born_plus_emp = GaussianKernelArray(born_emp_samples, born_plus_emp_samples, sigma) 260 | kernel_born_minus_emp = GaussianKernelArray(born_emp_samples, born_minus_emp_probs, sigma) 261 | 262 | elif kernel_choice.lower() == 'quantum': 263 | N_kernel_samples = N_samples[-1] #Number of kernel samples is the last element of N_samples 264 | #Compute the Quantum kernel on the fly for all pairs of samples required 265 | 266 | kernel_born_plus_emp ,_,_,_ = QuantumKernelArray(qc, N_kernel_samples, born_emp_samples, born_plus_emp_samples) 267 | kernel_born_minus_emp,_,_,_ = QuantumKernelArray(qc, N_kernel_samples, born_emp_samples, born_minus_emp_probs) 268 | 269 | elif flag.lower() == 'precompute': 270 | #To speed up computation, read in precomputed kernel dicrionary from a file. 271 | kernel_dict = KernelDictFromFile(qc, N_samples, kernel_choice) 272 | 273 | kernel_born_plus_emp = KernelSum(born_emp_samples, born_plus_emp_samples, kernel_dict) 274 | kernel_born_minus_emp = KernelSum(born_emp_samples, born_minus_emp_samples, kernel_dict) 275 | 276 | kernel_plus_born_emp = np.transpose(kernel_born_plus_emp) 277 | kernel_minus_born_emp = np.transpose(kernel_born_minus_emp) 278 | 279 | stein_kernel_choice = stein_params[3] 280 | 281 | # Compute the weighted kernel for each pair of samples required in the gradient of Stein Cost Function 282 | kappa_q_born_bornplus = sf.WeightedKernel(qc, stein_kernel_choice, kernel_born_plus_emp, N_samples, data_samples, data_exact_dict,\ 283 | born_emp_samples, born_plus_emp_samples, stein_params, flag) 284 | kappa_q_bornplus_born = sf.WeightedKernel(qc, stein_kernel_choice, kernel_plus_born_emp, N_samples, data_samples, \ 285 | data_exact_dict, born_plus_emp_samples, born_emp_samples, stein_params, flag) 286 | kappa_q_born_bornminus = sf.WeightedKernel(qc, stein_kernel_choice, kernel_born_minus_emp, N_samples, data_samples,\ 287 | data_exact_dict, born_emp_samples, born_minus_emp_samples, stein_params, flag) 288 | kappa_q_bornminus_born = sf.WeightedKernel(qc, stein_kernel_choice, kernel_minus_born_emp, N_samples, data_samples,\ 289 | data_exact_dict, born_minus_emp_samples, born_emp_samples, stein_params, flag) 290 | 291 | 292 | loss_grad = np.dot(np.dot(born_emp_probs, kappa_q_born_bornminus), born_minus_emp_probs) \ 293 | + np.dot(np.dot(born_minus_emp_probs, kappa_q_bornminus_born), born_emp_probs) \ 294 | - np.dot(np.dot(born_emp_probs, kappa_q_born_bornplus), born_plus_emp_probs) \ 295 | - np.dot(np.dot(born_plus_emp_probs, kappa_q_bornplus_born), born_emp_probs) 296 | 297 | 298 | elif cost_func.lower() == 'sinkhorn': 299 | # loss_grad = shornfun.SinkhornGrad(born_samples_pm, data_samples, sinkhorn_eps) 300 | loss_grad = shornfun.SinkGrad(born_samples, born_samples_pm, data_samples, sinkhorn_eps) 301 | else: raise ValueError('\'cost_func\' must be either \'MMD\', \'Stein\', or \'Sinkhorn\' ') 302 | 303 | return loss_grad 304 | 305 | -------------------------------------------------------------------------------- /feydy_sinkhorn.py: -------------------------------------------------------------------------------- 1 | #-------------------------------------------------------------------------------------------- 2 | # Simplistic implementation of the Sinkhorn divergences, with a vanilla PyTorch backend 3 | #-------------------------------------------------------------------------------------------- 4 | 5 | import numpy as np 6 | import torch 7 | 8 | ####################################################################################################################### 9 | # Elementary operations ..................................................................... 10 | ####################################################################################################################### 11 | 12 | def scal( α, f ) : 13 | return torch.dot( α.view(-1), f.view(-1) ) 14 | 15 | def lse( v_ij ): 16 | """[lse(v_ij)]_i = log sum_j exp(v_ij), with numerical accuracy.""" 17 | V_i = torch.max(v_ij, 1)[0].view(-1,1) 18 | 19 | return V_i + (v_ij - V_i).exp().sum(1).log().view(-1,1) 20 | 21 | def dist_matrix(x_i, y_j, p, eps) : 22 | ε = eps 23 | x_y = x_i.unsqueeze(1) - y_j.unsqueeze(0) 24 | if p == 1 : return x_y.norm(dim=2) / ε 25 | elif p == 2 : return ( x_y ** 2).sum(2) / ε 26 | else : return x_y.norm(dim=2)**(p/2) / ε 27 | 28 | ####################################################################################################################### 29 | # Sinkhorn iterations ..................................................................... 30 | ####################################################################################################################### 31 | 32 | def sink(α_i, x_i, β_j, y_j, p=2, eps=.1, nits=100, **kwargs): 33 | ε = eps # Python supports Unicode. So fancy! 34 | # Sinkhorn loop with A = a/eps , B = b/eps .................................................... 35 | 36 | α_i_log, β_j_log = α_i.log(), β_j.log() # Precompute the logs of the measures' weights 37 | B_i, A_j = torch.zeros_like(α_i) , torch.zeros_like(β_j) # Sampled influence fields 38 | 39 | Cxy_e = dist_matrix(x_i, y_j, p, ε) 40 | # print(Cxy_e.size()) 41 | for i in range(nits): 42 | 43 | A_j = -lse( (B_i + α_i_log).view(1, -1) - Cxy_e.t() ) # a(y)/ε = Smin_ε,x~α [ C(x,y) - b(x) ] / ε 44 | B_i = -lse( (A_j + β_j_log).view(1, -1) - Cxy_e ) # b(x)/ε = Smin_ε,y~β [ C(x,y) - a(y) ] / ε 45 | 46 | return ε*A_j.view(-1), ε*B_i.view(-1) 47 | 48 | 49 | def sym_sink(α_i, x_i, y_j=None, p=2, eps=.1, nits=100, **kwargs): 50 | ε = eps # Python supports Unicode. So fancy! 51 | # Sinkhorn loop ...................................................................... 52 | 53 | α_i_log = α_i.log() 54 | A_i = torch.zeros_like(α_i) 55 | Cxx_e = dist_matrix(x_i, x_i, p, eps) 56 | 57 | for i in range(nits-1): 58 | A_i = 0.5 * (A_i - lse( (A_i + α_i_log).view(1, -1) - Cxx_e )) # a(x)/ε = .5*(a(x)/ε + Smin_ε,y~α [ C(x,y) - a(y) ] / ε) 59 | 60 | a_x = -ε*lse( (A_i + α_i_log) - Cxx_e ).view(-1) # a(x) = Smin_e,z~α [ C(x,z) - a(z) ] 61 | if y_j is None : 62 | return None, a_x 63 | else : # extrapolate "a" to the point cloud "y_j" 64 | Cyx_e = dist_matrix(y_j, x_i, p) 65 | a_y = - ε * lse( (A_i + α_i_log).view(1,-1) - Cyx_e ).view(-1) # a(z) = Smin_e,z~α [ C(y,z) - a(z) ] 66 | return a_y, a_x 67 | 68 | 69 | ####################################################################################################################### 70 | # Derived Functionals ..................................................................... 71 | ####################################################################################################################### 72 | 73 | def regularized_ot( α, x, β, y, **params): # OT_ε 74 | a_y, b_x = sink( α, x, β, y, **params) 75 | return scal(α, b_x) + scal(β, a_y) 76 | 77 | def hausdorff_divergence(α, x, β, y, **params): # H_ε 78 | a_y, a_x = sym_sink( α, x, y, **params) 79 | b_x, b_y = sym_sink( β, y, x, **params) 80 | return .5 * ( scal( α, b_x - a_x ) + scal( β, a_y - b_y ) ) 81 | 82 | def sinkhorn_divergence(α, x, β, y, **params): # S_ε 83 | a_y, b_x = sink( α, x, β, y, **params) 84 | _, a_x = sym_sink( α, x, **params ) 85 | _, b_y = sym_sink( β, y, **params ) 86 | 87 | return scal( α, b_x - a_x ) + scal( β, a_y - b_y ) 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /file_operations_in.py: -------------------------------------------------------------------------------- 1 | ## @package file_operations_in import functions 2 | # 3 | # A collection of functions for imported pre-computed data 4 | 5 | import numpy as np 6 | import ast 7 | import sys 8 | import json 9 | from auxiliary_functions import SampleListToArray 10 | import matplotlib 11 | 12 | from matplotlib import rc 13 | ## for Palatino and other serif fonts use: 14 | #rc('font',**{'family':'serif','serif':['Palatino']}) 15 | rc('text', usetex=True) 16 | rc('font', **{'family': 'serif', 'serif': ['Computer Modern']}) 17 | 18 | import matplotlib.pyplot as plt 19 | 20 | 21 | def FileLoad(file, *args): 22 | file_info = json.load(file) 23 | file_dict = json.loads(file_info) 24 | dict_keys = file_dict.keys() 25 | dict_values = file_dict.values() 26 | if 'probs_input' in args: 27 | keys = file_dict.keys() 28 | else: 29 | dict_keys = file_dict.keys() 30 | keys = [eval(key) for key in dict_keys] 31 | return file_dict, keys, dict_values 32 | 33 | ## Reads data dictionary from file 34 | # 35 | # @param[in] N_qubits The number of qubits 36 | # @param[in] N_samples The number of samples 37 | # 38 | # @returns A dictionary containing the appropriate data 39 | 40 | def DataDictFromFile(data_type, N_qubits, N_data_samples, *args): 41 | if data_type == 'Bernoulli_Data': 42 | if (N_data_samples == 'infinite'): 43 | with open('data/Bernoulli_Data_Dict_%iQBs_Exact' % (N_qubits), 'r') as f: 44 | raw_from_file = json.load(f) 45 | data_dict = json.loads(raw_from_file) 46 | else: 47 | with open('data/Bernoulli_Data_Dict_%iQBs_%iSamples' % (N_qubits, N_data_samples[0]), 'r') as g: 48 | raw_from_file = json.load(g) 49 | data_dict = json.loads(raw_from_file) 50 | 51 | elif data_type == 'Quantum_Data': 52 | circuit_choice = args[0] 53 | 54 | if (N_data_samples == 'infinite'): 55 | with open('data/Quantum_Data_Dict_%iQBs_Exact_%sCircuit' % (N_qubits, circuit_choice), 'r') as f: 56 | raw_from_file = json.load(f) 57 | data_dict = json.loads(raw_from_file) 58 | else: 59 | with open('data/Quantum_Data_Dict_%iQBs_%iSamples_%sCircuit' % (N_qubits, N_data_samples[0], circuit_choice), 'r') as g: 60 | raw_from_file = json.load(g) 61 | data_dict = json.loads(raw_from_file) 62 | else: raise IOError('Please enter either \'Quantum_Data\' or \'Bernoulli_Data\' for \'data_type\' ') 63 | 64 | return data_dict 65 | ## Returns relevant data 66 | # 67 | # @param[in] approx The approximation type 68 | # @param[in] N_qubits The number of qubits 69 | # @param[in] N_data_samples The number of data samples 70 | # @param[in] stein_approx The approximation type 71 | # 72 | # @param[out] data_samples The requested list of samples 73 | # @param[out] data_exact_dict The requested dictionary of exact samples 74 | # 75 | # @return Requested data 76 | def DataImport(data_type, N_qubits, N_data_samples, *args): 77 | 78 | data_exact_dict = DataDictFromFile(data_type, N_qubits, 'infinite', args) 79 | if data_type == 'Bernoulli_Data': 80 | 81 | data_samples_orig = list(np.loadtxt('data/Bernoulli_Data_%iQBs_%iSamples' % (N_qubits, N_data_samples), dtype = str)) 82 | data_samples = SampleListToArray(data_samples_orig, N_qubits, 'int') 83 | 84 | elif data_type == 'Quantum_Data': 85 | circuit_choice = args[0] 86 | 87 | data_samples_orig = list(np.loadtxt('data/Quantum_Data_%iQBs_%iSamples_%sCircuit' % (N_qubits, N_data_samples, circuit_choice), dtype = str)) 88 | data_samples = SampleListToArray(data_samples_orig, N_qubits, 'int') 89 | 90 | 91 | else: raise IOError('Please enter either \'Quantum_Data\' or \'Bernoulli_Data\' for \'data_type\' ') 92 | 93 | return data_samples, data_exact_dict 94 | 95 | ## Reads kernel dictionary from file 96 | def KernelDictFromFile(qc, N_samples, kernel_choice): 97 | N_qubits = len(qc.qubits()) 98 | #reads kernel dictionary from file 99 | N_kernel_samples = N_samples[-1] 100 | 101 | if (N_kernel_samples == 'infinite'): 102 | with open('kernel/%sKernel_Dict_%iQBs_Exact' % (kernel_choice[0], N_qubits), 'r') as f: 103 | _, keys, values = FileLoad(f) 104 | else: 105 | with open('kernel/%sKernel_Dict_%iQBs_%iKernelSamples' % (kernel_choice[0], N_qubits, N_kernel_samples), 'r') as f: 106 | _, keys, values = FileLoad(f) 107 | 108 | return dict(zip(*[keys, values])) 109 | 110 | def ConvertKernelDictToArray(N_qubits, N_kernel_samples, kernel_choice): 111 | '''This function converts a dictionary of kernels to a numpy array''' 112 | N_samples = [0, 0, 0, N_kernel_samples] 113 | #read kernel matrix in from file as dictionary 114 | kernel_dict = KernelDictFromFile(N_qubits, N_samples, kernel_choice) 115 | #convert dictionary to np array 116 | kernel_array = np.fromiter(kernel_dict.values(), dtype = float).reshape((2**N_qubits, 2**N_qubits)) 117 | 118 | return kernel_array 119 | 120 | def ParamsFromFile(N_qubits, circuit_choice, device_name): 121 | with np.load('data/Parameters_%iQbs_%sCircuit_%sDevice.npz' % (N_qubits, circuit_choice, device_name)) as circuit_params: 122 | J = circuit_params['J'] 123 | b = circuit_params['b'] 124 | gamma = circuit_params['gamma_x'] 125 | delta = circuit_params['gamma_y'] 126 | 127 | 128 | return J, b, gamma, delta 129 | # J, b, _, _ = ParamsFromFile(2, 'IQP', '2q-qvm') 130 | # print('\nJ', J) 131 | # print('\nb', b) 132 | 133 | def FindTrialNameFile(cost_func, data_type, data_circuit, N_epochs,learning_rate, qc, kernel_type, N_samples, stein_params, sinkhorn_eps, run): 134 | '''This function creates the file neame to be found with the given parameters''' 135 | 136 | [N_data_samples, N_born_samples, batch_size, N_kernel_samples] = N_samples 137 | score = stein_params[0] 138 | stein_eigvecs = stein_params[1] 139 | stein_eta = stein_params[2] 140 | if data_type.lower() == 'quantum_data': 141 | if cost_func.lower() == 'mmd': 142 | trial_name = "outputs/Output_MMD_%s_%s_%s_%skernel_%ikernel_samples_%iBorn_Samples%iData_samples_%iBatch_size_%iEpochs_%.3fLR_%s_Run%s" \ 143 | %(qc,\ 144 | data_type,\ 145 | data_circuit,\ 146 | kernel_type,\ 147 | N_kernel_samples,\ 148 | N_born_samples,\ 149 | N_data_samples,\ 150 | batch_size,\ 151 | N_epochs,\ 152 | learning_rate, \ 153 | score,\ 154 | str(run)) 155 | 156 | 157 | elif cost_func.lower() == 'stein': 158 | trial_name = "outputs/Output_Stein_%s_%s_%s_%skernel_%ikernel_samples_%iBorn_Samples%iData_samples_%iBatch_size_%iEpochs_%.3fLR_%s_%iEigvecs_%.3fEta_Run%s" \ 159 | %(qc,\ 160 | data_type,\ 161 | data_circuit,\ 162 | kernel_type,\ 163 | N_kernel_samples,\ 164 | N_born_samples,\ 165 | N_data_samples,\ 166 | batch_size,\ 167 | N_epochs,\ 168 | learning_rate,\ 169 | score,\ 170 | stein_eigvecs, \ 171 | stein_eta,\ 172 | str(run)) 173 | 174 | 175 | elif cost_func.lower() == 'sinkhorn': 176 | trial_name = "outputs/Output_Sinkhorn_%s_%s_%s_HammingCost_%iBorn_Samples%iData_samples_%iBatch_size_%iEpochs_%.3fLR_%.3fEpsilon_Run%s" \ 177 | %(qc,\ 178 | data_type,\ 179 | data_circuit,\ 180 | N_born_samples,\ 181 | N_data_samples,\ 182 | batch_size,\ 183 | N_epochs,\ 184 | learning_rate,\ 185 | sinkhorn_eps,\ 186 | str(run)) 187 | elif data_type.lower() == 'bernoulli_data': 188 | if cost_func.lower() == 'mmd': 189 | trial_name = "outputs/Output_MMD_%s_%skernel_%ikernel_samples_%iBorn_Samples%iData_samples_%iBatch_size_%iEpochs_%.3fLR_%s_Run%s" \ 190 | %(qc,\ 191 | kernel_type,\ 192 | N_kernel_samples,\ 193 | N_born_samples,\ 194 | N_data_samples,\ 195 | batch_size,\ 196 | N_epochs,\ 197 | learning_rate,\ 198 | score, \ 199 | str(run)) 200 | 201 | 202 | elif cost_func.lower() == 'stein': 203 | trial_name = "outputs/Output_Stein_%s_%skernel_%ikernel_samples_%iBorn_Samples%iData_samples_%iBatch_size_%iEpochs_%.3fLR_%s_%iEigvecs_%.3fEta_Run%s" \ 204 | %(qc,\ 205 | kernel_type,\ 206 | N_kernel_samples,\ 207 | N_born_samples,\ 208 | N_data_samples,\ 209 | batch_size,\ 210 | N_epochs,\ 211 | learning_rate,\ 212 | score,\ 213 | stein_eigvecs, 214 | stein_eta, \ 215 | str(run)) 216 | 217 | 218 | 219 | elif cost_func.lower() == 'sinkhorn': 220 | trial_name = "outputs/Output_Sinkhorn_%s_HammingCost_%iBorn_Samples%iData_samples_%iBatch_size_%iEpochs_%.3fLR_%.3fEpsilon_Run%s" \ 221 | %(qc,\ 222 | N_born_samples,\ 223 | N_data_samples,\ 224 | batch_size,\ 225 | N_epochs,\ 226 | learning_rate,\ 227 | sinkhorn_eps,\ 228 | str(run)) 229 | 230 | else: raise IOError('\'data_type\' must be either \'Quantum_Data\' or \'Bernoulli_Data\'') 231 | return trial_name 232 | 233 | def TrainingDataFromFile(cost_func, data_type, data_circuit, N_epochs, learning_rate, qc, kernel_type, N_samples, stein_params, sinkhorn_eps, run): 234 | '''This function reads in all information generated during the training process for a specified set of parameters''' 235 | 236 | trial_name = FindTrialNameFile(cost_func, data_type, data_circuit, N_epochs,learning_rate, qc, kernel_type, N_samples, stein_params, sinkhorn_eps, run) 237 | 238 | with open('%s/info' %trial_name, 'r') as training_data_file: 239 | training_data = training_data_file.readlines() 240 | print(training_data) 241 | 242 | circuit_params = {} 243 | loss = {} 244 | loss[('%s' %cost_func, 'Train')] = np.loadtxt('%s/loss/%s/train' %(trial_name,cost_func), dtype = float) 245 | loss[('%s' %cost_func, 'Test')] = np.loadtxt('%s/loss/%s/test' %(trial_name,cost_func), dtype = float) 246 | loss[('TV')] = np.loadtxt('%s/loss/TV' %(trial_name) , dtype = float) 247 | 248 | born_probs = [] 249 | data_probs = [] 250 | for epoch in range(0, N_epochs - 1): 251 | circuit_params[('J', epoch)] = np.loadtxt('%s/params/weights/epoch%s' %(trial_name, epoch), dtype = float) 252 | circuit_params[('b', epoch)] = np.loadtxt('%s/params/biases/epoch%s' %(trial_name, epoch), dtype = float) 253 | circuit_params[('gamma', epoch)] = np.loadtxt('%s/params/gammaX/epoch%s' %(trial_name, epoch), dtype = float) 254 | circuit_params[('delta', epoch)] = np.loadtxt('%s/params/gammaY/epoch%s' %(trial_name, epoch), dtype = float) 255 | 256 | with open('%s/probs/born/epoch%s' %(trial_name, epoch), 'r') as f: 257 | born_probs_dict, _, _ = FileLoad(f, 'probs_input') 258 | born_probs.append(born_probs_dict) 259 | with open('%s/probs/data/epoch%s' %(trial_name, epoch), 'r') as g: 260 | data_probs_dict, _, _ = FileLoad(g, 'probs_input') 261 | data_probs.append(data_probs_dict) 262 | 263 | return loss, circuit_params, born_probs, data_probs 264 | 265 | def ReadFromFile(N_epochs, learning_rate, data_type, data_circuit, 266 | N_born_samples, N_data_samples, N_kernel_samples, 267 | batch_size, kernel_type, cost_func, qc, score, 268 | stein_eigvecs, stein_eta, sinkhorn_eps, runs): 269 | 270 | if type(N_epochs) is not list: 271 | #If the Inputs are not a list, there is only one trial 272 | N_trials = 1 273 | else: 274 | N_trials = len(N_epochs) #Number of trials to be compared is the number of elements in each input list 275 | 276 | if type(runs) is int: 277 | N_runs = 1 278 | elif all(run == 0 for run in runs): 279 | #If the runs are equal to zero, there is only a single run to be considered 280 | N_runs = 1 281 | else: 282 | N_runs = len(runs) #Number of trials to be compared is the maximum value in runs list 283 | 284 | 285 | if N_trials == 1: 286 | if N_runs == 1: 287 | N_samples = [N_data_samples, N_born_samples, batch_size, N_kernel_samples] 288 | stein_params = {0: score , 1 : stein_eigvecs , 2 : stein_eta , 3 : kernel_type } 289 | 290 | loss, circuit_params, born_probs, data_probs = TrainingDataFromFile(cost_func,\ 291 | data_type, data_circuit, N_epochs, learning_rate, \ 292 | qc, kernel_type, N_samples, stein_params, sinkhorn_eps, 0) 293 | 294 | born_probs_final = born_probs[-1] 295 | data_probs_final= data_probs[-1] 296 | else: 297 | for run in runs: 298 | N_samples = [N_data_samples, N_born_samples, batch_size, N_kernel_samples] 299 | 300 | [loss, circuit_params, born_probs_final, data_probs_final] = [[] for _ in range(4)] 301 | 302 | stein_params = {0: score , 1 : stein_eigvecs , 2 : stein_eta , 3 : kernel_type } 303 | 304 | loss_per_run, circuit_params_per_run, born_probs_per_run, data_probs_per_run = TrainingDataFromFile(cost_func,\ 305 | data_type, data_circuit, N_epochs, learning_rate, \ 306 | qc, kernel_type, N_samples, stein_params, sinkhorn_eps, run) 307 | 308 | loss.append(loss_per_run) 309 | born_probs_final.append(born_probs[-1]) 310 | data_probs_final.append(data_probs_per_run[-1]) 311 | else: 312 | 313 | [loss, circuit_params, born_probs_final, data_probs_final] = [[] for _ in range(4)] 314 | if N_runs == 1: 315 | for trial in range(N_trials): 316 | N_samples = [N_data_samples[trial], N_born_samples[trial], batch_size[trial], N_kernel_samples[trial]] 317 | stein_params = {0: score[trial] , 1 : stein_eigvecs[trial] , 2 : stein_eta[trial] , 3 : kernel_type[trial] } 318 | 319 | loss_per_trial, circuit_params_per_trial, born_probs_per_trial, data_probs_per_trial = TrainingDataFromFile(cost_func[trial],\ 320 | data_type[trial], data_circuit[trial], N_epochs[trial], learning_rate[trial], \ 321 | qc[trial], kernel_type[trial], N_samples, stein_params, sinkhorn_eps[trial], 0) 322 | loss.append(loss_per_trial) 323 | circuit_params.append(circuit_params_per_trial) 324 | born_probs_final.append(born_probs_per_trial[-1]) 325 | data_probs_final.append(data_probs_per_trial[-1]) 326 | else: 327 | for run in range(N_runs): 328 | print('Runs', run) 329 | N_samples = [N_data_samples[run], N_born_samples[run], batch_size[run], N_kernel_samples[run]] 330 | stein_params = {0: score[run], 1: stein_eigvecs[run] , 2 : stein_eta[run] , 3 : kernel_type[run]} 331 | 332 | loss_per_run, circuit_params_per_run, born_probs_per_run, data_probs_per_run = TrainingDataFromFile(cost_func[run],\ 333 | data_type[run], data_circuit[run], N_epochs[run], learning_rate[run], \ 334 | qc[run], kernel_type[run], N_samples, stein_params, sinkhorn_eps[run], runs[run]) 335 | loss.append(loss_per_run) 336 | circuit_params.append(circuit_params_per_run) 337 | born_probs_final.append(born_probs_per_run[-1]) 338 | data_probs_final.append(data_probs_per_run[-1]) 339 | 340 | return loss, born_probs_final, data_probs_final 341 | 342 | def AverageCostsFromFile(N_epochs, learning_rate, data_type, data_circuit, 343 | N_born_samples, N_data_samples, N_kernel_samples, 344 | batch_size, kernel_type, cost_func, qc, score, 345 | stein_eigvecs, stein_eta, sinkhorn_eps): 346 | ''' 347 | This function reads the average cost functions, and upper and lower errors from fiel with given parameters 348 | ''' 349 | N_samples = [N_data_samples, N_born_samples, batch_size, N_kernel_samples] 350 | stein_params = {0: score , 1 : stein_eigvecs , 2 : stein_eta , 3 : kernel_type } 351 | 352 | trial_name = FindTrialNameFile(cost_func, data_type, data_circuit, N_epochs,learning_rate, qc, kernel_type, N_samples, stein_params, sinkhorn_eps, 'Average') 353 | 354 | with open('%s/info' %trial_name, 'r') as training_data_file: 355 | training_data = training_data_file.readlines() 356 | print(training_data) 357 | 358 | [average_loss, upper_error, lower_error] = [{} for _ in range(3)] 359 | 360 | average_loss[('%s' %cost_func, 'Train')] = np.loadtxt('%s/loss/%s/train_avg' %(trial_name,cost_func), dtype = float) 361 | average_loss[('%s' %cost_func, 'Test')] = np.loadtxt('%s/loss/%s/test_avg' %(trial_name,cost_func), dtype = float) 362 | average_loss[('TV')] = np.loadtxt('%s/loss/TV/average' %(trial_name) , dtype = float) 363 | 364 | upper_error[('%s' %cost_func, 'Train')] = np.loadtxt('%s/loss/%s/upper_error/train' %(trial_name,cost_func), dtype = float) 365 | upper_error[('%s' %cost_func, 'Test')] = np.loadtxt('%s/loss/%s/upper_error/test' %(trial_name,cost_func), dtype = float) 366 | upper_error[('TV')] = np.loadtxt('%s/loss/TV/upper_error' %(trial_name) , dtype = float) 367 | 368 | lower_error[('%s' %cost_func, 'Train')] = np.loadtxt('%s/loss/%s/lower_error/train' %(trial_name,cost_func), dtype = float) 369 | lower_error[('%s' %cost_func, 'Test')] = np.loadtxt('%s/loss/%s/lower_error/test' %(trial_name,cost_func), dtype = float) 370 | lower_error[('TV')] = np.loadtxt('%s/loss/TV/lower_error' %(trial_name) , dtype = float) 371 | 372 | return average_loss, upper_error, lower_error 373 | 374 | 375 | -------------------------------------------------------------------------------- /file_operations_out.py: -------------------------------------------------------------------------------- 1 | from train_generation import TrainingData, DataSampler 2 | from auxiliary_functions import EmpiricalDist, AllBinaryStrings, num_bytes_needed, SampleListToArray 3 | from kernel_functions import KernelAllBinaryStrings 4 | from param_init import NetworkParams 5 | from sample_gen import BornSampler 6 | import json 7 | import numpy as np 8 | import sys 9 | import os 10 | from pyquil.api import get_qc 11 | 12 | import argparse 13 | 14 | max_qubits = 8 15 | 16 | 17 | def ParseInputArguments(): 18 | ''' 19 | This function returns optional input arguments in order to print various things to files, i.e. kernels, data. 20 | ''' 21 | parser = argparse.ArgumentParser() 22 | 23 | parser.add_argument("Data", help = "Argument specifying which data should be printed", type = str) 24 | parser.add_argument("Qubits", help = "Argument specifying maximum number of qubits to be printed", type = int) 25 | parser.add_argument("Kernel", help = "Argument specifying which kernel should be printed", type = str) 26 | args = parser.parse_args() 27 | return args 28 | 29 | 30 | def MakeDirectory(path): 31 | '''Makes an directory in the given \'path\', if it does not exist already''' 32 | if not os.path.exists(path): 33 | os.makedirs(path) 34 | return 35 | 36 | def PrintParamsToFile(seed, max_qubits): 37 | 38 | for qubit_index in range(2, max_qubits): 39 | 40 | J_init, b_init, gamma_init, delta_init = NetworkParams(qubit_index, seed) 41 | np.savez('data/Parameters_%iQubits.npz' % (qubit_index), J_init = J_init, b_init = b_init, gamma_init = gamma_init, delta_init = delta_init) 42 | 43 | return 44 | 45 | #PrintParamsToFile(seed, max_qubits) 46 | 47 | def KernelDictToFile(N_qubits, N_kernel_samples, kernel_dict, kernel_choice): 48 | #writes kernel dictionary to file 49 | if (N_kernel_samples == 'infinite'): 50 | with open('kernel/%sKernel_Dict_%iQBs_Exact' % (kernel_choice[0], N_qubits), 'w') as f: 51 | dict_keys = kernel_dict.keys() 52 | dict_values = kernel_dict.values() 53 | k1 = [str(key) for key in dict_keys] 54 | print(json.dump(json.dumps(dict(zip(*[k1, dict_values])), sort_keys=True, indent=0),f)) 55 | print(json.dumps(dict(zip(*[k1, dict_values])), sort_keys=True, indent=0)) 56 | 57 | else: 58 | with open('kernel/%sKernel_Dict_%iQBs_%iKernelSamples' % (kernel_choice[0], N_qubits, N_kernel_samples), 'w') as f: 59 | dict_keys = kernel_dict.keys() 60 | dict_values = kernel_dict.values() 61 | k1 = [str(key) for key in dict_keys] 62 | print(json.dump(json.dumps(dict(zip(*[k1, dict_values])), sort_keys=True),f)) 63 | print(json.dumps(dict(zip(*[k1, dict_values])), sort_keys=True, indent=0)) 64 | 65 | return 66 | 67 | def PrintKernel(N_kernel_samples, kernel_choice, max_qubits): 68 | #print the required kernel out to a file, for all binary strings 69 | devices = [get_qc('%iq-qvm' %N_qubits , as_qvm = True) for N_qubits in range(2, max_qubits)] 70 | 71 | for qc in devices: 72 | N_qubits = len(qc.qubits()) 73 | print('This is qubit:', N_qubits) 74 | #The number of samples, N_samples = infinite if the exact kernel is being computed 75 | _,_, kernel_approx_dict,_ = KernelAllBinaryStrings(qc, N_kernel_samples, kernel_choice) 76 | 77 | KernelDictToFile(N_qubits, N_kernel_samples, kernel_approx_dict, kernel_choice) 78 | return 79 | 80 | def PrintSomeKernels(kernel_type, max_qubits): 81 | kernel_path = './kernel' #Create Folder for data if it does not exist 82 | MakeDirectory(kernel_path) 83 | N_kernel_samples_list = [10, 100, 200, 500, 1000, 2000] 84 | # N_kernel_samples_list = [10] 85 | 86 | for N_kernel_samples in N_kernel_samples_list: 87 | print("Kernel is printing for %i samples" %N_kernel_samples) 88 | PrintKernel(N_kernel_samples, kernel_type, max_qubits) 89 | 90 | print("Exact Kernel is Printing") 91 | PrintKernel('infinite', kernel_type, max_qubits) 92 | return 93 | 94 | 95 | def DataDictToFile(data_type, N_qubits, data_dict, N_data_samples, *args): 96 | ''' This function prepares data samples according to a a specified number of samples 97 | for all number of visible qubits up to max_qubits, and saves them to files''' 98 | 99 | if data_type.lower() == 'bernoulli_data': 100 | if (N_data_samples == 'infinite'): 101 | with open('data/Bernoulli_Data_Dict_%iQBs_Exact' % N_qubits, 'w') as f: 102 | json.dump(json.dumps(data_dict, sort_keys=True),f) 103 | else: 104 | with open('data/Bernoulli_Data_Dict_%iQBs_%iSamples' % (N_qubits, N_data_samples), 'w') as f: 105 | json.dump(json.dumps(data_dict, sort_keys=True),f) 106 | elif data_type.lower() == 'quantum_data': 107 | circuit_choice = args[0] 108 | if (N_data_samples == 'infinite'): 109 | with open('data/Quantum_Data_Dict_%iQBs_Exact_%sCircuit' % (N_qubits, circuit_choice), 'w') as f: 110 | json.dump(json.dumps(data_dict, sort_keys=True),f) 111 | else: 112 | with open('data/Quantum_Data_Dict_%iQBs_%iSamples_%sCircuit' % (N_qubits, N_data_samples, circuit_choice), 'w') as f: 113 | json.dump(json.dumps(data_dict, sort_keys=True),f) 114 | 115 | else: raise ValueError('Please enter either \'Quantum_Data\' or \'Bernoulli_Data\' for \'data_type\' ') 116 | 117 | return 118 | 119 | 120 | def PrintCircuitParamsToFile(random_seed, circuit_choice): 121 | quantum_computers = [get_qc('%iq-qvm' %N_qubits , as_qvm = True) for N_qubits in range(2, 7)] 122 | for qc in quantum_computers: 123 | device_name = qc.name 124 | qubits = qc.qubits() 125 | N_qubits = len(qubits) 126 | circuit_params = NetworkParams(qc, random_seed) 127 | np.savez('data/Parameters_%iQbs_%sCircuit_%sDevice.npz' % (N_qubits, circuit_choice, device_name),\ 128 | J = circuit_params['J'], b = circuit_params['b'], gamma = circuit_params['gamma'], delta = circuit_params['delta']) 129 | 130 | def string_to_int_byte(string, N_qubits, byte): 131 | 132 | total = 0 133 | for qubit in range(8 * byte, min(8 * (byte + 1), N_qubits)): 134 | total <<= 1 135 | total += int(string[qubit]) 136 | 137 | return total 138 | 139 | def PrintDataToFiles(data_type, N_samples, qc, circuit_choice, N_qubits): 140 | 141 | binary_data_path = 'binary_data/' 142 | MakeDirectory(binary_data_path) 143 | data_path = 'data/' 144 | MakeDirectory(data_path) 145 | if data_type == 'Bernoulli_Data': 146 | 147 | #Define training data along with all binary strings on the visible and hidden variables from train_generation 148 | #M_h is the number of hidden Bernoulli modes in the data 149 | M_h = 8 150 | N_h = 0 151 | data_probs, exact_data_dict = TrainingData(N_qubits, N_h, M_h) 152 | 153 | data_samples = DataSampler(N_qubits, N_h, M_h, N_samples, data_probs) 154 | 155 | #Save data as binary files 156 | with open('binary_data/Bernoulli_Data_%iQBs_%iSamples' % (N_qubits, N_samples), 'wb') as f: 157 | 158 | for string in data_samples: 159 | for byte in range(num_bytes_needed(N_qubits)): 160 | 161 | total = string_to_int_byte(string, N_qubits, byte) 162 | 163 | f.write(bytes([total])) 164 | 165 | np.savetxt('data/Bernoulli_Data_%iQBs_%iSamples' % (N_qubits, N_samples), data_samples, fmt='%s') 166 | data_samples_list = SampleListToArray(data_samples, N_qubits, 'int') 167 | emp_data_dist = EmpiricalDist(data_samples_list, N_qubits) 168 | DataDictToFile(data_type, N_qubits, emp_data_dist, N_samples) 169 | 170 | np.savetxt('data/Bernoulli_Data_%iQBs_Exact' % (N_qubits), np.asarray(data_probs), fmt='%.10f') 171 | DataDictToFile(data_type, N_qubits, exact_data_dict, 'infinite') 172 | 173 | elif data_type.lower() == 'quantum_data': 174 | 175 | #Set random seed differently to that which initialises the actual Born machine to be trained 176 | random_seed_for_data = 13 177 | N_Born_Samples = [0, N_samples] #BornSampler takes a list of sample values, the [1] entry is the important one 178 | circuit_params = NetworkParams(qc, random_seed_for_data) #Initialise a fixed instance of parameters to learn. 179 | quantum_data_samples, quantum_probs_dict, quantum_probs_dict_exact = BornSampler(qc, N_Born_Samples, circuit_params, circuit_choice) 180 | 181 | np.savetxt('data/Quantum_Data_%iQBs_%iSamples_%sCircuit' % (N_qubits, N_samples, circuit_choice), quantum_data_samples, fmt='%s') 182 | DataDictToFile(data_type, N_qubits, quantum_probs_dict, N_samples, circuit_choice) 183 | np.savetxt('data/Quantum_Data_%iQBs_Exact_%sCircuit' % (N_qubits, circuit_choice), np.asarray(quantum_data_samples), fmt='%.10f') 184 | DataDictToFile(data_type, N_qubits, quantum_probs_dict_exact, 'infinite', circuit_choice) 185 | 186 | else: raise ValueError('Please enter either \'Quantum_Data\' or \'Bernoulli_Data\' for \'data_type\' ') 187 | 188 | return 189 | 190 | def PrintAllDataToFiles(data_type, max_qubits, *args): 191 | ''' 192 | This function prints all data samples to files, for either Quantum or Classical Data 193 | for all number of qubits between 2 and max_qubits. 194 | ''' 195 | N_sample_trials = [10, 20, 30, 40, 50, 80, 100, 200, 300, 400, 500, 600, 700, 1000, 2000, 3000, 4000, 5000, 6000, 8000, 10000] 196 | 197 | for N_qubits in range(2, max_qubits): 198 | for N_samples in N_sample_trials: 199 | if data_type == 'Quantum_Data': 200 | qc = get_qc('%iq-qvm' %N_qubits , as_qvm = True) 201 | circuit_choice = args[0] 202 | 203 | print('Quantum Data is printing for %i qubits on qc %s using circuit choice %s' %(N_qubits, qc.name, circuit_choice)) 204 | PrintDataToFiles('Quantum_Data', N_samples, qc, circuit_choice, N_qubits) 205 | 206 | elif data_type == 'Bernoulli_Data': 207 | qc = None 208 | circuit_choice = None 209 | print('Bernoulli Data is printing for %i qubits' %N_qubits) 210 | PrintDataToFiles('Bernoulli_Data', N_samples, qc, circuit_choice, N_qubits) 211 | 212 | 213 | 214 | # #Uncomment to print circuit parameters to file, corresponding to the data, if the data is quantum 215 | # random_seed_for_data = 13 216 | # PrintCircuitParamsToFile(random_seed_for_data, 'IQP') 217 | 218 | def MakeTrialNameFile(cost_func,data_type, data_circuit, N_epochs,learning_rate, qc, kernel_type, N_samples, stein_params, sinkhorn_eps, run): 219 | '''This function prints out all information generated during the training process for a specified set of parameters''' 220 | 221 | [N_data_samples, N_born_samples, batch_size, N_kernel_samples] = N_samples 222 | if data_type == 'Quantum_Data': 223 | if cost_func == 'MMD': 224 | score = stein_params[0] 225 | 226 | trial_name = "outputs/Output_MMD_%s_%s_%s_%skernel_%ikernel_samples_%iBorn_Samples%iData_samples_%iBatch_size_%iEpochs_%.3fLR_%s_Run%s" \ 227 | %(qc,data_type, data_circuit, kernel_type,N_kernel_samples,N_born_samples, N_data_samples,batch_size,N_epochs,\ 228 | learning_rate, score[0:5], str(run)) 229 | 230 | path_to_output = './%s/' %trial_name 231 | MakeDirectory(path_to_output) 232 | 233 | with open('%s/info' %trial_name, 'w') as f: 234 | sys.stdout = f 235 | print("The data is:cost function:MMD chip:%s Data_type: %s Data Circuit: %s kernel:%s N kernel samples:%i N Born Samples:%i N Data samples:%s\ 236 | Batch size:%i Epochs:%i Adam Learning Rate:%.3f_%s Run: %i" 237 | %(qc,\ 238 | data_type,\ 239 | data_circuit,\ 240 | kernel_type,\ 241 | N_kernel_samples,\ 242 | N_born_samples,\ 243 | N_data_samples,\ 244 | batch_size,\ 245 | N_epochs,\ 246 | learning_rate,\ 247 | score[0:5],\ 248 | str(run))) 249 | 250 | elif cost_func == 'Stein': 251 | score = stein_params[0] 252 | stein_eigvecs = stein_params[1] 253 | stein_eta = stein_params[2] 254 | 255 | trial_name = "outputs/Output_Stein_%s_%s_%s_%skernel_%ikernel_samples_%iBorn_Samples%iData_samples_%iBatch_size_%iEpochs_%.3fLR_%s_%iEigvecs_%.3fEta_Run%s" \ 256 | %(qc, data_type, data_circuit, kernel_type, N_kernel_samples, N_born_samples, N_data_samples, batch_size,\ 257 | N_epochs, learning_rate, score, stein_eigvecs, stein_eta, str(run)) 258 | 259 | path_to_output = './%s/' %trial_name 260 | MakeDirectory(path_to_output) 261 | 262 | with open('%s/info' %trial_name, 'w') as f: 263 | sys.stdout = f 264 | print("The data is: cost function: Stein, chip:%s Data_type: %s Data Circuit: %s kernel:%s N kernel samples:%i \n N Born Samples:%i N Data samples:%s\ 265 | Batch size:%iEpochs:%iAdam Learning Rate:%.3fStein Score:%sN Nystrom Eigvecs:%iStein Eta:%.3f Run: %i" 266 | 267 | %(qc,\ 268 | data_type,\ 269 | data_circuit,\ 270 | kernel_type,\ 271 | N_kernel_samples,\ 272 | N_born_samples,\ 273 | N_data_samples,\ 274 | batch_size,\ 275 | N_epochs,\ 276 | learning_rate,\ 277 | score,\ 278 | stein_eigvecs, \ 279 | stein_eta,\ 280 | str(run))) 281 | 282 | elif cost_func == 'Sinkhorn': 283 | trial_name = "outputs/Output_Sinkhorn_%s_%s_%s_HammingCost_%iBorn_Samples%iData_samples_%iBatch_size_%iEpochs_%.3fLR_%.3fEpsilon_Run%s" \ 284 | %(qc,\ 285 | data_type,\ 286 | data_circuit,\ 287 | N_born_samples,\ 288 | N_data_samples,\ 289 | batch_size,\ 290 | N_epochs,\ 291 | learning_rate,\ 292 | sinkhorn_eps, \ 293 | str(run)) 294 | 295 | path_to_output = './%s/' %trial_name 296 | MakeDirectory(path_to_output) 297 | 298 | with open('%s/info' %trial_name, 'w') as f: 299 | sys.stdout = f 300 | print("The data is: cost function:Sinkhorn Data_type: %s Data Circuit: %s chip: %s kernel: %s N kernel samples: %i \ 301 | N Born Samples: %i N Data samples: %i Batch size: %i Epochs: %i Adam Learning Rate: %.3f Sinkhorn Epsilon: %.3f Run: %s" 302 | 303 | %(qc,\ 304 | data_type,\ 305 | data_circuit,\ 306 | kernel_type,\ 307 | N_kernel_samples,\ 308 | N_born_samples,\ 309 | N_data_samples,\ 310 | batch_size,\ 311 | N_epochs,\ 312 | learning_rate,\ 313 | sinkhorn_eps,\ 314 | str(run))) 315 | 316 | elif data_type == 'Bernoulli_Data': 317 | if cost_func == 'MMD': 318 | score = stein_params[0] 319 | trial_name = "outputs/Output_MMD_%s_%skernel_%ikernel_samples_%iBorn_Samples%iData_samples_%iBatch_size_%iEpochs_%.3fLR_%s_Run%s" \ 320 | %(qc,\ 321 | kernel_type,\ 322 | N_kernel_samples,\ 323 | N_born_samples,\ 324 | N_data_samples,\ 325 | batch_size,\ 326 | N_epochs,\ 327 | learning_rate,\ 328 | score,\ 329 | str(run)) 330 | 331 | path_to_output = './%s/' %trial_name 332 | MakeDirectory(path_to_output) 333 | 334 | with open('%s/info' %trial_name, 'w') as f: 335 | sys.stdout = f 336 | print("The data is:cost function:MMD chip:%s kernel:%s N kernel samples:%i N Born Samples:%i N Data samples:%s\ 337 | Batch size:%i Epochs:%i Adam Learning Rate:%.3f, Data Form: %s Run: %s" 338 | %(qc,\ 339 | kernel_type,\ 340 | N_kernel_samples,\ 341 | N_born_samples,\ 342 | N_data_samples,\ 343 | batch_size,\ 344 | N_epochs,\ 345 | learning_rate, \ 346 | score,\ 347 | str(run))) 348 | 349 | elif cost_func == 'Stein': 350 | score = stein_params[0] 351 | stein_eigvecs = stein_params[1] 352 | stein_eta = stein_params[2] 353 | 354 | trial_name = "outputs/Output_Stein_%s_%skernel_%ikernel_samples_%iBorn_Samples%iData_samples_%iBatch_size_%iEpochs_%.3fLR_%s_%iEigvecs_%.3fEta_Run%s" \ 355 | %(qc,\ 356 | kernel_type,\ 357 | N_kernel_samples,\ 358 | N_born_samples,\ 359 | N_data_samples,\ 360 | batch_size,\ 361 | N_epochs,\ 362 | learning_rate,\ 363 | score,\ 364 | stein_eigvecs, 365 | stein_eta,\ 366 | str(run)) 367 | path_to_output = './%s/' %trial_name 368 | MakeDirectory(path_to_output) 369 | 370 | with open('%s/info' %trial_name, 'w') as f: 371 | sys.stdout = f 372 | print("The data is: cost function: Stein, chip:%s kernel:%s N kernel samples:%i \n N Born Samples:%i N Data samples:%i\ 373 | Batch size:%iEpochs:%iAdam Learning Rate:%.3fStein Score:%sN Nystrom Eigvecs:%iStein Eta:%.3f Run: %s" 374 | 375 | %(qc,\ 376 | kernel_type,\ 377 | N_kernel_samples,\ 378 | N_born_samples,\ 379 | N_data_samples,\ 380 | batch_size,\ 381 | N_epochs,\ 382 | learning_rate,\ 383 | score,\ 384 | stein_eigvecs, \ 385 | stein_eta,\ 386 | str(run))) 387 | 388 | elif cost_func == 'Sinkhorn': 389 | trial_name = "outputs/Output_Sinkhorn_%s_HammingCost_%iBorn_Samples%iData_samples_%iBatch_size_%iEpochs_%.3fLR_%.3fEpsilon_Run%s" \ 390 | %(qc,\ 391 | N_born_samples,\ 392 | N_data_samples,\ 393 | batch_size,\ 394 | N_epochs,\ 395 | learning_rate,\ 396 | sinkhorn_eps,\ 397 | str(run)) 398 | 399 | path_to_output = './%s/' %trial_name 400 | MakeDirectory(path_to_output) 401 | 402 | with open('%s/info' %trial_name, 'w') as f: 403 | sys.stdout = f 404 | print("The data is: cost function: Sinkhorn chip: %s kernel: %sN kernel samples: %i N Born Samples: %iN Data samples: %i Batch size: %i \n \ 405 | Epochs: %i Adam Learning Rate: %.3f Sinkhorn Epsilon: %.3f Run: %s" 406 | 407 | %(qc,\ 408 | kernel_type,\ 409 | N_kernel_samples,\ 410 | N_born_samples,\ 411 | N_data_samples,\ 412 | batch_size,\ 413 | N_epochs,\ 414 | learning_rate,\ 415 | sinkhorn_eps,\ 416 | str(run))) 417 | 418 | 419 | else: raise ValueError('\'data_type\' must be either \'Quantum_Data\' or \'Bernoulli_Data\'') 420 | return trial_name 421 | 422 | def PrintFinalParamsToFile(cost_func, data_type, data_circuit, N_epochs, learning_rate, loss, 423 | circuit_params, data_exact_dict, born_probs_list, empirical_probs_list, qc, 424 | kernel_type, N_samples, stein_params, sinkhorn_eps, run): 425 | '''This function prints out all information generated during the training process for a specified set of parameters''' 426 | 427 | 428 | trial_name = MakeTrialNameFile(cost_func, data_type, data_circuit, \ 429 | N_epochs,learning_rate, qc.name, kernel_type, N_samples, stein_params, sinkhorn_eps, run) 430 | 431 | loss_path = '%s/loss/%s/' %(trial_name, cost_func) 432 | weight_path = '%s/params/weights/' %trial_name 433 | bias_path = '%s/params/biases/' %trial_name 434 | gammax_path = '%s/params/gammaX/' %trial_name 435 | gammay_path = '%s/params/gammaY/' %trial_name 436 | 437 | born_probs_path = '%s/probs/born/' %trial_name 438 | data_probs_path = '%s/probs/data/' %trial_name 439 | 440 | #create directories to store output training information 441 | MakeDirectory(loss_path) 442 | # MakeDirectory(tv_path) 443 | 444 | MakeDirectory(weight_path) 445 | MakeDirectory(bias_path) 446 | MakeDirectory(gammax_path) 447 | MakeDirectory(gammay_path) 448 | 449 | MakeDirectory(born_probs_path) 450 | MakeDirectory(data_probs_path) 451 | 452 | 453 | # with open('%s/loss' %trail_name, 'w'): 454 | np.savetxt('%s/loss/%s/train' %(trial_name,cost_func), loss[('%s' %cost_func, 'Train')]) 455 | np.savetxt('%s/loss/%s/test' %(trial_name,cost_func), loss[('%s' %cost_func, 'Test')] ) 456 | 457 | np.savetxt('%s/loss/TV' %(trial_name), loss[('TV')]) #Print Total Variation of Distributions during training 458 | 459 | data_path = '%s/data' %(trial_name) 460 | for epoch in range(0, N_epochs - 1): 461 | np.savetxt('%s/params/weights/epoch%s' %(trial_name, epoch), circuit_params[('J', epoch)]) 462 | np.savetxt('%s/params/biases/epoch%s' %(trial_name, epoch), circuit_params[('b', epoch)]) 463 | np.savetxt('%s/params/gammaX/epoch%s' %(trial_name, epoch), circuit_params[('gamma', epoch)]) 464 | np.savetxt('%s/params/gammaY/epoch%s' %(trial_name, epoch), circuit_params[('delta', epoch)]) 465 | 466 | with open('%s/probs/born/epoch%s' %(trial_name, epoch), 'w') as f: 467 | json.dump(json.dumps(empirical_probs_list[epoch], sort_keys=True),f) 468 | with open('%s/probs/data/epoch%s' %(trial_name, epoch), 'w') as f: 469 | json.dump(json.dumps(data_exact_dict, sort_keys=True),f) 470 | return 471 | 472 | def main(): 473 | args = ParseInputArguments() 474 | data = args.Data 475 | max_qubits = args.Qubits 476 | kernel = args.Kernel 477 | 478 | if data.lower() == 'bernoulli': 479 | 480 | # Bernoulli data needs to be printed to file 481 | PrintAllDataToFiles('Bernoulli_Data', max_qubits) 482 | 483 | elif data.lower() == 'quantum': 484 | 485 | #quantum data needs to be printed to file 486 | PrintAllDataToFiles('Quantum_Data', max_qubits, 'IQP') 487 | 488 | if kernel.lower() == 'gaussian': 489 | 490 | #Gaussian Kernel needed to be printed to file 491 | PrintSomeKernels('Gaussian', max_qubits) 492 | 493 | elif kernel.lower() == 'quantum': 494 | 495 | #Quantum Kernel needed to be printed to file 496 | PrintSomeKernels('Quantum', max_qubits) 497 | 498 | # #Uncomment to print circuit parameters to file, corresponding to the data, if the data is quantum 499 | # random_seed_for_data = 13 500 | # PrintCircuitParamsToFile(random_seed_for_data, 'IQP') 501 | 502 | if __name__ == '__main__': 503 | main() 504 | -------------------------------------------------------------------------------- /kernel_functions.py: -------------------------------------------------------------------------------- 1 | from pyquil.quil import Program 2 | import numpy as np 3 | 4 | from classical_kernel import GaussianKernelArray, GaussianKernelDict 5 | from quantum_kernel import QuantumKernelArray 6 | 7 | from auxiliary_functions import AllBinaryStrings 8 | 9 | def KernelAllBinaryStrings(qc, N_samples, kernel_choice): 10 | ''' 11 | This functions computes the kernel, either Gaussian or Quantum for *all* Binary strings of 12 | length N_qubits 13 | ''' 14 | N_qubits = len(qc.qubits()) 15 | binary_strings_array = AllBinaryStrings(N_qubits) 16 | if (kernel_choice == 'Gaussian'): 17 | sigma = np.array([0.25, 10, 1000]) 18 | kernel_approx_array = GaussianKernelArray(binary_strings_array, binary_strings_array, sigma) 19 | #Gaussian approx kernel is equal to exact kernel 20 | kernel_exact_array = kernel_approx_array 21 | kernel_exact_dict = GaussianKernelDict(binary_strings_array, binary_strings_array, sigma) 22 | kernel_approx_dict = kernel_exact_dict 23 | elif (kernel_choice == 'Quantum'): 24 | #compute for all binary strings 25 | kernel_approx_array, kernel_exact_array, kernel_approx_dict, kernel_exact_dict =\ 26 | QuantumKernelArray(qc, N_samples, binary_strings_array, binary_strings_array) 27 | print(binary_strings_array) 28 | else: raise IOError("Please enter either 'Gaussian' or 'Quantum' to choose a kernel") 29 | 30 | #compute the expectation values for including each binary string sample 31 | return kernel_approx_array, kernel_exact_array, kernel_approx_dict, kernel_exact_dict 32 | 33 | def NormaliseKernel(kernel_array, *argsv): 34 | ''' 35 | This function sums and normalises the kernel matrix to be used in the 36 | training cost function 37 | ''' 38 | if type(kernel_array) is not np.ndarray: 39 | raise IOError('\'kernel_array\' must be a np.ndarray') 40 | 41 | [N_samples1, N_samples2] = kernel_array.shape 42 | if ('same' not in argsv): 43 | #if the two sets of samples come from the same distribution 44 | normed_kernel = (1/(N_samples1*(N_samples2)))*kernel_array.sum() 45 | else: 46 | normed_kernel = (1/(N_samples1*(N_samples2 - 1)))*(kernel_array.sum() - np.diagonal(kernel_array).sum()) 47 | 48 | return normed_kernel -------------------------------------------------------------------------------- /param_init.py: -------------------------------------------------------------------------------- 1 | ## @package param_init 2 | # 3 | # Initialise some inputted variables 4 | 5 | from pyquil.quil import Program 6 | from pyquil.api import get_qc 7 | import pyquil.paulis as pl 8 | from pyquil.gates import H, CPHASE, PHASE, RESET, MEASURE 9 | import numpy as np 10 | import random as rand 11 | from numpy import pi,log2 12 | 13 | def HadamardToAll(prog, qubits): 14 | #qubits is an ordered list of the qubits available in the chip 15 | for qubit_index in qubits: 16 | prog.inst(H(qubit_index)) 17 | return prog 18 | 19 | 20 | ## Initialise weights and biases as random 21 | # 22 | # This function computes the initial parameter values, J, b randomly chosen on interval [0, pi/4], gamma, delta set to constant = pi/4 if untrained 23 | # 24 | # @param[in] qc The Rigetti QuantumComputer Object that is chosen, e.g. 'Aspen-1-2Q-B' 25 | # 26 | # @return initialised parameters 27 | def NetworkParams(qc, random_seed): 28 | 29 | N_qubits = len(qc.qubits()) 30 | 31 | #Initialise arrays for parameters 32 | 33 | J = np.zeros((N_qubits, N_qubits)) 34 | [b, gamma, delta, sigma] = [np.zeros((N_qubits)) for _ in range(4)] 35 | 36 | 37 | #Set random seed to be fixed for reproducibility, set random_seed differently depending on whether quantum data 38 | #is generated, or whether the actual Born machine is being used. 39 | rand.seed(random_seed) 40 | for j in range(0, N_qubits): 41 | b[j] = rand.uniform(0, pi/4) 42 | # If delta to be trained also and variable for each qubit 43 | # rand.seed(j+N) 44 | # gamma[j] = rand.uniform(0,pi/4) 45 | #If delta to be trained also and variable for each qubit 46 | #delta[j] = uniform(0,pi/4) 47 | 48 | gamma[j] = pi/4 #If gamma constant for all qubits 49 | delta[j] = pi/4 #If delta constant for all qubits 50 | sigma[j] = pi/4 #If sigma constant for all qubits 51 | 52 | for i in range(0, N_qubits): 53 | if i < j: 54 | J[i][j] = rand.uniform(0, pi/4) 55 | J[j][i] = J[i][j] 56 | 57 | initial_params = {'J': J, 'b': b, 'gamma': gamma, 'delta': delta, 'sigma': sigma} 58 | 59 | return initial_params 60 | 61 | def NetworkParamsSingleQubitGates(qc, layers): 62 | '''This function initilises single-qubit trainable parameters''' 63 | 64 | N_qubits = len(qc.qubits()) 65 | 66 | #Initialise arrays for parameters 67 | single_qubit_params = np.zeros(N_qubits, N_qubits, N_qubits, layers) 68 | #layers is the number of single qubit layers, each 'layer', l, consists of three gates, R_z(\theta_l^1)R_x(\theta_l^2)R_x(\theta_l^3) 69 | 70 | #Set random seed to be fixed for reproducibility 71 | rand.seed(0) 72 | for j in range(0, N_qubits): 73 | for l in range(0, layers): 74 | #initialise all single qubit gates at random 75 | single_qubit_params[j, :, :, l] = rand.uniform(0,pi/4) 76 | single_qubit_params[:, j, :, l] = rand.uniform(0,pi/4) 77 | single_qubit_params[:, :, j, l] = rand.uniform(0,pi/4) 78 | 79 | return single_qubit_params 80 | 81 | # Initialise Quantum State created after application of gate sequence 82 | def StateInit(qc, circuit_params, p, q, r, s, circuit_choice, control, sign): 83 | '''This function computes the state produced after the given circuit, either QAOA, IQP, or IQPy, 84 | depending on the value of circuit_choice.''' 85 | 86 | #sign = 'POSITIVE' for the positive probability version, sign = 'NEGATIVE' for the negative version of the probability (only used to compute the gradients) 87 | #final_layer is either 'IQP', 'QAOA', 'IQPy' for IQP (Final Hadamard), QAOA (Final X rotation) or IQPy (Final Y rotation) 88 | #control = 'BIAS' for updating biases, = 'WEIGHTS' for updating weights, = 'NEITHER' for neither 89 | 90 | #Initialise empty quantum program, with QuantumComputer Object, and Wavefunction Simulator 91 | '''With Active qubit reset''' 92 | # prog = Program(RESET()) 93 | '''Without Active qubit reset''' 94 | prog = Program() 95 | 96 | qubits = qc.qubits() 97 | N_qubits = len(qubits) 98 | #Unpack circuit parameters from dictionary 99 | J = circuit_params['J'] 100 | b = circuit_params['b'] 101 | gamma = circuit_params['gamma'] 102 | delta = circuit_params['delta'] 103 | 104 | #Apply hadarmard to all qubits in computation 105 | prog = HadamardToAll(prog, qubits) 106 | 107 | # print(qc.name) 108 | 109 | #Apply Control-Phase(4J) gates to each qubit, the factor of 4 comes from the decomposition of the Ising gate 110 | #with local Z corrections to neighbouring qubits, coming from the decomposition of the Ising gate 111 | #If weight J_{p,q} is updated, add a +/- pi/2 rotation 112 | if qc.name.lower() == 'aspen-3-3q-b-qvm': 113 | ''''Specific entanglement structure for Rigetti Aspen-3-2Q-C''' 114 | if (control.lower() == 'weights' and p == 0 and q == 1): 115 | #first weight parameter between qubit[1] and qubit[2] 116 | 117 | prog.inst(CPHASE(4*J[0, 1] + (-1)**(sign)*pi/2, qubits[0], qubits[1])) 118 | prog.inst(PHASE(-2*J[0, 1] + (-1)**(sign)*pi/2, qubits[0])) 119 | prog.inst(PHASE(-2*J[0, 1] + (-1)**(sign)*pi/2, qubits[1])) 120 | 121 | elif (control.lower() == 'weights' and p == 1 and q == 2): 122 | #Second weight parameter between qubit[1] and qubit[2] 123 | prog.inst(CPHASE(4*J[1, 2] + (-1)**(sign)*pi/2, qubits[1], qubits[2])) 124 | prog.inst(PHASE(-2*J[1, 2] + (-1)**(sign)*pi/2, qubits[1])) 125 | prog.inst(PHASE(-2*J[1, 2] + (-1)**(sign)*pi/2, qubits[2])) 126 | 127 | elif (control.lower() == 'neither' or 'bias' or 'gamma' and sign.lower() == 'neither'): 128 | prog.inst(CPHASE(4*J[0, 1], qubits[0], qubits[1])) 129 | prog.inst(PHASE(-2*J[0, 1], qubits[0])) 130 | prog.inst(PHASE(-2*J[0, 1], qubits[1])) 131 | 132 | prog.inst(CPHASE(4*J[1, 2], qubits[1], qubits[2])) 133 | prog.inst(PHASE(-2*J[1, 2], qubits[1])) 134 | prog.inst(PHASE(-2*J[1, 2], qubits[2])) 135 | 136 | elif qc.name.lower() =='aspen-4-3q-a' or qc.name.lower() == 'aspen-4-3q-a-qvm': 137 | 138 | ''''Specific entanglement structure for Rigetti Aspen-4-3Q-A 139 | 17 - 10 - 11 140 | ''' 141 | if (control.lower() == 'weights' and p == 0 and q == 1): 142 | #first weight parameter between qubit[1] and qubit[2] 143 | 144 | prog.inst(CPHASE(4*J[0, 1] + (-1)**(sign)*pi/2, qubits[0], qubits[1])) 145 | prog.inst(PHASE(-2*J[0, 1] + (-1)**(sign)*pi/2, qubits[0])) 146 | prog.inst(PHASE(-2*J[0, 1] + (-1)**(sign)*pi/2, qubits[1])) 147 | 148 | elif (control.lower() == 'weights' and p == 1 and q == 2): 149 | #Second weight parameter between qubit[1] and qubit[2] 150 | prog.inst(CPHASE(4*J[0, 2] + (-1)**(sign)*pi/2, qubits[0], qubits[2])) 151 | prog.inst(PHASE(-2*J[0, 2] + (-1)**(sign)*pi/2, qubits[0])) 152 | prog.inst(PHASE(-2*J[0, 2] + (-1)**(sign)*pi/2, qubits[2])) 153 | 154 | elif (control.lower() == 'neither' or 'bias' or 'gamma' and sign.lower() == 'neither'): 155 | prog.inst(CPHASE(4*J[0, 1], qubits[0], qubits[1])) 156 | prog.inst(PHASE(-2*J[0, 1], qubits[0])) 157 | prog.inst(PHASE(-2*J[0, 1], qubits[1])) 158 | 159 | prog.inst(CPHASE(4*J[0, 2], qubits[0], qubits[2])) 160 | prog.inst(PHASE(-2*J[0, 2], qubits[0])) 161 | prog.inst(PHASE(-2*J[0, 2], qubits[2])) 162 | 163 | elif qc.name.lower() =='aspen-4-4q-a' or qc.name.lower() == 'aspen-4-4q-a-qvm': 164 | '''' 165 | Specific entanglement structure for Rigetti Aspen-4-4Q-A 166 | 7 - 0 - 1 - 2 167 | ''' 168 | if (control.lower() == 'weights' and p == 0 and q == 1): 169 | #first weight parameter between qubit[1] and qubit[2] 170 | 171 | prog.inst(CPHASE(4*J[0, 1] + (-1)**(sign)*pi/2, qubits[0], qubits[1])) 172 | prog.inst(PHASE(-2*J[0, 1] + (-1)**(sign)*pi/2, qubits[0])) 173 | prog.inst(PHASE(-2*J[0, 1] + (-1)**(sign)*pi/2, qubits[1])) 174 | 175 | elif (control.lower() == 'weights' and p == 1 and q == 2): 176 | #Second weight parameter between qubit[1] and qubit[2] 177 | prog.inst(CPHASE(4*J[1, 2] + (-1)**(sign)*pi/2, qubits[1], qubits[2])) 178 | prog.inst(PHASE(-2*J[1, 2] + (-1)**(sign)*pi/2, qubits[1])) 179 | prog.inst(PHASE(-2*J[1, 2] + (-1)**(sign)*pi/2, qubits[2])) 180 | 181 | elif (control.lower() == 'weights' and p == 0 and q == 3): 182 | #Second weight parameter between qubit[1] and qubit[2] 183 | prog.inst(CPHASE(4*J[0, 3] + (-1)**(sign)*pi/2, qubits[0], qubits[3])) 184 | prog.inst(PHASE(-2*J[0, 3] + (-1)**(sign)*pi/2, qubits[0])) 185 | prog.inst(PHASE(-2*J[0, 3] + (-1)**(sign)*pi/2, qubits[3])) 186 | 187 | elif (control.lower() == 'neither' or 'bias' or 'gamma' and sign.lower() == 'neither'): 188 | prog.inst(CPHASE(4*J[0, 1], qubits[0], qubits[1])) 189 | prog.inst(PHASE(-2*J[0, 1], qubits[0])) 190 | prog.inst(PHASE(-2*J[0, 1], qubits[1])) 191 | 192 | prog.inst(CPHASE(4*J[1, 2], qubits[1], qubits[2])) 193 | prog.inst(PHASE(-2*J[1, 2], qubits[1])) 194 | prog.inst(PHASE(-2*J[1, 2], qubits[2])) 195 | 196 | prog.inst(CPHASE(4*J[0, 3], qubits[0], qubits[3])) 197 | prog.inst(PHASE(-2*J[0, 3], qubits[0])) 198 | prog.inst(PHASE(-2*J[0, 3], qubits[3])) 199 | else: 200 | for j in range(0, N_qubits): 201 | for i in range(0, N_qubits): 202 | if (i < j): #connection is symmetric, so don't overcount entangling gates 203 | if (control.lower() == 'weights' and i == p and j == q): 204 | prog.inst(CPHASE(4*J[i, j] + (-1)**(sign)*pi/2, qubits[i], qubits[j])) 205 | prog.inst(PHASE(-2*J[i, j] + (-1)**(sign)*pi/2, qubits[i])) 206 | prog.inst(PHASE(-2*J[i, j] + (-1)**(sign)*pi/2, qubits[j])) 207 | 208 | elif (control.lower() == 'neither' or 'bias' or 'gamma' and sign.lower() == 'neither'): 209 | prog.inst(CPHASE(4*J[i, j], qubits[i], qubits[j])) 210 | prog.inst(PHASE(-2*J[i, j], qubits[i])) 211 | prog.inst(PHASE(-2*J[i, j], qubits[j])) 212 | 213 | #Apply local Z rotations (b) to each qubit (with one phase changed by pi/2 if the corresponding parameter {r} is being updated 214 | for j in range(0, N_qubits): 215 | if (control == 'BIAS' and j == r): 216 | prog.inst(PHASE(-2*b[j] +(-1)**(sign)*pi/2, qubits[j])) 217 | elif (control== 'NEITHER' or 'WEIGHTS' or 'GAMMA' and sign == 'NEITHER'): 218 | prog.inst(PHASE(-2*b[j], qubits[j])) 219 | 220 | #Apply final 'measurement' layer to all qubits, either all Hadamard, or X or Y rotations 221 | if (circuit_choice == 'IQP'): 222 | prog = HadamardToAll(prog, qubits) #If the final 'measurement' layer is to be an IQP measurement (i.e. Hadamard on all qubits) 223 | 224 | elif (circuit_choice =='QAOA'): 225 | #If the final 'measurement' layer is to be a QAOA measurement (i.e. e^(-i(pi/4)X_i)on all qubits) 226 | for k in range(0, N_qubits): 227 | # if (control == 'GAMMA' and k == s): 228 | # prog.inst(pl.exponential_map(sX(k))(-float(gamma[k])+ (-1)**(sign)*pi/2)) 229 | 230 | # elif (control == 'NEITHER' or 'WEIGHTS' or 'BIAS' and sign == 'NEITHER'): 231 | H_temp = (-float(gamma[k]))*pl.sX(qubits[k]) 232 | prog.inst(pl.exponential_map(H_temp)(1.0)) 233 | # print('GAMMA IS:',-float(gamma[k])) 234 | elif (circuit_choice == 'IQPy' ): 235 | #If the final 'measurement' layer is to be a IQPy measurement (i.e. e^(-i(pi/4)Y_i) on all qubits) 236 | for k in qubits: 237 | H_temp = (-float(delta[k]))*pl.sY(qubits[k]) 238 | prog.inst(pl.exponential_map(H_temp)(1.0)) 239 | 240 | else: raise ValueError("circuit_choice must be either \ 241 | \'IQP\' for IQP (Final Hadamard), \ 242 | \'QAOA\' for QAOA (Final X rotation) or \ 243 | \'IQPy\' IQPy (Final Y rotation)") 244 | # print(prog) 245 | '''Insert explicit measure instruction if required''' 246 | # ro = prog.declare('ro', 'BIT', len(qubits)) 247 | # prog.inst([MEASURE(qubit, ro[idx]) for idx, qubit in enumerate(qubits)]) 248 | 249 | return prog 250 | 251 | class IsingBornMachine: 252 | 253 | def __init__(self, qc, circuit_params, meas_choice): 254 | 255 | self.circuit = Program() 256 | self.qubits = qc.qubits() 257 | 258 | self._num_qubits = len(self.qubits) 259 | self._meas_choice = meas_choice 260 | 261 | def _params(self, circuit_params): 262 | #Unpack circuit parameters from dictionary 263 | self.J = circuit_params['J'] 264 | self.b = circuit_params['b'] 265 | self.gamma = circuit_params['gamma'] 266 | self.delta = circuit_params['delta'] 267 | 268 | def _hadamard_to_all(self): 269 | """Adds Hadamard to all qubits in qubit list""" 270 | self.circuit = self.circuit + [H(qubit_index) for qubit_index in self.qubits] 271 | return 272 | 273 | # for j in range(0, N_qubits): 274 | # for i in range(0, N_qubits): 275 | # if (i < j): #connection is symmetric, so don't overcount entangling gates 276 | # if (control.lower() == 'weights' and i == p and j == q): 277 | # prog.inst(CPHASE(4*J[i, j] + (-1)**(sign)*pi/2, qubits[i], qubits[j])) 278 | # prog.inst(PHASE(-2*J[i, j] + (-1)**(sign)*pi/2, qubits[i])) 279 | # prog.inst(PHASE(-2*J[i, j] + (-1)**(sign)*pi/2, qubits[j])) 280 | 281 | # elif (control.lower() == 'neither' or 'bias' or 'gamma' and sign.lower() == 'neither'): 282 | # prog.inst(CPHASE(4*J[i, j], qubits[i], qubits[j])) 283 | # prog.inst(PHASE(-2*J[i, j], qubits[i])) 284 | # prog.inst(PHASE(-2*J[i, j], qubits[j])) 285 | 286 | def _measurement_layer(self, meas_choice): 287 | self._meas_choice = meas_choice 288 | #Apply final 'measurement' layer to all qubits, either all Hadamard, or X or Y rotations 289 | if (self._meas_choice.lower() == 'iqp'): 290 | self.circuit = self.circuit + [H(qubit_index) for qubit_index in self.qubits] 291 | 292 | # elif (circuit_choice =='QAOA'): 293 | # #If the final 'measurement' layer is to be a QAOA measurement (i.e. e^(-i(pi/4)X_i)on all qubits) 294 | # for k in range(0, N_qubits): 295 | # # if (control == 'GAMMA' and k == s): 296 | # # prog.inst(pl.exponential_map(sX(k))(-float(gamma[k])+ (-1)**(sign)*pi/2)) 297 | 298 | # # elif (control == 'NEITHER' or 'WEIGHTS' or 'BIAS' and sign == 'NEITHER'): 299 | # H_temp = (-float(gamma[k]))*pl.sX(qubits[k]) 300 | # self.circuit += pl.exponential_map(H_temp)(1.0) 301 | # # print('GAMMA IS:',-float(gamma[k])) 302 | # elif (circuit_choice == 'IQPy' ): 303 | # #If the final 'measurement' layer is to be a IQPy measurement (i.e. e^(-i(pi/4)Y_i) on all qubits) 304 | # for k in qubits: 305 | # H_temp = (-float(delta[k]))*pl.sY(qubits[k]) 306 | # prog.inst(pl.exponential_map(H_temp)(1.0)) 307 | 308 | # device_name = '2q-qvm' 309 | # as_qvm_value = True 310 | # qc = get_qc(device_name, as_qvm = as_qvm_value) 311 | 312 | # params = NetworkParams(qc, 123342) 313 | # ibm = IsingBornMachine(qc, params) 314 | # ibm._hadamard_to_all() 315 | # print(ibm.circuit) 316 | # ibm._measurement_layer( 'IQP') 317 | # print(ibm.circuit) -------------------------------------------------------------------------------- /quantum_kernel.py: -------------------------------------------------------------------------------- 1 | from pyquil.quil import Program 2 | from pyquil.paulis import * 3 | from pyquil.gates import * 4 | import numpy as np 5 | from pyquil.api import get_qc, WavefunctionSimulator 6 | from random import * 7 | 8 | from param_init import HadamardToAll 9 | from auxiliary_functions import IntegerToString 10 | 11 | def EncodingFunc(N_qubits, sample): 12 | '''This function defines Non-Linear function for encoded samples for Quantum Kernel Cirucit 13 | to act as graph weights/biases''' 14 | 15 | if (type(sample) is np.ndarray): 16 | ZZ = np.zeros((N_qubits, N_qubits)) 17 | Z = np.zeros((N_qubits)) 18 | for qubit in range(0, N_qubits): 19 | Z[qubit] = (np.pi/4)*int(sample[qubit]) 20 | j = 0 21 | while (j < qubit): 22 | # if int(sample[qubit]) == 1 and int(sample[j]) == 1: 23 | # ZZ[qubit,j] = np.pi 24 | # ZZ[j,qubit] = ZZ[qubit,j] 25 | ZZ[qubit,j] = (np.pi/4 - int(sample[qubit]))*(np.pi/4 - int(sample[j])) 26 | ZZ[j,qubit] = ZZ[qubit,j] 27 | j = j+1 28 | encoded_sample = {} 29 | encoded_sample['Interaction'] = ZZ 30 | encoded_sample['Local'] = Z 31 | else: raise IOError('\'samples\' must be a numpy array') 32 | return encoded_sample 33 | 34 | def TwoQubitGate(prog, two_q_arg, qubit_1, qubit_2): 35 | return prog.inst(CPHASE(4*two_q_arg, qubit_1, qubit_2)).inst(PHASE(-2*two_q_arg, qubit_1)).inst(PHASE(-2*two_q_arg, qubit_2)) 36 | 37 | def IQPLayer(prog, qubits, phi_Z, phi_ZZ): 38 | N_qubits = len(qubits) 39 | for j in range(0, N_qubits): 40 | #Apply local Z rotations (b) to each qubit 41 | #If the particular qubit sample == 0, apply no gate 42 | if (phi_Z[j] != False): 43 | prog.inst(PHASE(-2*phi_Z[j], qubits[j])) 44 | #Apply Control-Phase(Phi_ZZ_1) gates to each qubit 45 | for i in range(0, N_qubits): 46 | if (i < j): 47 | #If the particular qubit sample pair == 0, apply no gate 48 | if (phi_ZZ[i,j] != False): 49 | prog = TwoQubitGate(prog, phi_ZZ[i,j], qubits[i], qubits[j]) 50 | return prog 51 | 52 | def KernelCircuit(qc, sample1, sample2): 53 | '''Compute Quantum kernel given samples from the Born Machine (born_samples) and the Data Distribution (data_samples) 54 | This must be done for every sample from each distribution (batch gradient descent), (x, y)''' 55 | '''First layer, sample from first distribution (1), parameters phi_ZZ_1, phi_Z_1''' 56 | 57 | qubits = qc.qubits() 58 | N_qubits = len(qubits) 59 | 60 | prog = Program() 61 | 62 | kernel_circuit_params1 = EncodingFunc(N_qubits, sample1) 63 | kernel_circuit_params2 = EncodingFunc(N_qubits, sample2) 64 | 65 | phi_ZZ_1 = kernel_circuit_params1['Interaction'] 66 | phi_ZZ_2 = kernel_circuit_params2['Interaction'] 67 | 68 | phi_Z_1 = kernel_circuit_params1['Local'] 69 | phi_Z_2 = kernel_circuit_params2['Local'] 70 | 71 | ################################################################### 72 | '''First Layer, encoding samples from first distributions, (y)''' 73 | ################################################################### 74 | '''First layer of Hadamards''' 75 | prog = HadamardToAll(prog, qubits) 76 | '''First IQP layer, encoding sample y''' 77 | prog = IQPLayer(prog, qubits, phi_Z_1, phi_ZZ_1) 78 | 79 | ################################################################### 80 | '''Second Layer, encoding samples from both distributions, (x, y)''' 81 | ################################################################### 82 | '''Second layer of Hadamards''' 83 | prog = HadamardToAll(prog, qubits) 84 | '''Second IQP layer, encoding samples (x, y)''' 85 | prog = IQPLayer(prog, qubits, phi_Z_1-phi_Z_2, phi_ZZ_1-phi_ZZ_2) 86 | 87 | ################################################################### 88 | '''Third Layer, encoding samples from first distributions, (y)''' 89 | ################################################################### 90 | '''Third layer of Hadamards''' 91 | prog = HadamardToAll(prog, qubits) 92 | '''Second IQP layer, encoding samples (x, y)''' 93 | prog = IQPLayer(prog, qubits, -phi_Z_2, -phi_ZZ_2) #minus sign for complex conjugate 94 | 95 | '''Final layer of Hadamards''' 96 | prog = HadamardToAll(prog, qubits) 97 | 98 | return prog 99 | 100 | 101 | def QuantumKernel(qc, N_kernel_samples, sample1, sample2): 102 | '''This function computes the Quantum kernel for a single pair of samples''' 103 | 104 | 105 | if type(sample1) is np.ndarray and sample1.ndim != 1: #Check if there is only a single sample in the array of samples 106 | raise IOError('sample1 must be a 1D numpy array') 107 | if type(sample2) is np.ndarray and sample2.ndim != 1: #Check if there is only a single sample in the array of samples 108 | raise IOError('sample2 must be a 1D numpy array') 109 | 110 | qubits = qc.qubits() 111 | N_qubits = len(qubits) 112 | make_wf = WavefunctionSimulator() 113 | 114 | 115 | #run quantum circuit for a single pair of encoded samples 116 | prog = KernelCircuit(qc, sample1, sample2) 117 | kernel_outcomes = make_wf.wavefunction(prog).get_outcome_probs() 118 | 119 | #Create zero string to read off probability 120 | zero_string = '0'*N_qubits 121 | kernel_exact = kernel_outcomes[zero_string] 122 | 123 | if (N_kernel_samples == 'infinite'): 124 | #If the kernel is computed exactly, approximate kernel is equal to exact kernel 125 | kernel_approx = kernel_exact 126 | else: 127 | 128 | #Index list for classical registers we want to put measurement outcomes into. 129 | #Measure the kernel circuit to compute the kernel approximately, the kernel is the probability of getting (00...000) outcome. 130 | #All (N_qubits) qubits are measured at once into dictionary, convert into array 131 | kernel_measurements_all_qubits_dict = qc.run_and_measure(prog, N_kernel_samples) 132 | kernel_measurements_used_qubits = np.flip(np.vstack(kernel_measurements_all_qubits_dict[q] for q in sorted(qubits)).T, 1) 133 | 134 | #m is total number of samples, n is the number of used qubits 135 | (m,n) = kernel_measurements_used_qubits.shape 136 | 137 | N_zero_strings = m - np.count_nonzero(np.count_nonzero(kernel_measurements_used_qubits, 1)) 138 | #The kernel is given by = [Number of times outcome (00...000) occurred]/[Total number of measurement runs] 139 | 140 | kernel_approx = N_zero_strings/N_kernel_samples 141 | return kernel_exact, kernel_approx 142 | 143 | def QuantumKernelArray(qc, N_kernel_samples, samples1, samples2): 144 | '''This function computes the quantum kernel for all pairs of samples''' 145 | if type(samples1) is np.ndarray: 146 | if samples1.ndim == 1: #Check if there is only a single sample in the array of samples 147 | N_samples1 = 1 148 | else: 149 | N_samples1 = samples1.shape[0] 150 | else: N_samples1 = len(samples1) 151 | 152 | if type(samples2) is np.ndarray: 153 | if samples2.ndim == 1: 154 | N_samples2 = 1 155 | else: 156 | N_samples2 = samples2.shape[0] 157 | else: N_samples2 = len(samples2) 158 | 159 | N_qubits = len(qc.qubits()) 160 | 161 | kernel_approx_array = np.zeros((N_samples1, N_samples2)) 162 | kernel_exact_array = np.zeros((N_samples1, N_samples2)) 163 | #define a dictionary for both approximate and exact kernel 164 | kernel_approx_dict = {} 165 | kernel_exact_dict = {} 166 | 167 | for sample_index2 in range(0, N_samples2): 168 | for sample_index1 in range(0, sample_index2+1): 169 | 170 | s_temp1 = IntegerToString(sample_index1, N_qubits) 171 | s_temp2 = IntegerToString(sample_index2, N_qubits) 172 | 173 | kernel_approx_array[sample_index1, sample_index2], kernel_exact_array[sample_index1, sample_index2],\ 174 | = QuantumKernel(qc, \ 175 | N_kernel_samples, \ 176 | samples1[sample_index1],\ 177 | samples2[sample_index2] ) 178 | 179 | #kernel is symmetric, k(x,y) = k(y,x) 180 | kernel_approx_array[sample_index2, sample_index1] = kernel_approx_array[sample_index1, sample_index2] 181 | kernel_exact_array[sample_index2, sample_index1] = kernel_exact_array[sample_index1, sample_index2] 182 | 183 | kernel_approx_dict[s_temp1, s_temp2] = kernel_approx_array[sample_index1, sample_index2] 184 | kernel_approx_dict[s_temp2, s_temp1] = kernel_approx_dict[s_temp1, s_temp2] 185 | 186 | kernel_exact_dict[s_temp1, s_temp2] = kernel_exact_array[sample_index1, sample_index2] 187 | kernel_exact_dict[s_temp2, s_temp1] = kernel_exact_dict[s_temp1, s_temp2] 188 | 189 | return kernel_approx_array, kernel_exact_array, kernel_approx_dict, kernel_exact_dict 190 | -------------------------------------------------------------------------------- /run_and_compare.py: -------------------------------------------------------------------------------- 1 | ## @package run_and_compare 2 | # This is the main module for this project. 3 | # 4 | # More details. 5 | 6 | import math 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | from matplotlib import animation, style 10 | from param_init import NetworkParams 11 | from file_operations_out import PrintFinalParamsToFile, PrintDataToFiles, MakeDirectory, PrintFinalParamsToFile 12 | from file_operations_in import DataImport, DataDictFromFile 13 | from train_plot import CostPlot 14 | from cost_function_train import TrainBorn 15 | 16 | from random import shuffle 17 | from auxiliary_functions import TrainTestPartition, num_bytes_needed 18 | from pyquil.api import get_qc 19 | import sys 20 | import os 21 | import time 22 | ## This function gathers inputs from file 23 | # 24 | # @param[in] file_name name of file to gather inputs from 25 | # 26 | # @param[out] N_epochs number of epochs 27 | # @param[out] data_type 28 | # @param[out] N_data_samples 29 | # @param[out] N_born_samples 30 | # @param[out] N_kernel_samples 31 | # @param[out] batch_size 32 | # @param[out] kernel_type 33 | # @param[out] cost_func 34 | # @param[out] device_name 35 | # @param[out] as_qvm_value 36 | # 37 | # @returns listed parameters 38 | def get_inputs(file_name): 39 | 40 | with open(file_name, 'r') as input_file: 41 | 42 | input_values = input_file.readlines() 43 | 44 | N_epochs = int(input_values[0]) 45 | learning_rate = float(input_values[1]) 46 | data_type = str(input_values[2]) 47 | data_type = data_type[0:len(data_type) - 1] 48 | N_data_samples = int(input_values[3]) 49 | N_born_samples = int(input_values[4]) 50 | N_kernel_samples = int(input_values[5]) 51 | batch_size = int(input_values[6]) 52 | kernel_type = str(input_values[7]) 53 | kernel_type = kernel_type[0:len(kernel_type) - 1] 54 | cost_func = str(input_values[8]) 55 | cost_func = cost_func[0:len(cost_func) - 1] 56 | device_name = str(input_values[9]) 57 | device_name = device_name[0:len(device_name) - 1] 58 | 59 | if int(input_values[10]) == 1: 60 | as_qvm_value = True 61 | else: 62 | as_qvm_value = False 63 | 64 | score = str(input_values[11]) 65 | score = score[0:len(score) - 1] 66 | 67 | stein_eigvecs = int(input_values[12]) 68 | stein_eta = float(input_values[13]) 69 | 70 | stein_params = {} 71 | stein_params[0] = score #Choice of method to approximate Stein Score: score 72 | stein_params[1] = stein_eigvecs #Number of Nystrom Eigenvectors, J for spectral_stein method: J 73 | stein_params[2] = stein_eta #regularization paramter for identity_stein method: \chi 74 | stein_params[3] = kernel_type #Kernel for computing Stein Score, set to be the same as kernel used in Stein Discrpancy 75 | 76 | '''Number of samples:''' 77 | N_samples = [N_data_samples,\ 78 | N_born_samples,\ 79 | batch_size,\ 80 | N_kernel_samples] 81 | sinkhorn_eps = float(input_values[14]) 82 | run = int(input_values[15]) 83 | 84 | return N_epochs, learning_rate, data_type, N_samples, kernel_type, cost_func, device_name, as_qvm_value, stein_params, sinkhorn_eps, run 85 | 86 | def SaveAnimation(framespersec, fig, N_epochs, N_qubits, N_born_samples, cost_func, kernel_type, data_exact_dict, born_probs_list, axs, N_data_samples): 87 | 88 | Writer = animation.writers['ffmpeg'] 89 | 90 | writer = Writer(fps=framespersec, metadata=dict(artist='Me'), bitrate=-1) 91 | 92 | ani = animation.FuncAnimation(fig, animate, frames=len(born_probs_list), fargs=(N_qubits, N_born_samples, kernel_type, data_exact_dict, born_probs_list, axs, N_data_samples), interval = 10) 93 | 94 | animations_path = './animations/' 95 | MakeDirectory(animations_path) 96 | 97 | ani.save("animations/%s_%iQbs_%s_Kernel_%iSamples_%iEpochs.mp4" \ 98 | %(cost_func[0:1], N_qubits, kernel_type[0][0], N_born_samples, N_epochs)) 99 | 100 | plt.show(block=False) 101 | plt.pause(1) 102 | plt.close() 103 | def PlotAnimate(N_qubits, N_epochs, N_born_samples, cost_func, kernel_type, data_exact_dict): 104 | 105 | plt.legend(prop={'size': 7}, loc='best').draggable() 106 | 107 | plots_path = './plots/' 108 | MakeDirectory(plots_path) 109 | plt.savefig("plots/%s_%iQbs_%s_%iBSamps_%iEpoch.pdf" \ 110 | %(cost_func[0], N_qubits, kernel_type[0][0], N_born_samples, N_epochs)) 111 | 112 | fig, axs = plt.subplots() 113 | 114 | axs.set_xlabel("Outcomes") 115 | axs.set_ylabel("Probability") 116 | axs.legend(('Born Probs','Data Probs')) 117 | axs.set_xticks(range(len(data_exact_dict))) 118 | axs.set_xticklabels(list(data_exact_dict.keys()),rotation=70) 119 | axs.set_title("%i Qubits, %s Kernel, %i Born Samples" \ 120 | %(N_qubits, kernel_type[0][0], N_born_samples)) 121 | 122 | plt.tight_layout() 123 | 124 | return fig, axs 125 | 126 | def animate(i, N_qubits, N_born_samples, kernel_type, data_exact_dict, born_probs_list, axs, N_data_samples): 127 | plot_colour = ['r', 'b'] 128 | axs.clear() 129 | x = np.arange(len(data_exact_dict)) 130 | axs.bar(x, born_probs_list[i].values(), width=0.2, color= plot_colour[0], align='center') 131 | axs.bar(x-0.2, data_exact_dict.values(), width=0.2, color='b', align='center') 132 | axs.set_title("%i Qbs, %s Kernel, %i Data Samps, %i Born Samps" \ 133 | %(N_qubits, kernel_type[0][0], N_data_samples, N_born_samples)) 134 | axs.set_xlabel("Outcomes") 135 | axs.set_ylabel("Probability") 136 | axs.legend(('Born Probs','Data Probs')) 137 | axs.set_xticks(range(len(data_exact_dict))) 138 | axs.set_xticklabels(list(data_exact_dict.keys()),rotation=70) 139 | 140 | def bytes_to_int(bytes_list): 141 | total = 0 142 | for byte in bytes_list: 143 | total *= 256 144 | total += byte 145 | 146 | return total 147 | 148 | def read_ints_from_file(N_qubits, N_data_samples, f): 149 | int_list = [0] * N_data_samples 150 | bytes_list = list(f.read()) 151 | for sample in range(N_data_samples): 152 | int_list[sample] = bytes_to_int(bytes_list[sample * num_bytes_needed(N_qubits):(sample + 1) * num_bytes_needed(N_qubits)]) 153 | 154 | return int_list 155 | 156 | 157 | ## This is the main function 158 | def main(): 159 | 160 | if len(sys.argv) != 2: 161 | sys.exit("[ERROR] : There should be exactly one input. Namely, a txt file containing the input values. Please see the README.md file for more details.") 162 | else: 163 | N_epochs, learning_rate, data_type, N_samples, kernel_type,cost_func, device_name, as_qvm_value, stein_params, sinkhorn_eps, run = get_inputs(sys.argv[1]) 164 | 165 | if type(device_name) is not str: 166 | raise ValueError('The device name must be a string') 167 | if (as_qvm_value is not True and as_qvm_value is not False): 168 | raise ValueError('\'as_qvm_value\' must be an integer, either 0, or 1') 169 | 170 | qc = get_qc(device_name, as_qvm = as_qvm_value) 171 | N_qubits = len(qc.qubits()) 172 | data_circuit_choice = 'IQP' 173 | 174 | N_data_samples = N_samples[0] 175 | N_born_samples = N_samples[1] 176 | 177 | if data_type == 'Quantum_Data': 178 | 179 | try: 180 | data_samples= list(np.loadtxt('data/Quantum_Data_%iQBs_%iSamples_%sCircuit' % (N_qubits, N_data_samples, data_circuit_choice), dtype = str)) 181 | except: 182 | PrintDataToFiles(data_type, N_data_samples, qc, data_circuit_choice, N_qubits) 183 | data_samples = list(np.loadtxt('data/Quantum_Data_%iQBs_%iSamples_%sCircuit' % (N_qubits, N_data_samples, data_circuit_choice), dtype = str)) 184 | 185 | elif data_type == 'Bernoulli_Data': 186 | 187 | try: 188 | with open('binary_data/Bernoulli_Data_%iQBs_%iSamples' % (N_qubits, N_data_samples), 'rb') as f: 189 | data_samples_orig = read_ints_from_file(N_qubits, N_data_samples, f) 190 | 191 | except: 192 | PrintDataToFiles(data_type, N_data_samples, qc, data_circuit_choice, N_qubits) 193 | with open('binary_data/Bernoulli_Data_%iQBs_%iSamples' % (N_qubits, N_data_samples), 'rb') as f: 194 | data_samples_orig = read_ints_from_file(N_qubits, N_data_samples, f) 195 | 196 | print("read data =") 197 | data_samples_orig 198 | data_samples = np.zeros((N_data_samples, N_qubits), dtype = int) 199 | 200 | for sample in range(0, N_data_samples): 201 | 202 | temp = data_samples_orig[sample] 203 | 204 | for outcome in range(0, N_qubits): 205 | 206 | data_samples[sample, N_qubits - 1 - outcome] = temp % 2 207 | temp >>= 1 208 | 209 | else: 210 | sys.exit("[ERROR] : data_type should be either 'Quantum_Data' or 'Bernoulli_Data'") 211 | 212 | np.random.shuffle(data_samples) 213 | 214 | #Split data into training/test sets 215 | data_train_test = TrainTestPartition(data_samples) 216 | 217 | random_seed = 0 218 | 219 | #Parameters, J, b for epoch 0 at random, gamma = constant = pi/4 220 | #Set random seed to 0 to initialise the actual Born machine to be trained 221 | initial_params = NetworkParams(qc, random_seed) 222 | 223 | data_exact_dict = DataDictFromFile(data_type, N_qubits, 'infinite', data_circuit_choice) 224 | t0 = time.time() 225 | loss, circuit_params, born_probs_list, empirical_probs_list = TrainBorn(qc, cost_func, initial_params, \ 226 | N_epochs, N_samples, data_train_test, data_exact_dict, \ 227 | kernel_type, 'Precompute', learning_rate, \ 228 | stein_params, sinkhorn_eps) 229 | 230 | 231 | t1 = time.time() 232 | print('Execution Time is:', t1-t0) 233 | # plt.figure(1) 234 | 235 | # CostPlot(N_qubits, kernel_type, data_train_test, N_samples, cost_func, loss, circuit_params, born_probs_list, empirical_probs_list) 236 | 237 | 238 | # fig, axs = PlotAnimate(N_qubits, N_epochs, N_born_samples, cost_func, kernel_type, data_exact_dict) 239 | # SaveAnimation(5, fig, N_epochs, N_qubits, N_born_samples, cost_func, kernel_type, data_exact_dict, born_probs_list, axs, N_data_samples) 240 | 241 | 242 | PrintFinalParamsToFile(cost_func, data_type, data_circuit_choice, N_epochs, \ 243 | learning_rate, loss, circuit_params, data_exact_dict, born_probs_list, empirical_probs_list, \ 244 | qc, kernel_type, N_samples, stein_params, sinkhorn_eps, run) 245 | 246 | if __name__ == "__main__": 247 | 248 | main() 249 | -------------------------------------------------------------------------------- /sample_gen.py: -------------------------------------------------------------------------------- 1 | from pyquil.quil import Program 2 | from pyquil.paulis import * 3 | from pyquil.gates import * 4 | import numpy as np 5 | from pyquil.api import get_qc, WavefunctionSimulator 6 | from pyquil.quilbase import DefGate 7 | 8 | from param_init import NetworkParams, StateInit 9 | 10 | # from param_init import IsingBornMachine as IBM 11 | 12 | from train_generation import DataSampler 13 | from auxiliary_functions import EmpiricalDist 14 | import sys 15 | 16 | 17 | def BornSampler(qc, N_samples, circuit_params, circuit_choice): 18 | ''' 19 | This Program generates samples from the output distribution of the IQP/QAOA/IQPy circuit according to the Born Rule: 20 | P(z) = ||^2, where |s> is the uniform superposition 21 | ''' 22 | 23 | make_wf = WavefunctionSimulator() 24 | #final_layer = ('IQP'for IQP), = ('QAOA' for QAOA), = ('IQPy' for Y-Rot) 25 | #control = 'BIAS' for updating biases, = 'WEIGHTS' for updating weights, ='GAMMA' for gamma params, ='NEITHER' for neither 26 | #sign = 'POSITIVE' to run the positive circuit, = 'NEGATIVE' for the negative circuit, ='NEITHER' for neither 27 | 28 | prog = StateInit(qc, circuit_params, 0, 0, 0, 0, circuit_choice , 'NEITHER', 'NEITHER') 29 | # print(prog) 30 | N_born_samples = N_samples[1] 31 | 32 | '''Generate (N_born_samples) samples from output distribution on (N_qubits) visible qubits''' 33 | born_samples_all_qubits_dict = qc.run_and_measure(prog, N_born_samples) 34 | # print(born_samples_all_qubits_dict) 35 | born_samples = np.flip(np.vstack(born_samples_all_qubits_dict[q] for q in sorted(qc.qubits())).T, 1) #put outcomes into array 36 | 37 | born_probs_approx_dict = EmpiricalDist(born_samples, len(qc.qubits()), 'full_dist') #Compute empirical distribution of the output samples 38 | born_probs_exact_dict = make_wf.wavefunction(prog).get_outcome_probs() 39 | 40 | return born_samples, born_probs_approx_dict, born_probs_exact_dict 41 | 42 | def PlusMinusSampleGen(qc, circuit_params, 43 | p, q, r, s, circuit_choice, control, N_samples): 44 | ''' This function computes the samples required in the estimator, in the +/- terms of the MMD loss function gradient 45 | with respect to parameter, J_{p, q} (control = 'WEIGHT') , b_r (control = 'BIAS') or gamma (control == 'GAMMA') 46 | ''' 47 | 48 | #probs_minus, probs_plus are the exact probabilites outputted from the circuit 49 | prog_plus= StateInit(qc, circuit_params, p, q, r, s, circuit_choice, control, 0) 50 | prog_minus = StateInit(qc, circuit_params, p, q, r, s, circuit_choice, control, 1) 51 | 52 | batch_size = N_samples[2] 53 | #generate batch_size samples from measurements of +/- shifted circuits 54 | born_samples_plus_all_qbs_dict = qc.run_and_measure(prog_plus, batch_size) 55 | born_samples_minus_all_qbs_dict = qc.run_and_measure(prog_minus, batch_size) 56 | born_samples_pm = [] 57 | #put outcomes into list of arrays 58 | born_samples_pm.append(np.flip(np.vstack(born_samples_plus_all_qbs_dict[q] for q in sorted(qc.qubits())).T, 1)) 59 | born_samples_pm.append(np.flip(np.vstack(born_samples_minus_all_qbs_dict[q] for q in sorted(qc.qubits())).T, 1)) 60 | 61 | 62 | return born_samples_pm 63 | 64 | 65 | -------------------------------------------------------------------------------- /sinkhorn_functions.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from auxiliary_functions import L2Norm,SampleListToArray 3 | from file_operations_in import DataImport 4 | import feydy_sinkhorn as feydy_sink 5 | import torch 6 | import auxiliary_functions as aux 7 | 8 | ''' 9 | This function computes the Sinkhorn Cost function, and its gradient, following the Method of: 10 | Interpolating between Optimal Transport and MMD using Sinkhorn Divergences by Feydy et. al. 11 | https://arxiv.org/abs/1810.08278 12 | 13 | The functions used from feydy_sinkhorn are those in https://github.com/jeanfeydy/global-divergences in common/sinkhorn_balanced_simple 14 | to directly compute the Sinkhorn Divergence, and adapted from there to compute its gradient. 15 | ''' 16 | 17 | 18 | 19 | def FeydySink(born_samples_all, data_samples_all, epsilon): 20 | ''' 21 | Converts sample vectors to required format, first to the empirical distribution, 22 | then into torch tensors to feed into Feydy Implementation of the Sinkhorn Divergence 23 | ''' 24 | #Extract samples (unrepeated) as array, along with corresponding empirical probabilities 25 | # Convert everything to pytorch tensors to be compatible with Feydy library 26 | _, _, born_samples_tens, born_probs_tens = aux.ExtractSampleInformation(born_samples_all) 27 | _, _, data_samples_tens, data_probs_tens = aux.ExtractSampleInformation(data_samples_all) 28 | 29 | sinkhorn_feydy = feydy_sink.sinkhorn_divergence(born_probs_tens, born_samples_tens, data_probs_tens, data_samples_tens, eps=epsilon) 30 | 31 | return sinkhorn_feydy 32 | 33 | def SinkGrad(born_samples, born_samples_pm, data_samples, epsilon): 34 | ''' 35 | Converts sample vectors for gradient to required format, first to the empirical distribution, 36 | then into torch tensors to feed into Feydy Implementation of the Sinkhorn Divergence 37 | ''' 38 | [born_samples_plus, born_samples_minus] = born_samples_pm 39 | 40 | #The following extracts the empirical probabilities, and corresponding sample values from the 41 | #arrays of samples. These are then converted into pytorch tensors to be compatable with functions in feydy_sinkhorn 42 | _, _, born_samples_tens, born_probs_tens = aux.ExtractSampleInformation(born_samples) 43 | _, _, born_plus_samples_tens, born_plus_probs_tens = aux.ExtractSampleInformation(born_samples_plus) 44 | _, _, born_minus_samples_tens, born_minus_probs_tens = aux.ExtractSampleInformation(born_samples_minus) 45 | _, _, data_samples_tens, data_probs_tens = aux.ExtractSampleInformation(data_samples) 46 | 47 | 48 | g_data , f_born = feydy_sink.sink(born_probs_tens, born_samples_tens, data_probs_tens, data_samples_tens, eps=epsilon)# Compute optimal dual vectors between born samples and data 49 | _ , s_born = feydy_sink.sym_sink(born_probs_tens, born_samples_tens, eps=epsilon) #Compute autocorrelation vectors for Born data 50 | 51 | 52 | p=2 #Sinkhorn Cost will be Euclidean Distance squared, or l_2 norm squared. Equivalent to Hamming Distance in this case. 53 | 54 | cost_matrix_plus_born = feydy_sink.dist_matrix(born_plus_samples_tens, born_samples_tens, p, epsilon) #Compute cost matrix between born_plus and born samples 55 | cost_matrix_minus_born = feydy_sink.dist_matrix(born_minus_samples_tens, born_samples_tens, p, epsilon) #Compute cost matrix between born_minus and born samples 56 | 57 | cost_matrix_plus_data = feydy_sink.dist_matrix(born_plus_samples_tens, data_samples_tens, p, epsilon) #Compute cost matrix between born_plus and data samples 58 | cost_matrix_minus_data = feydy_sink.dist_matrix(born_minus_samples_tens, data_samples_tens, p, epsilon) #Compute cost matrix between born_minus and data samples 59 | 60 | f_plus = -epsilon*feydy_sink.lse((g_data + data_probs_tens.log().view(1, -1)).view(1, -1) - cost_matrix_plus_data ) 61 | s_plus = -epsilon*feydy_sink.lse((s_born + born_probs_tens.log().view(1, -1)).view(1, -1) - cost_matrix_plus_born ) 62 | 63 | f_minus = -epsilon*feydy_sink.lse((g_data + data_probs_tens.log().view(1, -1)).view(1, -1) - cost_matrix_minus_data) 64 | s_minus = -epsilon*feydy_sink.lse((s_born + born_probs_tens.log().view(1, -1)).view(1, -1) - cost_matrix_minus_born) 65 | 66 | feydy_sink_grad =feydy_sink.scal(born_minus_probs_tens, f_minus - s_minus) - feydy_sink.scal(born_plus_probs_tens, f_plus - s_plus) 67 | return feydy_sink_grad 68 | 69 | 70 | def _gamma_(f, p, g, q, M, sinkhorn_eps=.01): 71 | ''' 72 | This function computes the optimal coupling between the two input distributions, p, q 73 | From the dual vectors, f, g 74 | ''' 75 | return ((f.view(-1, 1) + g.view(1, -1))/sinkhorn_eps - M/sinkhorn_eps + p.log().view(-1, 1) 76 | + q.log().view(1, -1)).exp() 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /stein_functions.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | 4 | from pyquil.api import get_qc 5 | from classical_kernel import GaussianKernelArray, GaussianKernel 6 | from quantum_kernel import QuantumKernelArray, QuantumKernel 7 | 8 | from kernel_functions import NormaliseKernel 9 | from file_operations_in import KernelDictFromFile, DataDictFromFile 10 | from auxiliary_functions import ShiftString, EmpiricalDist, ToString 11 | 12 | import stein_score as ss 13 | import json 14 | 15 | def DeltaTerms(qc, kernel_choice, kernel_array, N_samples, sample_array_1, sample_array_2, flag): 16 | '''This kernel computes the shifted *Delta* terms used in the Stein Discrepancy''' 17 | N_qubits = len(qc.qubits()) 18 | N_samples1 = len(sample_array_1) 19 | N_samples2 = len(sample_array_2) 20 | 21 | kernel_x_shifted, kernel_y_shifted, kernel_xy_shifted = [np.zeros((N_samples1, N_samples2, N_qubits)) for _ in range(3)] 22 | delta_x_kernel, delta_y_kernel, delta_xy_kernel = [np.zeros((N_samples1, N_samples2, N_qubits)) for _ in range(3)] 23 | 24 | for sample_index1 in range(0, N_samples1): 25 | for sample_index2 in range(0, N_samples2): 26 | for qubit in range(0, N_qubits): 27 | if flag == 'Onfly': 28 | kernel = kernel_array[sample_index1][sample_index2] 29 | sample1 = sample_array_1[sample_index1] 30 | sample2 = sample_array_2[sample_index2] 31 | shiftedsample1 = ShiftString(sample1, qubit) 32 | shiftedsample2 = ShiftString(sample2, qubit) 33 | 34 | if (kernel_choice == 'Gaussian'): 35 | 36 | sigma = np.array([0.25, 10, 1000]) 37 | 38 | kernel_x_shifted[sample_index1, sample_index2, qubit] = GaussianKernel(shiftedsample1, sample2, sigma) 39 | kernel_y_shifted[sample_index1, sample_index2, qubit] = GaussianKernel(sample1, shiftedsample2, sigma) 40 | kernel_xy_shifted[sample_index1, sample_index2, qubit] = GaussianKernel(shiftedsample1, shiftedsample2, sigma) 41 | 42 | elif flag == 'Precompute': #Use Precomputed kernel dictionary to accelerate training 43 | kernel_dict = KernelDictFromFile(qc, N_samples, kernel_choice) 44 | 45 | sample1 = ToString(sample_array_1[sample_index1]) 46 | sample2 = ToString(sample_array_2[sample_index2]) 47 | shiftedsample1 = ToString(ShiftString(sample1, qubit)) 48 | shiftedsample2 = ToString(ShiftString(sample2, qubit)) 49 | kernel = kernel_dict[(sample1, sample2)] 50 | 51 | kernel_x_shifted[sample_index1][sample_index2][qubit] = kernel_dict[(shiftedsample1, sample2)] 52 | kernel_y_shifted[sample_index1][sample_index2][qubit] = kernel_dict[(sample1, shiftedsample2)] 53 | kernel_xy_shifted[sample_index1][sample_index2][qubit] = kernel_dict[(shiftedsample1, shiftedsample2)] 54 | 55 | delta_x_kernel[sample_index1, sample_index2, qubit] = kernel - kernel_x_shifted[sample_index1, sample_index2, qubit] 56 | delta_y_kernel[sample_index1, sample_index2, qubit] = kernel - kernel_y_shifted[sample_index1, sample_index2, qubit] 57 | delta_xy_kernel[sample_index1, sample_index2, qubit] = kernel - kernel_xy_shifted[sample_index1, sample_index2, qubit] 58 | 59 | 60 | trace = N_qubits*kernel_array - kernel_x_shifted.sum(axis = 2) - kernel_y_shifted.sum(axis = 2) + kernel_xy_shifted.sum(axis = 2) 61 | return delta_x_kernel, delta_y_kernel, trace 62 | 63 | def WeightedKernel(qc, kernel_choice, kernel_array, N_samples, data_samples, data_probs, sample_array_1, sample_array_2, stein_params, flag): 64 | '''This kernel computes the weighted kernel for all samples from the two distributions sample_list_1, sample_list_2''' 65 | 66 | delta_x_kernel, delta_y_kernel, trace = DeltaTerms(qc, kernel_choice, kernel_array, N_samples, sample_array_1, sample_array_2, flag) 67 | 68 | #Parameters required for computing the Stein Score 69 | score_approx = stein_params[0] 70 | J = stein_params[1] 71 | chi = stein_params[2] 72 | stein_kernel_choice = stein_params[3] 73 | 74 | if stein_kernel_choice.lower() == 'gaussian': 75 | stein_sigma = np.array([0.25, 10, 1000]) 76 | else: 77 | raise IOError('Stein kernel must be Gaussian for now.') 78 | 79 | if (score_approx.lower() == 'exact'): 80 | score_matrix_1 = ss.MassSteinScore(sample_array_1, data_probs) 81 | score_matrix_2 = ss.MassSteinScore(sample_array_2, data_probs) 82 | elif (score_approx.lower() == 'identity'): 83 | score_matrix_1 = ss.IdentitySteinScore(data_samples, stein_kernel_choice, chi, stein_sigma) 84 | score_matrix_2 = ss.IdentitySteinScore(data_samples, stein_kernel_choice, chi, stein_sigma) 85 | elif (score_approx.lower() == 'spectral'): 86 | #compute score matrix using spectral method for all samples, x and y according to the data distribution. 87 | score_matrix_1 = ss.SpectralSteinScore(sample_array_1, data_samples, J, stein_sigma) 88 | score_matrix_2 = ss.SpectralSteinScore(sample_array_2, data_samples, J, stein_sigma) 89 | else: raise IOError('Please enter \'Exact\', \'Identity_Score\' or \'Spectral_Score\' for score_approx') 90 | 91 | N_samples1 = len(sample_array_1) 92 | N_samples2 = len(sample_array_2) 93 | 94 | weighted_kernel= np.zeros((N_samples1, N_samples2)) 95 | 96 | for sample_index1 in range(0, N_samples1): 97 | for sample_index2 in range(0, N_samples2): 98 | 99 | delta_x = np.transpose(delta_x_kernel[sample_index1][sample_index2]) 100 | delta_y = delta_y_kernel[sample_index1][sample_index2] 101 | kernel = kernel_array[sample_index1][sample_index2] 102 | 103 | # if sample_index1 == sample_index2 and 'same' in argsv: 104 | # weighted_kernel[sample_index1, sample_index2] = 0 105 | 106 | # else: 107 | 108 | weighted_kernel[sample_index1, sample_index2] = np.dot(np.transpose(score_matrix_1[sample_index1]), kernel*score_matrix_2[sample_index2])\ 109 | - np.dot(np.transpose(score_matrix_1[sample_index1]), delta_y) \ 110 | - np.dot(delta_x, score_matrix_2[sample_index2])\ 111 | + trace[sample_index1][sample_index2] 112 | 113 | 114 | return weighted_kernel 115 | -------------------------------------------------------------------------------- /stein_score.py: -------------------------------------------------------------------------------- 1 | from pyquil.quil import Program 2 | import numpy as np 3 | from pyquil.api import get_qc 4 | import numpy.linalg as LA 5 | 6 | from train_generation import DataSampler 7 | from classical_kernel import GaussianKernelArray, GaussianKernel 8 | from file_operations_in import KernelDictFromFile, DataImport 9 | 10 | from auxiliary_functions import ShiftString, ToString, EmpiricalDist 11 | 12 | import matplotlib.pyplot as plt 13 | 14 | '''Functions for the Identity Method to compute the Stein Score''' 15 | 16 | def ComputeInverseTerm(kernel_array, N_samples, chi): 17 | '''This function computes the inverse matrix required by the Stein Score Approximator''' 18 | return LA.inv(kernel_array - chi*np.identity(N_samples)) 19 | 20 | def ComputeKernelShift(samples, stein_kernel, stein_sigma): 21 | ''' 22 | This kernel will not be the same as the one used in the MMD, it will only be computed 23 | between all samples from distribution P, with every sample from the SAME distribution P 24 | ''' 25 | N_samples = len(samples) 26 | N_qubits = len(samples[0]) 27 | 28 | shifted_kernel_for_score = np.zeros((N_samples, N_samples, N_qubits)) 29 | 30 | for sample_1_index in range(0, N_samples): 31 | for sample_2_index in range(0, N_samples): 32 | for qubit in range(0, N_qubits): 33 | 34 | sample_1 = ToString(samples[sample_1_index]) 35 | sample_2 = ToString(samples[sample_2_index]) 36 | shiftedstring2 = ShiftString(sample_2, qubit) 37 | 38 | shifted_kernel_for_score[sample_1_index][sample_2_index][qubit] = \ 39 | GaussianKernel(sample_1, sample_2, stein_sigma) - GaussianKernel(sample_1, shiftedstring2, stein_sigma) 40 | 41 | shifted_kernel_array = shifted_kernel_for_score.sum(axis = 1)/N_samples 42 | 43 | return shifted_kernel_array 44 | 45 | def IdentitySteinScore(samples, kernel_choice, chi, stein_sigma): 46 | '''This function computes the Stein Score matrix for all samples, based 47 | on the method of inverting Stein's identity''' 48 | 49 | N_samples = len(samples) 50 | 51 | #compute kernel matrix between all samples 52 | if kernel_choice == 'Gaussian': 53 | kernel_array = GaussianKernelArray(samples, samples, stein_sigma) 54 | else: raise ValueError('\'kernel_choice\' must be \'Gaussian\'') 55 | #Compute inverse term in Stein score approximation 56 | inverse = ComputeInverseTerm(kernel_array, N_samples, chi) 57 | #Compute shifted kernel term in Stein Score Approximation 58 | shifted_kernel_matrix = ComputeKernelShift(samples, kernel_choice, stein_sigma) 59 | 60 | #Compute Approximate kernel 61 | stein_score_array_identity = N_samples*np.dot(inverse, np.transpose(shifted_kernel_matrix)) 62 | 63 | return stein_score_array_identity 64 | 65 | '''Functions for the Probability Mass method to compute the Stein Score''' 66 | 67 | def MassSteinScoreSingleSample(sample, data_dict): 68 | '''This computes the exact Stein Score function in the discrete case for a single 69 | sample which is a 1D numpy array, based on probability *mass* function data_dict''' 70 | if type(sample) is np.ndarray and sample.ndim != 1: 71 | raise TypeError('If \'sample\' is a numpy array, it must be 1 - Dimensional') 72 | N_qubits = len(sample) 73 | sample_string = ToString(sample) 74 | stein_score_sample_mass = np.zeros((N_qubits)) 75 | for bit_index in range(0, N_qubits): 76 | shifted_string = ToString(ShiftString(sample_string, bit_index)) 77 | stein_score_sample_mass[bit_index] = 1 - data_dict[shifted_string]/data_dict[sample_string] 78 | 79 | return stein_score_sample_mass 80 | 81 | def MassSteinScore(samples, data_dict): 82 | '''This computes the Stein Matrix for all samples, based on probability *mass* function ''' 83 | N_samples = len(samples) 84 | N_qubits = len(samples[0]) 85 | stein_score_mass_array = np.zeros((N_samples, N_qubits)) 86 | if type(samples) is not np.ndarray and type(samples) is not list: 87 | raise TypeError('\'samples\' must be a numpy array or a list') 88 | 89 | for sample_index in range(0, N_samples): 90 | stein_score_mass_array[sample_index] = MassSteinScoreSingleSample(samples[sample_index], data_dict) 91 | 92 | return stein_score_mass_array 93 | 94 | 95 | '''Functions for the Spectral Method to compute the Stein Score''' 96 | 97 | def LargestEigValsVecs(kernel_array, J): 98 | '''This function returns the J^th largest eigenvalues and eigenvectors 99 | of the kernel matrix to compute score using spectral method''' 100 | 101 | kernel_eigvals, kernel_eigvecs = LA.eig(kernel_array) 102 | #put all eigenvalues and eigenvectors in dictionary 103 | eig_dict = {} 104 | eig_iterator = 0 105 | for eigenvalue in kernel_eigvals: 106 | eig_dict[eigenvalue] = kernel_eigvecs[:, eig_iterator] 107 | eig_iterator += 1 108 | 109 | #Put eigenvectors in dictionary corresponding to J^th largest eigenvalues 110 | largest_eigvals = list(sorted(eig_dict.keys(), reverse = True))[0:J] 111 | largest_eigvecs = [] 112 | for eigenvalue in largest_eigvals: 113 | largest_eigvecs.append(eig_dict[eigenvalue]) 114 | 115 | return largest_eigvals, largest_eigvecs 116 | 117 | 118 | def NystromEigenvectorsSingleSample(new_sample, samples, largest_eigvals, largest_eigvecs, J, stein_sigma): 119 | '''This function computes the approximate eigenvectors psi of the 120 | weighed kernel using the Nystrom method, for a given sample, x''' 121 | psi = np.zeros((J)) #initialise numpy array for J^th approximate eigenvectors 122 | M = len(samples) 123 | np.set_printoptions(linewidth=np.inf) 124 | kernel_array_single_sample = GaussianKernelArray(new_sample, samples, stein_sigma) #Compute 1 x len(samples) kernel array for a sample, with all others 125 | 126 | for j in range(0, J): 127 | psi[j] = np.real((np.sqrt(M)/largest_eigvals[j])*np.dot(kernel_array_single_sample, largest_eigvecs[j])) 128 | 129 | return psi 130 | 131 | def NystromEigenvectorsAllSamples(samples, largest_eigvals, largest_eigvecs, J, stein_sigma): 132 | '''This function computes the set of nystrom eigenvectors for all samples''' 133 | NystromEigenvectorsAllSamples = [] 134 | for sample in samples: 135 | NystromEigenvectorsAllSamples.append(NystromEigenvectorsSingleSample(sample, samples, largest_eigvals, largest_eigvecs, J, stein_sigma)) 136 | return NystromEigenvectorsAllSamples 137 | 138 | def SpectralBetaArray(samples, largest_eigvals, largest_eigvecs, J, stein_sigma): 139 | 140 | N_qubits = len(samples[0]) 141 | #List of arrays of Nystrom eigenvectors, for all samples 142 | psi_all_samples = NystromEigenvectorsAllSamples(samples, largest_eigvals, largest_eigvecs, J, stein_sigma) 143 | D = len(samples) 144 | #initialise array to be summed over with each index being 145 | # (shifted bit, Nystrom eigenvec index, sample index) 146 | beta_summand = np.zeros((N_qubits, D, J)) 147 | 148 | for bit_index in range(0, N_qubits): 149 | for sample_index in range(0, D): 150 | 151 | shifted_string = ShiftString(samples[sample_index], bit_index) 152 | 153 | beta_summand[bit_index, sample_index, :]= psi_all_samples[sample_index] \ 154 | -NystromEigenvectorsSingleSample(shifted_string,samples,\ 155 | largest_eigvals, largest_eigvecs,\ 156 | J, stein_sigma) 157 | 158 | beta = (1/D)*beta_summand.sum(axis = 1) 159 | return beta 160 | 161 | def SpectralSteinScoreSingleSample(new_sample, samples, largest_eigvals, largest_eigvecs, J, stein_sigma): 162 | '''Compute Stein Score using Spectral method''' 163 | beta = SpectralBetaArray(samples, largest_eigvals, largest_eigvecs, J, stein_sigma) 164 | psi = NystromEigenvectorsSingleSample(new_sample, samples,\ 165 | largest_eigvals, largest_eigvecs,\ 166 | J, stein_sigma) 167 | 168 | return np.dot(beta, psi) 169 | 170 | def SpectralSteinScore(samples1, samples2, J, stein_sigma): 171 | '''This function computes the Approximate Stein Score matrix for all samples using the spectral method ''' 172 | #samples2 are from the data distribution that we want the score function for 173 | #samples1 are the samples from the Born Machine 174 | kernel_array_all_samples = GaussianKernelArray(samples2, samples2, stein_sigma) 175 | largest_eigvals, largest_eigvecs = LargestEigValsVecs(kernel_array_all_samples, J) 176 | 177 | N_qubits = len(samples1[0]) 178 | N_samples = len(samples1) 179 | 180 | stein_score_array_spectral = np.zeros((N_samples, N_qubits)) 181 | for sample_index in range(0, N_samples): 182 | #Compute the Stein score for every sample in the Born machine, based on the data samples 183 | sample1 = samples1[sample_index] 184 | stein_score_array_spectral[sample_index] = SpectralSteinScoreSingleSample(sample1, samples2, \ 185 | largest_eigvals, largest_eigvecs,\ 186 | J, stein_sigma) 187 | return stein_score_array_spectral 188 | 189 | 190 | 191 | 192 | 193 | 194 | ##################################################################################################################### 195 | 196 | def ComputeScoreDifference(array_1, array_2, norm_type): 197 | '''This function computes either the Frobenius Norm, Infinity norm or a simple sum difference 198 | between the two arrays''' 199 | 200 | if (norm_type is 'Frobenius'): 201 | Norm = np.linalg.norm((array_1 - array_2), ord = None) 202 | elif (norm_type is 'Infinity'): 203 | Norm = np.linalg.norm(array_1 - array_2, ord = np.inf) 204 | else: raise ValueError('\'norm_type\' must be \'Frobenius\', \'Infinity\'') 205 | return Norm 206 | 207 | def CheckScoreApproximationDifference(max_qubits, eta): 208 | N_qubits_list = [i for i in range(2, max_qubits)] 209 | stein_sigma = [0.25, 10, 100] 210 | N_data_samples = [10, 20] 211 | 212 | # N_data_samples = [10, 100, 200, 300, 400] 213 | kernel_choice = 'Gaussian' 214 | data_type = 'Classical_Data' 215 | 216 | spectral_exact_diff, identity_exact_diff, mass_exact_diff = [np.zeros((len(N_qubits_list), len(N_data_samples))) for _ in range(3)] 217 | 218 | for qubit_index in range(0, len(N_qubits_list)): 219 | N_qubits = N_qubits_list[qubit_index] 220 | for sample_index in range(0, len(N_data_samples)): 221 | J = N_qubits + 2 222 | N_samples = N_data_samples[sample_index] 223 | data_samples, data_dict = DataImport(data_type, N_qubits, N_samples) 224 | 225 | emp_data_dict = EmpiricalDist(data_samples, N_qubits) 226 | 227 | stein_score_array_approx_identity = IdentitySteinScore(data_samples, kernel_choice, eta, stein_sigma) 228 | # print('The Identity Score matrix is:\n' , stein_score_array_approx_identity) 229 | 230 | stein_score_array_approx_spectral = SpectralSteinScore(data_samples, data_samples, J, stein_sigma) 231 | # print('The Spectral Score matrix is:\n' , stein_score_array_approx_spectral) 232 | 233 | stein_score_array_exact_mass = MassSteinScore(data_samples, data_dict) 234 | # print('\nThe Exact Score matrix is:\n', stein_score_array_exact_mass) 235 | 236 | stein_score_array_approx_mass = MassSteinScore(data_samples, emp_data_dict) 237 | # print('\nThe Approx Score matrix using empirical density is:\n', stein_score_array_approx_mass) 238 | 239 | spectral_exact_diff[qubit_index, sample_index] = ComputeScoreDifference(stein_score_array_approx_spectral, stein_score_array_exact_mass, 'Frobenius') 240 | identity_exact_diff[qubit_index, sample_index] = ComputeScoreDifference(stein_score_array_approx_identity, stein_score_array_exact_mass, 'Frobenius') 241 | mass_exact_diff[qubit_index, sample_index] = ComputeScoreDifference(stein_score_array_approx_mass, stein_score_array_exact_mass, 'Frobenius') 242 | 243 | print('Difference between exact and spectral method is:', spectral_exact_diff[qubit_index, sample_index]) 244 | print('Difference between exact and identity method is:', identity_exact_diff[qubit_index, sample_index]) 245 | print('Difference between exact and density method is:', mass_exact_diff[qubit_index, sample_index]) 246 | return spectral_exact_diff, identity_exact_diff, mass_exact_diff, N_data_samples, N_qubits_list 247 | 248 | max_qubits = 9 249 | eta = 0.01 250 | # J = 4 251 | def PlotScoreGivenNumberSamples(max_qubits, N_samples, eta): 252 | spectral_exact_diff, identity_exact_diff, mass_exact_diff, N_data_samples, N_qubits_list = CheckScoreApproximationDifference(max_qubits, eta) 253 | 254 | fig, ax = plt.subplots() 255 | spectral_exact_diff_plot = np.zeros((len(N_qubits_list)), dtype = int) 256 | identity_exact_diff_plot = np.zeros((len(N_qubits_list)), dtype = int) 257 | mass_exact_diff_plot = np.zeros((len(N_qubits_list)), dtype = int) 258 | for qubit_index in range(0, len(N_qubits_list)): 259 | spectral_exact_diff_plot[qubit_index] = spectral_exact_diff[qubit_index, N_data_samples.index(N_samples)] 260 | identity_exact_diff_plot[qubit_index] = identity_exact_diff[qubit_index, N_data_samples.index(N_samples)] 261 | mass_exact_diff_plot[qubit_index] = mass_exact_diff[qubit_index, N_data_samples.index(N_samples)] 262 | 263 | 264 | ax.plot(spectral_exact_diff_plot, '%so' %('r'), label ='Spectral Score') 265 | ax.plot(identity_exact_diff_plot, '%s+' %('b'), label ='Identity Score') 266 | ax.plot(mass_exact_diff_plot, '%sx' %('g'), label ='Mass Score') 267 | ax.set_title("Frobenius Norm of Score Matrix using %i samples," %(N_samples)) 268 | ax.set_xlabel("Number of Qubits") 269 | ax.set_ylabel("Frobenius Norm") 270 | 271 | ax.set_xticks(np.arange(len(N_qubits_list))) 272 | ax.set_xticklabels(N_qubits_list) 273 | ax.legend(('Spectral Score','Identity Score', 'Mass Score')) 274 | plt.show() 275 | 276 | return 277 | # PlotScoreGivenNumberSamples(max_qubits, 10, eta) 278 | # PlotScoreGivenNumberSamples(max_qubits, 20, eta) 279 | 280 | 281 | 282 | 283 | -------------------------------------------------------------------------------- /train_generation.py: -------------------------------------------------------------------------------- 1 | import math 2 | import numpy as np 3 | from auxiliary_functions import IntegerToString 4 | from numpy import random as rand 5 | import matplotlib.pyplot as plt 6 | 7 | 8 | """Calculate the Hamming distance between two bit strings""" 9 | def HammingWeight(s1, s2): 10 | assert len(s1) == len(s2) 11 | return sum(c1 != c2 for c1,c2 in zip(s1, s2)) 12 | 13 | """Generate all bit strings between 0 and N_v and N_v to N_h""" 14 | def Perms(n_v, n_h, m_h): 15 | s_visible = np.zeros((2**n_v, n_v)) 16 | s_hidden = np.zeros((2**n_h, n_h)) 17 | s_modes = np.zeros((2**m_h, m_h)) 18 | 19 | #Visible units all permutations 20 | for v_index in range(0,2**n_v): 21 | v_string = IntegerToString(v_index, n_v) 22 | for v in range(0,n_v): 23 | s_visible[v_index, v] = float(v_string[v]) 24 | 25 | #Hidden units all permutations 26 | for h_index in range(0,2**n_h): 27 | h_string = IntegerToString(h_index, n_h) 28 | for h in range(0, n_h): 29 | s_hidden[h_index][h] = float(h_string[h]) 30 | 31 | #Hidden Modes all permutations, only used to compute training data - doesn't need to be outputted 32 | for hidden_mode_index in range(0,2**m_h): 33 | hidden_mode_string = IntegerToString(hidden_mode_index, m_h) 34 | for h in range(0,m_h): 35 | s_modes[hidden_mode_index][h] = float(hidden_mode_string[h]) 36 | return s_visible, s_hidden, s_modes 37 | 38 | """Generates Random centre modes """ 39 | def CentreModes(n_v, m_h): 40 | s_cent = np.zeros((m_h, n_v)) 41 | for h in range(0, m_h): 42 | #Fix random seed for reproducibility, for each centre mode, h 43 | np.random.seed(h) 44 | s_cent[h] = np.random.binomial(1, 0.5, n_v) 45 | 46 | return s_cent 47 | 48 | """Finds the Hamming weight of each possible input relative to each of the centre points""" 49 | def HamWeightModes(s_visible, s_cent, n_v, m_h): 50 | hamweight = np.zeros((2**n_v, m_h)) 51 | 52 | for string in range(0,2**n_v): 53 | for h in range(0,m_h): 54 | hamweight[string][h] = HammingWeight(s_cent[h][:], s_visible[:][string]) 55 | 56 | return hamweight 57 | 58 | """This defines the full probability distribution over the visible nodes, and the hidden 'modes' """ 59 | def ProbDist(n_v, m_h, p, hamw): 60 | dist = np.zeros((2**n_v, m_h)) 61 | for v_string in range(0,2**n_v): 62 | for h in range(0, m_h): 63 | dist[v_string][h] = ((p**(n_v - hamw[v_string][h]))*((1-p)**(hamw[v_string][h]))) 64 | return dist 65 | 66 | def all_binary_values(power): 67 | 68 | binary_list = np.zeros((2**power, power)) 69 | for i in range(2**power): 70 | temp = i 71 | for j in range(power): 72 | binary_list[i,power - j - 1] = temp % 2 73 | temp >>= 1 74 | 75 | return binary_list 76 | 77 | def TrainingData(N_v, N_h, M_h): 78 | """This function constructs example training data""" 79 | 80 | '''s_hidden/s_visible is all possible output strings of the qubits''' 81 | '''s_modes is all possible output strings over the modes''' 82 | 83 | centre_modes = CentreModes(N_v, M_h) 84 | # centre_modes = np.random.binomial(1, 0.5, (M_h,N_v)) 85 | # bin_visible, _,_ = Perms(N_v, N_h, M_h) 86 | bin_visible = all_binary_values(N_v) 87 | hamweight = HamWeightModes(bin_visible, centre_modes, N_v, M_h) 88 | jointdist = ProbDist(N_v, M_h, 0.9, hamweight) 89 | data_dist = (1/M_h)*jointdist.sum(axis=1) 90 | #put data in dictionary 91 | data_dist_dict = {} 92 | for v_string in range(0,2**N_v): 93 | bin_string_visible = IntegerToString(v_string, N_v) 94 | data_dist_dict[bin_string_visible] = data_dist[v_string] 95 | return data_dist, data_dist_dict 96 | 97 | # def DataSampler(N_v, N_h, M_h, N_samples, data_probs, exact_data_dict): 98 | def DataSampler(N_v, N_h, M_h, N_samples, data_probs): 99 | '''This functions generates (N_samples) samples according to the given probability distribution 100 | data_probs''' 101 | 102 | '''Uncomment next line if a new run is required, i.e. not printing to file''' 103 | #data_dist, bin_visible, bin_hidden, data_dist_dict = TrainingData(N_v, N_h, M_h) 104 | 105 | #labels for output possibilities, integer in range [0, 2**N_v], corresponds to bitstring over N_v bits 106 | elements = [IntegerToString(i, N_v) for i in range(2**N_v)] 107 | data_samples = np.random.choice(elements, N_samples, True, data_probs) 108 | 109 | return data_samples 110 | 111 | # Generates the set of all Bars-As-Stripes matrices and reshapes them into a vector 112 | def generate_artificial_bas(rng): 113 | all_data = np.zeros(shape=3) 114 | size = 3 115 | big_enough = 0 116 | while big_enough < 500: 117 | data_i = np.zeros(shape=(3,3)) 118 | if rng.uniform() < 0.5: 119 | # to see whether we fill horizontally 120 | # direction = horizontal 121 | for s in range(0, size): 122 | if rng.uniform() < 0.5: 123 | data_i[s] = np.zeros(shape=size) 124 | else: 125 | data_i[s] = np.ones(shape=size) 126 | all_data = np.vstack([all_data, data_i.reshape(-1)]) 127 | else: 128 | # direction = vertical 129 | for s in range(0, size): 130 | if rng.uniform() < 0.5: 131 | data_i[:, s] = np.zeros(shape=size) 132 | else: 133 | data_i[:, s] = np.ones(shape=size) 134 | all_data = np.vstack([all_data, data_i.reshape(-1)]) 135 | big_enough += 1 136 | # uniqueness 137 | y = np.vstack({tuple(row) for row in all_data}) 138 | return y 139 | 140 | 141 | # data = generate_artificial_bas(rand.RandomState(123456)) 142 | # print(data) 143 | 144 | # for i in range(0, data.shape[0]): 145 | # plt.imshow(data[i].reshape(3,3)) 146 | # plt.show(block=False) 147 | # plt.pause(1.5) 148 | # plt.close() -------------------------------------------------------------------------------- /train_plot.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | 4 | from file_operations_in import TrainingDataFromFile 5 | import sys 6 | 7 | def CostPlot(N_qubits, kernel_type, data_train_test, N_samples,\ 8 | cost_func, loss, circuit_params, born_probs_list, empirical_probs_list): 9 | 10 | plot_colour = ['r', 'b'] 11 | 12 | if kernel_type == 'Quantum': 13 | if (cost_func == 'MMD'): 14 | plt.plot(loss[('MMD', 'Train')], '%so-' %(plot_colour[0]), label ='MMD, %i Training Points, %i Born Samples for a %s kernel with %i Measurements.' \ 15 | %(len(data_train_test[0]), N_samples[1], kernel_type[0], N_samples[-1])) 16 | plt.plot(loss[('MMD', 'Test')], '%sx-' %(plot_colour[0]), label ='MMD, %i Test Points, %i Born Samples for a %s kernel with %i Measurements.' \ 17 | %(len(data_train_test[1]), N_samples[1], kernel_type[0], N_samples[-1])) 18 | elif (cost_func == 'Stein'): 19 | plt.plot(loss[('Stein', 'Train')],'%so' %(plot_colour[1]) , label ='Stein, %i Training Points, %i Born Samples for a %s kernel with %i Measurements.' \ 20 | %(len(data_train_test[0]), N_samples[1], kernel_type[0], N_samples[-1])) 21 | plt.plot(loss[('Stein', 'Test')],'%so-' %(plot_colour[1]) , label ='Stein, %i Test Points, %i Born Samples for a %s kernel with %i Measurements.' \ 22 | %(len(data_train_test[1]), N_samples[1], kernel_type[0], N_samples[-1])) 23 | elif (cost_func == 'TV'): 24 | plt.plot(loss['TV'], '%so-' %(plot_colour[1]), label ='TV, %i Data Samples, %i Born Samples for a %s kernel with %i Measurements.' \ 25 | %(N_samples[0], N_samples[1], kernel_type[0], N_samples[-1])) 26 | else: 27 | if (cost_func == 'MMD'): 28 | plt.plot(loss[('MMD', 'Train')], '%so-' %(plot_colour[0]), label ='MMD, %i Training Points, %i Born Samples for a %s kernel.' \ 29 | %(len(data_train_test[0]), N_samples[1], kernel_type[0])) 30 | plt.plot(loss[('MMD', 'Test')], '%sx-' %(plot_colour[0]), label ='MMD, %i Test Points, %i Born Samples for a %s kernel.' \ 31 | %(len(data_train_test[1]), N_samples[1], kernel_type[0])) 32 | elif (cost_func == 'Stein'): 33 | plt.plot(loss[('Stein', 'Train')],'%so-' %(plot_colour[1]) , label ='Stein, %i Training Points, %i Born Samples for a %s kernel.' \ 34 | %(len(data_train_test[0]), N_samples[1], kernel_type[0])) 35 | plt.plot(loss[('Stein', 'Test')],'%sx-' %(plot_colour[1]) , label ='Stein, %i Test Points, %i Born Samples for a %s kernel.' \ 36 | %(len(data_train_test[1]), N_samples[1], kernel_type[0])) 37 | elif (cost_func == 'Sinkhorn'): 38 | plt.plot(loss[('Sinkhorn', 'Train')],'%so-' %(plot_colour[1]) , label ='Sinkhorn, %i Training Points, %i Born Samples for a Hamming cost.' \ 39 | %(len(data_train_test[0]), N_samples[1])) 40 | plt.plot(loss[('Sinkhorn', 'Test')],'%sx-' %(plot_colour[1]) , label ='Sinkhorn, %i Test Points, %i Born Samples for a Hamming cost.' \ 41 | %(len(data_train_test[1]), N_samples[1])) 42 | elif (cost_func == 'TV'): 43 | plt.plot(loss['TV'], '%so-' %(plot_colour[1]), label ='TV, %i Data Samples, %i Born Samples for a %s kernel.' \ 44 | %(N_samples[0], N_samples[1], kernel_type[0])) 45 | 46 | # plt.rc('text', usetex=True) 47 | # plt.rc('font', family='serif') 48 | plt.xlabel("Epochs") 49 | plt.ylabel("Loss") 50 | plt.title("Loss for %i qubits" % N_qubits) 51 | 52 | 53 | # plt.show(block=False) 54 | # plt.pause(1) 55 | # plt.close() 56 | return loss, circuit_params, born_probs_list, empirical_probs_list 57 | -------------------------------------------------------------------------------- /unit_test.py: -------------------------------------------------------------------------------- 1 | from file_operations_out import string_to_int_byte 2 | from run_and_compare import bytes_to_int, read_ints_from_file 3 | 4 | def create_test_binary_file(file_name, int_list): 5 | 6 | with open(file_name, 'wb') as f: 7 | 8 | for i in int_list: 9 | 10 | f.write(bytes([i])) 11 | 12 | def test_string_to_int_byte(): 13 | assert string_to_int_byte('101', 3, 0) == 5 14 | assert string_to_int_byte('1011010101011', 13, 0) == 181 15 | assert string_to_int_byte('1011010101011', 13, 1) == 11 16 | 17 | def test_bytes_to_int(): 18 | assert bytes_to_int([5,4,1]) == 328705 19 | 20 | def test_read_ints_from_file(): 21 | 22 | file_name = 'binary_file_system_example' 23 | 24 | create_test_binary_file(file_name, [4,6,18]) 25 | 26 | with open(file_name, 'rb') as f: 27 | 28 | int_list = read_ints_from_file(5,3,f) 29 | 30 | assert int_list == [4,6,18] 31 | 32 | create_test_binary_file(file_name, [8 , 9 , 18 , 7]) 33 | 34 | with open(file_name, 'rb') as f: 35 | 36 | int_list = read_ints_from_file(16, 2, f) 37 | 38 | assert int_list == [2057, 4615] 39 | --------------------------------------------------------------------------------