├── .gitignore ├── requirements.txt ├── research ├── 2018_06_14_hadfield_starting_city.md ├── 2018_06_02_qaoa_initial_state.md ├── 2018_03_29_TSP_cost_operators_report.md └── 2018_05_26_hadfield_qaoa.md ├── README.md ├── src ├── main.py ├── TSP_utilities.py ├── dwave_tsp_solver.py └── forest_tsp_solver.py └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.png 3 | .idea 4 | src/grove -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | dimod==0.7.9 2 | dwave-cloud-client==0.4.16 3 | dwave-networkx==0.6.9 4 | dwave-qbsolv==0.2.9 5 | dwave-system==0.5.1 6 | networkx==2.2 7 | matplotlib==3.1.0 8 | quantum-grove==2.0.0b0 9 | pyquil==2.8.0 -------------------------------------------------------------------------------- /research/2018_06_14_hadfield_starting_city.md: -------------------------------------------------------------------------------- 1 | # Hadfield QAOA - adding starting city 2 | 3 | ## Introduction 4 | 5 | The goal of this experiment was to implement an version of algorithm with specified starting node. This allows to change scaling of number of qubits from N^2 to (N-1)^2. 6 | 7 | ## Dependencies 8 | 9 | As a main engine for solving TSP we used code from this repository: https://github.com/BOHRTECHNOLOGY/quantum_tsp, imported as a subtree. 10 | 11 | We were using pyquil and grove. 12 | Pyquil: commit hash: `26ae363e9f5c85dc3aab298daebc9ec5023a32a1` 13 | Grove: commit hash: `e3fd7b9f3188e820dd19ff487dbf56c8faf43822` 14 | 15 | The exact versions of these repositories are commited to this project. 16 | 17 | ## Tests 18 | 19 | After the implementation we decided to check if it is correct. We ran the tests for 3 and 4 cities (4 and 9 qubits). 20 | 21 | We used the following parameters: 22 | 23 | - `tol=1e-3` 24 | - `steps=2` 25 | - `initial_state="all"` 26 | 27 | The placement of the cities was random. 28 | 29 | ## Results 30 | 31 | ### Observation 1 32 | 33 | This modification has not changed the calculation time for given number of qubits or the difference was too small to measure. 34 | 35 | ### Observation 2 36 | 37 | There were no significant differences between performance of different starting nodes. 38 | 39 | ### Observation 3 40 | 41 | For 4 qubits case (3 cities), the results were correct in 100% cases. For 9 qubits (4 cities) the results were correct in 70% of the cases. 42 | In the previous experiment for 9 qubits and 3 cities the accuracy was about 90%. 43 | 44 | 45 | ## Conclusions 46 | 47 | Adding starting point has not changed the performance of the algorithm very much and improved the size of the problem that can be feasibly tackled with it. 48 | 49 | Lower percentage of correct results for given number of qubits might be a result of slightly harder problem to solve - there are additional constraints for the initial point. It may be probably still improved by using different parameters. 50 | 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Quantum TSP 2 | 3 | This repository contains code for an open source program solving the Travelling Salesman Problem with Quantum Computing. 4 | 5 | ## Structure 6 | 7 | There are two directories. 8 | `src` contains all the source code necessary to solve TSP. 9 | `research` contains reports and references to research, which lead to improving the code base. 10 | 11 | 12 | ## Libraries used 13 | 14 | This project makes a use of quantum computing libraries, you can install them with pip: 15 | 16 | pip install pyquil 17 | pip install quantum-grove 18 | 19 | ### Pyquil 20 | 21 | Pyquil is a library allowing you to create code for quantum computers to be executed using Rigetti Forest platform. It's developed by Rigetti Computing. 22 | To run your code on the quantum virtual machine or quantum processor you need to configure file, as described here: 23 | http://pyquil.readthedocs.io/en/latest/start.html#connecting-to-the-rigetti-forest 24 | 25 | ### Grove 26 | 27 | Grove is a collection of quantum algorithms built using the Rigetti Forest platform. I use its implementation of QAOA for pyquil. 28 | 29 | https://github.com/rigetticomputing/grove 30 | 31 | ### DWave 32 | 33 | DWave is a quantum annealer - another type of quantum computing devices. To use it you need the following library: 34 | 35 | pip install dwave-system 36 | 37 | and have your own `sapi-token`. You can obtain it here: https://cloud.dwavesys.com/qubist/apikey/, though I am not sure if anyone is eligible to get it. 38 | 39 | *Warning!* 40 | 41 | There are a couple of things worth knowing when it comes to this version: 42 | 43 | 1. The biggest number of cities that can be solved on D-Wave 2000Q is 9. The amount of qubits needed to solve the problem grows as N^2 and finding embedding for the case with 10 cities will fail in most (if not all) cases. 44 | 45 | 2. This implementation doesn't allow you to specify the starting point - it needs some modifications to take this information into account. 46 | 47 | 3. If you experience any unexpected problems with D-Wave libraries, you might want to install an older version - this script definitely worked with `dwave-system==0.5.1`: 48 | 49 | ## Sources 50 | 51 | - QAOA paper: https://arxiv.org/abs/1411.4028 52 | - Demo from IBM: https://nbviewer.jupyter.org/github/Qiskit/qiskit-tutorial/blob/master/qiskit/aqua/optimization/maxcut_and_tsp.ipynb 53 | - Rigetti Maxcut paper: https://arxiv.org/pdf/1712.05771.pdf 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | import time 2 | import TSP_utilities 3 | from forest_tsp_solver import ForestTSPSolver 4 | from dwave_tsp_solver import DWaveTSPSolver 5 | 6 | def main(): 7 | nodes_array = TSP_utilities.create_nodes_array(4) 8 | tsp_matrix = TSP_utilities.get_tsp_matrix(nodes_array) 9 | starting_node = 0 10 | sapi_token = None 11 | dwave_url = 'https://cloud.dwavesys.com/sapi' 12 | 13 | print("Brute Force solution") 14 | start_time = time.time() 15 | bf_start_time = start_time 16 | brute_force_solution = TSP_utilities.solve_tsp_brute_force_from_given_node(nodes_array, starting_node) 17 | end_time = time.time() 18 | calculation_time = end_time - start_time 19 | print("Calculation time:", calculation_time) 20 | TSP_utilities.plot_solution('brute_force_' + str(start_time), nodes_array, brute_force_solution) 21 | 22 | if sapi_token is None or dwave_url is None: 23 | print("You cannot run code on DWave without specifying your sapi_token and url") 24 | elif len(nodes_array) >= 10: 25 | print("This problem size is to big to run on D-Wave.") 26 | else: 27 | print("DWave solution") 28 | start_time = time.time() 29 | dwave_solver = DWaveTSPSolver(tsp_matrix, sapi_token=sapi_token, url=dwave_url) 30 | dwave_solution, dwave_distribution = dwave_solver.solve_tsp() 31 | end_time = time.time() 32 | calculation_time = end_time - start_time 33 | print("Calculation time:", calculation_time) 34 | costs = [(sol, TSP_utilities.calculate_cost(tsp_matrix, sol), dwave_distribution[sol]) for sol in dwave_distribution] 35 | solution_cost = TSP_utilities.calculate_cost(tsp_matrix, dwave_solution) 36 | print("DWave:", dwave_solution, solution_cost) 37 | for cost in costs: 38 | print(cost) 39 | TSP_utilities.plot_solution('dwave_' + str(bf_start_time), nodes_array, dwave_solution) 40 | 41 | 42 | print("QAOA solution - Forest") 43 | start_time = time.time() 44 | forest_solver = ForestTSPSolver(tsp_matrix, starting_node=starting_node) 45 | forest_solution, forest_distribution = forest_solver.solve_tsp() 46 | end_time = time.time() 47 | calculation_time = end_time - start_time 48 | print("Calculation time:", calculation_time) 49 | costs = [(sol, TSP_utilities.calculate_cost(tsp_matrix, sol), forest_distribution[sol]) for sol in forest_distribution] 50 | print("Forest:") 51 | for cost in costs: 52 | print(cost) 53 | TSP_utilities.plot_solution('forest_' + str(bf_start_time), nodes_array, forest_solution) 54 | 55 | 56 | if __name__ == '__main__': 57 | main() -------------------------------------------------------------------------------- /research/2018_06_02_qaoa_initial_state.md: -------------------------------------------------------------------------------- 1 | # Hadfield QAOA - initial states 2 | 3 | This report was originally posted in https://github.com/mstechly/quantum_computing/tree/master/Experiments/2018_06_02_qaoa_initial_state. If a file is referenced in this report, you should look for it in that repository. At the moment the results from this research have not been implemented here - it will be after adding some additional features. 4 | 5 | ## Introduction 6 | 7 | The goal of this experiment was to check how implementation of Hadfield's QAOA works when initialized with various initial states. 8 | 9 | ## Dependencies 10 | 11 | As a main engine for solving TSP we use the code from this repository: https://github.com/BOHRTECHNOLOGY/quantum_tsp, imported as a subtree. 12 | 13 | We were using pyquil and grove. 14 | Pyquil: commit hash: `26ae363e9f5c85dc3aab298daebc9ec5023a32a1` 15 | Grove: commit hash: `e3fd7b9f3188e820dd19ff487dbf56c8faf43822` 16 | 17 | The exact versions of these repositories are commited to this project. 18 | 19 | ## Tests 20 | 21 | We've changed the initial state of the algorithm to the following: 22 | 23 | - [2 -> 0 -> 1] 24 | - [2 -> 1 -> 0] 25 | - superposition of all possible states 26 | 27 | We also had data from the previous experiment with the [0 -> 1 -> 2] state. 28 | In the rest of this report we will name states not being superposition ([0 -> 1 -> 2], [2 -> 0 -> 1] and [2 -> 1 -> 0]) as simple states as opposed to the complex state (superposition of all states). 29 | 30 | Tests were similar to those from the previous experiment, with the following parameters: 31 | 32 | - steps = 2 33 | - tol = 1e-3 34 | 35 | The variables were city placements (same as in the previous experiment, see appendix) and the initial states. 36 | 37 | ## Results 38 | 39 | Based on the results we've made the following observations: 40 | 41 | ### Observation 1 42 | 43 | Mean calculation time of all the simple states was very similar. 44 | 45 | ### Observation 2 46 | 47 | Mean calculation for complex state was about 2.5 times longer than for the simple states. 48 | 49 | ### Observation 3 50 | 51 | Algorithm starting from complex state explores all possible solution much better than for simple case. Best solution mean probability is roughly equal to 50% in the cases where there are two optimal solutions (e.g. 0 -> 1 -> 2 and 2 -> 1 -> 0) and to 17% in case of equilateral triangle, where every solution is optimal, so it should have probability of 16.6%. 52 | 53 | ### Observation 4 54 | 55 | The results are in general pretty good. For the random state number of correct solutions varied between 84 - 100% . What's interesting, state 2 -> 1 -> 0 gave good results in all 34 cases, where others had worse performance. Probably more simulations would level this effect. 56 | 57 | 58 | ## Conclusions 59 | 60 | Most of the observations are consistent with intuition. This is good since there are no unexpected effects, that affect the algorithm. 61 | 62 | Initializing algorithm with superposition of simple states is a good idea if we want to fully explore the space of possible solutions, however the time of calculation is longer. 63 | There are two ways to balance these approaches: 64 | - use weaker parameters (steps and tol) with complex state initialization 65 | - initialize algorithm with superposition of some, not all states 66 | 67 | Both methods have pros and cons and could be researched. 68 | 69 | ## Appendix - City placement 70 | 71 | Here is a list of the city placement we were using. Below we describe them with the following information: 72 | - Symmetrical/Asymmetrical 73 | - Order of the optimal route: e.g. [0 -> 1 -> 2]. In all cases at least two orderings were correct, since we don't specify the starting point. 74 | - Coordinates e.g.: [[0, 0], [0, 10], [0, 20]] 75 | - 1D / 2D - were all the points on one line or not. 76 | 77 | 78 | The coordinates were randomly scaled and shifted, so those listed here are just an example. 79 | 80 | 0. 1D, Symmetrical, [0 -> 1 -> 2] 81 | 82 | [[0, 0], [0, 10], [0, 20]] 83 | 84 | 1. 1D, Symmetrical [0 -> 2 -> 1] 85 | 86 | [[0, 0], [0, 20], [0, 10]] 87 | 88 | 2. 1D, Symmetrical [1 -> 0 -> 2] 89 | 90 | [[0, 10], [0, 20], [0, 0]] 91 | 92 | 3. 1D, Asymmetrical [0 -> 1 -> 2] 93 | 94 | [[0, 0], [0, 1], [0, 10]] 95 | 96 | 4. 1D, Asymmetrical [2 -> 0 -> 1] 97 | 98 | [[0, 1], [0, 0], [0, 10]] 99 | 100 | 5. 2D, Symmetrical (equilateral angle), all orderings are the same 101 | 102 | [[0, 0], [1, 0], [0.5, np.sqrt(3)/2]] 103 | 104 | 6. 2D, Symmetrical triangle [0 -> 2 -> 1] 105 | 106 | [[-5, 0], [5, 0], [0, 1]] 107 | 108 | 7. 2D, Assymetrical triangle [0 -> 2 -> 1] 109 | 110 | [[0, 0], [15, 0], [0, 1]] 111 | 112 | 8. 2D, random triangle 113 | 114 | In this case it was just a set of random points, so each case was different. -------------------------------------------------------------------------------- /src/TSP_utilities.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import itertools 3 | import matplotlib.pyplot as plt 4 | 5 | def create_nodes_array(N, seed=None): 6 | """ 7 | Creates array of random points of size N. 8 | """ 9 | if seed: 10 | print("seed", seed) 11 | np.random.seed(seed) 12 | 13 | nodes_list = [] 14 | for i in range(N): 15 | nodes_list.append(np.random.rand(2) * 10) 16 | return np.array(nodes_list) 17 | 18 | 19 | def get_tsp_matrix(nodes_array): 20 | """ 21 | Creates distance matrix out of given coordinates. 22 | """ 23 | number_of_nodes = len(nodes_array) 24 | matrix = np.zeros((number_of_nodes, number_of_nodes)) 25 | for i in range(number_of_nodes): 26 | for j in range(i, number_of_nodes): 27 | matrix[i][j] = distance_between_points(nodes_array[i], nodes_array[j]) 28 | matrix[j][i] = matrix[i][j] 29 | return matrix 30 | 31 | 32 | def distance_between_points(point_A, point_B): 33 | return np.sqrt((point_A[0] - point_B[0])**2 + (point_A[1] - point_B[1])**2) 34 | 35 | 36 | def solve_tsp_brute_force(nodes_array): 37 | number_of_nodes = len(nodes_array) 38 | initial_order = range(0, number_of_nodes) 39 | all_permutations = [list(x) for x in itertools.permutations(initial_order)] 40 | cost_matrix = get_tsp_matrix(nodes_array) 41 | best_permutation = all_permutations[0] 42 | best_cost = calculate_cost(cost_matrix, all_permutations[0]) 43 | for permutation in all_permutations: 44 | current_cost = calculate_cost(cost_matrix, permutation) 45 | if current_cost < best_cost: 46 | best_permutation = permutation 47 | best_cost = current_cost 48 | print("Brute force:", best_permutation, best_cost) 49 | return best_permutation 50 | 51 | 52 | def solve_tsp_brute_force_from_given_node(nodes_array, starting_node): 53 | number_of_nodes = len(nodes_array) 54 | initial_order = range(0, number_of_nodes) 55 | all_permutations = [list(x) for x in itertools.permutations(initial_order)] 56 | cost_matrix = get_tsp_matrix(nodes_array) 57 | best_permutation = all_permutations[0] 58 | best_cost = calculate_cost(cost_matrix, all_permutations[0])*1000 59 | for permutation in all_permutations: 60 | if permutation[0] != starting_node: 61 | continue 62 | current_cost = calculate_cost(cost_matrix, permutation) 63 | if current_cost < best_cost: 64 | best_permutation = permutation 65 | best_cost = current_cost 66 | print("Brute force:", best_permutation, best_cost) 67 | return best_permutation 68 | 69 | 70 | def calculate_cost(cost_matrix, solution): 71 | cost = 0 72 | for i in range(len(solution)): 73 | a = i%len(solution) 74 | b = (i+1)%len(solution) 75 | cost += cost_matrix[solution[a]][solution[b]] 76 | 77 | return cost 78 | 79 | 80 | def plot_solution(name, nodes_array, solution): 81 | plt.scatter(nodes_array[:, 0], nodes_array[:, 1], s=200) 82 | for i in range(len(nodes_array)): 83 | plt.annotate(i, (nodes_array[i, 0] + 0.15, nodes_array[i, 1] + 0.15), size=16, color='r') 84 | 85 | plt.xlim([min(nodes_array[:, 0]) - 1, max(nodes_array[:, 0]) + 1]) 86 | plt.ylim([min(nodes_array[:, 1]) - 1, max(nodes_array[:, 1]) + 1]) 87 | for i in range(len(solution)): 88 | a = i%len(solution) 89 | b = (i+1)%len(solution) 90 | A = solution[a] 91 | B = solution[b] 92 | plt.plot([nodes_array[A, 0], nodes_array[B, 0]], [nodes_array[A, 1], nodes_array[B, 1]], c='r') 93 | 94 | cost = calculate_cost(get_tsp_matrix(nodes_array), solution) 95 | title_string = "Cost:" + str(cost) 96 | title_string += "\n" + str(solution) 97 | plt.title(title_string) 98 | plt.savefig(name + '.png') 99 | plt.clf() 100 | 101 | def points_order_to_binary_state(points_order): 102 | """ 103 | Transforms the order of points from the standard representation: [0, 1, 2], 104 | to the binary one: [1,0,0,0,1,0,0,0,1] 105 | """ 106 | number_of_points = len(points_order) 107 | binary_state = np.zeros((len(points_order) - 1)**2) 108 | for j in range(1, len(points_order)): 109 | p = points_order[j] 110 | binary_state[(number_of_points - 1) * (j - 1) + (p - 1)] = 1 111 | return binary_state 112 | 113 | def binary_state_to_points_order(binary_state): 114 | """ 115 | Transforms the the order of points from the binary representation: [1,0,0,0,1,0,0,0,1], 116 | to the binary one: [0, 1, 2] 117 | """ 118 | points_order = [] 119 | number_of_points = int(np.sqrt(len(binary_state))) 120 | for p in range(number_of_points): 121 | for j in range(number_of_points): 122 | if binary_state[(number_of_points) * p + j] == 1: 123 | points_order.append(j) 124 | return points_order 125 | -------------------------------------------------------------------------------- /src/dwave_tsp_solver.py: -------------------------------------------------------------------------------- 1 | from dwave.system.samplers import DWaveSampler # Library to interact with the QPU 2 | from dwave.system.composites import EmbeddingComposite # Library to embed our problem onto the QPU physical graph 3 | 4 | import itertools 5 | import scipy.optimize 6 | import TSP_utilities 7 | import numpy as np 8 | 9 | class DWaveTSPSolver(object): 10 | """ 11 | Class for solving Travelling Salesman Problem using DWave. 12 | Specifying starting point is not implemented. 13 | """ 14 | def __init__(self, distance_matrix, sapi_token=None, url=None): 15 | 16 | max_distance = np.max(np.array(distance_matrix)) 17 | scaled_distance_matrix = distance_matrix / max_distance 18 | self.distance_matrix = scaled_distance_matrix 19 | self.constraint_constant = 400 20 | self.cost_constant = 10 21 | self.chainstrength = 800 22 | self.numruns = 1000 23 | self.qubo_dict = {} 24 | self.sapi_token = sapi_token 25 | self.url = url 26 | self.add_cost_objective() 27 | self.add_time_constraints() 28 | self.add_position_constraints() 29 | 30 | 31 | def add_cost_objective(self): 32 | n = len(self.distance_matrix) 33 | for t in range(n): 34 | for i in range(n): 35 | for j in range(n): 36 | if i == j: 37 | continue 38 | qubit_a = t * n + i 39 | qubit_b = (t + 1)%n * n + j 40 | self.qubo_dict[(qubit_a, qubit_b)] = self.cost_constant * self.distance_matrix[i][j] 41 | 42 | def add_time_constraints(self): 43 | n = len(self.distance_matrix) 44 | for t in range(n): 45 | for i in range(n): 46 | qubit_a = t * n + i 47 | if (qubit_a, qubit_a) not in self.qubo_dict.keys(): 48 | self.qubo_dict[(qubit_a, qubit_a)] = -self.constraint_constant 49 | else: 50 | self.qubo_dict[(qubit_a, qubit_a)] += -self.constraint_constant 51 | for j in range(n): 52 | qubit_b = t * n + j 53 | if i!=j: 54 | self.qubo_dict[(qubit_a, qubit_b)] = 2 * self.constraint_constant 55 | 56 | 57 | def add_position_constraints(self): 58 | n = len(self.distance_matrix) 59 | for i in range(n): 60 | for t1 in range(n): 61 | qubit_a = t1 * n + i 62 | if (qubit_a, qubit_a) not in self.qubo_dict.keys(): 63 | self.qubo_dict[(qubit_a, qubit_a)] = -self.constraint_constant 64 | else: 65 | self.qubo_dict[(qubit_a, qubit_a)] += -self.constraint_constant 66 | for t2 in range(n): 67 | qubit_b = t2 * n + i 68 | if t1!=t2: 69 | self.qubo_dict[(qubit_a, qubit_b)] = 2 * self.constraint_constant 70 | 71 | 72 | 73 | def solve_tsp(self): 74 | response = EmbeddingComposite(DWaveSampler(token=self.sapi_token, endpoint=self.url, solver='DW_2000Q_2_1')).sample_qubo(self.qubo_dict, chain_strength=self.chainstrength, num_reads=self.numruns) 75 | self.decode_solution(response) 76 | return self.solution, self.distribution 77 | 78 | def decode_solution(self, response): 79 | n = len(self.distance_matrix) 80 | distribution = {} 81 | min_energy = response.record[0].energy 82 | 83 | for record in response.record: 84 | sample = record[0] 85 | solution_binary = [node for node in sample] 86 | solution = TSP_utilities.binary_state_to_points_order(solution_binary) 87 | distribution[tuple(solution)] = (record.energy, record.num_occurrences) 88 | if record.energy <= min_energy: 89 | self.solution = solution 90 | self.distribution = distribution 91 | 92 | 93 | def calculate_solution(self): 94 | """ 95 | Samples the QVM for the results of the algorithm 96 | and returns a list containing the order of nodes. 97 | """ 98 | most_frequent_string, sampling_results = self.qaoa_inst.get_string(self.betas, self.gammas, samples=10000) 99 | reduced_solution = TSP_utilities.binary_state_to_points_order(most_frequent_string) 100 | full_solution = self.get_solution_for_full_array(reduced_solution) 101 | self.solution = full_solution 102 | 103 | all_solutions = sampling_results.keys() 104 | distribution = {} 105 | for sol in all_solutions: 106 | reduced_sol = TSP_utilities.binary_state_to_points_order(sol) 107 | full_sol = self.get_solution_for_full_array(reduced_sol) 108 | distribution[tuple(full_sol)] = sampling_results[sol] 109 | self.distribution = distribution 110 | -------------------------------------------------------------------------------- /research/2018_03_29_TSP_cost_operators_report.md: -------------------------------------------------------------------------------- 1 | # TSP cost operators test 2 | 3 | This report was originally posted in https://github.com/mstechly/quantum_computing/tree/master/Experiments/2018_03_29_TSP_cost_operators. If a file is referenced in this report, you should look for it in that repository. The version updated according to the suggestion in this report is one with commit hash: `66922554fc2016baf9c2fbc0417de051be57fd2c`. 4 | 5 | ## Introduction 6 | 7 | While working on the script for solving Traveling Salesman Problem with quantum computer, we encountered the following problems: 8 | 9 | - the results were not consistent - for the same parameters we could got different results depending on how the final angles for QAOA. 10 | - We've observed some correlation between the QAOA parameters and quality of results, but we weren't able to say how exactly changes in parameters influences time of computation and quality of the results. 11 | - due to that it was hard to determine if the changes we've introduced to the cost_operators are actually helping to achieve desirable results. 12 | 13 | Taking all of the above into the account we decided to take a more systematic approach. 14 | 15 | The main goals of this experiment were: 16 | - Create a setup for systematic testing different sets of parameters 17 | - Find set of parameters that's working reasonably well 18 | - Check what's the proper coefficient for all_ones_term 19 | 20 | ## Dependencies 21 | 22 | As a main engine for solving TSP we used code from this repository: https://github.com/BOHRTECHNOLOGY/quantum_tsp, imported as a subtree. 23 | 24 | We were using pyquil and grove. 25 | Pyquil version about 1.8.0 (Unfortunately we have not written down the exact version). 26 | Grove version 1.6.0 + commit `c1f51f671e5704cb246025b85c9d24d5d8bee2a8`. 27 | 28 | We had to use a modified version of grove due to a bug (commit `c1f51f671e5704cb246025b85c9d24d5d8bee2a8` fixes it). We decided to commit the exact same version we used. 29 | 30 | ## Experiment description 31 | 32 | The whole experiment was performed on a 3-nodes graphs. The coordinates of the graph were fixed and equal to [[0, 0], [0, 7], [0, 14]]. 33 | The case of 2 nodes was trivial, and for 4 nodes the calculations took too much time. 34 | 35 | ### Phase 1 36 | 37 | In the first phase we've created a script for running forest_tsp_solver with different parameters. 38 | Parameters we were changing were: `steps` and `tol`. `steps` is number of steps in the QAOA algorithms. `xtol` and `ftol` are parameters of the classical minizer used in QAOA, however we used the same value for both of them and called it `tol`. 39 | 40 | Since running Forest code requires internet access and for the given test we didn't have a access to a reliable internet connection, we decided to use randomized choice of parameters. 41 | This way we were sure that even if the internet connection is broken, none set of parameters will be over or under represented in our results. 42 | 43 | We were rating the results based on the following criterias: 44 | - what's the percentage of the correct solutions 45 | - is the best solution valid. 46 | 47 | The best set of parameters was `steps=3` and `tol=1e-4`. The more detailed results can be found below. 48 | 49 | 50 | ### Phase 2 51 | 52 | After finding a reasonably good set of parameters in Phase 1, we used it to find proper coefficients for all_ones_term in forest_tsp_solver. 53 | This term is used to ban [1, 1, 1] groups in the TSP solution. 54 | 55 | During the initial tests we crossed out values of 1 and 2 of the coefficient and decided to test values of -1 and -2. 56 | 57 | Both values gave very similar results - the proper solution was the most probable in about 73% of cases, and the mean number of correct solutions was about 3300/10000. These results are labeled as `phase_2_1`. 58 | 59 | We then repeated the calculations, but this time for all the possible combinations of parameters. For -1 there were about 20 results for each parameter set, for -2 about 70. 60 | Using -2 gave slightly better results (68% vs 60% and 2354/10000 vs 2121/10000), which may indicate that making this value even lower, may bring even better results. We decided to stay with the -2 value for now. 61 | 62 | These results are labeled as `phase_2_2`. You can find some visualizations of these results in `results/plots`. 63 | 64 | ### Phase 3 65 | 66 | In the third phase we decided to check if we can get a good solution of the actual TSP problem. So we added cost associated with the distances between nodes. 67 | 68 | Since the initial tests showed that the number of valid solutions dropped, we decided to check if using higher weight for the penalty operators will help with this. We checked values of 10 and 100. By valid solution we mean a solution where we visit every city once and only one city at any given point in time. 69 | The results are stored in `phase_3_results_weight_10_standard_case.csv` and `phase_3_results_weight_100_standard_case.csv`. 70 | 71 | Indeed, using higher weight gave better results in most cases. We checked the following metrics. The numbers given here are for the best set of parameters (steps=3 and tol=10e-4): 72 | 1. Percentage of best solutions being valid (71% vs 83%) 73 | 2. Mean count of valid solutions in all of the results (1030/10000 vs 1677/10000) 74 | 3. Mean count of the best solution if it was valid (377/10000 vs 1162/10000) 75 | 4. Mean error of valid solutions (3.5 vs 2.65) 76 | 77 | The differences for other sets of parameters were also present, but not as high as in this case. 78 | 79 | Changing the weight has not changed calculation time significantly - mean calculation times for this case were 430 and 417 seconds respectively. Higher weights may lead to faster convergence but in this case it's more probable that the difference is statistically insignificant. 80 | 81 | You can find some visualizations of the results in `results/plots`. 82 | 83 | ## Results 84 | 85 | What we achieved with this experiment: 86 | - we've written code which solves the TSP problem for 3 nodes with specific coordinates: [[0,0], [0,7], [0,14]]. 87 | - we've checked what's the relationship between the algorithm performance and different parameters. 88 | - we've created basic framework for running repeatable tests of quantum algorithms. 89 | 90 | The final set of parameters that we recommend is: 91 | - steps = 3 92 | - tol = 1e-4 93 | - all_ones coefficient = -2 94 | - penalty operators weight = 100 95 | 96 | ## Next steps 97 | 98 | The next steps should be: 99 | - Check how the algorithm works for any set of coordinates with 3 nodes 100 | - Check how the algorithm works for 4 nodes 101 | 102 | 103 | -------------------------------------------------------------------------------- /research/2018_05_26_hadfield_qaoa.md: -------------------------------------------------------------------------------- 1 | # Hadfield QAOA 2 | 3 | This report was originally posted in https://github.com/mstechly/quantum_computing/tree/master/Experiments/2018_05_26_hadfield_qaoa. If a file is referenced in this report, you should look for it in that repository. At the moment the results from this research have not been implemented here - it will be after adding some additional features. 4 | 5 | ## Introduction 6 | 7 | The goal of this experiment was to implement version of QAOA more suited for bounded problems like TSP. It's description is in the `resources` directory. 8 | 9 | ## Dependencies 10 | 11 | As a main engine for solving TSP we used code from this repository: https://github.com/BOHRTECHNOLOGY/quantum_tsp, imported as a subtree. 12 | 13 | We were using pyquil and grove. 14 | Pyquil: commit hash: `26ae363e9f5c85dc3aab298daebc9ec5023a32a1` 15 | Grove: commit hash: `e3fd7b9f3188e820dd19ff487dbf56c8faf43822` 16 | 17 | The exact versions of these repositories are commited to this project. 18 | 19 | ## Tests 20 | 21 | After implementing the algorithm we decided to test how it works for different cases city placement and parameters. 22 | We decided to use the following set of parameters: 23 | - steps: 1, 2 or 3 24 | - tolerance: 1e-2, 1e-3, 1e-4 25 | 26 | After the initial tests we decided to remove `steps=3` and `tol=1e-4` since other parameters seemed sufficient for this problem and the time of calculation for these was significantly larger. 27 | 28 | We also tried many different types of city placement to how the algorithm behaves in various cases. You can find the description in the appendix below and the results in the `results` directory. 29 | Below are the most interesting observations we made: 30 | 31 | ### Observation 1 32 | 33 | In 4 cases 100% of the results were correct. These were cases 0, 1, 5 and 6, which all are symmetrical and all of them have optimal solution starting from city 0. 34 | In case 2, which is same as cases 0 and 1, but the optimal solution needs to start from city 1 or 2, algorithm achieved 20% accuracy for `steps=1` and 100% accuracy for `steps=2`. 35 | 36 | ### Observation 2 37 | 38 | In all cases results for `steps=2` are higher than the results for `steps=1` and usually are enough to achieve correct results. Calculation time for the former is usually 2 times longer than for the latter - about 100 seconds for the `tol=1e-2` and 150 for `tol=1e-3`. 39 | 40 | ### Observation 3 41 | 42 | The worst results were achieved for highly assymetric cases: 3, 4 and 7. 43 | What's interesting, for case 3 `steps=1` gave correct results in 100% cases and `steps=2` in only 45/60 (depending on `tol`). That's probably because the initial state was actually a correct solution, since for case 4, which has the same coordinates, but in different order, for `steps=1` there were no correct solutions (same for case 7). 44 | 45 | ### Observation 4 46 | 47 | For cities placed on the vertices of an equilateral triangle (case 5) all the solutions are correct, so all should be equally probable. 48 | However, it wasn't the case. It's interesting to see, that with more precise parameters of the algorithm, we get better distribution. Below is the list containing parameters and the mean probability of the best solution. 49 | 50 | - `steps=1`, `tol=1e-2`: 70(25)% 51 | - `steps=1`, `tol=1e-3`: 67(24)% 52 | - `steps=2`, `tol=1e-2`: 61(19)% 53 | - `steps=2`, `tol=1e-3`: 57(19)% 54 | 55 | The ideal value would be 16.6%, and even though the mean values presented here are far from that, teh standard deviation is pretty high, which shows that this algorithm sometimes may give sometimes yield well or badly distributed results. 56 | 57 | ### Observation 5 58 | 59 | Since we have not introduced any boundary conditions, for every case there should be at least two correct solutions, e.g.: 0 -> 1 -> 2 and 2 -> 1 -> 0. However, the probability of the correct solution returned by the algorithm was usually very close to 100%. It means, that it was finding only one of the two possible solutions. Usually it was the one same as inital state or closest to it. 60 | 61 | ### Observation 6 62 | 63 | When we tried to calculate the 4-city case, the Forest API returned a 400 error. That's probably due to the fact, that the payload - Quil program containing the quantum code - was too big. This, however, need to be confirmed by Rigetti engineers. 64 | 65 | ## Conclusions 66 | 67 | Based on the observations made in the previous section we drew the following about this implementation of the algorithm: 68 | 69 | - it's sensitive to the initial state 70 | - it gives correct results in most cases - for the random city placement and `steps=2` algorithm yields correct results in 90% of cases. 71 | - in case of multiple correct solution it's not very good at distinguishing between them 72 | 73 | We also decided to use `steps=2` and `tol=1e-2` for further research. 74 | 75 | ### Further steps 76 | 77 | The next step would be checking how this algorithm works when initialized with different initial states, e.g. mixture of all possible states or some subset of them. 78 | 79 | The other thing is explaining error we got for bigger number of cities. One way to go around this would be test if it's possible to add boundary condition (e.g. we start from city 0) in order to make number of qubits scale like (N - 1)^2 instead of N^2. 80 | 81 | 82 | ## Appendix - City placement 83 | 84 | Here is a list of the city placement we were using. Below we describe them with the following information: 85 | - Symmetrical/Asymmetrical 86 | - Order of the optimal route: e.g. [0 -> 1 -> 2]. In all cases at least two orderings were correct, since we don't specify the starting point. 87 | - Coordinates e.g.: [[0, 0], [0, 10], [0, 20]] 88 | - 1D / 2D - were all the points on one line or not. 89 | 90 | 91 | The coordinates were randomly scaled and shifted, so those listed here are just an example. 92 | 93 | 0. 1D, Symmetrical, [0 -> 1 -> 2] 94 | [[0, 0], [0, 10], [0, 20]] 95 | 96 | 1. 1D, Symmetrical [0 -> 2 -> 1] 97 | [[0, 0], [0, 20], [0, 10]] 98 | 99 | 2. 1D, Symmetrical [1 -> 0 -> 2] 100 | [[0, 10], [0, 20], [0, 0]] 101 | 102 | 3. 1D, Asymmetrical [0 -> 1 -> 2] 103 | [[0, 0], [0, 1], [0, 10]] 104 | 105 | 4. 1D, Asymmetrical [2 -> 0 -> 1] 106 | [[0, 1], [0, 0], [0, 10]] 107 | 108 | 5. 2D, Symmetrical (equilateral angle), all orderings are the same 109 | [[0, 0], [1, 0], [0.5, np.sqrt(3)/2]] 110 | 111 | 6. 2D, Symmetrical triangle [0 -> 2 -> 1] 112 | [[-5, 0], [5, 0], [0, 1]] 113 | 114 | 7. 2D, Assymetrical triangle [0 -> 2 -> 1] 115 | [[0, 0], [15, 0], [0, 1]] 116 | 117 | 8. 2D, random triangle 118 | In this case it was just a set of random points, so each case was different. -------------------------------------------------------------------------------- /src/forest_tsp_solver.py: -------------------------------------------------------------------------------- 1 | import pyquil.api as api 2 | import numpy as np 3 | from grove.pyqaoa.qaoa import QAOA 4 | from grove.alpha.arbitrary_state.arbitrary_state import create_arbitrary_state 5 | from pyquil.paulis import PauliTerm, PauliSum 6 | import pyquil.quil as pq 7 | from pyquil.gates import X 8 | import itertools 9 | 10 | import scipy.optimize 11 | import TSP_utilities 12 | import pdb 13 | 14 | class ForestTSPSolver(object): 15 | """ 16 | Class for solving Travelling Salesman Problem (with starting point) using Forest - quantum computing library. 17 | It uses QAOA method with operators as described in the following paper: 18 | https://arxiv.org/pdf/1709.03489.pdf by Stuart Hadfield et al. 19 | 20 | """ 21 | def __init__(self, distance_matrix, steps=2, ftol=1.0e-3, xtol=1.0e-3, initial_state="all", starting_node=0): 22 | 23 | self.distance_matrix = distance_matrix 24 | self.starting_node = starting_node 25 | # Since we fixed the starting city, the effective number of nodes is smaller by 1 26 | self.reduced_number_of_nodes = len(self.distance_matrix) - 1 27 | self.qvm = api.QVMConnection() 28 | self.steps = steps 29 | self.ftol = ftol 30 | self.xtol = xtol 31 | self.betas = None 32 | self.gammas = None 33 | self.qaoa_inst = None 34 | self.number_of_qubits = self.get_number_of_qubits() 35 | self.solution = None 36 | self.distribution = None 37 | 38 | cost_operators = self.create_phase_separator() 39 | driver_operators = self.create_mixer() 40 | initial_state_program = self.create_initial_state_program(initial_state) 41 | 42 | minimizer_kwargs = {'method': 'Nelder-Mead', 43 | 'options': {'ftol': self.ftol, 'xtol': self.xtol, 44 | 'disp': False}} 45 | 46 | vqe_option = {'disp': print_fun, 'return_all': True, 47 | 'samples': None} 48 | 49 | qubits=list(range(self.number_of_qubits)); 50 | 51 | self.qaoa_inst = QAOA(self.qvm, 52 | qubits, 53 | steps=self.steps, 54 | init_betas=None, 55 | init_gammas=None, 56 | cost_ham=cost_operators, 57 | ref_ham=driver_operators, 58 | driver_ref=initial_state_program, 59 | minimizer=scipy.optimize.minimize, 60 | minimizer_kwargs=minimizer_kwargs, 61 | rand_seed=None, 62 | vqe_options=vqe_option, 63 | store_basis=True) 64 | 65 | def solve_tsp(self): 66 | """ 67 | Calculates the optimal angles (betas and gammas) for the QAOA algorithm 68 | and returns a list containing the order of nodes. 69 | """ 70 | self.find_angles() 71 | self.calculate_solution() 72 | return self.solution, self.distribution 73 | 74 | def find_angles(self): 75 | """ 76 | Runs the QAOA algorithm for finding the optimal angles. 77 | """ 78 | self.betas, self.gammas = self.qaoa_inst.get_angles() 79 | return self.betas, self.gammas 80 | 81 | def calculate_solution(self): 82 | """ 83 | Samples the QVM for the results of the algorithm 84 | and returns a list containing the order of nodes. 85 | """ 86 | most_frequent_string, sampling_results = self.qaoa_inst.get_string(self.betas, self.gammas, samples=10000) 87 | reduced_solution = TSP_utilities.binary_state_to_points_order(most_frequent_string) 88 | full_solution = self.get_solution_for_full_array(reduced_solution) 89 | self.solution = full_solution 90 | 91 | all_solutions = sampling_results.keys() 92 | distribution = {} 93 | for sol in all_solutions: 94 | reduced_sol = TSP_utilities.binary_state_to_points_order(sol) 95 | full_sol = self.get_solution_for_full_array(reduced_sol) 96 | distribution[tuple(full_sol)] = sampling_results[sol] 97 | self.distribution = distribution 98 | 99 | def get_solution_for_full_array(self, reduced_solution): 100 | """ 101 | Transforms the solution from its reduced version to the full initial version. 102 | """ 103 | full_solution = reduced_solution 104 | for i in range(len(full_solution)): 105 | if full_solution[i] >= self.starting_node: 106 | full_solution[i] += 1 107 | full_solution.insert(0, self.starting_node) 108 | return full_solution 109 | 110 | def create_phase_separator(self): 111 | """ 112 | Creates phase-separation operators, which depend on the objective function. 113 | """ 114 | cost_operators = [] 115 | reduced_distance_matrix = np.delete(self.distance_matrix, self.starting_node, axis=0) 116 | reduced_distance_matrix = np.delete(reduced_distance_matrix, self.starting_node, axis=1) 117 | for t in range(self.reduced_number_of_nodes - 1): 118 | for city_1 in range(self.reduced_number_of_nodes): 119 | for city_2 in range(self.reduced_number_of_nodes): 120 | if city_1 != city_2: 121 | distance = reduced_distance_matrix[city_1, city_2] 122 | qubit_1 = t * (self.reduced_number_of_nodes) + city_1 123 | qubit_2 = (t + 1) * (self.reduced_number_of_nodes) + city_2 124 | cost_operators.append(PauliTerm("Z", qubit_1, distance) * PauliTerm("Z", qubit_2)) 125 | 126 | costs_to_starting_node = np.delete(self.distance_matrix[:, self.starting_node], self.starting_node) 127 | 128 | for city in range(self.reduced_number_of_nodes): 129 | distance_from_0 = -costs_to_starting_node[city] 130 | qubit = city 131 | cost_operators.append(PauliTerm("Z", qubit, distance_from_0)) 132 | 133 | for city in range(self.reduced_number_of_nodes): 134 | distance_from_0 = -costs_to_starting_node[city] 135 | qubit = self.number_of_qubits - (self.reduced_number_of_nodes) + city 136 | cost_operators.append(PauliTerm("Z", qubit, distance_from_0)) 137 | 138 | phase_separator = [PauliSum(cost_operators)] 139 | return phase_separator 140 | 141 | def create_mixer(self): 142 | """ 143 | Creates mixing operators, which depend on the structure of the problem. 144 | Indexing comes directly from 4.1.2 from the https://arxiv.org/pdf/1709.03489.pdf article, 145 | equations 54 - 58. 146 | """ 147 | mixer_operators = [] 148 | 149 | for t in range(self.reduced_number_of_nodes - 1): 150 | for city_1 in range(self.reduced_number_of_nodes): 151 | for city_2 in range(self.reduced_number_of_nodes): 152 | i = t 153 | u = city_1 154 | v = city_2 155 | first_part = 1 156 | first_part *= self.s_plus(u, i) 157 | first_part *= self.s_plus(v, i+1) 158 | first_part *= self.s_minus(u, i+1) 159 | first_part *= self.s_minus(v, i) 160 | 161 | second_part = 1 162 | second_part *= self.s_minus(u, i) 163 | second_part *= self.s_minus(v, i+1) 164 | second_part *= self.s_plus(u, i+1) 165 | second_part *= self.s_plus(v, i) 166 | mixer_operators.append(first_part + second_part) 167 | return mixer_operators 168 | 169 | def create_initial_state_program(self, initial_state): 170 | """ 171 | Creates a pyquil program representing the initial state for the QAOA. 172 | As an argument it takes either a list with order of the cities, or 173 | a string "all". In the second case the initial state is superposition 174 | of all possible states for this problem. 175 | """ 176 | initial_state_program = pq.Program() 177 | if type(initial_state) is list: 178 | for i in range(self.reduced_number_of_nodes): 179 | initial_state_program.inst(X(i * (self.reduced_number_of_nodes) + initial_state[i])) 180 | 181 | elif initial_state == "all": 182 | vector_of_states = np.zeros(2**self.number_of_qubits) 183 | list_of_possible_states = [] 184 | initial_order = range(0, self.reduced_number_of_nodes) 185 | all_permutations = [list(x) for x in itertools.permutations(initial_order)] 186 | for permutation in all_permutations: 187 | coding_of_permutation = 0 188 | for i in range(len(permutation)): 189 | coding_of_permutation += 2**(i * (self.reduced_number_of_nodes) + permutation[i]) 190 | vector_of_states[coding_of_permutation] = 1 191 | initial_state_program = create_arbitrary_state(vector_of_states) 192 | 193 | return initial_state_program 194 | 195 | def get_number_of_qubits(self): 196 | return (self.reduced_number_of_nodes)**2 197 | 198 | def s_plus(self, city, time): 199 | qubit = time * (self.reduced_number_of_nodes) + city 200 | return PauliTerm("X", qubit) + PauliTerm("Y", qubit, 1j) 201 | 202 | def s_minus(self, city, time): 203 | qubit = time * (self.reduced_number_of_nodes) + city 204 | return PauliTerm("X", qubit) - PauliTerm("Y", qubit, 1j) 205 | 206 | 207 | def print_fun(x): 208 | print(x) 209 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2018 Bohr Technology 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------