def __init__(self, **kwargs): self.qubo = kwargs.get("qubo", None) if self.qubo is not None: self.operator, self.offset = self.qubo.to_ising() self.no_cars = kwargs.get("no_cars", 0) self.no_routes = kwargs.get("no_routes", 0) self.symmetrise = kwargs.get("symmetrise", False) self.customise = kwargs.get("customise", False) opt_str = kwargs.get('opt_str', "LN_BOBYQA") print("Optimizer: {}".format(opt_str)) self.optimizer = NLOPT_Optimizer(opt_str) self.optimizer.set_options(maxeval=200) self.original_qubo = deepcopy(self.qubo) #Benchmarking, using classical result for ground state energy, and then random energy measurement. self.classical_result = kwargs.get("classical_result", None) self.solve_classically() self.random_instance = QuantumInstance( backend=Aer.get_backend("aer_simulator_matrix_product_state"), shots=1000) #1000 randomly measured self.get_random_energy() #Simulation methods simulator = kwargs.get("simulator", None) noise_model = kwargs.get("noise_model", None) if simulator == None: simulator = "aer_simulator_density_matrix" print("Using " + simulator) self.quantum_instance = QuantumInstance( backend=Aer.get_backend(simulator), shots=8192, noise_model=noise_model, basis_gates=["cx", "x", "sx", "rz", "id"]) #Symmetrise if self.symmetrise: self.symmetrise_qubo() #Customise QAOA if self.customise: self.construct_initial_state(symmetrise=self.symmetrise) self.construct_mixer() else: self.initial_state = None self.mixer = None #Results placeholder self.prob_s = [] self.eval_s = [] self.approx_s = []
def __init__(self, qubo, no_cars, no_routes): var_list = qubo.variables opt_str = "LN_SBPLX" print("Optimizer: {}".format(opt_str)) self.optimizer = NLOPT_Optimizer(opt_str) self.original_qubo = qubo self.qubo = qubo op, offset = qubo.to_ising() self.operator = op self.offset = offset self.quantum_instance = QuantumInstance(backend = Aer.get_backend("aer_simulator_matrix_product_state"), shots = 1024) self.replacements = {var.name:None for var in var_list} self.no_cars = no_cars self.no_routes = no_routes self.car_blocks = np.empty(shape = (no_cars,), dtype=object) for car_no in range(no_cars): self.car_blocks[car_no] = ["X_{}_{}".format(car_no, route_no) for route_no in range(no_routes)] self.qaoa_result = None self.benchmark_energy = None self.var_values = {} self.construct_initial_state() self.construct_mixer() self.get_random_energy() self.get_benchmark_energy()
def __init__(self, qubo, no_cars, no_routes, **kwargs): opt_str = kwargs.get('opt_str', "LN_SBPLX") self.symmetrise = kwargs.get('symmetrise', False) var_list = qubo.variables self.optimizer = NLOPT_Optimizer(opt_str) self.optimizer.set_options(max_eval=1000) self.original_qubo = qubo self.qubo = qubo self.solve_classically() op, offset = qubo.to_ising() self.operator = op self.offset = offset if self.symmetrise: self.symmetrise_qubo() self.quantum_instance = QuantumInstance( backend=Aer.get_backend("aer_simulator_matrix_product_state"), shots=4096) self.replacements = {var.name: None for var in var_list} self.no_cars = no_cars self.no_routes = no_routes self.car_blocks = np.empty(shape=(no_cars, ), dtype=object) for car_no in range(no_cars): self.car_blocks[car_no] = [ "X_{}_{}".format(car_no, route_no) for route_no in range(no_routes) ] self.qaoa_result = None self.benchmark_energy = None self.var_values = {} self.construct_initial_state() self.construct_mixer() self.get_random_energy() self.get_benchmark_energy() self.prob_s = [] self.approx_s = [] self.optimal_point = None
class QAOA_Base: def __init__(self, **kwargs): self.qubo = kwargs.get("qubo", None) if self.qubo is not None: self.operator, self.offset = self.qubo.to_ising() self.no_cars = kwargs.get("no_cars", 0) self.no_routes = kwargs.get("no_routes", 0) self.symmetrise = kwargs.get("symmetrise", False) self.customise = kwargs.get("customise", False) opt_str = kwargs.get('opt_str', "LN_BOBYQA") print("Optimizer: {}".format(opt_str)) self.optimizer = NLOPT_Optimizer(opt_str) self.optimizer.set_options(maxeval=200) self.original_qubo = deepcopy(self.qubo) #Benchmarking, using classical result for ground state energy, and then random energy measurement. self.classical_result = kwargs.get("classical_result", None) self.solve_classically() self.random_instance = QuantumInstance( backend=Aer.get_backend("aer_simulator_matrix_product_state"), shots=1000) #1000 randomly measured self.get_random_energy() #Simulation methods simulator = kwargs.get("simulator", None) noise_model = kwargs.get("noise_model", None) if simulator == None: simulator = "aer_simulator_density_matrix" print("Using " + simulator) self.quantum_instance = QuantumInstance( backend=Aer.get_backend(simulator), shots=8192, noise_model=noise_model, basis_gates=["cx", "x", "sx", "rz", "id"]) #Symmetrise if self.symmetrise: self.symmetrise_qubo() #Customise QAOA if self.customise: self.construct_initial_state(symmetrise=self.symmetrise) self.construct_mixer() else: self.initial_state = None self.mixer = None #Results placeholder self.prob_s = [] self.eval_s = [] self.approx_s = [] def symmetrise_qubo(self): new_operator = [] operator, _ = self.qubo.to_ising() for op_1 in operator: coeff, op_1 = op_1.to_pauli_op().coeff, op_1.to_pauli_op( ).primitive op_1_str = op_1.to_label() Z_counts = op_1_str.count('Z') if Z_counts == 1: op_1_str = "Z" + op_1_str #Add a Z in the last qubit to single Z terms else: op_1_str = "I" + op_1_str #Add an I in the last qubit to ZZ terms (no change in operator) pauli = PauliOp(primitive=Pauli(op_1_str), coeff=coeff) new_operator.append(pauli) symmetrised_qubo = QuadraticProgram() symmetrised_operator = sum(new_operator) symmetrised_qubo.from_ising(symmetrised_operator, self.offset, linear=True) self.qubo = symmetrised_qubo self.rename_qubo_variables() self.operator, self.offset = self.qubo.to_ising() def rename_qubo_variables(self): original_qubo = self.original_qubo qubo = self.qubo variable_names = [variable.name for variable in qubo.variables] original_variable_names = [(variable.name, 1) for variable in original_qubo.variables] new_variable_names = original_variable_names + [ ("X_anc", 1) ] if self.symmetrise else original_variable_names variables_dict = dict(zip(variable_names, new_variable_names)) for new_variable_name in variables_dict.values(): qubo.binary_var(name=new_variable_name[0]) qubo = qubo.substitute_variables(variables=variables_dict) if qubo.status == QuadraticProgram.Status.INFEASIBLE: raise QiskitOptimizationError( 'Infeasible due to variable substitution') self.qubo = qubo def get_random_energy(self): #Get random benchmark energy for 0 layer Custom-QAOA (achieved by using layer 1 Cust-QAOA with [0,0] angles i.e. sampling from feasible states with equal prob) self.construct_initial_state(symmetrise=False) self.construct_mixer() random_energy, _ = CustomQAOA( operator=self.operator, quantum_instance=self.random_instance, optimizer=self.optimizer, reps=1, initial_state=self.initial_state, mixer=self.mixer, solve=False, ) #Remove custom initial state if using BASE QAOA self.initial_state = None self.mixer = None temp = random_energy self.random_energy = temp + self.offset if np.round(self.random_energy - self.opt_value, 6) < 1e-7: print( "0 layer QAOA converged to exact solution. Shifting value up by |exact_ground_energy| instead to avoid dividing by 0 in approx quality." ) self.random_energy += np.abs(self.random_energy) self.benchmark_energy = self.random_energy print("random energy: {}\n".format(self.random_energy)) def construct_initial_state(self, **kwargs): symmetrise = kwargs.get("symmetrise", False) self.initial_state = construct_initial_state(self.no_routes, self.no_cars) if symmetrise: ancilla_reg = QuantumRegister(1, 'ancilla') self.initial_state.add_register(ancilla_reg) self.initial_state.h(ancilla_reg[0]) def construct_mixer(self): self.mixer = n_qbit_mixer(self.initial_state) def solve_classically(self): if self.classical_result: print( "There is an existing classical result. Using this as code proceeds." ) print(self.classical_result) self.opt_value = self.classical_result.fval else: print("No classical result already available.") print("Now solving classically") _, opt_value, classical_result, _ = find_all_ground_states( self.original_qubo) self.classical_result = classical_result self.opt_value = opt_value #Using Gary's Qiskit code def solve_qaoa(self, p, **kwargs): print("USING CUSTOM CODE") point = kwargs.get( "point", None ) #Make sure point here is already in FOURIER space of length 2(p-1) fourier_parametrise = kwargs.get("fourier_parametrise", False) tqa = kwargs.get('tqa', False) points = kwargs.get("points", None) construct_circ = kwargs.get("construct_circ", False) if tqa: deltas = np.arange(0.25, 0.91, 0.05) point = np.append([(i + 1) / p for i in range(p)], [1 - (i + 1) / p for i in range(p)]) points = [delta * point for delta in deltas] if fourier_parametrise: points = [ convert_to_fourier_point(point, len(point)) for point in points ] qaoa_results, circ = CustomQAOA( self.operator, self.quantum_instance, self.optimizer, reps=p, initial_state=self.initial_state, mixer=self.mixer, fourier_parametrise=fourier_parametrise, list_points=points, qubo=self.qubo, construct_circ=construct_circ) elif points is not None: if fourier_parametrise: points = [ convert_to_fourier_point(point, len(point)) for point in points ] if point is not None: points.append(convert_to_fourier_point(point, len(point))) else: points.append(point) qaoa_results, circ = CustomQAOA( self.operator, self.quantum_instance, self.optimizer, reps=p, initial_state=self.initial_state, mixer=self.mixer, fourier_parametrise=fourier_parametrise, list_points=points, qubo=self.qubo, construct_circ=construct_circ) elif point is not None: if fourier_parametrise: initial_point = convert_to_fourier_point(point, len(point)) qaoa_results, circ = CustomQAOA( self.operator, self.quantum_instance, self.optimizer, reps=p, initial_state=self.initial_state, initial_point=point, mixer=self.mixer, fourier_parametrise=fourier_parametrise, qubo=self.qubo, construct_circ=construct_circ) else: points = [[0] * (2 * p)] + [[ 1.98 * np.pi * (np.random.rand() - 0.5) for _ in range(2 * p) ] for _ in range(10)] qaoa_results, circ = CustomQAOA( self.operator, self.quantum_instance, self.optimizer, reps=p, initial_state=self.initial_state, list_points=points, mixer=self.mixer, fourier_parametrise=fourier_parametrise, qubo=self.qubo, construct_circ=construct_circ) if circ: print(circ.draw(fold=200)) optimal_point = qaoa_results.optimal_point eigenvalue = sum([x[1] * x[2] for x in qaoa_results.eigenstate]) qaoa_results.eigenvalue = eigenvalue self.optimal_point = optimal_point self.qaoa_result = qaoa_results #Sort states by decreasing probability sorted_eigenstate_by_prob = sorted(qaoa_results.eigenstate, key=lambda x: x[2], reverse=True) #print sorted state in a table self.print_state(sorted_eigenstate_by_prob) #Other print stuff print("Eigenvalue: {}".format(eigenvalue)) print("Optimal point: {}".format(optimal_point)) print("Optimizer Evals: {}".format(qaoa_results.optimizer_evals)) scale = self.random_energy - self.opt_value approx_quality_2 = np.round( (self.random_energy - sorted_eigenstate_by_prob[0][1]) / scale, 3) energy_prob = {} for x in qaoa_results.eigenstate: energy_prob[np.round( x[1], 6)] = energy_prob.get(np.round(x[1], 6), 0) + x[2] prob_s = np.round(energy_prob.get(np.round(self.opt_value, 6), 0), 6) self.prob_s.append(prob_s) self.eval_s.append(eigenvalue) self.approx_s.append(approx_quality_2) print("\nQAOA most probable solution: {}".format( sorted_eigenstate_by_prob[0])) print("Approx_quality: {}".format(approx_quality_2)) def solve_qiskit_qaoa(self, p, **kwargs): print("USING QISKIT CODE") point = kwargs.get("point", None) tqa = kwargs.get('tqa', False) points = kwargs.get("points", None) construct_circ = kwargs.get("construct_circ", False) fourier_parametrise = kwargs.get("fourier_parametrise", False) if tqa: deltas = np.arange(0.25, 0.91, 0.05) point = np.append([(i + 1) / p for i in range(p)], [1 - (i + 1) / p for i in range(p)]) points = [delta * point for delta in deltas] if fourier_parametrise: points = [ convert_to_fourier_point(point, len(point)) for point in points ] qaoa_results, circ = QiskitQAOA( self.operator, self.quantum_instance, self.optimizer, reps=p, initial_state=self.initial_state, mixer=self.mixer, list_points=points, construct_circ=construct_circ, fourier_parametrise=fourier_parametrise) elif points is not None: if point is not None: points.append(point) if fourier_parametrise: points = [ convert_to_fourier_point(point, len(point)) for point in points ] qaoa_results, circ = QiskitQAOA( self.operator, self.quantum_instance, self.optimizer, reps=p, initial_state=self.initial_state, mixer=self.mixer, list_points=points, construct_circ=construct_circ, fourier_parametrise=fourier_parametrise) elif point is not None: if fourier_parametrise: point = convert_to_fourier_point(point, 2 * p) qaoa_results, circ = QiskitQAOA( self.operator, self.quantum_instance, self.optimizer, reps=p, initial_state=self.initial_state, initial_point=point, mixer=self.mixer, construct_circ=construct_circ, fourier_parametrise=fourier_parametrise) else: points = [[0] * (2 * p)] + [[ 1.98 * np.pi * (np.random.rand() - 0.5) for _ in range(2 * p) ] for _ in range(10)] qaoa_results, circ = QiskitQAOA( self.operator, self.quantum_instance, self.optimizer, reps=p, initial_state=self.initial_state, list_points=points, mixer=self.mixer, construct_circ=construct_circ, fourier_parametrise=fourier_parametrise) if circ: print(circ.draw(fold=200)) optimal_point = qaoa_results.optimal_point self.optimal_point = optimal_point self.qaoa_result = qaoa_results eigenstate = qaoa_results.eigenstate if self.quantum_instance.is_statevector: from qiskit.quantum_info import Statevector eigenstate = Statevector(eigenstate) eigenstate = eigenstate.probabilities_dict() else: eigenstate = dict([(u, v**2) for u, v in eigenstate.items() ]) #Change to probabilities num_qubits = len(list(eigenstate.items())[0][0]) solutions = [] eigenvalue = 0 for bitstr, sampling_probability in eigenstate.items(): bitstr = bitstr[::-1] value = self.qubo.objective.evaluate([int(bit) for bit in bitstr]) eigenvalue += value * sampling_probability solutions += [(bitstr, value, sampling_probability)] qaoa_results.eigenstate = solutions qaoa_results.eigenvalue = eigenvalue #Sort states by decreasing probability sorted_eigenstate_by_prob = sorted(qaoa_results.eigenstate, key=lambda x: x[2], reverse=True) #print sorted state in a table self.print_state(sorted_eigenstate_by_prob) #Other print stuff print("Eigenvalue: {}".format(eigenvalue)) print("Optimal point: {}".format(optimal_point)) print("Optimizer Evals: {}".format(qaoa_results.optimizer_evals)) scale = self.random_energy - self.opt_value approx_quality_2 = np.round( (self.random_energy - sorted_eigenstate_by_prob[0][1]) / scale, 3) energy_prob = {} for x in qaoa_results.eigenstate: energy_prob[np.round( x[1], 6)] = energy_prob.get(np.round(x[1], 6), 0) + x[2] prob_s = np.round(energy_prob.get(np.round(self.opt_value, 6), 0), 6) self.prob_s.append(prob_s) self.eval_s.append(eigenvalue) self.approx_s.append(approx_quality_2) print("\nQAOA most probable solution: {}".format( sorted_eigenstate_by_prob[0])) print("Approx_quality: {}".format(approx_quality_2)) def print_state(self, eigenstate): header = '|' for var in self.qubo.variables: var_name = var.name header += '{:<5}|'.format(var_name.replace("_", "")) header += '{:<6}|'.format('Cost') header += '{:<5}|'.format("Prob") print("-" * len(header)) print(header) print("-" * len(header)) count = 0 for item in eigenstate: string = '|' for binary_var in item[0]: string += '{:<5} '.format(binary_var) #Binary string string += '{:<6} '.format(np.round(item[1], 2)) #Cost string += '{:<5}|'.format(np.round(item[2], 3)) #Prob print(string) #Print only first 20 states of lowest energy count += 1 if count == 20: break print("-" * len(header))
def main(args=None): """[summary] Args: raw_args ([type], optional): [description]. Defaults to None. """ start = time() if args == None: args = parse() qubo_no = args["no_samples"] print_to_file("-" * 50) print_to_file("QUBO_{}".format(qubo_no)) #Load generated qubo_no with open( 'qubos_{}_car_{}_routes/qubo_{}.pkl'.format( args["no_cars"], args["no_routes"], qubo_no), 'rb') as f: qubo, max_coeff, operator, offset, routes = pkl.load(f) qubo = QuadraticProgram() qubo.from_ising(operator) x_s, opt_value, classical_result = find_all_ground_states(qubo) print_to_file(classical_result) #Set optimizer method method = args["method"] optimizer = NLOPT_Optimizer(method=method, result_message=False) # optimizer = COBYLA() backend = Aer.get_backend("statevector_simulator") quantum_instance = QuantumInstance(backend=backend) approx_ratios = [] prob_s_s = [] p_max = args["p_max"] no_routes, no_cars = (args["no_routes"], args["no_cars"]) custom = True if custom: initial_state = construct_initial_state(no_routes=no_routes, no_cars=no_cars) mixer = n_qbit_mixer(initial_state) else: initial_state, mixer = (None, None) fourier_parametrise = args["fourier"] print_to_file("-" * 50) print_to_file( "Now solving with TQA_QAOA... Fourier Parametrisation: {}".format( fourier_parametrise)) # maxeval = 125 for p in range(1, p_max + 1): construct_circ = False deltas = np.arange(0.45, 0.91, 0.05) point = np.append([(i + 1) / p for i in range(p)], [1 - (i + 1) / p for i in range(p)]) points = [delta * point for delta in deltas] print_to_file("-" * 50) print_to_file(" " + "p={}".format(p)) if fourier_parametrise: points = [ convert_to_fourier_point(point, len(point)) for point in points ] # maxeval *= 2 #Double max_allowed evals for optimizer # optimizer.set_options(maxeval = maxeval) optimizer.set_options(maxeval=1000 * p) qaoa_results, optimal_circ = CustomQAOA( operator, quantum_instance, optimizer, reps=p, initial_state=initial_state, mixer=mixer, construct_circ=construct_circ, fourier_parametrise=fourier_parametrise, list_points=points, qubo=qubo) exp_val = qaoa_results.eigenvalue * max_coeff state_solutions = { item[0][::-1]: item[1:] for item in qaoa_results.eigenstate } for item in sorted(state_solutions.items(), key=lambda x: x[1][1], reverse=True)[0:5]: print_to_file(item) prob_s = 0 for string in x_s: prob_s += state_solutions[string][ 1] if string in state_solutions else 0 prob_s /= len(x_s) #normalise optimal_point = qaoa_results.optimal_point if fourier_parametrise: optimal_point = convert_from_fourier_point(optimal_point, len(optimal_point)) approx_ratio = 1 - np.abs((opt_value - exp_val) / opt_value) nfev = qaoa_results.cost_function_evals print_to_file( " " + "Optimal_point: {}, Nfev: {}".format(optimal_point, nfev)) print_to_file(" " + "Exp_val: {}, Prob_s: {}, approx_ratio: {}".format( exp_val, prob_s, approx_ratio)) approx_ratios.append(approx_ratio) prob_s_s.append(prob_s) print_to_file("-" * 50) print_to_file("QAOA terminated") print_to_file("-" * 50) print_to_file("Approximation ratios per layer: {}".format(approx_ratios)) print_to_file("Prob_success per layer: {}".format(prob_s_s)) save_results = np.append(approx_ratios, prob_s_s) if fourier_parametrise: with open( 'results_{}cars{}routes/TQA_F_{}.csv'.format( args["no_cars"], args["no_routes"], args["no_samples"]), 'w') as f: np.savetxt(f, save_results, delimiter=',') print_to_file( "Results saved in results_{}cars{}routes/TQA_F_{}.csv".format( args["no_cars"], args["no_routes"], args["no_samples"])) else: with open( 'results_{}cars{}routes/TQA_NF_{}.csv'.format( args["no_cars"], args["no_routes"], args["no_samples"]), 'w') as f: np.savetxt(f, save_results, delimiter=',') print_to_file( "Results saved in results_{}cars{}routes/TQA_NF_{}.csv".format( args["no_cars"], args["no_routes"], args["no_samples"])) finish = time() print_to_file("Time Taken: {}".format(finish - start))
class RQAOA: def __init__(self, qubo, no_cars, no_routes, **kwargs): opt_str = kwargs.get('opt_str', "LN_SBPLX") self.symmetrise = kwargs.get('symmetrise', False) var_list = qubo.variables self.optimizer = NLOPT_Optimizer(opt_str) self.optimizer.set_options(max_eval=1000) self.original_qubo = qubo self.qubo = qubo self.solve_classically() op, offset = qubo.to_ising() self.operator = op self.offset = offset if self.symmetrise: self.symmetrise_qubo() self.quantum_instance = QuantumInstance( backend=Aer.get_backend("aer_simulator_matrix_product_state"), shots=4096) self.replacements = {var.name: None for var in var_list} self.no_cars = no_cars self.no_routes = no_routes self.car_blocks = np.empty(shape=(no_cars, ), dtype=object) for car_no in range(no_cars): self.car_blocks[car_no] = [ "X_{}_{}".format(car_no, route_no) for route_no in range(no_routes) ] self.qaoa_result = None self.benchmark_energy = None self.var_values = {} self.construct_initial_state() self.construct_mixer() self.get_random_energy() self.get_benchmark_energy() self.prob_s = [] self.approx_s = [] self.optimal_point = None def construct_initial_state(self): qc = QuantumCircuit() for car_no in range(self.no_cars): car_block = self.car_blocks[car_no] R = len(car_block) if R != 0 and R != 1: num_qubits = qc.num_qubits q_regs = QuantumRegister(R, 'car_{}'.format((car_no))) qc.add_register(q_regs) w_one = QuantumCircuit(q_regs) for r in range(0, R - 1): if r == 0: w_one.ry(2 * np.arccos(1 / np.sqrt(R - r)), r) elif r != R - 1: w_one.cry(2 * np.arccos(1 / np.sqrt(R - r)), r - 1, r) for r in range(1, R): w_one.cx(R - r - 1, R - r) w_one.x(0) qc.append(w_one, range(num_qubits, num_qubits + R)) elif R == 1: num_qubits = qc.num_qubits q_regs = QuantumRegister(R, 'car_{}'.format((car_no))) qc.add_register(q_regs) w_one = QuantumCircuit(q_regs) w_one.h(0) qc.append(w_one, range(num_qubits, num_qubits + R)) else: continue qc = qc.decompose() self.initial_state = qc if self.symmetrise: ancilla_reg = QuantumRegister(1, 'ancilla') self.initial_state.add_register(ancilla_reg) self.initial_state.h(ancilla_reg[0]) def symmetrise_qubo(self): new_operator = [] operator, _ = self.qubo.to_ising() for op_1 in operator: coeff, op_1 = op_1.to_pauli_op().coeff, op_1.to_pauli_op( ).primitive op_1_str = op_1.to_label() Z_counts = op_1_str.count('Z') if Z_counts == 1: op_1_str = "Z" + op_1_str #Add a Z in the last qubit to single Z terms else: op_1_str = "I" + op_1_str #Add an I in the last qubit to ZZ terms (no change in operator) pauli = PauliOp(primitive=Pauli(op_1_str), coeff=coeff) new_operator.append(pauli) symmetrised_qubo = QuadraticProgram() symmetrised_operator = sum(new_operator) symmetrised_qubo.from_ising(symmetrised_operator, self.offset, linear=True) self.qubo = symmetrised_qubo self.rename_qubo_variables() operator, _ = self.qubo.to_ising() self.operator = operator def rename_qubo_variables(self): original_qubo = self.original_qubo qubo = self.qubo variable_names = [variable.name for variable in qubo.variables] original_variable_names = [(variable.name, 1) for variable in original_qubo.variables] new_variable_names = original_variable_names + [ ("X_anc", 1) ] if self.symmetrise else original_variable_names variables_dict = dict(zip(variable_names, new_variable_names)) for (variable_name, new_variable_name) in variables_dict.items(): qubo.binary_var(name=new_variable_name[0]) qubo = qubo.substitute_variables(variables=variables_dict) if qubo.status == QuadraticProgram.Status.INFEASIBLE: raise QiskitOptimizationError( 'Infeasible due to variable substitution') self.qubo = qubo def construct_mixer(self): from qiskit.circuit.parameter import Parameter initial_state = self.initial_state no_qubits = initial_state.num_qubits t = Parameter('t') mixer = QuantumCircuit(no_qubits) mixer.append(initial_state.inverse(), range(no_qubits)) mixer.rz(2 * t, range(no_qubits)) mixer.append(initial_state, range(no_qubits)) self.mixer = mixer def solve_classically(self): x_s, opt_value, classical_result, _ = find_all_ground_states( self.original_qubo) self.result = classical_result x_arr = classical_result.x self.x_s = [x_str[::-1] for x_str in x_s] self.opt_value = opt_value solve def get_random_energy(self): #Get random benchmark energy for 0 layer QAOA (achieved by using layer 1 QAOA with [0,0] angles) random_energy, _ = CustomQAOA( operator=self.operator, quantum_instance=self.quantum_instance, optimizer=self.optimizer, reps=1, initial_state=self.initial_state, mixer=self.mixer, solve=False, ) temp = random_energy self.random_energy = temp + self.offset if np.round(self.random_energy - self.opt_value, 6) < 1e-7: print( "0 layer QAOA converged to exact solution. Shifting value up by |exact_ground_energy| instead to avoid dividing by 0 in approx quality." ) self.random_energy += np.abs(self.random_energy) print("random energy: {}".format(self.random_energy)) def get_benchmark_energy(self): #Get benchmark energy with 0-layer QAOA (just as random_energy) benchmark_energy, _ = CustomQAOA( operator=self.operator, quantum_instance=self.quantum_instance, optimizer=self.optimizer, reps=1, initial_state=self.initial_state, mixer=self.mixer, solve=False, ) temp = benchmark_energy + self.offset #Choose minimum of benchmark_energy if there already exists self.benchmark_energy self.benchmark_energy = min(self.benchmark_energy, temp) if self.benchmark_energy else temp return self.benchmark_energy def solve_qaoa(self, p, **kwargs): if self.optimal_point and 'point' not in kwargs: point = self.optimal_point else: point = kwargs.get("point", None) fourier_parametrise = True self.optimizer.set_options(maxeval=1000) tqa = kwargs.get('tqa', False) points = kwargs.get("points", None) symmetrised = self.symmetrise #Can sometimes end up with zero operator when substituting variables when we only have ZZ terms (symmetrised qubo), #e.g. if H = ZIZ (=Z1Z3 for 3 qubit system) and we know <Z1 Z3> = 1, so after substition H = II for the 2 qubit system. #H = II is then treated as an offset and not a Pauli operator, so the QUBO results to a zero (pauli) operator. #In such cases it means the QUBO is fully solved and any solution will do, so chose "0" string as the solution. #This also makes sure that ancilla bit is in 0 state. (we could equivalently choose something like "100" instead the "000" for 3 remaining variables)solve def valid_operator(qubo): num_vars = qubo.get_num_vars() operator, _ = qubo.to_ising() valid = False operator = [operator] if isinstance( operator, PauliOp) else operator #Make a list if only one single PauliOp for op_1 in operator: coeff, op_1 = op_1.to_pauli_op().coeff, op_1.to_pauli_op( ).primitive if coeff >= 1e-6 and op_1 != "I" * num_vars: #if at least one non-zero then return valid ( valid = True ) valid = True return valid valid_op = valid_operator(self.qubo) num_vars = self.qubo.get_num_vars() if num_vars >= 1 and symmetrised and not valid_op: qaoa_results = self.qaoa_result qaoa_results.eigenstate = [ ('0' * num_vars, self.qubo.objective.evaluate([0] * num_vars), 1) ] qaoa_results.optimizer_evals = 0 qaoa_results.eigenvalue = self.qubo.objective.evaluate([0] * num_vars) qc = QuantumCircuit(num_vars) elif tqa: deltas = np.arange(0.45, 0.91, 0.05) point = np.append([(i + 1) / p for i in range(p)], [1 - (i + 1) / p for i in range(p)]) points = [delta * point for delta in deltas] fourier_parametrise = True if fourier_parametrise: points = [ QAOAEx.convert_to_fourier_point(point, len(point)) for point in points ] qaoa_results, _ = CustomQAOA( self.operator, self.quantum_instance, self.optimizer, reps=p, initial_state=self.initial_state, mixer=self.mixer, fourier_parametrise=fourier_parametrise, list_points=points, qubo=self.qubo) elif points is not None: fourier_parametrise = True if fourier_parametrise: points = [ QAOAEx.convert_to_fourier_point(point, len(point)) for point in points ] qaoa_results, _ = CustomQAOA( self.operator, self.quantum_instance, self.optimizer, reps=p, initial_state=self.initial_state, mixer=self.mixer, fourier_parametrise=fourier_parametrise, list_points=points, qubo=self.qubo) elif point is None: list_points = [0] * (2 * p) + [[ 2 * np.pi * (np.random.rand() - 0.5) for _ in range(2 * p) ] for _ in range(5)] fourier_parametrise = True if fourier_parametrise: points = [ QAOAEx.convert_to_fourier_point(point, len(point)) for point in points ] qaoa_results, _ = CustomQAOA( self.operator, self.quantum_instance, self.optimizer, reps=p, initial_state=self.initial_state, list_points=points, mixer=self.mixer, fourier_parametrise=fourier_parametrise, qubo=self.qubo) else: fourier_parametrise = True if fourier_parametrise: point = QAOAEx.convert_to_fourier_point(point, len(point)) qaoa_results, _ = CustomQAOA( self.operator, self.quantum_instance, self.optimizer, reps=p, initial_state=self.initial_state, initial_point=point, mixer=self.mixer, fourier_parametrise=fourier_parametrise, qubo=self.qubo) point = qaoa_results.optimal_point qaoa_results.eigenvalue = sum( [x[1] * x[2] for x in qaoa_results.eigenstate]) self.optimal_point = QAOAEx.convert_to_fourier_point( point, len(point)) if fourier_parametrise else point self.qaoa_result = qaoa_results #Sort states by increasing energy and decreasing probability sorted_eigenstate_by_energy = sorted(qaoa_results.eigenstate, key=lambda x: x[1]) sorted_eigenstate_by_prob = sorted(qaoa_results.eigenstate, key=lambda x: x[2], reverse=True) #print energy-sorted state in a table self.print_state(sorted_eigenstate_by_energy) #Other print stuff print("Eigenvalue: {}".format(qaoa_results.eigenvalue)) print("Optimal point: {}".format(qaoa_results.optimal_point)) print("Optimizer Evals: {}".format(qaoa_results.optimizer_evals)) scale = self.random_energy - self.result.fval approx_quality = np.round( (self.random_energy - sorted_eigenstate_by_energy[0][1]) / scale, 3) approx_quality_2 = np.round( (self.random_energy - sorted_eigenstate_by_prob[0][1]) / scale, 3) energy_prob = {} for x in qaoa_results.eigenstate: energy_prob[np.round( x[1], 6)] = energy_prob.get(np.round(x[1], 6), 0) + x[2] prob_s = np.round(energy_prob.get(np.round(self.result.fval, 6), 0), 6) self.prob_s.append(prob_s) self.approx_s.append([approx_quality, approx_quality_2]) print("\nQAOA lowest energy solution: {}".format( sorted_eigenstate_by_energy[0])) print("Approx_quality: {}".format(approx_quality)) print("\nQAOA most probable solution: {}".format( sorted_eigenstate_by_prob[0])) print("Approx_quality: {}".format(approx_quality_2)) return qaoa_results def print_state(self, eigenstate): header = '|' for var in self.qubo.variables: var_name = var.name header += '{:<5}|'.format(var_name.replace("_", "")) header += '{:<6}|'.format('Cost') header += '{:<5}|'.format("Prob") print("-" * len(header)) print(header) print("-" * len(header)) for item in eigenstate: string = '|' for binary_var in item[0]: string += '{:<5} '.format(binary_var) #Binary string string += '{:<6} '.format(np.round(item[1], 2)) #Cost string += '{:<5}|'.format(np.round(item[2], 3)) #Prob print(string) print("-" * len(header)) def perform_substitution_from_qaoa_results(self, qaoa_results, update_benchmark_energy=True, biased=True): correlations = self.get_biased_correlations( qaoa_results.eigenstate) if biased else self.get_correlations( qaoa_results.eigenstate) i, j = self.find_strongest_correlation(correlations) correlation = correlations[i, j] new_qubo = deepcopy(self.qubo) x_i, x_j = new_qubo.variables[i].name, new_qubo.variables[j].name if x_i == "X_anc": print("X_i was ancilla. Swapped") x_i, x_j = x_j, x_i #So ancilla qubit is never substituted out i, j = j, i #Also swap i and j print("\nCorrelation: < {} {} > = {}".format(x_i.replace("_", ""), x_j.replace("_", ""), correlation)) car_block = int(x_i[2]) # #If same car_block and x_i = x_j, then both must be 0 since only one 1 in a car block # if x_i[2] == x_j[2] and correlation > 0 and len(self.car_blocks[car_block]) > 2: # # set x_i = x_j = 0 # new_qubo = new_qubo.substitute_variables({x_i: 0, x_j:0}) # if new_qubo.status == QuadraticProgram.Status.INFEASIBLE: # raise QiskitOptimizationError('Infeasible due to variable substitution {} = {} = 0'.format(x_i, x_j)) # self.var_values[x_i] = 0 # self.var_values[x_j] = 0 # self.car_blocks[car_block].remove(x_i) # self.car_blocks[car_block].remove(x_j) # print("Two variable substitutions were performed due to extra information from constraints.") if correlation > 0: # set x_i = x_j new_qubo = new_qubo.substitute_variables(variables={x_i: (x_j, 1)}) if new_qubo.status == QuadraticProgram.Status.INFEASIBLE: raise QiskitOptimizationError( 'Infeasible due to variable substitution {} = {}'.format( x_i, x_j)) self.replacements[x_i] = (x_j, 1) self.car_blocks[car_block].remove(x_i) else: # set x_i = 1 - x_j, this is done in two steps: # 1. set x_i = 1 + x_i # 2. set x_i = -x_j # 1a. get additional offset from the 1 on the RHS of (xi -> 1+xi) constant = new_qubo.objective.constant constant += new_qubo.objective.linear[i] constant += new_qubo.objective.quadratic[i, i] new_qubo.objective.constant = constant #1b get additional linear part from quadratic terms becoming linear due to the same 1 in the 1+xi as above for k in range(new_qubo.get_num_vars()): coeff = new_qubo.objective.linear[k] if k == i: coeff += 2 * new_qubo.objective.quadratic[i, k] else: coeff += new_qubo.objective.quadratic[i, k] # set new coefficient if not too small if np.abs(coeff) > 1e-10: new_qubo.objective.linear[k] = coeff else: new_qubo.objective.linear[k] = 0 #2 set xi = -xj new_qubo = new_qubo.substitute_variables( variables={x_i: (x_j, -1)}) if new_qubo.status == QuadraticProgram.Status.INFEASIBLE: raise QiskitOptimizationError( 'Infeasible due to variable substitution {} = -{}'.format( x_i, x_j)) self.replacements[x_i] = (x_j, -1) self.car_blocks[car_block].remove(x_i) # #If only one remaining variable and all other variables are 0, then remaining must be 1. # check = sum( [self.var_values.get("X_{}_{}".format(car_block, route_no), 0) for route_no in range(self.no_routes)] ) # if len(self.car_blocks[car_block]) == 1 and check == 0: # x_r = self.car_blocks[car_block][0] #remaining variable # new_qubo = new_qubo.substitute_variables({x_r: 1}) # if new_qubo.status == QuadraticProgram.Status.INFEASIBLE: # raise QiskitOptimizationError('Infeasible due to variable substitution {} = 1'.format(x_r)) # self.car_blocks[car_block].remove(x_r) # print("{} = 1 can also be determined from all other variables being 0 for car_{}".format(x_r, car_block)) #Update variable eliminated QUBO self.qubo = new_qubo op, offset = new_qubo.to_ising() self.operator = op self.offset = offset self.construct_initial_state() self.construct_mixer() # if update_benchmark_energy: # temp = self.get_benchmark_energy() def get_correlations(self, states) -> np.ndarray: """ Get <Zi x Zj> correlation matrix from the eigenstate(state: Dict). Returns: A correlation matrix. """ x, _, prob = states[0] n = len(x) correlations = np.zeros((n, n)) for x, cost, prob in states: for i in range(n): for j in range(i): if x[i] == x[j]: correlations[i, j] += prob else: correlations[i, j] -= prob return correlations def get_biased_correlations(self, states) -> np.ndarray: """ Get <Zi x Zj> correlation matrix from the eigenstate(states: Dict). Returns: A correlation matrix. """ x, _, prob = states[0] n = len(x) correlations = np.zeros((n, n)) for x, cost, prob in states: scaled_approx_quality = 1 / (1 + 2**(-self.benchmark_energy + cost)) for i in range(n): for j in range(i): if x[i] == x[j]: correlations[i, j] += scaled_approx_quality * prob else: correlations[i, j] -= scaled_approx_quality * prob return correlations def find_strongest_correlation(self, correlations): # get absolute values and set diagonal to -1 to make sure maximum is always on off-diagonal abs_correlations = np.abs(correlations) diagonal = np.diag(np.ones(len(correlations))) abs_correlations = abs_correlations - diagonal # get index of maximum (by construction on off-diagonal) m_max = np.argmax(abs_correlations.flatten()) # translate back to indices i = int(m_max // len(correlations)) j = int(m_max - i * len(correlations)) return (i, j)
class RQAOA: def __init__(self, qubo, no_cars, no_routes): var_list = qubo.variables opt_str = "LN_SBPLX" print("Optimizer: {}".format(opt_str)) self.optimizer = NLOPT_Optimizer(opt_str) self.original_qubo = qubo self.qubo = qubo op, offset = qubo.to_ising() self.operator = op self.offset = offset self.quantum_instance = QuantumInstance(backend = Aer.get_backend("aer_simulator_matrix_product_state"), shots = 1024) self.replacements = {var.name:None for var in var_list} self.no_cars = no_cars self.no_routes = no_routes self.car_blocks = np.empty(shape = (no_cars,), dtype=object) for car_no in range(no_cars): self.car_blocks[car_no] = ["X_{}_{}".format(car_no, route_no) for route_no in range(no_routes)] self.qaoa_result = None self.benchmark_energy = None self.var_values = {} self.construct_initial_state() self.construct_mixer() self.get_random_energy() self.get_benchmark_energy() def construct_initial_state(self): qc = QuantumCircuit() for car_no in range(self.no_cars): car_block = self.car_blocks[car_no] R = len(car_block) if R != 0 and R != 1: num_qubits = qc.num_qubits q_regs = QuantumRegister(R, 'car_{}'.format((car_no))) qc.add_register(q_regs) w_one = QuantumCircuit(q_regs) for r in range(0,R-1): if r == 0: w_one.ry(2*np.arccos(1/np.sqrt(R-r)),r) elif r != R-1: w_one.cry(2*np.arccos(1/np.sqrt(R-r)), r-1, r) for r in range(1,R): w_one.cx(R-r-1,R-r) w_one.x(0) qc.append(w_one, range(num_qubits, num_qubits+R)) elif R == 1: num_qubits = qc.num_qubits q_regs = QuantumRegister(R, 'car_{}'.format((car_no))) qc.add_register(q_regs) w_one = QuantumCircuit(q_regs) w_one.h(0) qc.append(w_one, range(num_qubits, num_qubits+R)) else: continue qc = qc.decompose() self.initial_state = qc def construct_mixer(self): from qiskit.circuit.parameter import Parameter initial_state = self.initial_state no_qubits = initial_state.num_qubits t = Parameter('t') mixer = QuantumCircuit(no_qubits) mixer.append(initial_state.inverse(), range(no_qubits)) mixer.rz(2*t, range(no_qubits)) mixer.append(initial_state, range(no_qubits)) self.mixer = mixer def solve_classically(self): classical_result, _ = solve_classically(self.qubo) self.result = classical_result def get_random_energy(self): #Get random benchmark energy for 0 layer QAOA (achieved by using layer 1 QAOA with [0,0] angles) random_energy = CustomQAOA(operator = self.operator, quantum_instance = self.quantum_instance, optimizer = self.optimizer, reps = 1, initial_state = self.initial_state, mixer = self.mixer, solve = False, ) temp = random_energy self.random_energy = temp + self.offset print("random energy: {}".format(self.random_energy)) def get_benchmark_energy(self): #Get benchmark energy with 0-layer QAOA (just as random_energy) benchmark_energy = CustomQAOA(operator = self.operator, quantum_instance = self.quantum_instance, optimizer = self.optimizer, reps = 1, initial_state = self.initial_state, mixer = self.mixer, solve = False, ) temp = benchmark_energy + self.offset #Choose minimum of benchmark_energy if there already exists self.benchmark_energy self.benchmark_energy = min(self.benchmark_energy, temp) if self.benchmark_energy else temp return self.benchmark_energy def solve_tqa_qaoa(self, p): deltas = np.arange(0.45, 0.91, 0.05) point = np.append( [ (i+1)/p for i in range(p) ] , [ 1-(i+1)/p for i in range(p) ] ) points = [delta*point for delta in deltas] fourier_parametrise = True if fourier_parametrise: points = [ QAOAEx.convert_to_fourier_point(point, len(point)) for point in points ] self.optimizer.set_options(maxeval = 1000) qaoa_results, _, _ = CustomQAOA( self.operator, self.quantum_instance, self.optimizer, reps = p, initial_state = self.initial_state, mixer = self.mixer, fourier_parametrise = fourier_parametrise, list_points = points, qubo = self.qubo ) point = qaoa_results.optimal_point qaoa_results.eigenvalue = sum( [ x[1] * x[2] for x in qaoa_results.eigenstate ] ) self.optimal_point = QAOAEx.convert_to_fourier_point(point, len(point)) if fourier_parametrise else point self.qaoa_result = qaoa_results return qaoa_results def solve_qaoa(self, p, **kwargs): point = self.optimal_point if 'point' not in kwargs else kwargs['point'] fourier_parametrise = True self.optimizer.set_options(maxeval = 1000) qaoa_results, _, _ = CustomQAOA( self.operator, self.quantum_instance, self.optimizer, reps = p, initial_state = self.initial_state, initial_point = point, mixer = self.mixer, fourier_parametrise = fourier_parametrise, qubo = self.qubo ) point = qaoa_results.optimal_point qaoa_results.eigenvalue = sum( [ x[1] * x[2] for x in qaoa_results.eigenstate ] ) self.optimal_point = QAOAEx.convert_to_fourier_point(point, len(point)) if fourier_parametrise else point self.qaoa_result = qaoa_results return qaoa_results def perform_substitution_from_qaoa_results(self, qaoa_results, update_benchmark_energy=True): sorted_eigenstate_by_energy = sorted(qaoa_results.eigenstate, key = lambda x: x[1]) sorted_eigenstate_by_prob = sorted(qaoa_results.eigenstate, key = lambda x: x[2], reverse = True) scale = self.random_energy - self.result.fval approx_quality = (self.random_energy - sorted_eigenstate_by_energy[0][1])/ scale eigenstate = dict( [ (x[0], x[2]) for x in qaoa_results.eigenstate ] ) self.approx_quality = approx_quality self.eigenstate = eigenstate print( "QAOA lowest energy solution: {}".format(sorted_eigenstate_by_energy[0]) ) print( "Approx_quality: {}".format(approx_quality) print( "QAOA most probable solution: {}".format(sorted_eigenstate_by_prob[0]) ) print( "Approx_quality: {}".format((self.random_energy - sorted_eigenstate_by_prob[0][1])/ scale) ) correlations = self.get_correlations(qaoa_results.eigenstate) i, j = self.find_strongest_correlation(correlations) new_qubo = deepcopy(self.qubo) x_i, x_j = new_qubo.variables[i].name, new_qubo.variables[j].name print( "\nCorrelation: < {} {} > = {}".format(x_i, x_j, correlations[i, j])) car_block = int(x_i[2]) #If same car_block and x_i = x_j, then both must be 0 since only one 1 in a car block if x_i[2] == x_j[2] and correlations[i, j] > 0 and len(self.car_blocks[car_block]) > 2: # set x_i = x_j = 0 new_qubo = new_qubo.substitute_variables({x_i: 0, x_j:0}) if new_qubo.status == QuadraticProgram.Status.INFEASIBLE: raise QiskitOptimizationError('Infeasible due to variable substitution {} = {} = 0'.format(x_i, x_j)) self.var_values[x_i] = 0 self.var_values[x_j] = 0 self.car_blocks[car_block].remove(x_i) self.car_blocks[car_block].remove(x_j) print("Two variable substitutions were performed due to extra information from constraints.") if len(self.car_blocks[car_block]) == 1: #If only one remaining variable x_r = self.car_blocks[car_block][0] #remaining variable #Check if all other variables are 0 (then their sum should be 0) -> so x_r must be 1 check = sum( [self.var_values.get("X_{}_{}".format(car_block, route_no), 0) for route_no in range(self.no_routes)] ) if check == 0: new_qubo = new_qubo.substitute_variables({x_r: 1}) if new_qubo.status == QuadraticProgram.Status.INFEASIBLE: raise QiskitOptimizationError('Infeasible due to variable substitution {} = 1'.format(x_r)) self.car_blocks[car_block].remove(x_r) print("{} = 1 can also be determined from all other variables being 0 for car_{}".format(x_r, car_block)) elif x_i[2] != x_j[2] and correlations[i, j] > 0: # set x_i = x_j new_qubo = new_qubo.substitute_variables(variables={i: (j, 1)}) if new_qubo.status == QuadraticProgram.Status.INFEASIBLE: raise QiskitOptimizationError('Infeasible due to variable substitution {} = {}'.format(x_i, x_j)) self.replacements[x_i] = (x_j, 1) self.car_blocks[car_block].remove(x_i) else: # set x_i = 1 - x_j, this is done in two steps: # 1. set x_i = 1 + x_i # 2. set x_i = -x_j # 1a. get additional offset from the 1 on the RHS of (xi -> 1+xi) constant = new_qubo.objective.constant constant += new_qubo.objective.linear[i] constant += new_qubo.objective.quadratic[i, i] new_qubo.objective.constant = constant #1b get additional linear part from quadratic terms becoming linear due to the same 1 in the 1+xi as above for k in range(new_qubo.get_num_vars()): coeff = new_qubo.objective.linear[k] if k == i: coeff += 2*new_qubo.objective.quadratic[i, k] else: coeff += new_qubo.objective.quadratic[i, k] # set new coefficient if not too small if np.abs(coeff) > 1e-10: new_qubo.objective.linear[k] = coeff else: new_qubo.objective.linear[k] = 0 #2 set xi = -xj new_qubo = new_qubo.substitute_variables(variables={i: (j, -1)}) if new_qubo.status == QuadraticProgram.Status.INFEASIBLE: raise QiskitOptimizationError('Infeasible due to variable substitution {} = -{}'.format(x_i, x_j)) self.replacements[x_i] = (x_j, -1) self.car_blocks[car_block].remove(x_i) self.qubo = new_qubo op, offset = new_qubo.to_ising() self.operator = op self.offset = offset self.construct_initial_state() self.construct_mixer() if update_benchmark_energy: temp = self.get_benchmark_energy() def get_correlations(self, state) -> np.ndarray: """ Get <Zi x Zj> correlation matrix from the eigenstate(state: Dict). Returns: A correlation matrix. """ states = state # print(states) x, _, prob = states[0] n = len(x) correlations = np.zeros((n, n)) for x, cost, prob in states: if cost < self.benchmark_energy: scaled_approx_quality = (self.benchmark_energy - cost) for i in range(n): for j in range(i): if x[i] == x[j]: correlations[i, j] += scaled_approx_quality * prob else: correlations[i, j] -= scaled_approx_quality * prob return correlations def find_strongest_correlation(self, correlations): # get absolute values and set diagonal to -1 to make sure maximum is always on off-diagonal abs_correlations = np.abs(correlations) diagonal = np.diag( np.ones(len(correlations)) ) abs_correlations = abs_correlations - diagonal # get index of maximum (by construction on off-diagonal) m_max = np.argmax(abs_correlations.flatten()) # translate back to indices i = int(m_max // len(correlations)) j = int(m_max - i*len(correlations)) return (i, j) def main(args=None): start = time() if args == None: args = parse() with open('qubos_{}_car_{}_routes/qubo_{}.pkl'.format(args["no_cars"], args["no_routes"], args["no_samples"]), 'rb') as f: load_data = pkl.load(f) if len(load_data) == 5: qubo, max_coeff, operator, offset, routes = load_data else: qubo, max_coeff, operator, offset, routes, classical_result = load_data rqaoa = RQAOA(qubo, args["no_cars"], args["no_routes"]) rqaoa.solve_classically() print(rqaoa.result) print("First round of TQA-QAOA. Results below:") qaoa_results = rqaoa.solve_tqa_qaoa(2) rqaoa.perform_substitution_from_qaoa_results(qaoa_results) print("Performed variable substition(s) and constructed new initial state and mixer.") num_vars = rqaoa.qubo.get_num_vars() print("Remaining variables: {}".format(num_vars)) t = 1 while num_vars > 1: print("-"*50) t+=1 qaoa_results = rqaoa.solve_tqa_qaoa(2) print( "Round {} of TQA-QAOA. Results below:".format(t) ) rqaoa.perform_substitution_from_qaoa_results(qaoa_results) print("Performed variable substition(s) and constructed new initial state and mixer.") num_vars = rqaoa.qubo.get_num_vars() print("Remaining variables: {}".format(num_vars)) print("-"*50) p=2 qaoa_results = rqaoa.solve_qaoa( p, point = [0]* (2*p) ) print( "Final round of QAOA Done. Eigenstate below:" ) pprint(qaoa_results.eigenstate) print( rqaoa.prob_s ) print( rqaoa.approx_s ) max_state = sorted(qaoa_results.eigenstate, key = lambda x: x[2], reverse=True)[0] var_last = rqaoa.qubo.variables[0].name rqaoa.var_values[var_last] = int(max_state[0]) var_values = rqaoa.var_values replacements = rqaoa.replacements while True: for var, replacement in replacements.items(): if replacement == None: continue elif replacement[0] in var_values and var not in var_values and replacement[1] != None: var_values[var] = var_values[ replacement[0] ] if replacement[1] == 1 else 1 - var_values[ replacement[0] ] if len(var_values.keys()) == args["no_cars"]*args["no_routes"]: break print(rqaoa.result) print(var_values) list_values = list(var_values.values()) cost = rqaoa.original_qubo.objective.evaluate(var_values) print("{}, Cost: {}".format(list_values, cost)) if __name__ == '__main__': main() finish = time() print("Time taken: {} s".format(finish - start))
def main(args=None): """[summary] Args: raw_args ([type], optional): [description]. Defaults to None. """ start = time() if args == None: args = parse() prob_s_s = [] qubo_no = args["no_samples"] print("__" * 50, "\nQUBO NO: {}\n".format(qubo_no), "__" * 50) #Load generated qubo_no with open( 'qubos_{}_car_{}_routes/qubo_{}.pkl'.format( args["no_cars"], args["no_routes"], qubo_no), 'rb') as f: qubo, max_coeff, operator, offset, routes = pkl.load(f) print(operator) x_s = find_all_ground_states(qubo) # Visualise if args["visual"]: graph = import_map('melbourne.pkl') visualise_solution(graph, routes) # Solve QAOA from QUBO with valid solution no_couplings = count_coupling_terms(operator) print("Number of couplings: {}".format(no_couplings)) print("Solving with QAOA...") no_shots = 10000 backend = Aer.get_backend('statevector_simulator') quantum_instance = QuantumInstance(backend, shots=no_shots) optimizer_method = "LN_SBPLX" optimizer = NLOPT_Optimizer(method=optimizer_method) print("_" * 50, "\n" + optimizer.__class__.__name__) print("_" * 50) quantum_instance = QuantumInstance(backend) prob_s_s = [] initial_state = construct_initial_state(args["no_routes"], args["no_cars"]) mixer = n_qbit_mixer(initial_state) next_fourier_point, next_fourier_point_B = [0, 0], [ 0, 0 ] #Not used for p=1 then gets updated for p>1. for p in range(1, args["p_max"] + 1): print("p = {}".format(p)) if p == 1: points = [[0.75,0]] \ # + [[ np.pi*(2*np.random.rand() - 1) for _ in range(2) ] for _ in range(args["no_restarts"])] draw_circuit = True else: penalty = 0.6 points = generate_points(next_fourier_point, no_perturb=10, penalty=0.6) print(points) \ # + generate_points(next_fourier_point_B, 10, penalty) draw_circuit = False #empty lists to save following results to choose best result results = [] exp_vals = [] for r in range(len(points)): point = points[r] if np.amax(np.abs(point)) < np.pi / 2: qaoa_results, optimal_circ = CustomQAOA( operator, quantum_instance, optimizer, reps=p, initial_fourier_point=points[r], initial_state=initial_state, mixer=mixer, construct_circ=draw_circuit) if r == 0: next_fourier_point = np.array(qaoa_results.optimal_point) next_fourier_point = QAOAEx.convert_from_fourier_point( next_fourier_point, 2 * p + 2) next_fourier_point = QAOAEx.convert_to_fourier_point( next_fourier_point, 2 * p + 2) exp_val = qaoa_results.eigenvalue * max_coeff + offset exp_vals.append(exp_val) prob_s = 0 for string in x_s: prob_s += qaoa_results.eigenstate[ string] if string in qaoa_results.eigenstate else 0 results.append((qaoa_results, optimal_circ, prob_s)) print("Point_no: {}, Exp_val: {}, Prob_s: {}".format( r, exp_val, prob_s)) else: print( "Point_no: {}, was skipped because it is outside of bounds" .format(r)) minim_index = np.argmin(exp_vals) optimal_qaoa_result, optimal_circ, optimal_prob_s = results[ minim_index] # if draw_circuit: # print(optimal_circ.draw()) minim_exp_val = exp_vals[minim_index] print("Minimum: {}, prob_s: {}".format(minim_exp_val, optimal_prob_s)) prob_s_s.append(optimal_prob_s) next_fourier_point_B = np.array(optimal_qaoa_result.optimal_point) print("Optimal_point: {}".format(next_fourier_point_B)) next_fourier_point_B = QAOAEx.convert_from_fourier_point( next_fourier_point_B, 2 * p + 2) next_fourier_point_B = QAOAEx.convert_to_fourier_point( next_fourier_point_B, 2 * p + 2) print(prob_s_s) with open( 'results/{}cars{}routes_qubo{}.csv'.format(args["no_cars"], args["no_routes"], args["no_samples"]), 'w') as f: np.savetxt(f, prob_s_s, delimiter=',') finish = time() print("Time Taken: {}".format(finish - start))
def main(args = None): """[summary] Args: raw_args ([type], optional): [description]. Defaults to None. """ start = time() if args == None: args = parse() qubo_no = args["no_samples"] print_to_file("-"*50) print_to_file("QUBO_{}".format(qubo_no)) #Load generated qubo_no with open('qubos_{}_car_{}_routes/qubo_{}.pkl'.format(args["no_cars"], args["no_routes"], qubo_no), 'rb') as f: qubo, max_coeff, operator, offset, routes = pkl.load(f) qubo = QuadraticProgram() qubo.from_ising(operator) x_s, opt_value, classical_result = find_all_ground_states(qubo) print_to_file(classical_result) #Set optimizer method method = args["method"] optimizer = NLOPT_Optimizer(method = method, result_message=False) backend = Aer.get_backend("statevector_simulator") quantum_instance = QuantumInstance(backend = backend) approx_ratios = [] prob_s_s = [] p_max = args["p_max"] no_routes, no_cars = (args["no_routes"], args["no_cars"]) custom = True if custom: initial_state = construct_initial_state(no_routes = no_routes, no_cars = no_cars) mixer = n_qbit_mixer(initial_state) else: initial_state, mixer = (None, None) fourier_parametrise = args["fourier"] print_to_file("-"*50) print_to_file("Now solving with QAOA... Fourier Parametrisation: {}".format(fourier_parametrise)) for p in range(1, p_max+1): if p == 1: points = [[0,0]] + [ np.random.uniform(low = -np.pi/2+0.01, high = np.pi/2-0.01, size = 2*p) for _ in range(2**p)] next_point = [] else: penalty = 0.6 points = [next_point_l] + generate_points(next_point, no_perturb=min(2**p-1,10), penalty=penalty) construct_circ = False #empty lists to save following results to choose best result results = [] exp_vals = [] print_to_file("-"*50) print_to_file(" "+"p={}".format(p)) optimizer.set_options(maxeval = 1000*p) for r, point in enumerate(points): qaoa_results, optimal_circ = CustomQAOA(operator, quantum_instance, optimizer, reps = p, initial_fourier_point= point, initial_state = initial_state, mixer = mixer, construct_circ= construct_circ, fourier_parametrise = fourier_parametrise, qubo = qubo ) if r == 0: if fourier_parametrise: next_point_l = np.zeros(shape = 2*p + 2) next_point_l[0:p] = qaoa_results.optimal_point[0:p] next_point_l[p+1:2*p+1] = qaoa_results.optimal_point[p:2*p] else: next_point_l = interp_point(qaoa_results.optimal_point) exp_val = qaoa_results.eigenvalue * max_coeff exp_vals.append(exp_val) state_solutions = { item[0][::-1]: item[1:] for item in qaoa_results.eigenstate } for item in sorted(state_solutions.items(), key = lambda x: x[1][1], reverse = True)[0:5]: print_to_file( item ) prob_s = 0 for string in x_s: prob_s += state_solutions[string][1] if string in state_solutions else 0 prob_s /= len(x_s) #normalise results.append((qaoa_results, optimal_circ, prob_s)) print_to_file(" "+"Point_{}, Exp_val: {}, Prob_s: {}".format(r, exp_val, prob_s)) minim_index = np.argmin(exp_vals) optimal_qaoa_result, optimal_circ, optimal_prob_s = results[minim_index] if fourier_parametrise: next_point = convert_from_fourier_point( optimal_qaoa_result.optimal_point, 2*p ) next_point = convert_to_fourier_point( interp_point(next_point), 2*p + 2 ) # next_point = np.zeros(shape = 2*p + 2) # next_point[0:p] = optimal_qaoa_result.optimal_point[0:p] # next_point[p+1:2*p+1] = optimal_qaoa_result.optimal_point[p:2*p] else: next_point = interp_point(optimal_qaoa_result.optimal_point) if construct_circ: print_to_file(optimal_circ.draw(fold=150)) minim_exp_val = exp_vals[minim_index] approx_ratio = 1.0 - np.abs( (opt_value - minim_exp_val ) / opt_value ) print_to_file(" "+"Minimum: {}, prob_s: {}, approx_ratio {}".format(minim_exp_val, optimal_prob_s, approx_ratio)) approx_ratios.append(approx_ratio) prob_s_s.append(optimal_prob_s) print_to_file("-"*50) print_to_file("QAOA terminated") print_to_file("-"*50) print_to_file("Approximation ratios per layer: {}".format(approx_ratios)) print_to_file("Prob_success per layer: {}".format(prob_s_s)) save_results = np.append(approx_ratios, prob_s_s) if fourier_parametrise: with open('results_{}cars{}routes/RI_F_{}.csv'.format(args["no_cars"], args["no_routes"], args["no_samples"]), 'w') as f: np.savetxt(f, save_results, delimiter=',') print_to_file("Results saved in results_{}cars{}routes/RI_F_{}.csv".format(args["no_cars"], args["no_routes"], args["no_samples"])) else: with open('results_{}cars{}routes/RI_NF_{}.csv'.format(args["no_cars"], args["no_routes"], args["no_samples"]), 'w') as f: np.savetxt(f, save_results, delimiter=',') print_to_file("Results saved in results_{}cars{}routes/RI_NF_{}.csv".format(args["no_cars"], args["no_routes"], args["no_samples"])) finish = time() print_to_file("Time Taken: {}".format(finish - start))
def __init__(self, qubo, no_cars, no_routes, **kwargs): opt_str = kwargs.get('opt_str', "LN_COBYLA") self.symmetrise = kwargs.get('symmetrise', False) self.customise = kwargs.get('customise', True) self.classical_result = kwargs.get("classical_result", None) simulator = kwargs.get("simulator", "aer_simulator_matrix_product_state") noise_model = kwargs.get("noise_model", None) if simulator == None: simulator = "aer_simulator_matrix_product_state" #Initializing other algorithm required objects var_list = qubo.variables if noise_model == None: self.quantum_instance = QuantumInstance( backend = Aer.get_backend(simulator), shots = 4096) elif noise_model: self.quantum_instance = QuantumInstance( backend = Aer.get_backend(simulator), shots = 4096, noise_model = noise_model, basis_gates = ["cx", "x", "sx", "rz", "id"] ) self.random_instance = QuantumInstance(backend = Aer.get_backend("aer_simulator_matrix_product_state"), shots = 1000) #print backend name print("Quantum Instance: {}\n".format(self.quantum_instance.backend_name)) #print backend name self.optimizer = NLOPT_Optimizer(opt_str) self.optimizer.set_options(max_eval = 1000) self.original_qubo = qubo self.qubo = qubo self.operator, self.offset = qubo.to_ising() #If no classical result, this will compute the appropriate self.classical_result, else this will simply re-organise already available result self.solve_classically() #Setup for variable replacements self.replacements = {var.name:None for var in var_list} self.no_cars = no_cars self.no_routes = no_routes self.car_blocks = np.empty(shape = (no_cars,), dtype=object) for car_no in range(no_cars): self.car_blocks[car_no] = ["X_{}_{}".format(car_no, route_no) for route_no in range(no_routes)] #Initialize variable placeholders in self.qaoa_result = None self.initial_state = None self.mixer = None self.benchmark_energy = None self.var_values = {} self.prob_s = [] self.approx_s = [] self.optimal_point = None #Random energy self.get_random_energy() #Symmetrise QUBO if required (after getting random energy WITHOUT ancilla) if self.symmetrise: self.symmetrise_qubo() #Custom initial state and mixer if required if self.customise: if self.symmetrise: self.construct_initial_state(ancilla = True) else: self.construct_initial_state() self.construct_mixer()
class RQAOA: def __init__(self, qubo, no_cars, no_routes, **kwargs): opt_str = kwargs.get('opt_str', "LN_COBYLA") self.symmetrise = kwargs.get('symmetrise', False) self.customise = kwargs.get('customise', True) self.classical_result = kwargs.get("classical_result", None) simulator = kwargs.get("simulator", "aer_simulator_matrix_product_state") noise_model = kwargs.get("noise_model", None) if simulator == None: simulator = "aer_simulator_matrix_product_state" #Initializing other algorithm required objects var_list = qubo.variables if noise_model == None: self.quantum_instance = QuantumInstance( backend = Aer.get_backend(simulator), shots = 4096) elif noise_model: self.quantum_instance = QuantumInstance( backend = Aer.get_backend(simulator), shots = 4096, noise_model = noise_model, basis_gates = ["cx", "x", "sx", "rz", "id"] ) self.random_instance = QuantumInstance(backend = Aer.get_backend("aer_simulator_matrix_product_state"), shots = 1000) #print backend name print("Quantum Instance: {}\n".format(self.quantum_instance.backend_name)) #print backend name self.optimizer = NLOPT_Optimizer(opt_str) self.optimizer.set_options(max_eval = 1000) self.original_qubo = qubo self.qubo = qubo self.operator, self.offset = qubo.to_ising() #If no classical result, this will compute the appropriate self.classical_result, else this will simply re-organise already available result self.solve_classically() #Setup for variable replacements self.replacements = {var.name:None for var in var_list} self.no_cars = no_cars self.no_routes = no_routes self.car_blocks = np.empty(shape = (no_cars,), dtype=object) for car_no in range(no_cars): self.car_blocks[car_no] = ["X_{}_{}".format(car_no, route_no) for route_no in range(no_routes)] #Initialize variable placeholders in self.qaoa_result = None self.initial_state = None self.mixer = None self.benchmark_energy = None self.var_values = {} self.prob_s = [] self.approx_s = [] self.optimal_point = None #Random energy self.get_random_energy() #Symmetrise QUBO if required (after getting random energy WITHOUT ancilla) if self.symmetrise: self.symmetrise_qubo() #Custom initial state and mixer if required if self.customise: if self.symmetrise: self.construct_initial_state(ancilla = True) else: self.construct_initial_state() self.construct_mixer() def construct_initial_state(self, ancilla=False): qc = QuantumCircuit() for car_no in range(self.no_cars): car_block = self.car_blocks[car_no] R = len(car_block) if R != 0 and R != 1: num_qubits = qc.num_qubits q_regs = QuantumRegister(R, 'car_{}'.format((car_no))) qc.add_register(q_regs) w_one = QuantumCircuit(q_regs) for r in range(0,R-1): if r == 0: w_one.ry(2*np.arccos(1/np.sqrt(R-r)),r) elif r != R-1: w_one.cry(2*np.arccos(1/np.sqrt(R-r)), r-1, r) for r in range(1,R): w_one.cx(R-r-1,R-r) w_one.x(0) qc.append(w_one, range(num_qubits, num_qubits+R)) elif R == 1: num_qubits = qc.num_qubits q_regs = QuantumRegister(R, 'car_{}'.format((car_no))) qc.add_register(q_regs) w_one = QuantumCircuit(q_regs) w_one.h(0) qc.append(w_one, range(num_qubits, num_qubits+R)) else: continue qc = qc.decompose() self.initial_state = qc if ancilla: ancilla_reg = QuantumRegister(1, 'ancilla') self.initial_state.add_register(ancilla_reg) self.initial_state.h(ancilla_reg[0]) def symmetrise_qubo(self): new_operator = [] operator, _ = self.qubo.to_ising() for op_1 in operator: coeff, op_1 = op_1.to_pauli_op().coeff, op_1.to_pauli_op().primitive op_1_str = op_1.to_label() Z_counts = op_1_str.count('Z') if Z_counts == 1: op_1_str = "Z" + op_1_str #Add a Z in the last qubit to single Z terms else: op_1_str = "I" + op_1_str #Add an I in the last qubit to ZZ terms (no change in operator) pauli = PauliOp( primitive = Pauli(op_1_str), coeff = coeff ) new_operator.append(pauli) symmetrised_qubo = QuadraticProgram() symmetrised_operator = sum(new_operator) symmetrised_qubo.from_ising(symmetrised_operator, self.offset, linear=True) self.qubo = symmetrised_qubo self.rename_qubo_variables() operator, _ = self.qubo.to_ising() self.operator = operator def rename_qubo_variables(self): original_qubo = self.original_qubo qubo = self.qubo variable_names = [ variable.name for variable in qubo.variables ] original_variable_names = [ (variable.name, 1) for variable in original_qubo.variables ] new_variable_names = original_variable_names + [("X_anc", 1)] if self.symmetrise else original_variable_names variables_dict = dict(zip(variable_names, new_variable_names)) for new_variable_name in variables_dict.values(): qubo.binary_var(name = new_variable_name[0]) qubo = qubo.substitute_variables(variables = variables_dict) if qubo.status == QuadraticProgram.Status.INFEASIBLE: raise QiskitOptimizationError('Infeasible due to variable substitution') self.qubo = qubo def construct_mixer(self): from qiskit.circuit.parameter import Parameter initial_state = self.initial_state no_qubits = initial_state.num_qubits t = Parameter('t') mixer = QuantumCircuit(no_qubits) mixer.append(initial_state.inverse(), range(no_qubits)) mixer.rz(2*t, range(no_qubits)) mixer.append(initial_state, range(no_qubits)) self.mixer = mixer def solve_classically(self): if self.classical_result: print("There is an existing classical result. Using this as code proceeds.") print(self.classical_result) self.opt_value = self.classical_result.fval else: print("No classical result already available.") print("Now solving classically") _, opt_value, classical_result, _ = find_all_ground_states(self.original_qubo) self.classical_result = classical_result self.opt_value = opt_value def get_random_energy(self): #Get random benchmark energy for 0 layer QAOA (achieved by using layer 1 QAOA with [0,0] angles) and with only feasible states (custom initial state) self.construct_initial_state() self.construct_mixer() random_energy, _ = CustomQAOA(operator = self.operator, quantum_instance = self.random_instance, optimizer = self.optimizer, reps = 1, initial_state = self.initial_state, mixer = self.mixer, solve = False, ) #Remove custom initial state if using BASE QAOA self.initial_state = None self.mixer = None temp = random_energy self.random_energy = temp + self.offset if np.round( self.random_energy - self.opt_value, 6 ) < 1e-7: print("0 layer QAOA converged to exact solution. Shifting value up by |exact_ground_energy| instead to avoid dividing by 0 in approx quality.") self.random_energy += np.abs(self.random_energy) self.benchmark_energy = self.random_energy print("random energy: {}\n".format(self.random_energy)) def solve_qaoa(self, p, **kwargs): if self.optimal_point is not None and 'point' not in kwargs: point = self.optimal_point else: point = kwargs.get("point", None) fourier_parametrise = kwargs.get("fourier_parametrise", False) self.optimizer.set_options(maxeval = 1000) tqa = kwargs.get('tqa', False) points = kwargs.get("points", None) symmetrised = self.symmetrise #Can sometimes end up with zero operator when substituting variables when we only have ZZ terms (symmetrised qubo), #e.g. if H = ZIZ (=Z1Z3 for 3 qubit system) and we know <Z1 Z3> = 1, so after substition H = II for the 2 qubit system. #H = II is then treated as an offset and not a Pauli operator, so the QUBO.to_ising() method returns a zero (pauli) operator. #In such cases it means the QUBO is fully solved and any solution will do, so chose "0" string as the solution. #This also makes sure that ancilla bit is in 0 state. (we could equivalently choose any other string with ancilla in 0 state) def valid_operator(qubo): num_vars = qubo.get_num_vars() operator, _ = qubo.to_ising() valid = False operator = [operator] if isinstance(operator, PauliOp) else operator #Make a list if only one single PauliOp for op_1 in operator: coeff, op_1 = op_1.to_pauli_op().coeff, op_1.to_pauli_op().primitive if coeff >= 1e-6 and op_1 != "I"*num_vars: #if at least one non-zero then return valid ( valid = True ) valid = True return valid valid_op = valid_operator(self.qubo) num_vars = self.qubo.get_num_vars() if num_vars >= 1 and symmetrised and not valid_op: qaoa_results = self.qaoa_result qaoa_results.eigenstate = np.array( [ 1 ] + [ 0 ]*(2**num_vars - 1) ) qaoa_results.optimizer_evals = 0 qaoa_results.eigenvalue = self.qubo.objective.evaluate([0]*num_vars) qc = QuantumCircuit(num_vars) elif tqa: deltas = np.arange(0.45, 0.91, 0.05) point = np.append( [ (i+1)/p for i in range(p) ] , [ 1-(i+1)/p for i in range(p) ] ) points = [delta*point for delta in deltas] if fourier_parametrise: points = [ QAOAEx.convert_to_fourier_point(point, len(point)) for point in points ] qaoa_results, _ = QiskitQAOA( self.operator, self.quantum_instance, self.optimizer, reps = p, initial_state = self.initial_state, mixer = self.mixer, fourier_parametrise = fourier_parametrise, list_points = points, qubo = self.qubo ) elif points is not None: if fourier_parametrise: points = [ QAOAEx.convert_to_fourier_point(point, len(point)) for point in points ] qaoa_results, _ = QiskitQAOA( self.operator, self.quantum_instance, self.optimizer, reps = p, initial_state = self.initial_state, mixer = self.mixer, fourier_parametrise = fourier_parametrise, list_points = points, qubo = self.qubo ) elif point is None: points = [ [0]*(2*p) ] + [ [ 2 * np.pi* ( np.random.rand() - 0.5 ) for _ in range(2*p)] for _ in range(10) ] qaoa_results, _ = QiskitQAOA( self.operator, self.quantum_instance, self.optimizer, reps = p, initial_state = self.initial_state, list_points = points, mixer = self.mixer, fourier_parametrise = fourier_parametrise, qubo = self.qubo ) else: if fourier_parametrise: point = QAOAEx.convert_to_fourier_point(point, len(point)) qaoa_results, _ = QiskitQAOA( self.operator, self.quantum_instance, self.optimizer, reps = p, initial_state = self.initial_state, initial_point = point, mixer = self.mixer, fourier_parametrise = fourier_parametrise, qubo = self.qubo ) point = qaoa_results.optimal_point eigenstate = qaoa_results.eigenstate if self.quantum_instance.is_statevector: from qiskit.quantum_info import Statevector eigenstate = Statevector(eigenstate) eigenstate = eigenstate.probabilities_dict() else: eigenstate = dict([(u, v**2) for u, v in eigenstate.items()]) #Change to probabilities num_qubits = len(list(eigenstate.items())[0][0]) solutions = [] eigenvalue = 0 for bitstr, sampling_probability in eigenstate.items(): bitstr = bitstr[::-1] value = self.qubo.objective.evaluate([int(bit) for bit in bitstr]) eigenvalue += value * sampling_probability solutions += [(bitstr, value, sampling_probability)] qaoa_results.eigenstate = solutions qaoa_results.eigenvalue = eigenvalue self.optimal_point = point self.qaoa_result = qaoa_results #Sort states by increasing energy and decreasing probability sorted_eigenstate_by_energy = sorted(qaoa_results.eigenstate, key = lambda x: x[1]) sorted_eigenstate_by_prob = sorted(qaoa_results.eigenstate, key = lambda x: x[2], reverse = True) #print energy-sorted state in a table self.print_state(sorted_eigenstate_by_energy) #Other print stuff print("Eigenvalue: {}".format(qaoa_results.eigenvalue)) print("Optimal point: {}".format(qaoa_results.optimal_point)) print("Optimizer Evals: {}".format(qaoa_results.optimizer_evals)) scale = self.random_energy - self.opt_value approx_quality = np.round( (self.random_energy - sorted_eigenstate_by_energy[0][1])/ scale, 3 ) approx_quality_2 = np.round( ( self.random_energy - sorted_eigenstate_by_prob[0][1] ) / scale, 3 ) energy_prob = {} for x in qaoa_results.eigenstate: energy_prob[ np.round(x[1], 6) ] = energy_prob.get(np.round(x[1], 6), 0) + x[2] prob_s = np.round( energy_prob.get(np.round(self.opt_value, 6), 0), 6 ) self.prob_s.append( prob_s ) self.approx_s.append( [approx_quality, approx_quality_2] ) print( "\nQAOA lowest energy solution: {}".format(sorted_eigenstate_by_energy[0]) ) print( "Approx_quality: {}".format(approx_quality) ) print( "\nQAOA most probable solution: {}".format(sorted_eigenstate_by_prob[0]) ) print( "Approx_quality: {}".format(approx_quality_2) ) return qaoa_results def print_state(self, eigenstate): header = '|' for var in self.qubo.variables: var_name = var.name header += '{:<5}|'.format(var_name.replace("_", "")) header += '{:<6}|'.format('Cost') header += '{:<5}|'.format("Prob") print("-"*len(header)) print(header) print("-"*len(header)) count = 0 for item in eigenstate: string = '|' for binary_var in item[0]: string += '{:<5} '.format(binary_var) #Binary string string += '{:<6} '.format(np.round(item[1], 2)) #Cost string += '{:<5}|'.format(np.round(item[2], 3)) #Prob print(string) #Print only first 20 states of lowest energy count += 1 if count == 20: break print("-"*len(header)) def perform_substitution_from_qaoa_results(self, qaoa_results, update_benchmark_energy=True, biased = True): correlations = self.get_biased_correlations(qaoa_results.eigenstate) if biased else self.get_correlations(qaoa_results.eigenstate) i, j = self.find_strongest_correlation(correlations) correlation = correlations[i, j] new_qubo = deepcopy(self.qubo) x_i, x_j = new_qubo.variables[i].name, new_qubo.variables[j].name if x_i == "X_anc": print("X_i was ancilla. Swapped") x_i, x_j = x_j, x_i #So ancilla qubit is never substituted out i, j = j, i #Also swap i and j print( "\nCorrelation: < {} {} > = {}".format(x_i.replace("_", ""), x_j.replace("_", ""), correlation)) car_block = int(x_i[2]) # #If same car_block and x_i = x_j, then both must be 0 since only one 1 in a car block # if x_i[2] == x_j[2] and correlation > 0 and len(self.car_blocks[car_block]) > 2: # # set x_i = x_j = 0 # new_qubo = new_qubo.substitute_variables({x_i: 0, x_j:0}) # if new_qubo.status == QuadraticProgram.Status.INFEASIBLE: # raise QiskitOptimizationError('Infeasible due to variable substitution {} = {} = 0'.format(x_i, x_j)) # self.var_values[x_i] = 0 # self.var_values[x_j] = 0 # self.car_blocks[car_block].remove(x_i) # self.car_blocks[car_block].remove(x_j) # print("Two variable substitutions were performed due to extra information from constraints.") if correlation > 0: # set x_i = x_j new_qubo = new_qubo.substitute_variables(variables={x_i: (x_j, 1)}) if new_qubo.status == QuadraticProgram.Status.INFEASIBLE: raise QiskitOptimizationError('Infeasible due to variable substitution {} = {}'.format(x_i, x_j)) self.replacements[x_i] = (x_j, 1) self.car_blocks[car_block].remove(x_i) else : # set x_i = 1 - x_j, this is done in two steps: # 1. set x_i = 1 + x_i # 2. set x_i = -x_j # 1a. get additional offset from the 1 on the RHS of (xi -> 1+xi) constant = new_qubo.objective.constant constant += new_qubo.objective.linear[i] constant += new_qubo.objective.quadratic[i, i] new_qubo.objective.constant = constant #1b get additional linear part from quadratic terms becoming linear due to the same 1 in the 1+xi as above for k in range(new_qubo.get_num_vars()): coeff = new_qubo.objective.linear[k] if k == i: coeff += 2*new_qubo.objective.quadratic[i, k] else: coeff += new_qubo.objective.quadratic[i, k] # set new coefficient if not too small if np.abs(coeff) > 1e-10: new_qubo.objective.linear[k] = coeff else: new_qubo.objective.linear[k] = 0 #2 set xi = -xj new_qubo = new_qubo.substitute_variables(variables={x_i: (x_j, -1)}) if new_qubo.status == QuadraticProgram.Status.INFEASIBLE: raise QiskitOptimizationError('Infeasible due to variable substitution {} = -{}'.format(x_i, x_j)) self.replacements[x_i] = (x_j, -1) self.car_blocks[car_block].remove(x_i) self.qubo = new_qubo op, offset = new_qubo.to_ising() self.operator = op self.offset = offset if self.customise: if self.symmetrise: self.construct_initial_state(ancilla = True) else: self.construct_initial_state() self.construct_mixer() def get_correlations(self, states) -> np.ndarray: """ Get <Zi x Zj> correlation matrix from the eigenstate(state: Dict). Returns: A correlation matrix. """ x, _, prob = states[0] n = len(x) correlations = np.zeros((n, n)) for x, cost, prob in states: for i in range(n): for j in range(i): if x[i] == x[j]: correlations[i, j] += prob else: correlations[i, j] -= prob return correlations def get_z_zz(self, states) -> np.ndarray: """ Get <Zi x Zj> correlation matrix from the eigenstate(state: Dict) along with < Zi > on the diagonal Returns: A correlation matrix. """ x, _, prob = states[0] n = len(x) z_zz_matrix = np.zeros((n, n)) for x, cost, prob in states: for i in range(n): #Diagonal entries if x[i] == '0': z_zz_matrix[i, i] += prob elif x[i] == '1': z_zz_matrix[i, i] -= prob #Lower triangular entries for j in range(i): if x[i] == x[j]: z_zz_matrix[i, j] += prob else: z_zz_matrix[i, j] -= prob return z_zz_matrix def find_strongest_z_or_zz(self, z_zz_matrix): # get absolute values and set diagonal to -1 to make sure maximum is always on off-diagonal abs_z_zz = np.abs(z_zz_matrix) # get index of maximum (by construction on off-diagonal) m_max = np.argmax(abs_z_zz.flatten()) # translate back to indices i = int(m_max // len(z_zz_matrix)) j = int(m_max - i*len(z_zz_matrix)) return (i, j) def perform_substitution_from_qaoa_results_z(self, qaoa_results): z_zz_matrix = self.get_z_zz(qaoa_results.eigenstate) i, j = self.find_strongest_z_or_zz(z_zz_matrix) sign = z_zz_matrix[i, j] new_qubo = deepcopy(self.qubo) x_i, x_j = new_qubo.variables[i].name, new_qubo.variables[j].name if x_i == "X_anc": print("X_i was ancilla. Swapped") x_i, x_j = x_j, x_i #So ancilla qubit is never substituted out i, j = j, i #Also swap i and j print(z_zz_matrix) print( "\nMaximum: < {} {} > = {}".format(x_i.replace("_", ""), x_j.replace("_", ""), sign)) print(new_qubo) car_block = int(x_i[2]) if i != j: if sign > 0: # set x_i = x_j new_qubo = new_qubo.substitute_variables(variables={x_i: (x_j, 1)}) if new_qubo.status == QuadraticProgram.Status.INFEASIBLE: raise QiskitOptimizationError('Infeasible due to variable substitution {} = {}'.format(x_i, x_j)) self.replacements[x_i] = (x_j, 1) self.car_blocks[car_block].remove(x_i) else: # set x_i = 1 - x_j, this is done in two steps: # 1. set x_i = 1 + x_i # 2. set x_i = -x_j # 1a. get additional offset from the 1 on the RHS of (xi -> 1+xi) constant = new_qubo.objective.constant constant += new_qubo.objective.linear[i] constant += new_qubo.objective.quadratic[i, i] new_qubo.objective.constant = constant #1b get additional linear part from quadratic terms becoming linear due to the same 1 in the 1+xi as above for k in range(new_qubo.get_num_vars()): coeff = new_qubo.objective.linear[k] if k == i: coeff += 2*new_qubo.objective.quadratic[i, k] else: coeff += new_qubo.objective.quadratic[i, k] # set new coefficient if not too small if np.abs(coeff) > 1e-10: new_qubo.objective.linear[k] = coeff else: new_qubo.objective.linear[k] = 0 #2 set xi = -xj new_qubo = new_qubo.substitute_variables(variables={x_i: (x_j, -1)}) if new_qubo.status == QuadraticProgram.Status.INFEASIBLE: raise QiskitOptimizationError('Infeasible due to variable substitution {} = -{}'.format(x_i, x_j)) self.replacements[x_i] = (x_j, -1) self.car_blocks[car_block].remove(x_i) else: if sign > 0: # set x_i = 0 try: new_qubo = new_qubo.substitute_variables(constants={x_i: 0.0}) except Exception as e: print(e) # if new_qubo.status == QuadraticProgram.Status.INFEASIBLE: # raise QiskitOptimizationError('Infeasible due to variable substitution {} = {}'.format(x_i, 0)) self.replacements[x_i] = None self.var_values[x_i] = 0 self.car_blocks[car_block].remove(x_i) else: #set x_i = 1 try: new_qubo = new_qubo.substitute_variables(constants={x_i: 1.0}) except Exception as e: print(e) # if new_qubo.status == QuadraticProgram.Status.INFEASIBLE: # raise QiskitOptimizationError('Infeasible due to variable substitution {} = {}'.format(x_i, 1)) self.replacements[x_i] = None self.var_values[x_i] = 1 self.car_blocks[car_block].remove(x_i) self.qubo = new_qubo op, offset = new_qubo.to_ising() self.operator = op self.offset = offset if self.customise: if self.symmetrise: self.construct_initial_state(ancilla = True) else: self.construct_initial_state() self.construct_mixer() def get_biased_correlations(self, states) -> np.ndarray: """ Get <Zi x Zj> correlation matrix from the eigenstate(states: Dict). Returns: A correlation matrix. """ x, _, prob = states[0] n = len(x) correlations = np.zeros((n, n)) for x, cost, prob in states: scaled_approx_quality = 1 / ( 1 + 2 ** (-self.benchmark_energy + cost) ) for i in range(n): for j in range(i): if x[i] == x[j]: correlations[i, j] += scaled_approx_quality * prob else: correlations[i, j] -= scaled_approx_quality * prob return correlations def find_strongest_correlation(self, correlations): # get absolute values and set diagonal to -1 to make sure maximum is always on off-diagonal abs_correlations = np.abs(correlations) diagonal = np.diag( np.ones(len(correlations)) ) abs_correlations = abs_correlations - diagonal # get index of maximum (by construction on off-diagonal) m_max = np.argmax(abs_correlations.flatten()) # translate back to indices i = int(m_max // len(correlations)) j = int(m_max - i*len(correlations)) return (i, j)
def main(args=None): start = time() if args == None: args = parse() prob_s_s = [] qubo_no = args["no_samples"] print("__"*50, "\nQUBO NO: {}\n".format(qubo_no), "__"*50) #Load generated qubo_no with open('qubos_{}_car_{}_routes/qubo_{}.pkl'.format(args["no_cars"], args["no_routes"], qubo_no), 'rb') as f: qubo, max_coeff, operator, offset, routes = pkl.load(f) # print(operator) classical_result = solve_classically(qubo) print(classical_result) x_arr = classical_result.x optimal_value = qubo.objective.evaluate(x_arr) # print(optimal_value) x_str = arr_to_str(x_arr) sort_values = get_costs(qubo) # print("_"*50) # up_to = 27 # print("{} lowest states:".format(up_to)) # avg = 0 # for i in range(up_to): # print(sort_values[i]) # avg += sort_values[i][1] # print("_"*50) # print("Avg: {}".format(avg/up_to)) #Remake QUBO to introduce only ZiZj terms op, offset = qubo.to_ising() new_operator = [] for i, op_1 in enumerate(op): coeff, op_1 = op_1.to_pauli_op().coeff, op_1.to_pauli_op().primitive op_1_str = op_1.to_label() Z_counts = op_1_str.count('Z') if Z_counts == 1: op_1_str = 'Z' + op_1_str else: op_1_str = 'I' + op_1_str pauli = PauliOp( primitive = Pauli(op_1_str), coeff = coeff ) new_operator.append(pauli) qubo_2 = QuadraticProgram() operator = sum(new_operator) qubo_2.from_ising(operator, offset, linear=True) print("Solving with QAOA...") no_shots = 10000 backend = Aer.get_backend('statevector_simulator') quantum_instance = QuantumInstance(backend, shots = no_shots) optimizer_method = "LN_SBPLX" optimizer = NLOPT_Optimizer(method = optimizer_method) print("_"*50,"\n"+optimizer.__class__.__name__) print("_"*50) p = 1 delta_points = [] deltas = np.arange(0.75, 0.76, 0.05) print("delta_t's: ", deltas) original_point = np.append([], [[(i+1)/p, 1-(i+1)/p] for i in range(p)]) for delta in deltas: delta_points.append(delta*original_point) # point = [ 0.60081404, 0.11785113, 0.02330747, 1.10006101, 0.46256391, # -0.96823671] draw_circuit = True initial_state = construct_initial_state_2(args["no_routes"], args["no_cars"]) mixer = n_qbit_mixer(initial_state) quantum_instance = QuantumInstance(backend) results = [] exp_vals = [] for point in delta_points: point = QAOAEx.convert_to_fourier_point(point, 2*p) qaoa_results, _ = Fourier_QAOA(operator, quantum_instance, optimizer, reps = p, initial_fourier_point=point, initial_state = initial_state, mixer = mixer, construct_circ=draw_circuit, fourier_parametrise = True ) # print(optimal_circ.draw()) results.append(qaoa_results) exp_val = qaoa_results.eigenvalue + offset print("ExpVal {}".format(exp_val)) exp_vals.append(exp_val) minim_index = np.argmin(exp_vals) qaoa_results = results[minim_index] sort_states = sorted(qaoa_results.eigenstate.items(), key=lambda x: x[1], reverse=True) correlations = get_correlations(sort_states) # print(correlations) i,j = find_strongest_correlation(correlations) if correlations[i,j] > 0: condition = "Z_{i} = Z_{j}".format(i=i,j=j) else: condition = "Z_{i} = - Z_{j}".format(i=i,j=j) print("Max: <Z_{i}Z_{j}>={maxim}, condition: ".format(i=i, j=j, maxim = correlations[i,j])+condition) ground_energy = sort_values[0][1] x_s = [x_str] for i in range(0,10): if sort_values[i][0] == x_str or np.round(sort_values[i][1], 4) != np.round(ground_energy, 4): continue else: print("Other ground state(s) found: '{}'".format(sort_values[i][0])) x_s.append(sort_values[i][0]) x_s_2 = [] print("All the possible solutions were originally: ") print("[Z0 Z1 Z2 Z3 Z4 Z5 Z6 Z7 Z8 Z9]") for string in x_s: x = '0' + string x_arr = np.array(str_to_arr(x)) # print("f({x})={obj}".format(x=x, obj = qubo_2.objective.evaluate(x_arr))) x_s_2.append(x_arr) formatted_arr = ["{} ".format(item) for item in x_arr] formatted_arr = ' '.join(formatted_arr) print("[{}]".format(formatted_arr)) finish = time() print("Time taken {time}s".format(time=finish-start))