# How to construct and load Hamiltonians in PennyLane

The Hamiltonian $$H$$ is a core part of many NISQ algorithms. Depending on the problem that we are interested in - quantum simulation, calculating orbital energies or solving a combinatorial optimization problem, we need to construct $$H$$ in the appropriate way. In this how-to, we will highlight how PennyLane can be used to build to the Hamiltonians for combinatorial optimization problems, quantum many-body physics and quantum chemistry.

In general, we can write the Hamiltonian as a linear combination of $$M$$ local operators

\begin{equation*} H = \sum_m^M c_m O_m, \end{equation*}

with $$c_m\in \mathbb{R}$$. To work with Hamiltonians in PennyLane, we make use of the qml.Hamiltonian object (check the docs here), an abstract class that can be used directly with other PennyLane functions to work with observables. For example, consider the 4-qubit Hamiltonian

\begin{equation*} H = \frac{1}{2} X_0 X_1 -\frac{1}{4} Z_0 Y_2 + Z_3. \end{equation*}

We can directly define this Hamiltonian in PennyLane by summing qml.Observable objects and multiplying them by scalars:

import pennylane as qml
import pennylane.numpy as np
hamiltonian = 0.5 * qml.PauliX(0) @ qml.PauliX(1) - 0.25 * qml.PauliZ(0) @ qml.PauliY(2) \
+ qml.PauliZ(3)


Note that the matrix multiplication operator @ works as the tensor product $$\otimes$$ between the Pauli operators. A more general approach for constructing a Hamiltonian in PennyLane is from a list of qml.Observable objects and a corresponding list of coefficients:

coeffs = [0.5, -0.25, 1.]
obs = [qml.PauliX(0) @ qml.PauliX(1), qml.PauliZ(0) @ qml.PauliY(2), qml.PauliZ(3)]
hamiltonian = qml.Hamiltonian(coeffs, obs)


The returned qml.Hamiltonian object also has user-friendly string representation:

>>> print(hamiltonian)
(1.0) [Z3]
+ (-0.25) [Z0 Y2]
+ (0.5) [X0 X1]


## Combinatorial optimization

Classical combinatorial optimization problems are defined over a set of $$N$$ bits $$b \in \{0,1\}^N$$ and require minimizing the objective function

\begin{equation*} C(b) = \sum_i^M C_i(b). \end{equation*}

Mathematically, there exists a mapping from the binary cost function $$C(b)$$ to the Ising Hamiltonian on a graph with a polynomial number of steps [Lucas, 2014]. PennyLane offers several functions that perform this mapping to the Ising Hamiltonian for its users.

For example, let us consider the following graph:

and construct the appropriate Hamiltonian for the MaxCut problem on this graph. With NetworkX, we can create an abstract graph class by calling nx.Graph on a tuple of edges:

import networkx as nx
graph = nx.Graph(((0, 1), (2, 0), (3, 2), (2, 1)))


Creating a VQE-ready Hamiltonian is now a piece of cake; we simply call maxcut from the qml.qaoa module on the graph we just created:

from pennylane.qaoa import maxcut
hamiltonian_maxcut, hamiltonian_mixer = maxcut(graph)


This returns a qml.Hamiltonian object corresponding to the cost function.:

>>> print(type(hamiltonian_maxcut))
<class 'pennylane.vqe.vqe.Hamiltonian'>
>>> print(hamiltonian_maxcut)
(-2.0) [I0]
+ (0.5) [Z0 Z1]
+ (0.5) [Z0 Z2]
+ (0.5) [Z1 Z2]
+ (0.5) [Z2 Z3]


The second output that is returned, hamiltonian_mixer is the commonly used mixer Hamiltonian for a QAOA optimization., Analogously, we can create the Hamiltonians for a number of other combinatorial problems, detailed here.

## Quantum many-body Hamiltonians

In quantum many-body physics we are often interested in studying the ground state properties of a quantum spin system. Depending on the system, the spins are located at specific points on a 1D, 2D or 3D lattice. This spatial layout, combined with the different interaction between the spins governs the microscopic and macroscopic physics of the system. By combining NetworkX and PennyLane, we can quickly construct Hamiltonians for quantum many-body systems with different lattice structures.

For instance, we can look at the Heisenberg spin model defined on a rectangular lattice. The Hamiltonian of is this system is given by

\begin{equation*} H = \sum_{\langle i,j\rangle} [X_i X_j + Y_i Y_j + Z_i Z_j ] \end{equation*}

with the sum over the edges of the following graph:

After we relabel the nodes to integers, we only have to loop over the edges and add the interactions of the model with the correct coefficients:

graph = nx.generators.lattice.grid_2d_graph(3,3)
graph = nx.relabel.convert_node_labels_to_integers(graph)
obs = []
coeffs = []
for edge in graph.edges():
coeffs.extend([1.0, 1.0, 1.0])
obs.extend([qml.PauliX(edge[0]) @ qml.PauliX(edge[1]),
qml.PauliY(edge[0]) @ qml.PauliY(edge[1]),
qml.PauliZ(edge[0]) @ qml.PauliZ(edge[1])])
hamiltonian_heisenberg = qml.Hamiltonian(coeffs, obs)


## Quantum Chemistry

Note: to run the code below, make sure that the pennylane-qchem package is installed with pip!

In quantum chemistry we want to calculate properties of different kinds of molecules. One such property is called the electronic structure of the molecule. Characterizing the electronic structure of a molecule is a notoriously difficult computational problem where quantum computers can potentially outshine classical methods. It should come as no surprise that the physics of this problem can be described with a Hamiltonian.

To create a molecular Hamiltonian in Pennylane, we can either directly define the types and positions types of the atoms in the molecule

from pennylane import qchem
symbols, coordinates = (['H', 'H'], np.array([0., 0., -0.66140414, 0., 0., 0.66140414]))


or load .xyz files from disk (more formats are supported if OpenBabel is installed)

symbols, coordinates = qchem.read_structure('h2.xyz')


Then, we can use the symbols and coordinates to call the function molecular_hamiltonian() to create the Hamiltonian and the qubits required to perform

h, qubits = qchem.molecular_hamiltonian(
symbols,
coordinates,
charge=0,
mult=1,
basis='sto-3g',
active_electrons=2,
active_orbitals=2
)


which gives the following qml.Hamiltonian object:

>>> print(h)
(-0.24274280706347268) [Z2]
+ (-0.24274280706347268) [Z3]
+ (-0.04207897085891747) [I0]
+ (0.1777128752691193) [Z0]
+ (0.17771287526911933) [Z1]
+ (0.12293305078371519) [Z0 Z2]
+ (0.12293305078371519) [Z1 Z3]
+ (0.16768319474683535) [Z0 Z3]
+ (0.16768319474683535) [Z1 Z2]
+ (0.17059738347065292) [Z0 Z1]
+ (0.17627640822374238) [Z2 Z3]
+ (-0.04475014396312016) [Y0 Y1 X2 X3]
+ (-0.04475014396312016) [X0 X1 Y2 Y3]
+ (0.04475014396312016) [Y0 X1 X2 Y3]
+ (0.04475014396312016) [X0 Y1 Y2 X3]


To understand all the arguments of this function, refer to the documentation here, or check out this quantum chemistry tutorial.

Tags: hamiltonians, vqe, pennylane