Source code for openqaoa.qaoa_components.variational_parameters.extendedparams

from typing import List, Union
import math
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
import numpy as np

from .variational_baseparams import QAOAVariationalBaseParams
from ..ansatz_constructor import QAOADescriptor
from ..ansatz_constructor.baseparams import shapedArray


[docs]class QAOAVariationalExtendedParams(QAOAVariationalBaseParams): """ QAOA parameters in their most general form with different angles for each operator. This means, that at the i-th timestep the evolution hamiltonian is given by .. math:: H(t_i) = \sum_{\textrm{qubits } j} \beta_{ij} X_j + \sum_{\textrm{qubits } j} \gamma_{\textrm{single } ij} Z_j + \sum_{\textrm{qubit pairs} (jk)} \gamma_{\textrm{pair } i(jk)} Z_j Z_k and the complete circuit is then .. math:: U = e^{-i H(t_p)} \cdots e^{-iH(t_1)}. Attributes ---------- qaoa_descriptor: QAOADescriptor Specify the circuit parameters to construct circuit angles to be used for training betas_singles: list 2D array with the gammas from above for each timestep and qubit. 1st index goes over the timelayers, 2nd over the qubits. betas_pairs : list gammas_pairs: list gammas_singles: list """ def __init__( self, qaoa_descriptor: QAOADescriptor, betas_singles: List[Union[float, int]], betas_pairs: List[Union[float, int]], gammas_singles: List[Union[float, int]], gammas_pairs: List[Union[float, int]], ): # setup reg, qubits_singles and qubits_pairs super().__init__(qaoa_descriptor) self.betas_singles = betas_singles if self.mixer_1q_coeffs else [] self.betas_pairs = betas_pairs if self.mixer_2q_coeffs else [] self.gammas_singles = gammas_singles if self.cost_1q_coeffs else [] self.gammas_pairs = gammas_pairs if self.cost_2q_coeffs else [] def __repr__(self): string = "Extended Parameterisation:\n" string += "\tp: " + str(self.p) + "\n" string += "Parameters:\n" string += ( "\tbetas_singles: " + str(self.betas_singles).replace("\n", ",") + "\n" ) string += "\tbetas_pairs: " + str(self.betas_pairs).replace("\n", ",") + "\n" string += ( "\tgammas_singles: " + str(self.gammas_singles).replace("\n", ",") + "\n" ) string += "\tgammas_pairs: " + str(self.gammas_pairs).replace("\n", ",") + "\n" return string def __len__(self): return self.p * ( len(self.mixer_1q_coeffs) + len(self.mixer_2q_coeffs) + len(self.cost_1q_coeffs) + len(self.cost_2q_coeffs) ) @shapedArray def betas_singles(self): return (self.p, len(self.mixer_1q_coeffs)) @shapedArray def betas_pairs(self): return (self.p, len(self.mixer_2q_coeffs)) @shapedArray def gammas_singles(self): return (self.p, len(self.cost_1q_coeffs)) @shapedArray def gammas_pairs(self): return (self.p, len(self.cost_2q_coeffs)) @property def mixer_1q_angles(self): return 2 * (self.mixer_1q_coeffs * self.betas_singles) @property def mixer_2q_angles(self): return 2 * (self.mixer_2q_coeffs * self.betas_pairs) @property def cost_1q_angles(self): return 2 * (self.cost_1q_coeffs * self.gammas_singles) @property def cost_2q_angles(self): return 2 * (self.cost_2q_coeffs * self.gammas_pairs)
[docs] def update_from_raw(self, new_values): self.betas_singles = np.array(new_values[: len(self.mixer_1q_coeffs) * self.p]) self.betas_singles = self.betas_singles.reshape( (self.p, len(self.mixer_1q_coeffs)) ) new_values = new_values[len(self.betas_singles.flatten()) :] self.betas_pairs = np.array(new_values[: len(self.mixer_2q_coeffs) * self.p]) self.betas_pairs = self.betas_pairs.reshape((self.p, len(self.mixer_2q_coeffs))) new_values = new_values[len(self.betas_pairs.flatten()) :] self.gammas_singles = np.array(new_values[: len(self.cost_1q_coeffs) * self.p]) self.gammas_singles = self.gammas_singles.reshape( (self.p, len(self.cost_1q_coeffs)) ) new_values = new_values[len(self.gammas_singles.flatten()) :] self.gammas_pairs = np.array(new_values[: len(self.cost_2q_coeffs) * self.p]) self.gammas_pairs = self.gammas_pairs.reshape( (self.p, len(self.cost_2q_coeffs)) ) new_values = new_values[len(self.gammas_pairs.flatten()) :] # PEP8 complains, but new_values could be np.array and not list! if len(new_values) != 0: raise RuntimeWarning( "Incorrect dimension specified for new_values" "to construct the new betas and new gammas" )
[docs] def raw(self): raw_data = np.concatenate( ( self.betas_singles.flatten(), self.betas_pairs.flatten(), self.gammas_singles.flatten(), self.gammas_pairs.flatten(), ) ) return raw_data
[docs] @classmethod def linear_ramp_from_hamiltonian( cls, qaoa_descriptor: QAOADescriptor, time: float = None ): """ Returns ------- ExtendedParams The initial parameters according to a linear ramp for the Hamiltonian specified by register, terms, weights. Todo ---- Refactor this s.t. it supers from __init__ """ # create evenly spaced timelayers at the centers of p intervals p = qaoa_descriptor.p if time is None: time = float(0.7 * p) dt = time / p n_gamma_singles = len(qaoa_descriptor.cost_single_qubit_coeffs) n_gamma_pairs = len(qaoa_descriptor.cost_pair_qubit_coeffs) n_beta_singles = len(qaoa_descriptor.mixer_single_qubit_coeffs) n_beta_pairs = len(qaoa_descriptor.mixer_pair_qubit_coeffs) betas = np.linspace( (dt / time) * (time * (1 - 0.5 / p)), (dt / time) * (time * 0.5 / p), p ) gammas = betas[::-1] betas_singles = betas.repeat(n_beta_singles).reshape(p, n_beta_singles) betas_pairs = betas.repeat(n_beta_pairs).reshape(p, n_beta_pairs) gammas_singles = gammas.repeat(n_gamma_singles).reshape(p, n_gamma_singles) gammas_pairs = gammas.repeat(n_gamma_pairs).reshape(p, n_gamma_pairs) params = cls( qaoa_descriptor, betas_singles, betas_pairs, gammas_singles, gammas_pairs ) return params
[docs] @classmethod def random(cls, qaoa_descriptor: QAOADescriptor, seed: int = None): """ Returns ------- ExtendedParams Randomly initialised ``ExtendedParams`` object """ if seed is not None: np.random.seed(seed) p = qaoa_descriptor.p n_gamma_singles = len(qaoa_descriptor.cost_single_qubit_coeffs) n_gamma_pairs = len(qaoa_descriptor.cost_pair_qubit_coeffs) n_beta_singles = len(qaoa_descriptor.mixer_single_qubit_coeffs) n_beta_pairs = len(qaoa_descriptor.mixer_pair_qubit_coeffs) betas_singles = np.random.uniform(0, np.pi, (p, n_beta_singles)) betas_pairs = np.random.uniform(0, np.pi, (p, n_beta_pairs)) gammas_singles = np.random.uniform(0, np.pi, (p, n_gamma_singles)) gammas_pairs = np.random.uniform(0, np.pi, (p, n_gamma_pairs)) params = cls( qaoa_descriptor, betas_singles, betas_pairs, gammas_singles, gammas_pairs ) return params
[docs] @classmethod def empty(cls, qaoa_descriptor: QAOADescriptor): """ Initialise Extended parameters with empty arrays """ p = qaoa_descriptor.p n_gamma_singles = len(qaoa_descriptor.cost_single_qubit_coeffs) n_gamma_pairs = len(qaoa_descriptor.cost_pair_qubit_coeffs) n_beta_singles = len(qaoa_descriptor.mixer_single_qubit_coeffs) n_beta_pairs = len(qaoa_descriptor.mixer_pair_qubit_coeffs) betas_singles = np.empty((p, n_beta_singles)) betas_pairs = np.empty((p, n_beta_pairs)) gammas_singles = np.empty((p, n_gamma_singles)) gammas_pairs = np.empty((p, n_gamma_pairs)) params = cls( qaoa_descriptor, betas_singles, betas_pairs, gammas_singles, gammas_pairs ) return params
[docs] def get_constraints(self): """Constraints on the parameters for constrained parameters. Returns ------- List[Tuple]: A list of tuples (0, upper_boundary) of constraints on the parameters s.t. we are exploiting the periodicity of the cost function. Useful for constrained optimizers. """ beta_constraints = [(0, math.pi)] * ( len(self.betas_singles.flatten() + len(self.betas_pairs.flatten())) ) beta_pair_constraints = [(0, math.pi / w) for w in self.mixer_2q_coeffs] beta_pair_constraints *= self.p beta_single_constraints = [(0, math.pi / w) for w in self.mixer_1q_coeffs] beta_single_constraints *= self.p gamma_pair_constraints = [(0, 2 * math.pi / w) for w in self.cost_2q_coeffs] gamma_pair_constraints *= self.p gamma_single_constraints = [(0, 2 * math.pi / w) for w in self.cost_1q_coeffs] gamma_single_constraints *= self.p all_constraints = ( beta_single_constraints + beta_pair_constraints + gamma_single_constraints + gamma_pair_constraints ) return all_constraints
[docs] def plot(self, ax=None, **kwargs): list_names_ = ["betas singles", "betas pairs", "gammas singles", "gammas pairs"] list_values_ = [ self.betas_singles % (2 * (np.pi)), self.betas_pairs % (2 * (np.pi)), self.gammas_singles % (2 * (np.pi)), self.gammas_pairs % (2 * (np.pi)), ] list_names, list_values = list_names_.copy(), list_values_.copy() n_pop = 0 for i in range(len(list_values_)): if list_values_[i].size == 0: list_values.pop(i - n_pop) list_names.pop(i - n_pop) n_pop += 1 n = len(list_values) p = self.p if ax is None: fig, ax = plt.subplots((n + 1) // 2, 2, figsize=(9, 9 if n > 2 else 5)) else: fig = ax.get_figure() fig.tight_layout(pad=4.0) for k, (name, values) in enumerate(zip(list_names, list_values)): i, j = k // 2, k % 2 axes = ax[i, j] if n > 2 else ax[k] if values.size == p: axes.plot(values.T[0], marker="^", color="green", ls="", **kwargs) axes.set_xlabel("p", fontsize=12) axes.set_title(name) axes.xaxis.set_major_locator(MaxNLocator(integer=True)) else: n_terms = values.shape[1] plt1 = axes.pcolor( np.arange(p), np.arange(n_terms), values.T, vmin=0, vmax=2 * np.pi, cmap="seismic", ) axes.set_aspect(p / n_terms) axes.xaxis.set_major_locator(MaxNLocator(integer=True)) axes.yaxis.set_major_locator(MaxNLocator(integer=True)) axes.set_ylabel("terms") axes.set_xlabel("p") axes.set_title(name) plt.colorbar(plt1, **kwargs) if k == 0: ax[1].axis("off") elif k == 2: ax[1, 1].axis("off") return fig, ax