Synthesis of Trotter-Suzuki first order expansions¶

Lets assume that we would like to build a circuit implementing the propagator of the following Hamiltonian:

$$ H = \alpha \sigma_Z^{(0)}\sigma_Z^{(1)} + \beta \sigma_X^{(0)} \sigma_X^{(1)} + \gamma \sigma_X^{(0)}\sigma_Y^{(1)}\sigma_Z^{(2)} $$

Since the first and last term do not commute, we cannot implement the propagator of each individual to implement $e^{-i\theta H}$.

The Trotter expansion tells us that we can implement the following unitary for some large $n$: $$ \left(\prod e^{-i \frac{\theta}{n}c_i h_i}\right)^n $$ where $c_i, h_i$ and the terms and coefficients of $H$. This unitary will approximate efficiently our goal, as long as $n$ is large enough.

In practice, this expansion neglects the fact that the Hamiltonian's term do not commute. The direct consequence is that we can try to optimize the ordering of the terms in each product in order to minimize the implementation cost of the Trotter expansion.

In synthopline, we provide several backend methods that try and synthesize efficient parametrized variational circuits by optimizing the rotation ordering. These methods are wrapped in the gnerate_trotter_ansatz method.

Lets see how it works:

In [1]:
from qat.core import Observable, Term, Variable
from qat.synthopline.pauli_synth import generate_trotter_ansatz
In [2]:
H = Observable(3, pauli_terms=[
    Term(1, "ZZ", [0, 1]), 
    Term(1, "XX", [0, 1]),
    Term(1, "XZZ", [0, 1, 2])
])

job = generate_trotter_ansatz(H)
circuit = job.circuit
circuit.display()

job = generate_trotter_ansatz(H, strategy="pauli_synth")
circuit = job.circuit
circuit.display()
No description has been provided for this image
No description has been provided for this image

As you can see the backend can be specified using the strategy keyword.

It is possible to ask for deeper circuit by iterating the Trotter expansion for several steps:

In [3]:
job = generate_trotter_ansatz(H, nsteps=2)
circuit = job.circuit
circuit.display()

job = generate_trotter_ansatz(H, nsteps=2, strategy="pauli_synth")
circuit = job.circuit
circuit.display()
No description has been provided for this image
No description has been provided for this image

In some applications, one needs to perform a parametrized Trotter expansion while preserving some ordering of the terms. Consider for instance the case of QAOA Ansätze. In these Ansätze, one needs to alternate between Trotterizations of two Hamiltonians $H_1$ and $H_0$ (in that order).

This can be achieved by provifing a list of Hamiltonians instead of a single large Hamiltonian. Consequently, each propagator will be parametrized by a different variable:

In [4]:
import networkx as nx
n = 5

graph = nx.generators.path_graph(n)

H0 = - sum(Observable.sigma_x(i, n) for i in range(n))
H1 = sum(Observable.sigma_z(a, n) * Observable.sigma_z(b, n) for a, b in graph.edges())

ansatz = generate_trotter_ansatz(H1, H0, nsteps=3, init_state="+" * n, strategy="naive")
circuit = ansatz.circuit
circuit.display()
No description has been provided for this image

If you are used to deal with QAOA MAX-CUT, this circuit should ring a bell.

One can used different strategies to achieve different circuit that will be, for this precise case, all equivalent:

In [5]:
ansatz = generate_trotter_ansatz(H1, H0, nsteps=3, init_state="+" * n, strategy="pauli_synth")
circuit = ansatz.circuit
circuit.display()
No description has been provided for this image
In [ ]: