Efficient Hamiltonian Sum for QAOA

For those looking for a solution to the problem I described in my original post: you can take advantage of the qml.Hamiltonian() method to construct Hamiltonians as the sum of other Hamiltonians times some factors using the following code.

import pennylane as qml
import numpy as np

# Example of Hamiltonians and their factors to sum up.
N = 4
# Replace the obs_to_sum array with the list of Hamiltonians which you would like to sum-up.
obs_to_sum = [(1/2 * (qml.Identity(i) - qml.PauliZ(i))) for i in range(N)]
# Replace the coeffs_to_sum array with a list of the factors with which each Hamiltonian in obs_to_array will be multiplied in the sum.
coeffs_for_sum = [i for i in range(N)]
# Check that there is one multiplication factor for each Hamiltonian and one Hamiltonian for each multiplication factor.
assert(len(obs_to_sum) == len(coeffs_for_sum))

# The methods below compute the Hamiltian sum([coeffs_to_sum[i]*obs_to_sum[i] for i in range(len(obs_to_sum))]).

def SimplePythonSum(obs_to_sum, coeffs_for_sum):
    # Check that there is one multiplication factor for each Hamiltonian and one Hamiltonian for each multiplication factor.
    assert(len(obs_to_sum) == len(coeffs_for_sum))

    # Sum up all Hamiltonians in obst_to_sum times their respective coefficient from coeffs_for_sum.
    cost_h = sum([coeffs_for_sum[i]*obs_to_sum[i] for i in range(len(obs_to_sum))])
    # Return summed Hamiltonian.
    return cost_h

def SumWithQMLHamiltonian(obs_to_sum, coeffs_for_sum):
    # Check that there is one multiplication factor for each Hamiltonian and one Hamiltonian for each multiplication factor.
    assert(len(obs_to_sum) == len(coeffs_for_sum))

    # Create a new list to save the observables.
    obs = []
    # Create a new numpy array to save the coefficients.
    coeffs = np.array([])

    # Extract the coefficients and observables from each Hamiltonian in obst_to_sum.
    for i in range(len(obs_to_sum)):
        # Save the extracted Hamiltonians from the ith obs_to_sum Hamiltonian.
        obs = obs + obs_to_sum[i].terms()[1]
        # Save the extracted coefficients from the ith obs_to_sum Hamiltonian and multiply them by the corresponding factor for the ithobs_to_sum Hamiltonian.
        coeffs = np.concatenate((coeffs, coeffs_for_sum[i] * obs_to_sum[i].terms()[0]))

    # Check that there is one multiplication factor for each Hamiltonian and one Hamiltonian for each multiplication factor.
    assert(len(obs) == len(coeffs))

    # Create a new Hamiltonian from the extracted observables and the adjusted coefficients.
    cost_h = qml.Hamiltonian(coeffs=coeffs,
                             observables=obs)
    # Return summed Hamiltonian.
    return cost_h

cost_h_from_coeffs_obs = SumWithQMLHamiltonian(obs_to_sum, coeffs_for_sum)
print("Hamiltonian from coeffs. obs. construction")
print(cost_h_from_coeffs_obs)
print("Simplified Hamiltonian from coeffs. obs. construction")
print(cost_h_from_coeffs_obs.simplify())

cost_h_from_sum = SimplePythonSum(obs_to_sum, coeffs_for_sum)
print("Hamiltonian from sum")
print(cost_h_from_sum)

print("Equivalent Hamiltonians?", cost_h_from_coeffs_obs.compare(cost_h_from_sum))

This code snippet returns the following:

Hamiltonian from coeffs. obs. construction
  (-1.5) [Z3]
+ (-1.0) [Z2]
+ (-0.5) [Z1]
+ (-0.0) [Z0]
+ (0.0) [I0]
+ (0.5) [I1]
+ (1.0) [I2]
+ (1.5) [I3]
Simplified Hamiltonian from coeffs. obs. construction
  (-1.5) [Z3]
+ (-1.0) [Z2]
+ (-0.5) [Z1]
+ (-0.0) [Z0]
+ (3.0) [I0]
Hamiltonian from sum
  (-1.5) [Z3]
+ (-1.0) [Z2]
+ (-0.5) [Z1]
+ (-0.0) [Z0]
+ (3.0) [I0]
Equivalent Hamiltonians? True

The main advantage of the SumWithQMLHamiltonian method defined above is that it runs substantially faster than the SimplePythonSum method, which computes the Hamiltonian using Python’s sum method. I have tested this on my Asus UX410UAK laptop with an i7-7500U CPU and 8GB of RAM.

N = 100
obs_to_sum = [(1/2 * (qml.Identity(i) - qml.PauliZ(i))) for i in range(N)]
coeffs_for_sum = [i for i in range(N)]
assert(len(obs_to_sum) == len(coeffs_for_sum))

%timeit SumWithQMLHamiltonian(obs_to_sum, coeffs_for_sum)
%timeit SimplePythonSum(obs_to_sum, coeffs_for_sum)

The result:

1.56 ms ± 82.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
3.02 s ± 43.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

As you can see then SumWithQMLHamiltonian method is clearly the preferred method to compute the sum of Hamiltonians. An explanation why the code from my original post did not work for QAOA and why the observables have to be extracted is provided in the answer provided by @nathan.

1 Like