# 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