Exemplo n.º 1
0
def ising(h,
          J,
          num_steps=0,
          verbose=True,
          rand_seed=None,
          connection=None,
          samples=None,
          initial_beta=None,
          initial_gamma=None,
          minimizer_kwargs=None,
          vqe_option=None):
    """
    Ising set up method

    :param h: External magnectic term of the Ising problem. List.
    :param J: Interaction term of the Ising problem. Dictionary.
    :param num_steps: (Optional.Default=2 * len(h)) Trotterization order for the
                  QAOA algorithm.
    :param verbose: (Optional.Default=True) Verbosity of the code.
    :param rand_seed: (Optional. Default=None) random seed when beta and
                      gamma angles are not provided.
    :param connection: (Optional) connection to the QVM. Default is None.
    :param samples: (Optional. Default=None) VQE option. Number of samples
                    (circuit preparation and measurement) to use in operator
                    averaging.
    :param initial_beta: (Optional. Default=None) Initial guess for beta
                         parameters.
    :param initial_gamma: (Optional. Default=None) Initial guess for gamma
                          parameters.
    :param minimizer_kwargs: (Optional. Default=None). Minimizer optional
                             arguments.  If None set to
                             {'method': 'Nelder-Mead',
                             'options': {'ftol': 1.0e-2, 'xtol': 1.0e-2,
                                        'disp': False}
    :param vqe_option: (Optional. Default=None). VQE optional
                             arguments.  If None set to
                       vqe_option = {'disp': print_fun, 'return_all': True,
                       'samples': samples}
    :return: Most frequent Ising string, Energy of the Ising string, Circuit used to obtain result.
    :rtype: List, Integer or float, 'pyquil.quil.Program'.

    """
    if num_steps == 0:
        num_steps = 2 * len(h)

    n_nodes = len(h)

    cost_operators = []
    driver_operators = []
    for i, j in J.keys():
        cost_operators.append(
            PauliSum([PauliTerm("Z", i, J[(i, j)]) * PauliTerm("Z", j)]))

    for i in range(n_nodes):
        cost_operators.append(PauliSum([PauliTerm("Z", i, h[i])]))

    for i in range(n_nodes):
        driver_operators.append(PauliSum([PauliTerm("X", i, -1.0)]))

    if connection is None:
        connection = CXN

    if minimizer_kwargs is None:
        minimizer_kwargs = {
            'method': 'Nelder-Mead',
            'options': {
                'ftol': 1.0e-2,
                'xtol': 1.0e-2,
                'disp': False
            }
        }
    if vqe_option is None:
        vqe_option = {
            'disp': print_fun,
            'return_all': True,
            'samples': samples
        }

    if not verbose:
        vqe_option['disp'] = None

    qaoa_inst = QAOA(connection,
                     n_nodes,
                     steps=num_steps,
                     cost_ham=cost_operators,
                     ref_hamiltonian=driver_operators,
                     store_basis=True,
                     rand_seed=rand_seed,
                     init_betas=initial_beta,
                     init_gammas=initial_gamma,
                     minimizer=minimize,
                     minimizer_kwargs=minimizer_kwargs,
                     vqe_options=vqe_option)

    betas, gammas = qaoa_inst.get_angles()
    most_freq_string, sampling_results = qaoa_inst.get_string(betas, gammas)
    most_freq_string_ising = [ising_trans(it) for it in most_freq_string]
    energy_ising = energy_value(h, J, most_freq_string_ising)
    param_prog = qaoa_inst.get_parameterized_program()
    circuit = param_prog(np.hstack((betas, gammas)))

    return most_freq_string_ising, energy_ising, circuit
Exemplo n.º 2
0
    def make_unclamped_QAOA(self):
        """
		Internal helper function for building QAOA circuit to get RBM expectation
		using Rigetti Quantum simulator

		Returns
		---------------------------------------------------
		nus:		(list) optimal parameters for cost hamiltonians in each layer of QAOA
		gammas:		(list) optimal parameters for mixer hamiltonians in each layer of QAOA
		para_prog:  (fxn closure) fxn to return QAOA circuit for any supplied nus and gammas
		---------------------------------------------------

		"""

        visible_indices = [i for i in range(0, self.n_visible)]
        hidden_indices = [i + self.n_visible for i in range(0, self.n_hidden)]

        full_cost_operator = []
        full_mixer_operator = []
        for i in visible_indices:
            for j in hidden_indices:
                full_cost_operator.append(
                    PauliSum([
                        PauliTerm("Z", i,
                                  -1.0 * self.WEIGHTS[i][j - self.n_visible]) *
                        PauliTerm("Z", j, 1.0)
                    ]))

        # UNCOMMENT THIS TO ADD BIAS IN *untested* in this version of code*
        # for i in hidden_indices:
        # 	full_cost_operator.append(PauliSum([PauliTerm("Z", i, -1.0 * self.BIAS[i - self.n_visible])]))

        for i in hidden_indices + visible_indices:
            full_mixer_operator.append(PauliSum([PauliTerm("X", i, 1.0)]))

# 		n_system = len(visible_indices) + len(hidden_indices)
        n_system = visible_indices + hidden_indices
        print("# of qubits: ", n_system)

        state_prep = pq.Program()
        for i in visible_indices + hidden_indices:
            tmp = pq.Program()
            tmp.inst(RX(self.state_prep_angle, i + len(n_system)),
                     CNOT(i + len(n_system), i))
            state_prep += tmp

        full_QAOA = QAOA(self.qvm,
                         qubits=n_system,
                         steps=self.n_qaoa_steps,
                         ref_ham=full_mixer_operator,
                         cost_ham=full_cost_operator,
                         driver_ref=state_prep,
                         store_basis=True,
                         minimizer=fmin_bfgs,
                         minimizer_kwargs={'maxiter': 50},
                         vqe_options={'samples': self.n_quantum_measurements},
                         rand_seed=1234)

        nus, gammas = full_QAOA.get_angles()

        if self.verbose:
            print("Found following for nus and gammas from QAOA")
            print(nus)
            print(gammas)
            print('-' * 80)

        program = full_QAOA.get_parameterized_program()
        return nus, gammas, program, 0  #full_QAOA.result['fun']
Exemplo n.º 3
0
class OptimizationEngine(object):
    """
    The optimization engine for the VQF algorithm.

    This class takes a problem encoded as clauses, further encodes it into hamiltonian
    and solves it using QAOA.

    Args:
        clauses (list): List of clauses (sympy expressions) representing the problem.
        m (int): Number to be factored. Needed only for the purpose of tagging result files.
        steps (int, optional): Number of steps in the QAOA algorithm. Default: 1
        grid_size (int, optional): The resolution of the grid for grid search. Default: None
        tol (float, optional): Parameter of BFGS optimization method. Gradient norm must be less than tol before successful termination. Default:1e-5
        gate_noise (float, optional): Specifies gate noise for qvm. Default: None.
        verbose (bool): Boolean flag, if True, information about the execution will be printed to the console. Default: False
        visualize (bool): Flag indicating if visualizations should be created. Default: False

    Attributes:
        clauses (list): See Args.
        grid_size (int): See Args.
        mapping (dict): Maps variables into qubit indices.
        qaoa_inst (object): Instance of QAOA class from Grove.
        samples (int): If noise model is active, specifies how many samples we should take for any given quantum program.
        ax (object): Matplotlib `axis` object, used for plotting optimization trajectory.

    """
    def __init__(self,
                 clauses,
                 m=None,
                 steps=1,
                 grid_size=None,
                 tol=1e-5,
                 gate_noise=None,
                 verbose=False,
                 visualize=False):
        self.clauses = clauses
        self.m = m
        self.verbose = verbose
        self.visualize = visualize
        self.gate_noise = gate_noise
        if grid_size is None:
            self.grid_size = len(clauses) + len(qubits)
        else:
            self.grid_size = grid_size

        cost_operators, mapping = self.create_operators_from_clauses()
        self.mapping = mapping
        mixing_operators = self.create_mixing_operators()
        minimizer_kwargs = {
            'method': 'BFGS',
            'options': {
                'gtol': tol,
                'disp': False
            }
        }
        if self.verbose:
            print_fun = print
        else:
            print_fun = pass_fun

        qubits = list(range(len(mapping)))

        if gate_noise:
            self.samples = int(1e3)
            pauli_channel = [gate_noise] * 3
        else:
            self.samples = None
            pauli_channel = None
        connection = ForestConnection()
        qvm = QVM(connection=connection, gate_noise=pauli_channel)
        topology = nx.complete_graph(len(qubits))
        device = NxDevice(topology=topology)
        qc = QuantumComputer(name="my_qvm",
                             qam=qvm,
                             device=device,
                             compiler=QVMCompiler(
                                 device=device,
                                 endpoint=connection.compiler_endpoint))

        vqe_option = {
            'disp': print_fun,
            'return_all': True,
            'samples': self.samples
        }

        self.qaoa_inst = QAOA(qc,
                              qubits,
                              steps=steps,
                              init_betas=None,
                              init_gammas=None,
                              cost_ham=cost_operators,
                              ref_ham=mixing_operators,
                              minimizer=scipy.optimize.minimize,
                              minimizer_kwargs=minimizer_kwargs,
                              rand_seed=None,
                              vqe_options=vqe_option,
                              store_basis=True)

        self.ax = None

    def create_operators_from_clauses(self):
        """
        Creates cost hamiltonian from clauses.
        For details see section IIC from the article.
        """
        operators = []
        mapping = {}
        variable_counter = 0
        for clause in self.clauses:
            if clause == 0:
                continue
            variables = list(clause.free_symbols)
            for variable in variables:
                if str(variable) not in mapping.keys():
                    mapping[str(variable)] = variable_counter
                    variable_counter += 1
            pauli_terms = []
            quadratic_pauli_terms = []
            if type(clause) == Add:
                clause_terms = clause.args
            elif type(clause) == Mul:
                clause_terms = [clause]
            for single_term in clause_terms:
                if len(single_term.free_symbols) == 0:
                    pauli_terms.append(PauliTerm("I", 0, int(single_term)))
                elif len(single_term.free_symbols) == 1:
                    multiplier = 1
                    if type(single_term) == Mul:
                        multiplier = int(single_term.args[0])
                    symbol = list(single_term.free_symbols)[0]
                    symbol_id = mapping[str(symbol)]
                    pauli_terms.append(
                        PauliTerm("I", symbol_id, 1 / 2 * multiplier))
                    pauli_terms.append(
                        PauliTerm("Z", symbol_id, -1 / 2 * multiplier))
                elif len(single_term.free_symbols) == 2 and type(
                        single_term) == Mul:
                    multiplier = 1
                    if isinstance(single_term.args[0], Number):
                        multiplier = int(single_term.args[0])
                    symbol_1 = list(single_term.free_symbols)[0]
                    symbol_2 = list(single_term.free_symbols)[1]
                    symbol_id_1 = mapping[str(symbol_1)]
                    symbol_id_2 = mapping[str(symbol_2)]
                    pauli_term_1 = PauliTerm(
                        "I", symbol_id_1, 1 / 2 * multiplier) - PauliTerm(
                            "Z", symbol_id_1, 1 / 2 * multiplier)
                    pauli_term_2 = PauliTerm("I", symbol_id_2,
                                             1 / 2) - PauliTerm(
                                                 "Z", symbol_id_2, 1 / 2)
                    quadratic_pauli_terms.append(pauli_term_1 * pauli_term_2)
                else:
                    Exception(
                        "Terms of orders higher than quadratic are not handled."
                    )

            clause_operator = PauliSum(pauli_terms)
            for quadratic_term in quadratic_pauli_terms:
                clause_operator += quadratic_term

            squared_clause_operator = clause_operator**2
            if self.verbose:
                print("C:", clause_operator)
                print("C**2:", squared_clause_operator)
            operators.append(squared_clause_operator)

        return operators, mapping

    def create_mixing_operators(self):
        """
        Creates mixing hamiltonian. (eq. 10)
        """

        mixing_operators = []

        for key, value in self.mapping.items():
            mixing_operators.append(PauliSum([PauliTerm("X", value, -1.0)]))

        return mixing_operators

    def perform_qaoa(self):
        """
        Finds optimal angles for QAOA.

        Returns:
            sampling_results (Counter): Counter, where each element represents a bitstring that has been obtained.
            mapping (dict): See class description.

        """
        # betas, gammas = self.simple_grid_search_angles(save_data=True)
        betas, gammas = self.step_by_step_grid_search_angles()
        self.qaoa_inst.betas = betas
        self.qaoa_inst.gammas = gammas
        betas, gammas = self.get_angles()
        _, sampling_results = self.qaoa_inst.get_string(betas,
                                                        gammas,
                                                        samples=10000)
        return sampling_results, self.mapping

    def get_angles(self):
        """
        Finds optimal angles with the quantum variational eigensolver method.
        
        It's direct copy of the function `get_angles` from Grove. I decided to copy it here
        to access to the optimization trajectory (`angles_history`).
        Returns:
            best_betas, best_gammas (np.arrays): best values of the betas and gammas found. 

        """
        stacked_params = np.hstack(
            (self.qaoa_inst.betas, self.qaoa_inst.gammas))
        vqe = VQE(self.qaoa_inst.minimizer,
                  minimizer_args=self.qaoa_inst.minimizer_args,
                  minimizer_kwargs=self.qaoa_inst.minimizer_kwargs)
        cost_ham = reduce(lambda x, y: x + y, self.qaoa_inst.cost_ham)
        # maximizing the cost function!
        param_prog = self.qaoa_inst.get_parameterized_program()
        result = vqe.vqe_run(param_prog,
                             cost_ham,
                             stacked_params,
                             qc=self.qaoa_inst.qc,
                             **self.qaoa_inst.vqe_options)
        best_betas = result.x[:self.qaoa_inst.steps]
        best_gammas = result.x[self.qaoa_inst.steps:]
        optimization_trajectory = result.iteration_params
        energy_history = result.expectation_vals

        if self.ax is not None and self.visualize and self.qaoa_inst.steps == 1:
            plot_optimization_trajectory(self.ax, optimization_trajectory)
        return best_betas, best_gammas

    def simple_grid_search_angles(self, save_data=False):
        """
        Finds optimal angles for QAOA by performing grid search on all the angles.
        This is not recommended for higher values of steps parameter, 
        since it results in grid_size**(2*steps) evaluations.

        Returns:
            best_betas, best_gammas (np.arrays): best values of the betas and gammas found. 

        """
        best_betas = None
        best_gammas = None
        best_energy = np.inf

        # For some reasons np.meshgrid returns columns in order, where values in second
        # grow slower than in the first one. This a fix to it.
        if self.qaoa_inst.steps == 1:
            column_order = [0]
        else:
            column_order = [1, 0] + list(range(2, self.qaoa_inst.steps))

        new_indices = np.argsort(column_order)
        beta_ranges = [np.linspace(0, np.pi, self.grid_size)
                       ] * self.qaoa_inst.steps
        all_betas = np.vstack(np.meshgrid(*beta_ranges)).reshape(
            self.qaoa_inst.steps, -1).T
        all_betas = all_betas[:, column_order]

        gamma_ranges = [np.linspace(0, 2 * np.pi, self.grid_size)
                        ] * self.qaoa_inst.steps
        all_gammas = np.vstack(np.meshgrid(*gamma_ranges)).reshape(
            self.qaoa_inst.steps, -1).T
        all_gammas = all_gammas[:, column_order]

        vqe = VQE(self.qaoa_inst.minimizer,
                  minimizer_args=self.qaoa_inst.minimizer_args,
                  minimizer_kwargs=self.qaoa_inst.minimizer_kwargs)
        cost_hamiltonian = reduce(lambda x, y: x + y, self.qaoa_inst.cost_ham)
        all_energies = []
        data_to_save = []
        if save_data:
            file_name = "_".join(
                [str(self.m), "grid",
                 str(self.grid_size),
                 str(time.time())]) + ".csv"
        for betas in all_betas:
            for gammas in all_gammas:
                stacked_params = np.hstack((betas, gammas))
                program = self.qaoa_inst.get_parameterized_program()
                energy = vqe.expectation(program(stacked_params),
                                         cost_hamiltonian, self.samples,
                                         self.qaoa_inst.qc)
                all_energies.append(energy)
                if self.verbose:
                    print(betas, gammas, energy, end="\r")
                if save_data:
                    data_to_save.append(np.hstack([betas, gammas, energy]))
                if energy < best_energy:
                    best_energy = energy
                    best_betas = betas
                    best_gammas = gammas
                    if self.verbose:
                        print("Lowest energy:", best_energy)
                        print("Angles:", best_betas, best_gammas)
            if save_data:
                np.savetxt(file_name, np.array(data_to_save), delimiter=",")

        if self.visualize:
            if self.qaoa_inst.steps == 1:
                self.ax = plot_energy_landscape(all_betas,
                                                all_gammas,
                                                np.array(all_energies),
                                                log_legend=True)
            else:
                plot_variance_landscape(all_betas, all_gammas,
                                        np.array(all_energies))

        return best_betas, best_gammas

    def step_by_step_grid_search_angles(self):
        """
        Finds optimal angles for QAOA by performing "step-by-step" grid search.
        It finds optimal angles by performing grid search on the QAOA instance with steps=1.
        Then it fixes these angles and performs grid search on the second pair of angles.
        This method requires steps*grid_size**2 evaluations and hence is more suitable
        for higger values of steps.

        Returns:
            best_betas, best_gammas (np.arrays): best values of the betas and gammas found. 

        """

        max_step = self.qaoa_inst.steps
        self.qaoa_inst.betas = np.array([])
        self.qaoa_inst.gammas = np.array([])
        best_betas = np.array([])
        best_gammas = np.array([])
        for current_step in range(1, max_step + 1):
            if self.verbose:
                print("step:", current_step, "\n")
            beta, gamma = self.one_step_grid_search(current_step)
            best_betas = np.append(best_betas, beta)
            best_gammas = np.append(best_gammas, gamma)
            self.qaoa_inst.betas = best_betas
            self.qaoa_inst.gammas = best_gammas

        return best_betas, best_gammas

    def one_step_grid_search(self, current_step):
        """
        Grid search on n-th pair of QAOA angles, where n=current_step.

        Args:
            current_step (int): specify on which layer do we perform search.

        Returns:
            best_beta, best_gamma (floats): best values of the beta and gamma found. 
        """
        self.qaoa_inst.steps = current_step
        best_beta = None
        best_gamma = None
        best_energy = np.inf

        fixed_betas = self.qaoa_inst.betas
        fixed_gammas = self.qaoa_inst.gammas
        beta_range = np.linspace(0, np.pi, self.grid_size)
        gamma_range = np.linspace(0, 2 * np.pi, self.grid_size)

        vqe = VQE(self.qaoa_inst.minimizer,
                  minimizer_args=self.qaoa_inst.minimizer_args,
                  minimizer_kwargs=self.qaoa_inst.minimizer_kwargs)
        cost_hamiltonian = reduce(lambda x, y: x + y, self.qaoa_inst.cost_ham)
        for beta in beta_range:
            for gamma in gamma_range:
                betas = np.append(fixed_betas, beta)
                gammas = np.append(fixed_gammas, gamma)
                stacked_params = np.hstack((betas, gammas))
                program = self.qaoa_inst.get_parameterized_program()
                energy = vqe.expectation(program(stacked_params),
                                         cost_hamiltonian, self.samples,
                                         self.qaoa_inst.qc)
                print(beta, gamma, end="\r")
                if energy < best_energy:
                    best_energy = energy
                    best_beta = beta
                    best_gamma = gamma

        return best_beta, best_gamma
Exemplo n.º 4
0
def ising_qaoa(h, J, num_steps=0, embedding=None, driver_operators=None, verbose=True,
               rand_seed=None, connection=None, samples=None, initial_state=None,
               initial_beta=None, initial_gamma=None, minimizer_kwargs=None,
               vqe_option=None):
    """
    Ising set up method for QAOA. Supports 2-local as well as k-local interaction terms.

    :param h: (dict) External magnectic term of the Ising problem.
    :param J: (dict) Interaction terms of the Ising problem (may be k-local).
    :param num_steps: (Optional.Default=2 * len(h)) Trotterization order for the
                  QAOA algorithm.
    :param embedding: (dict) (Optional. Default: Identity dict) Mapping of logical to physical
                qubits in the QPU hardware graph. Logical qubits must be the
                dict keys.
    :param driver_operators: (Optional. Default: X on all qubits.) The mixer/driver
                Hamiltonian used in QAOA. Can be used to enforce hard constraints
                and ensure that solution stays in feasible subspace.
                Must be PauliSum objects.
    :param verbose: (Optional.Default=True) Verbosity of the code.
    :param rand_seed: (Optional. Default=None) random seed when beta and
                      gamma angles are not provided.
    :param connection: (Optional) connection to the QVM. Default is None.
    :param samples: (Optional. Default=None) VQE option. Number of samples
                    (circuit preparation and measurement) to use in operator
                    averaging. Required when using QPU backend.
    :param initial_state: (Optional. Default=Superposition of all bitstrings) A quantum circuit
                    to initialize the initial state. Must be a pyquil Program.
    :param initial_beta: (Optional. Default=None) Initial guess for beta
                         parameters.
    :param initial_gamma: (Optional. Default=None) Initial guess for gamma
                          parameters.
    :param minimizer_kwargs: (Optional. Default=None). Minimizer optional
                             arguments.  If None set to
                             {'method': 'Nelder-Mead', 'options': {'ftol': 1.0e-2,
                             'xtol': 1.0e-2, disp': False}
    :param vqe_option: (Optional. Default=None). VQE optional arguments. If None set to
                       vqe_option = {'disp': print_fun, 'return_all': True,
                       'samples': samples}
    :return: Most frequent Ising string, Energy of the Ising string, Circuit used to obtain result.
    :rtype: List, Integer or float, 'pyquil.quil.Program'.

    """

    n_nodes = len(set([ index for tuple_ in list(J.keys()) for index in tuple_]
                        + list(h.keys())))

    if num_steps == 0:
        num_steps = 2 * len(n_nodes)

    if embedding is None:
        embedding = {i:i for i in range(n_nodes)}

    cost_operators = []
    driver_operators = []
    for key in J.keys():
        # first PauliTerm is multiplied with coefficient obtained from J
        pauli_product = PauliTerm("Z", embedding[key[0]], J[key])

        for i in range(1,len(key)):
            # multiply with additional Z PauliTerms depending
            # on the locality of the interaction terms
            pauli_product *= PauliTerm("Z", embedding[key[i]])

        cost_operators.append(PauliSum([pauli_product]))

    for i in h.keys():
        cost_operators.append(PauliSum([PauliTerm("Z", embedding[i], h[i])]))

    if driver_operators is None:
        driver_operators = []
        # default to X mixer
        for i in embedding.values():
            driver_operators.append(PauliSum([PauliTerm("X", i, -1.0)]))

    if connection is None:
        connection = CXN

    if minimizer_kwargs is None:
        minimizer_kwargs = {'method': 'Nelder-Mead',
                            'options': {'ftol': 1.0e-2, 'xtol': 1.0e-2,
                                        'disp': False}}
    if vqe_option is None:
        vqe_option = {'disp': print_fun, 'return_all': True,
                      'samples': samples}

    if not verbose:
        vqe_option['disp'] = None

    qaoa_inst = QAOA(connection, qubits=list(sorted(embedding.values())), steps=num_steps, cost_ham=cost_operators,
                     ref_ham=driver_operators, store_basis=True,
                     rand_seed=rand_seed,
                     embedding=embedding,
                     init_betas=initial_beta,
                     init_gammas=initial_gamma,
                     minimizer=minimize,
                     minimizer_kwargs=minimizer_kwargs,
                     vqe_options=vqe_option)

    betas, gammas = qaoa_inst.get_angles()
    most_freq_string, sampling_results = qaoa_inst.get_string(betas, gammas)
    most_freq_string_ising = [ising_trans(it) for it in most_freq_string]
    energy_ising = energy_value(h, J, most_freq_string_ising)
    param_prog = qaoa_inst.get_parameterized_program()
    circuit = param_prog(np.hstack((betas, gammas)))

    return most_freq_string_ising, energy_ising, circuit