Source code for braket.experimental.algorithms.quantum_approximate_optimization.quantum_approximate_optimization

# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
#     http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.


import numpy as np

from braket.circuits import Circuit, FreeParameter, Observable, circuit
from braket.devices import Device
from braket.tasks import QuantumTask


[docs] def cost_function( values: np.ndarray, device: Device, circ: Circuit, coeffs: np.ndarray, cost_history: list[float], shots: int = 0, ) -> float: """Cost function and append to loss history list. Args: values (ndarray): Values for the parameters. device (Device): Braket device to run on. circ (Circuit): QAOA circuit to run. coeffs (ndarray): The coefficients of the cost Hamiltonian. cost_history (List[float]): History of cost evaluations. shots (int): Number of shots. Defaults to 0. Returns: float: The cost function value """ task = run_qaoa_circuit(device, circ, values, shots=shots) cost = get_cost(task, coeffs) cost_history.append(cost) return cost
[docs] def run_qaoa_circuit(device: Device, circ: Circuit, values: np.ndarray, shots: int) -> QuantumTask: """Evaluate a QAOA circuit with parameters=values. Args: device (Device): Braket device to run on. circ (Circuit): QAOA circuit to run. values (np.ndarray): Values for the parameters. shots (int): Number of shots. Returns: QuantumTask: The Braket task to run. """ fixed_circuit = circ.make_bound_circuit( dict(zip(np.array(list(circ.parameters), dtype=str), values)) ) task = device.run(fixed_circuit, shots=shots) return task
[docs] def get_cost(task: QuantumTask, coeffs: np.ndarray) -> float: """Evaluate the cost function from a QAOA task. Args: task (QuantumTask): QAOA task. coeffs (np.ndarray): The coefficients of the cost Hamiltonian. Returns: float: Loss function value. """ exp_vals = task.result().result_types cost = sum(c * s.value for c, s in zip(coeffs, exp_vals)) return cost
[docs] def qaoa(n_qubits: int, n_layers: int, ising: np.ndarray) -> Circuit: """QAOA template. Args: n_qubits (int): Number of qubits n_layers (int): Number of layers. Defaults to 1. ising (ndarray): Ising interaction matrix. Returns: Circuit: The parameteric QAOA Circuit """ gammas = [FreeParameter(f"gamma_{p}") for p in range(n_layers)] betas = [FreeParameter(f"beta_{p}") for p in range(n_layers)] circ = Circuit() circ.h(range(n_qubits)) # prepare |+> state for gamma, beta in zip(gammas, betas): circ.cost_layer(gamma, ising) circ.driver_layer(beta, n_qubits) # add Result types idx = ising.nonzero() for qubit_pair in zip(idx[0], idx[1]): # get interaction strength from Ising matrix circ.expectation(observable=Observable.Z() @ Observable.Z(), target=qubit_pair) return circ
[docs] @circuit.subroutine(register=True) def driver_layer(beta: float, n_qubits: int) -> Circuit: """Returns circuit for driver Hamiltonian U(Hb, beta). Args: beta (float): Rotation angle to apply parameterized rotation around x n_qubits (int): number of qubits to apply rx gate Returns: Circuit: Circuit object that implements evolution with driver Hamiltonian """ return Circuit().rx(range(n_qubits), 2 * beta)
[docs] @circuit.subroutine(register=True) def cost_layer( gamma: float, ising: np.ndarray, ) -> Circuit: """Returns circuit for evolution with cost Hamiltonian. Args: gamma (float): Rotation angle to apply parameterized rotation around z ising (np.ndarray): Ising matrix Returns: Circuit: Circuit for evolution with cost Hamiltonian """ circ = Circuit() # get all non-zero entries (edges) from Ising matrix idx = ising.nonzero() edges = list(zip(idx[0], idx[1])) # apply ZZ gate for every edge (with corresponding interaction strength) for qubit_pair in edges: # get interaction strength from Ising matrix interaction_strength = ising[qubit_pair[0], qubit_pair[1]] circ.decomposed_zz_gate(qubit_pair[0], qubit_pair[1], gamma * interaction_strength) return circ
[docs] @circuit.subroutine(register=True) def decomposed_zz_gate(qubit0: int, qubit1: int, gamma: float) -> Circuit: """ Return a circuit implementing :math:`exp(-i * \\gamma * Z_i * Z_j)` using CNOT gates if ZZ not supported. Args: qubit0 (int): Index value for the controlling qubit for CNOT gate qubit1 (int): Index value for the target qubit for CNOT gate gamma (float): Rotation angle to apply parameterized rotation around z Returns: Circuit: Circuit object that implements ZZ gate using CNOT gates """ circ_zz = Circuit() circ_zz.cnot(qubit0, qubit1).rz(qubit1, gamma).cnot(qubit0, qubit1) return circ_zz