Multi-product formulas to reduce Trotter error
Die Sigg es noch nit övversatz. Ehr luurt üch de änglesche Originalversion aan.
Estimated QPU usage:Four minutes on a Heron r2 processor (NOTE: This is an estimate only. Your runtime might vary.)
Background
This tutorial demonstrates how to use a Multi-Product Formula (MPF) to achieve a lower Trotter error on our observable compared to the one incurred by the deepest Trotter circuit that we will actually execute. MPFs reduce the Trotter error of Hamiltonian dynamics through a weighted combination of several circuit executions. Consider the task of finding observable expectation values for the quantum state with the Hamiltonian . One can use Product Formulas (PFs) to approximate the time-evolution by doing the following:
- Write the Hamiltonian as where are Hermitian operators such that each corresponding unitary can be efficiently implemented on a quantum device.
- Approximate the terms that do not commute with each other.
Then, the first-order PF (Lie-Trotter formula) is:
which has a quadratic error term . One can also use higher-order PFs (Lie-Trotter-Suzuki formulas), which converge faster, and are defined recursively as:
where is the order of the symmetric PF and . For long time-evolutions, one can split the time interval into intervals, called Trotter steps, of duration and approximate the time-evolution in each interval with a order product formula . Thus, the PF of order for time-evolution operator over Trotter steps is:
where the error term decreases with the number of Trotter steps and the order of the PF.
Given an integer and a product formula , the approximate time-evolved state can be obtained from by applying iterations of the product formula .
is an approximation for with the Trotter approximation error ||. If we consider a linear combination of Trotter approximations of :
where are our weighting coefficients, is the density matrix corresponding to the pure state obtained by evolving the initial state with the product formula, , involving Trotter steps, and indexes the number of PFs that make up the MPF. All the terms in use the same product formula as its base. The goal is to improve upon || by finding with even lower .
- needs not be a physical state as need not be positive. The goal here is to minimize the error in the expectation value of the observables and not to find a physical replacement for .
- determines both the circuit depth and level of Trotter approximation. Smaller values of lead to shorter circuits, which incur fewer circuit errors but will be a less accurate approximation to the desired state.
The key here is that the remaining Trotter error given by is smaller than the Trotter error that one would obtain by simply using the largest value.
You can view the usefulness of this from two perspectives:
- For a fixed budget of Trotter steps that you can execute, you can obtain results with a Trotter error that is smaller in total.
- Given some target number of Trotter steps that is too large to execute, you can use the MPF to find a collection of lower-depth circuits to run that results in a similar Trotter error.
Requirements
Before starting this tutorial, ensure that you have the following installed:
- Qiskit SDK v1.0 or later, with visualization support
- Qiskit Runtime v0.22 or later (
pip install qiskit-ibm-runtime) - MPF Qiskit addons (
pip install qiskit_addon_mpf) - Qiskit addons utils (
pip install qiskit_addon_utils) - Quimb library (
pip install quimb) - Qiskit Quimb library (
pip install qiskit-quimb) - Numpy v0.21 for compatibility across packages (
pip install numpy==0.21)
Part I. Small-scale example
Explore the stability of MPF
There is no obvious restriction on the choice of number of Trotter steps that make up the MPF state . However, these must be picked carefully to avoid instabilities in the resulting expectation values calculated from . A good general rule is to set the smallest Trotter step so that . If you want to learn more about this and how to choose your other values, refer to the How to choose the Trotter steps for an MPF guide.
In the example below we explore the stability of the MPF solution by calculating the expectation value of the magnetization for a range of times using different time-evolved states. Specifically, we compare the expectation values calculated from each of the approximate time-evolutions implemented with the corresponding Trotter steps and the various MPF models (static and dynamic coefficients) with the exact values of the time-evolved observable. First, let's define the parameters for the Trotter formulas and the evolution times
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-addon-mpf qiskit-addon-utils qiskit-aer qiskit-ibm-runtime rustworkx scipy
import numpy as np
mpf_trotter_steps = [1, 2, 4]
order = 2
symmetric = False
trotter_times = np.arange(0.5, 1.55, 0.1)
exact_evolution_times = np.arange(trotter_times[0], 1.55, 0.05)
For this example we will use the Neel state as the initial state and the Heisenberg model on a line of 10 sites for the Hamiltonian governing the time-evolution
where is the coupling strength for nearest-neighbor edges.
from qiskit.transpiler import CouplingMap
from rustworkx.visualization import graphviz_draw
from qiskit_addon_utils.problem_generators import generate_xyz_hamiltonian
import numpy as np
L = 10
# Generate some coupling map to use for this example
coupling_map = CouplingMap.from_line(L, bidirectional=False)
graphviz_draw(coupling_map.graph, method="circo")
# Get a qubit operator describing the Heisenberg field model
hamiltonian = generate_xyz_hamiltonian(
coupling_map,
coupling_constants=(1.0, 1.0, 1.0),
ext_magnetic_field=(0.0, 0.0, 0.0),
)
print(hamiltonian)
SparsePauliOp(['IIIIIIIXXI', 'IIIIIIIYYI', 'IIIIIIIZZI', 'IIIIIXXIII', 'IIIIIYYIII', 'IIIIIZZIII', 'IIIXXIIIII', 'IIIYYIIIII', 'IIIZZIIIII', 'IXXIIIIIII', 'IYYIIIIIII', 'IZZIIIIIII', 'IIIIIIIIXX', 'IIIIIIIIYY', 'IIIIIIIIZZ', 'IIIIIIXXII', 'IIIIIIYYII', 'IIIIIIZZII', 'IIIIXXIIII', 'IIIIYYIIII', 'IIIIZZIIII', 'IIXXIIIIII', 'IIYYIIIIII', 'IIZZIIIIII', 'XXIIIIIIII', 'YYIIIIIIII', 'ZZIIIIIIII'],
coeffs=[1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j,
1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j,
1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j])
The observable that we will be measuring is magnetization on a pair of qubits in the middle of the chain.
from qiskit.quantum_info import SparsePauliOp
observable = SparsePauliOp.from_sparse_list(
[("ZZ", (L // 2 - 1, L // 2), 1.0)], num_qubits=L
)
print(observable)
SparsePauliOp(['IIIIZZIIII'],
coeffs=[1.+0.j])
We define a transpiler pass to collect the XX and YY rotations in the circuit as a single XX+YY gate. This will allow us to leverage TeNPy's spin conservation properties during the MPO computation, significantly speeding up the calculation.
from qiskit.circuit.library import XXPlusYYGate
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes.optimization.collect_and_collapse import (
CollectAndCollapse,
collect_using_filter_function,
collapse_to_operation,
)
from functools import partial
def filter_function(node):
return node.op.name in {"rxx", "ryy"}
collect_function = partial(
collect_using_filter_function,
filter_function=filter_function,
split_blocks=True,
min_block_size=1,
)
def collapse_to_xx_plus_yy(block):
param = 0.0
for node in block.data:
param += node.operation.params[0]
return XXPlusYYGate(param)
collapse_function = partial(
collapse_to_operation,
collapse_function=collapse_to_xx_plus_yy,
)
pm = PassManager()
pm.append(CollectAndCollapse(collect_function, collapse_function))
Then we create the circuits implementing the approximate Trotter time-evolutions.
from qiskit.synthesis import SuzukiTrotter
from qiskit_addon_utils.problem_generators import (
generate_time_evolution_circuit,
)
from qiskit import QuantumCircuit
# Initial Neel state preparation
initial_state_circ = QuantumCircuit(L)
initial_state_circ.x([i for i in range(L) if i % 2 != 0])
all_circs = []
for total_time in trotter_times:
mpf_trotter_circs = [
generate_time_evolution_circuit(
hamiltonian,
time=total_time,
synthesis=SuzukiTrotter(reps=num_steps, order=order),
)
for num_steps in mpf_trotter_steps
]
mpf_trotter_circs = pm.run(
mpf_trotter_circs
) # Collect XX and YY into XX + YY
mpf_circuits = [
initial_state_circ.compose(circuit) for circuit in mpf_trotter_circs
]
all_circs.append(mpf_circuits)
mpf_circuits[-1].draw("mpl", fold=-1)

Next, we calculate the time-evolved expectation values from the Trotter circuits.
from copy import deepcopy
from qiskit_aer import AerSimulator
from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
aer_sim = AerSimulator()
estimator = Estimator(mode=aer_sim)
mpf_expvals_all_times, mpf_stds_all_times = [], []
for t, mpf_circuits in zip(trotter_times, all_circs):
mpf_expvals = []
circuits = [deepcopy(circuit) for circuit in mpf_circuits]
pm_sim = generate_preset_pass_manager(
backend=aer_sim, optimization_level=3
)
isa_circuits = pm_sim.run(circuits)
result = estimator.run(
[(circuit, observable) for circuit in isa_circuits], precision=0.005
).result()
mpf_expvals = [res.data.evs for res in result]
mpf_stds = [res.data.stds for res in result]
mpf_expvals_all_times.append(mpf_expvals)
mpf_stds_all_times.append(mpf_stds)
We also calculate the exact expectation values for comparison.
from scipy.linalg import expm
from qiskit.quantum_info import Statevector
exact_expvals = []
for t in exact_evolution_times:
# Exact expectation values
exp_H = expm(-1j * t * hamiltonian.to_matrix())
initial_state = Statevector(initial_state_circ).data
time_evolved_state = exp_H @ initial_state
exact_obs = (
time_evolved_state.conj()
@ observable.to_matrix()
@ time_evolved_state
).real
exact_expvals.append(exact_obs)
Static MPF coefficients
Static MPFs are those where the values do not depend on the evolution time, . Let's consider the order PF with Trotter steps, this can be written as:
where are matrices which depend on the commutators of terms in the decomposition of the Hamiltonian. It is important to note that themselves are independent of time and the number of Trotter steps . Therefore, it is possible to cancel out lower-order error terms contributing to with a careful choice of the weights of the linear combination. To cancel the Trotter error for the first terms (these will give the largest contributions as they correspond to the lower number of Trotter steps) in the expression for , the coefficients must satisfy the following equations:
with . The first equation guarantees that there is no bias in the constructed state , while the second equation ensures the cancellation of the Trotter errors. For higher-order PF, the second equation becomes where for symmetric PFs and otherwise, with . The resulting error (Refs. [1],[2]) is then
Determining the static MPF coefficients for a given set of values amounts to solving the linear system of equations defined by the two equations above for the variables : . Where are our coefficients of interest, is a matrix which depends on and the type of PF we use (), and is a vector of constraints. Specifically:
where is the order, is if symmetric is True and otherwise, are the trotter_steps, and are the variables to solve for. The indices and start at . We can also visualize this in matrix form: