From 1815743f24cb90ae6255264b1be05e3c5d179321 Mon Sep 17 00:00:00 2001 From: shangtai Date: Thu, 2 Jan 2025 02:31:12 +0800 Subject: [PATCH] including training QAOA code, for both regular QAOA and XQAOA and also for cvar and regular expectation. --- .../optimisation_class/optimisation_class.py | 90 ++++++++++++++++++- src/utils/utils.py | 47 ++++++++++ 2 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 src/utils/utils.py diff --git a/src/qibo_comb_optimisation/optimisation_class/optimisation_class.py b/src/qibo_comb_optimisation/optimisation_class/optimisation_class.py index c45b223..664b021 100644 --- a/src/qibo_comb_optimisation/optimisation_class/optimisation_class.py +++ b/src/qibo_comb_optimisation/optimisation_class/optimisation_class.py @@ -1,10 +1,13 @@ import itertools +import random +import math import numpy as np from qibo import hamiltonians, gates from qibo.models import QAOA, Circuit from qibo.hamiltonians import SymbolicHamiltonian from qibo.symbols import Z +from qibo.optimizers import optimize class QUBO: @@ -501,6 +504,91 @@ def build(circuit, gammas, betas, alphas=None): return build(circuit, gammas, betas, alphas) return build(circuit, gammas, betas) + def train_QAOA(self, p, nshots=10, regular_QAOA=True, regular_loss=True, maxiter=10, method='cobyla', alpha=0.25): + gammas = [random.uniform(0,2*math.pi) for i in range(p)] + betas = [random.uniform(0,2*math.pi) for i in range(p)] + if regular_QAOA: + circuit = self.qubo_to_qaoa_circuit(self, gammas, betas) + n_params = 2*p + parameters = [] + for i in range(p): + parameters.append(gammas[i]) + parameters.append(betas[i]) + else: + alphas = [random.uniform(0,2*math.pi) for i in range(p)] + circuit = self.qubo_to_qaoa_circuit(self, gammas, betas, alphas) + n_params = 3*p + parameters = [] + for i in range(p): + parameters.append(gammas[i]) + parameters.append(betas[i]) + parameters.append(alphas[i]) + # we have prepared the circuit, now we want to train them + + if regular_loss: + def myloss(parameters): + # parameters to be optimize is the input, the output is a counter object + circuit.set_parameters(parameters) + result = circuit(nshots) + result_counter = result.frequencies(binary=True) + energy_dict = itertools.defaultdict(int) + for key, value in result_counter: + # key is the binary string, value is the frequencies + x = [int(sub_key) for sub_key in key] + energy_dict[self.evaluate_f(x)] += value + loss = sum(key*value/nshots for key, value in energy_dict) + return loss + + + else: + def myloss(parameters, alpha=0.1): + """ + Compute the CVaR of the energy distribution for a given quantile threshold `alpha`. + + Parameters: + - parameters: The parameters to set in the circuit. + - alpha: The quantile threshold (default is 0.1 for 10%). + + Returns: + - CVaR: The computed CVaR value. + """ + circuit.set_parameters(parameters) + result = circuit(nshots) + result_counter = result.frequencies(binary=True) + + energy_dict = itertools.defaultdict(int) + for key, value in result_counter.items(): + # key is the binary string, value is the frequency + x = [int(sub_key) for sub_key in key] + energy_dict[self.evaluate_f(x)] += value + + # Normalize frequencies to probabilities + total_counts = sum(energy_dict.values()) + energy_probs = {key: value / total_counts for key, value in energy_dict.items()} + + # Sort energies and compute cumulative probability + sorted_energies = sorted(energy_probs.items()) # List of (energy, probability) + cumulative_prob = 0 + selected_energies = [] + + for energy, prob in sorted_energies: + if cumulative_prob + prob > alpha: + # Include only the fraction of the probability needed to reach `alpha` + excess_prob = alpha - cumulative_prob + selected_energies.append((energy, excess_prob)) + cumulative_prob = alpha + break + else: + selected_energies.append((energy, prob)) + cumulative_prob += prob + + # Compute CVaR as weighted average of selected energies + cvar = sum(energy * prob for energy, prob in selected_energies) / alpha + return cvar + best, params, extra = optimize(myloss, parameters, method=method, options={'maxiter': maxiter}) + circuit.set_parameters(params) + result = circuit(nshots) + return result.frequencies(binary=True) def qubo_to_qaoa_object(self,params: list = None): """ @@ -534,7 +622,6 @@ def qubo_to_qaoa_object(self,params: list = None): # Optionally set parameters if params is not None: qaoa.set_parameters(params) - return qaoa @@ -542,6 +629,7 @@ def qubo_to_qaoa_object(self,params: list = None): + class linear_problem: """ A class used to represent a linear problem of the form Ax + b. diff --git a/src/utils/utils.py b/src/utils/utils.py new file mode 100644 index 0000000..03a0d50 --- /dev/null +++ b/src/utils/utils.py @@ -0,0 +1,47 @@ +from qibo import Circuit, gates +from qibo.optimizers import optimize +from qibo.quantum_info import infidelity +import numpy as np + +# custom loss function, computes fidelity +def myloss(parameters, circuit, target): + circuit.set_parameters(parameters) + final_state = circuit().state() + return infidelity(final_state, target) + +nqubits = 6 +dims = 2**nqubits +nlayers = 2 + +# Create variational circuit +circuit = Circuit(nqubits) +for l in range(nlayers): + circuit.add(gates.RY(qubit, theta=0.0) for qubit in range(nqubits)) + circuit.add(gates.CZ(qubit, qubit + 1) for qubit in range(0, nqubits - 1, 2)) + circuit.add(gates.RY(qubit, theta=0.0) for qubit in range(nqubits)) + circuit.add(gates.CZ(qubit, qubit + 1) for qubit in range(1, nqubits - 2, 2)) + circuit.add(gates.CZ(0, nqubits - 1)) +circuit.add(gates.RY(qubit, theta=0.0) for qubit in range(nqubits)) + +# Optimize starting from a random guess for the variational parameters +x0 = np.random.uniform(0, 2 * np.pi, nqubits * (2 * nlayers + 1)) +data = np.random.normal(0, 1, size=dims) + +# perform optimization +best, params, extra = optimize(myloss, x0, args=(circuit, data), method='BFGS', options={'maxiter':10}) + +# set final solution to circuit instance +circuit.set_parameters(params) + +from qibo import Circuit, gates + +circuit = Circuit(2) +circuit.add(gates.X(0)) +# Add a measurement register on both qubits +circuit.add(gates.M(0, 1)) +# Execute the circuit with the default initial state |00>. +result = circuit(nshots=100) +print(result.frequencies(binary=True)) + + +